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(cart): Data models #5986

Merged
merged 21 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
82 changes: 82 additions & 0 deletions packages/cart/src/models/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { DAL } from "@medusajs/types"
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Entity,
OnInit,
OptionalProps,
PrimaryKey,
Property
} from "@mikro-orm/core"


type OptionalAddressProps = DAL.EntityDateColumns // TODO: To be revisited when more clear

@Entity({ tableName: "cart_address" })
Copy link
Member

Choose a reason for hiding this comment

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

question: any reason we store that in cart_address and not address?

Copy link
Contributor Author

@olivermrbl olivermrbl Jan 5, 2024

Choose a reason for hiding this comment

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

We need to couple table names to the module for when we start combining them.

E.g. @medusajs/cart and @medusajs/customer will both ship an address table. To avoid clashes/conflicts with tables across the modules, we need to differentiate them. Otherwise, the address table would be shared, which can lead to unexpected issues.

It will be the consumer's responsibility to ensure that addresses are managed correctly for these cases where the model lives in both modules. Here, with the Customer and Cart module, you would likely use the customer's address from the Customer Module to create a cart address in the Cart Module

This will introduce some data redundancy, but it is for the better. It will make the modules much more usable in a standalone context.

Copy link
Member

Choose a reason for hiding this comment

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

yes you are entirely right, I ve indentified that with the rest of the pr :D my bad

export default class Address {
[OptionalProps]: OptionalAddressProps

@PrimaryKey({ columnType: "text" })
id!: string

@Property({ columnType: "text", nullable: true })
customer_id?: string | null
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved

@Property({ columnType: "text", nullable: true })
company?: string | null

@Property({ columnType: "text", nullable: true })
first_name?: string | null

@Property({ columnType: "text", nullable: true })
last_name?: string | null

@Property({ columnType: "text", nullable: true })
address_1?: string | null

@Property({ columnType: "text", nullable: true })
address_2?: string | null
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved

@Property({ columnType: "text", nullable: true })
city?: string | null

@Property({ columnType: "text", nullable: true })
country_code?: string | null

@Property({ columnType: "text", nullable: true })
province?: string | null

@Property({ columnType: "text", nullable: true })
postal_code?: string | null

@Property({ columnType: "text", nullable: true })
phone?: string | null

@Property({ columnType: "jsonb", nullable: true })
metadata?: Record<string, unknown> | 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, "caaddr")
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
}

