Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: order editing data model #2184

Merged
merged 23 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/medusa/src/loaders/feature-flags/order-editing.ts
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions packages/medusa/src/migrations/1663059812399-order_editing.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
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<void> {
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"`)
}
}
82 changes: 82 additions & 0 deletions packages/medusa/src/models/order-edit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
fPolic marked this conversation as resolved.
Show resolved Hide resolved
BeforeInsert,
Column,
CreateDateColumn,
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
adrien2p marked this conversation as resolved.
Show resolved Hide resolved

@Column()
created_by: string // customer or user ID

@CreateDateColumn({ type: resolveDbType("timestamptz") })
created_at: Date
fPolic marked this conversation as resolved.
Show resolved Hide resolved

@Column({ nullable: true })
requested_by?: string // customer or user ID

@Column({ type: resolveDbType("timestamptz"), nullable: true })
requested_at?: Date
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment: same comment as the other for all optional fields that are nullable but can't be undefined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just followed the established pattern for that case we have in the codebase: for example https://github.com/medusajs/medusa/blob/master/packages/medusa/src/models/batch-job.ts#L49:L65.

If we add | null typeorm will in some cases generate a wrong type if explicit type is not set in the decorator

Copy link
Member

@adrien2p adrien2p Sep 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn’t mean to not set it in the decorator. But the problem is that If we manipulate the property you can’t set null even if the field is nullable. Because of the type error you get. Let it that way, but each time we create a new entity i mentioned that point ^^. Also, it is not a pattern since the cart, address, shipping etc specify that null possibility if you look at it. I think it is more an inconsistency and I know that we choose to not change the old entity since it can get you in more refactoring than expected 😂


@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")
}
}
59 changes: 59 additions & 0 deletions packages/medusa/src/models/order-item-change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
fPolic marked this conversation as resolved.
Show resolved Hide resolved
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
fPolic marked this conversation as resolved.
Show resolved Hide resolved

@Column()
order_edit_id: string
fPolic marked this conversation as resolved.
Show resolved Hide resolved

@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")
}
}
18 changes: 14 additions & 4 deletions packages/medusa/src/models/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: We should add that in the description below with [EXPERIMENTAL] first

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can update the property description in the oas comment bellow with [EXPERIMENTAL]


@OneToMany(() => LineItem, (lineItem) => lineItem.order, {
cascade: ["insert"],
})
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions packages/medusa/src/repositories/order-edit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { EntityRepository, Repository } from "typeorm"

import { OrderEdit } from "../models/order-edit"

@EntityRepository(OrderEdit)
export class OrderEditRepository extends Repository<OrderEdit> {}
6 changes: 6 additions & 0 deletions packages/medusa/src/repositories/order-item-change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { EntityRepository, Repository } from "typeorm"

import { OrderItemChange } from "../models/order-item-change"

@EntityRepository(OrderItemChange)
export class OrderItemChangeRepository extends Repository<OrderItemChange> {}