@OnInit()
onInit() {
this.id = generateEntityId(this.id, "caaddr")
}
}
41 changes: 41 additions & 0 deletions packages/cart/src/models/adjustment-line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { DAL } from "@medusajs/types"
import { OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"

type OptionalAdjustmentLineProps = DAL.EntityDateColumns // TODO: To be revisited when more clear

export default class AdjustmentLine {
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
[OptionalProps]: OptionalAdjustmentLineProps

@PrimaryKey({ columnType: "text" })
id: string

@Property({ columnType: "text", nullable: true })
description?: string | null

@Property({ columnType: "text", nullable: true })
promotion_id?: string | null

@Property({ columnType: "text" })
code: string

@Property({ columnType: "numeric" })
amount: number
Copy link
Member

Choose a reason for hiding this comment

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

question: do we need the serializer/deserializer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, nice catch.

Copy link
Contributor

Choose a reason for hiding this comment

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

We need a util to be used for money amounts everywhere to normalize this. Probably a good time to sync and update pricing, promotion and cart.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, would make sense.

Were you thinking something along the lines of what this guy proposes, but instead of parseFloat use bignumber.js to return a BigNumber when reading the column?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes. we could install bignumber.js on our utils and use it from there.
however that will impact the calculations. but we can do it in two steps.
first normalizing all the db + serialize but returning the BigNumber .toNumber(), and next step use the methods to perform the calculations where it is needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

yes, that sounds good. Since bignumberjs also works with strings, this would amount to amount: string? or are we still serializing it back to a number.

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 think the property still needs to be typed as a number, so we work with numbers on data mutations:

manager.create(LineItem, { amount: 10000000 })

But I am actually not sure. Would like others to pitch in too.

Copy link
Member

Choose a reason for hiding this comment

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

yes the property can be kept as number, it doesn't prevent us from using float numbers for example. But it would be more about the serizliation and deserialization related no?

Copy link
Contributor

Choose a reason for hiding this comment

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

in a first moment we keep it as number.
later we change it to a BigNumber type and refactor all the math operations to use the methods to calculate

Copy link
Member

Choose a reason for hiding this comment

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

just to summarize the idea from carlos

  • step1: everything number with current ser/deser
  • step2: include bignumbers or similar as the ser/deser to number
  • step3: change number usage into all calculations


@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
}
115 changes: 112 additions & 3 deletions packages/cart/src/models/cart.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,119 @@
import { DAL } from "@medusajs/types"
import { generateEntityId } from "@medusajs/utils"
import { BeforeCreate, Entity, OnInit, PrimaryKey } from "@mikro-orm/core"
import {
BeforeCreate,
Collection,
Entity,
OnInit,
OneToMany,
OneToOne,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import Address from "./address"
import LineItem from "./line-item"
import ShippingMethod from "./shipping-method"

@Entity()
type OptionalCartProps =
| "shipping_address"
| "billing_address"
| DAL.EntityDateColumns // TODO: To be revisited when more clear

@Entity({ tableName: "cart" })
export default class Cart {
@PrimaryKey({ columnType: "text" })
id!: string
id: string

@Property({ columnType: "text", nullable: true })
region_id?: string | null

@Property({ columnType: "text", nullable: true })
customer_id?: string | null

@Property({ columnType: "text", nullable: true })
sales_channel_id?: string | null

@Property({ columnType: "text", nullable: true })
email?: string | null

@Property({ columnType: "text" })
currency_code: string

@OneToOne({
entity: () => Address,
joinColumn: "shipping_address_id",
orphanRemoval: true,
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
})
shipping_address?: Address | null

@OneToOne({
entity: () => Address,
joinColumn: "billing_address_id",
orphanRemoval: true,
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
})
billing_address?: Address | null

@Property({ columnType: "jsonb", nullable: true })
metadata?: Record<string, unknown> | null

@OneToMany(() => LineItem, (lineItem) => lineItem.cart, {
orphanRemoval: true,
})
items = new Collection<LineItem>(this)

@OneToMany(() => ShippingMethod, (shippingMethod) => shippingMethod.cart, {
orphanRemoval: true,
})
shipping_methods = new Collection<ShippingMethod>(this)

/** COMPUTED PROPERTIES - START */

// compare_at_item_total?: number
// compare_at_item_subtotal?: number
// compare_at_item_tax_total?: number

// original_item_total: number
// original_item_subtotal: number
// original_item_tax_total: number

// item_total: number
// item_subtotal: number
// item_tax_total: number

// original_total: number
// original_subtotal: number
// original_tax_total: number

// total: number
// subtotal: number
// tax_total: number
// discount_total: number
// discount_tax_total: number

// shipping_total: number
// shipping_subtotal: number
// shipping_tax_total: number

// original_shipping_total: number
// original_shipping_subtotal: number
// original_shipping_tax_total: number

/** COMPUTED PROPERTIES - END */

@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() {
Expand Down
11 changes: 10 additions & 1 deletion packages/cart/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
export { default as Cart } from "./cart";
export { default as Address } from "./address"
export { default as AdjustmentLine } from "./adjustment-line"
export { default as Cart } from "./cart"
export { default as LineItem } from "./line-item"
export { default as LineItemAdjustmentLine } from "./line-item-adjustment-line"
export { default as LineItemTaxLine } from "./line-item-tax-line"
export { default as ShippingMethod } from "./shipping-method"
export { default as ShippingMethodAdjustmentLine } from "./shipping-method-adjustment-line"
export { default as ShippingMethodTaxLine } from "./shipping-method-tax-line"
export { default as TaxLine } from "./tax-line"
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved

28 changes: 28 additions & 0 deletions packages/cart/src/models/line-item-adjustment-line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { generateEntityId } from "@medusajs/utils"
import {
BeforeCreate,
Entity,
ManyToOne,
OnInit
} from "@mikro-orm/core"
import AdjustmentLine from "./adjustment-line"
import LineItem from "./line-item"

@Entity({ tableName: "cart_line_item_adjustment_line" })
export default class LineItemAdjustmentLine extends AdjustmentLine {
olivermrbl marked this conversation as resolved.
Show resolved Hide resolved
@ManyToOne(() => LineItem, {
joinColumn: "line_item",
fieldName: "line_item_id",
})
line_item: LineItem

@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "caliadj")
}

@OnInit()
onInit() {
this.id = generateEntityId(this.id, "caliadj")
}
}
23 changes: 23 additions & 0 deletions packages/cart/src/models/line-item-tax-line.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { generateEntityId } from "@medusajs/utils"
import { BeforeCreate, Entity, ManyToOne, OnInit } from "@mikro-orm/core"
import LineItem from "./line-item"
import TaxLine from "./tax-line"

@Entity({ tableName: "cart_line_item_tax_line" })
export default class LineItemTaxLine extends TaxLine {
@ManyToOne(() => LineItem, {
joinColumn: "line_item",
fieldName: "line_item_id",
})
line_item: LineItem

@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "calitxl")
}

@OnInit()
onInit() {
this.id = generateEntityId(this.id, "calitxl")
}
}
Loading