feat(cart): Line item adjustments (#6112)

This commit is contained in:
Oli Juhl authored and GitHub committed 2024-01-22 09:55:47 +01:00
1 parent af7af73745
commit 06b33a9b45
21 files changed
+974 -71

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"@medusajs/types": patch
"@medusajs/utils": patch
---
feat(cart): Line item adjustments
@@ -799,4 +799,515 @@ describe("Cart Module Service", () => {
expect(cart.shipping_methods?.length).toBe(0) expect(cart.shipping_methods?.length).toBe(0)
}) })
}) })
describe("setLineItemAdjustments", () => {
it("should set line item adjustments for a cart", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const [itemTwo] = await service.addLineItems(createdCart.id, [
{
quantity: 2,
unit_price: 200,
title: "test-2",
},
])
const adjustments = await service.setLineItemAdjustments(createdCart.id, [
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
{
item_id: itemTwo.id,
amount: 200,
code: "FREE-2",
},
])
expect(adjustments).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 100,
code: "FREE",
}),
expect.objectContaining({
item_id: itemTwo.id,
amount: 200,
code: "FREE-2",
}),
])
)
})
it("should replace line item adjustments for a cart", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const adjustments = await service.setLineItemAdjustments(createdCart.id, [
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
])
expect(adjustments).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 100,
code: "FREE",
}),
])
)
await service.setLineItemAdjustments(createdCart.id, [
{
item_id: itemOne.id,
amount: 50,
code: "50%",
},
])
const cart = await service.retrieve(createdCart.id, {
relations: ["items.adjustments"],
})
expect(cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: itemOne.id,
adjustments: expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 50,
code: "50%",
}),
]),
}),
])
)
expect(cart.items?.length).toBe(1)
expect(cart.items?.[0].adjustments?.length).toBe(1)
})
it("should remove all line item adjustments for a cart", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const adjustments = await service.setLineItemAdjustments(createdCart.id, [
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
])
expect(adjustments).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 100,
code: "FREE",
}),
])
)
await service.setLineItemAdjustments(createdCart.id, [])
const cart = await service.retrieve(createdCart.id, {
relations: ["items.adjustments"],
})
expect(cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: itemOne.id,
adjustments: [],
}),
])
)
expect(cart.items?.length).toBe(1)
expect(cart.items?.[0].adjustments?.length).toBe(0)
})
it("should update line item adjustments for a cart", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const adjustments = await service.setLineItemAdjustments(createdCart.id, [
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
])
expect(adjustments).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 100,
code: "FREE",
}),
])
)
await service.setLineItemAdjustments(createdCart.id, [
{
id: adjustments[0].id,
amount: 50,
code: "50%",
},
])
const cart = await service.retrieve(createdCart.id, {
relations: ["items.adjustments"],
})
expect(cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: itemOne.id,
adjustments: [
expect.objectContaining({
id: adjustments[0].id,
item_id: itemOne.id,
amount: 50,
code: "50%",
}),
],
}),
])
)
expect(cart.items?.length).toBe(1)
expect(cart.items?.[0].adjustments?.length).toBe(1)
})
})
describe("addLineItemAdjustments", () => {
it("should add line item adjustments for items in a cart", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const adjustments = await service.addLineItemAdjustments(createdCart.id, [
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
])
expect(adjustments).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 100,
code: "FREE",
}),
])
)
})
it("should add multiple line item adjustments for multiple line items", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const [itemTwo] = await service.addLineItems(createdCart.id, [
{
quantity: 2,
unit_price: 200,
title: "test-2",
},
])
const adjustments = await service.addLineItemAdjustments(createdCart.id, [
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
{
item_id: itemTwo.id,
amount: 150,
code: "CODE-2",
},
])
expect(adjustments).toEqual(
expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 100,
code: "FREE",
}),
expect.objectContaining({
item_id: itemTwo.id,
amount: 150,
code: "CODE-2",
}),
])
)
})
it("should add line item adjustments for line items on multiple carts", async () => {
const [cartOne] = await service.create([
{
currency_code: "eur",
},
])
const [cartTwo] = await service.create([
{
currency_code: "usd",
},
])
const [itemOne] = await service.addLineItems(cartOne.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const [itemTwo] = await service.addLineItems(cartTwo.id, [
{
quantity: 2,
unit_price: 200,
title: "test-2",
},
])
await service.addLineItemAdjustments([
// item from cart one
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
// item from cart two
{
item_id: itemTwo.id,
amount: 150,
code: "CODE-2",
},
])
const cartOneItems = await service.listLineItems(
{ cart_id: cartOne.id },
{ relations: ["adjustments"] }
)
const cartTwoItems = await service.listLineItems(
{ cart_id: cartTwo.id },
{ relations: ["adjustments"] }
)
expect(cartOneItems).toEqual(
expect.arrayContaining([
expect.objectContaining({
adjustments: expect.arrayContaining([
expect.objectContaining({
item_id: itemOne.id,
amount: 100,
code: "FREE",
}),
]),
}),
])
)
expect(cartTwoItems).toEqual(
expect.arrayContaining([
expect.objectContaining({
adjustments: expect.arrayContaining([
expect.objectContaining({
item_id: itemTwo.id,
amount: 150,
code: "CODE-2",
}),
]),
}),
])
)
})
it("should throw if line item is not associated with cart", async () => {
const [cartOne] = await service.create([
{
currency_code: "eur",
},
])
const [cartTwo] = await service.create([
{
currency_code: "eur",
},
])
const [itemOne] = await service.addLineItems(cartOne.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const error = await service
.addLineItemAdjustments(cartTwo.id, [
{
item_id: itemOne.id,
amount: 100,
code: "FREE",
},
])
.catch((e) => e)
expect(error.message).toBe(
`Line item with id ${itemOne.id} does not exist on cart with id ${cartTwo.id}`
)
})
})
describe("removeLineItemAdjustments", () => {
it("should remove a line item succesfully", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [item] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const [adjustment] = await service.addLineItemAdjustments(
createdCart.id,
[
{
item_id: item.id,
amount: 50,
},
]
)
expect(adjustment.item_id).toBe(item.id)
await service.removeLineItemAdjustments(adjustment.id)
const adjustments = await service.listLineItemAdjustments({
item_id: item.id,
})
expect(adjustments?.length).toBe(0)
})
it("should remove a line item succesfully with selector", async () => {
const [createdCart] = await service.create([
{
currency_code: "eur",
},
])
const [item] = await service.addLineItems(createdCart.id, [
{
quantity: 1,
unit_price: 100,
title: "test",
},
])
const [adjustment] = await service.addLineItemAdjustments(
createdCart.id,
[
{
item_id: item.id,
amount: 50,
},
]
)
expect(adjustment.item_id).toBe(item.id)
await service.removeLineItemAdjustments({ item_id: item.id })
const adjustments = await service.listLineItemAdjustments({
item_id: item.id,
})
expect(adjustments?.length).toBe(0)
})
})
}) })
+2
View File
@@ -3,6 +3,8 @@ module.exports = {
"^@models": "<rootDir>/src/models", "^@models": "<rootDir>/src/models",
"^@services": "<rootDir>/src/services", "^@services": "<rootDir>/src/services",
"^@repositories": "<rootDir>/src/repositories", "^@repositories": "<rootDir>/src/repositories",
"^@types": "<rootDir>/src/types",
"^@utils": "<rootDir>/src/utils",
}, },
transform: { transform: {
"^.+\\.[jt]s?$": [ "^.+\\.[jt]s?$": [
+3 -2
View File
@@ -1,7 +1,7 @@
import { moduleDefinition } from "./module-definition"
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import * as Models from "@models"
import { ModulesSdkUtils } from "@medusajs/utils" import { ModulesSdkUtils } from "@medusajs/utils"
import * as Models from "@models"
import { moduleDefinition } from "./module-definition"
export default moduleDefinition export default moduleDefinition
@@ -20,3 +20,4 @@ export const revertMigration = ModulesSdkUtils.buildRevertMigrationScript(
export * from "./initialize" export * from "./initialize"
export * from "./loaders" export * from "./loaders"
+6 -6
View File
@@ -14,19 +14,19 @@ export default abstract class AdjustmentLine {
id: string id: string
@Property({ columnType: "text", nullable: true }) @Property({ columnType: "text", nullable: true })
description?: string | null description: string | null = null
@Property({ columnType: "text", nullable: true }) @Property({ columnType: "text", nullable: true })
promotion_id?: string | null promotion_id: string | null = null
@Property({ columnType: "text" }) @Property({ columnType: "text", nullable: true })
code: string code: string | null = null
@Property({ columnType: "numeric" }) @Property({ columnType: "numeric", serializer: Number })
amount: number amount: number
@Property({ columnType: "text", nullable: true }) @Property({ columnType: "text", nullable: true })
provider_id?: string | null provider_id: string | null = null
@Property({ @Property({
onCreate: () => new Date(), onCreate: () => new Date(),
+1 -1
View File
@@ -1,7 +1,7 @@
export { default as Address } from "./address" export { default as Address } from "./address"
export { default as Cart } from "./cart" export { default as Cart } from "./cart"
export { default as LineItem } from "./line-item" export { default as LineItem } from "./line-item"
export { default as LineItemAdjustmentLine } from "./line-item-adjustment-line" export { default as LineItemAdjustment } from "./line-item-adjustment"
export { default as LineItemTaxLine } from "./line-item-tax-line" export { default as LineItemTaxLine } from "./line-item-tax-line"
export { default as ShippingMethod } from "./shipping-method" export { default as ShippingMethod } from "./shipping-method"
export { default as ShippingMethodAdjustmentLine } from "./shipping-method-adjustment-line" export { default as ShippingMethodAdjustmentLine } from "./shipping-method-adjustment-line"
@@ -1,20 +1,29 @@
import { generateEntityId } from "@medusajs/utils" import { generateEntityId } from "@medusajs/utils"
import { import {
BeforeCreate, BeforeCreate,
Check,
Entity, Entity,
ManyToOne, ManyToOne,
OnInit OnInit,
Property,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import AdjustmentLine from "./adjustment-line" import AdjustmentLine from "./adjustment-line"
import LineItem from "./line-item" import LineItem from "./line-item"
@Entity({ tableName: "cart_line_item_adjustment_line" }) @Entity({ tableName: "cart_line_item_adjustment_line" })
export default class LineItemAdjustmentLine extends AdjustmentLine { @Check<LineItemAdjustment>({
expression: (columns) => `${columns.amount} >= 0`,
})
export default class LineItemAdjustment extends AdjustmentLine {
@ManyToOne(() => LineItem, { @ManyToOne(() => LineItem, {
joinColumn: "line_item", onDelete: "cascade",
fieldName: "line_item_id", nullable: true,
index: "IDX_adjustment_line_item_id",
}) })
line_item: LineItem item?: LineItem | null
@Property({ columnType: "text" })
item_id: string
@BeforeCreate() @BeforeCreate()
onCreate() { onCreate() {
+4 -4
View File
@@ -14,7 +14,7 @@ import {
Property, Property,
} from "@mikro-orm/core" } from "@mikro-orm/core"
import Cart from "./cart" import Cart from "./cart"
import LineItemAdjustmentLine from "./line-item-adjustment-line" import LineItemAdjustment from "./line-item-adjustment"
import LineItemTaxLine from "./line-item-tax-line" import LineItemTaxLine from "./line-item-tax-line"
type OptionalLineItemProps = type OptionalLineItemProps =
@@ -116,13 +116,13 @@ export default class LineItem {
tax_lines = new Collection<LineItemTaxLine>(this) tax_lines = new Collection<LineItemTaxLine>(this)
@OneToMany( @OneToMany(
() => LineItemAdjustmentLine, () => LineItemAdjustment,
(adjustment) => adjustment.line_item, (adjustment) => adjustment.item,
{ {
cascade: [Cascade.REMOVE], cascade: [Cascade.REMOVE],
} }
) )
adjustments = new Collection<LineItemAdjustmentLine>(this) adjustments = new Collection<LineItemAdjustment>(this)
/** COMPUTED PROPERTIES - START */ /** COMPUTED PROPERTIES - START */
+1 -1
View File
@@ -1,7 +1,7 @@
import { ModuleExports } from "@medusajs/types" import { ModuleExports } from "@medusajs/types"
import { CartModuleService } from "@services"
import loadConnection from "./loaders/connection" import loadConnection from "./loaders/connection"
import loadContainer from "./loaders/container" import loadContainer from "./loaders/container"
import { CartModuleService } from "./services"
const service = CartModuleService const service = CartModuleService
const loaders = [loadContainer, loadConnection] as any const loaders = [loadContainer, loadConnection] as any
+2 -1
View File
@@ -1 +1,2 @@
export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils" export { MikroOrmBaseRepository as BaseRepository } from "@medusajs/utils";
+215 -14
View File
@@ -1,23 +1,21 @@
import { import {
CartTypes,
Context, Context,
DAL, DAL,
FilterableCartProps,
FindConfig, FindConfig,
ICartModuleService, ICartModuleService,
InternalModuleDeclaration, InternalModuleDeclaration,
ModuleJoinerConfig, ModuleJoinerConfig,
} from "@medusajs/types" } from "@medusajs/types"
import { CartTypes } from "@medusajs/types"
import { import {
InjectManager, InjectManager,
InjectTransactionManager, InjectTransactionManager,
MedusaContext, MedusaContext,
MedusaError,
isObject, isObject,
isString, isString,
} from "@medusajs/utils" } from "@medusajs/utils"
import { Cart, LineItem, ShippingMethod } from "@models" import { Cart, LineItem, LineItemAdjustment, ShippingMethod } from "@models"
import { CreateLineItemDTO, UpdateLineItemDTO } from "@types" import { CreateLineItemDTO, UpdateLineItemDTO } from "@types"
import { joinerConfig } from "../joiner-config" import { joinerConfig } from "../joiner-config"
import * as services from "../services" import * as services from "../services"
@@ -28,6 +26,7 @@ type InjectedDependencies = {
addressService: services.AddressService addressService: services.AddressService
lineItemService: services.LineItemService lineItemService: services.LineItemService
shippingMethodService: services.ShippingMethodService shippingMethodService: services.ShippingMethodService
lineItemAdjustmentService: services.LineItemAdjustmentService
} }
export default class CartModuleService implements ICartModuleService { export default class CartModuleService implements ICartModuleService {
@@ -36,6 +35,7 @@ export default class CartModuleService implements ICartModuleService {
protected addressService_: services.AddressService protected addressService_: services.AddressService
protected lineItemService_: services.LineItemService protected lineItemService_: services.LineItemService
protected shippingMethodService_: services.ShippingMethodService protected shippingMethodService_: services.ShippingMethodService
protected lineItemAdjustmentService_: services.LineItemAdjustmentService
constructor( constructor(
{ {
@@ -44,6 +44,7 @@ export default class CartModuleService implements ICartModuleService {
addressService, addressService,
lineItemService, lineItemService,
shippingMethodService, shippingMethodService,
lineItemAdjustmentService,
}: InjectedDependencies, }: InjectedDependencies,
protected readonly moduleDeclaration: InternalModuleDeclaration protected readonly moduleDeclaration: InternalModuleDeclaration
) { ) {
@@ -52,6 +53,7 @@ export default class CartModuleService implements ICartModuleService {
this.addressService_ = addressService this.addressService_ = addressService
this.lineItemService_ = lineItemService this.lineItemService_ = lineItemService
this.shippingMethodService_ = shippingMethodService this.shippingMethodService_ = shippingMethodService
this.lineItemAdjustmentService_ = lineItemAdjustmentService
} }
__joinerConfig(): ModuleJoinerConfig { __joinerConfig(): ModuleJoinerConfig {
@@ -86,7 +88,7 @@ export default class CartModuleService implements ICartModuleService {
@InjectManager("baseRepository_") @InjectManager("baseRepository_")
async listAndCount( async listAndCount(
filters: FilterableCartProps = {}, filters: CartTypes.FilterableCartProps = {},
config: FindConfig<CartTypes.CartDTO> = {}, config: FindConfig<CartTypes.CartDTO> = {},
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<[CartTypes.CartDTO[], number]> { ): Promise<[CartTypes.CartDTO[], number]> {
@@ -244,14 +246,14 @@ export default class CartModuleService implements ICartModuleService {
itemId: string, itemId: string,
config: FindConfig<CartTypes.CartLineItemDTO> = {}, config: FindConfig<CartTypes.CartLineItemDTO> = {},
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
) { ): Promise<CartTypes.CartLineItemDTO> {
const item = await this.lineItemService_.retrieve( const item = await this.lineItemService_.retrieve(
itemId, itemId,
config, config,
sharedContext sharedContext
) )
return await this.baseRepository_.serialize<CartTypes.CartLineItemDTO[]>( return await this.baseRepository_.serialize<CartTypes.CartLineItemDTO>(
item, item,
{ {
populate: true, populate: true,
@@ -300,7 +302,7 @@ export default class CartModuleService implements ICartModuleService {
addLineItems( addLineItems(
data: CartTypes.CreateLineItemForCartDTO data: CartTypes.CreateLineItemForCartDTO
): Promise<CartTypes.CartLineItemDTO> ): Promise<CartTypes.CartLineItemDTO[]>
addLineItems( addLineItems(
data: CartTypes.CreateLineItemForCartDTO[] data: CartTypes.CreateLineItemForCartDTO[]
): Promise<CartTypes.CartLineItemDTO[]> ): Promise<CartTypes.CartLineItemDTO[]>
@@ -318,7 +320,7 @@ export default class CartModuleService implements ICartModuleService {
| CartTypes.CreateLineItemForCartDTO, | CartTypes.CreateLineItemForCartDTO,
data?: CartTypes.CreateLineItemDTO[] | CartTypes.CreateLineItemDTO, data?: CartTypes.CreateLineItemDTO[] | CartTypes.CreateLineItemDTO,
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.CartLineItemDTO[] | CartTypes.CartLineItemDTO> { ): Promise<CartTypes.CartLineItemDTO[]> {
let items: LineItem[] = [] let items: LineItem[] = []
if (isString(cartIdOrData)) { if (isString(cartIdOrData)) {
items = await this.addLineItems_( items = await this.addLineItems_(
@@ -592,7 +594,9 @@ export default class CartModuleService implements ICartModuleService {
| string | string
| CartTypes.CreateShippingMethodDTO[] | CartTypes.CreateShippingMethodDTO[]
| CartTypes.CreateShippingMethodDTO, | CartTypes.CreateShippingMethodDTO,
data?: CartTypes.CreateShippingMethodDTO[], data?:
| CartTypes.CreateShippingMethodDTO[]
| CartTypes.CreateShippingMethodForSingleCartDTO[],
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise< ): Promise<
CartTypes.CartShippingMethodDTO[] | CartTypes.CartShippingMethodDTO CartTypes.CartShippingMethodDTO[] | CartTypes.CartShippingMethodDTO
@@ -601,12 +605,15 @@ export default class CartModuleService implements ICartModuleService {
if (isString(cartIdOrData)) { if (isString(cartIdOrData)) {
methods = await this.addShippingMethods_( methods = await this.addShippingMethods_(
cartIdOrData, cartIdOrData,
data as CartTypes.CreateShippingMethodDTO[], data as CartTypes.CreateShippingMethodForSingleCartDTO[],
sharedContext sharedContext
) )
} else { } else {
const data = Array.isArray(cartIdOrData) ? cartIdOrData : [cartIdOrData] const data = Array.isArray(cartIdOrData) ? cartIdOrData : [cartIdOrData]
methods = await this.addShippingMethodsBulk_(data, sharedContext) methods = await this.addShippingMethodsBulk_(
data as CartTypes.CreateShippingMethodDTO[],
sharedContext
)
} }
return await this.baseRepository_.serialize< return await this.baseRepository_.serialize<
@@ -619,7 +626,7 @@ export default class CartModuleService implements ICartModuleService {
@InjectTransactionManager("baseRepository_") @InjectTransactionManager("baseRepository_")
protected async addShippingMethods_( protected async addShippingMethods_(
cartId: string, cartId: string,
data: CartTypes.CreateShippingMethodDTO[], data: CartTypes.CreateShippingMethodForSingleCartDTO[],
@MedusaContext() sharedContext: Context = {} @MedusaContext() sharedContext: Context = {}
): Promise<ShippingMethod[]> { ): Promise<ShippingMethod[]> {
const cart = await this.retrieve(cartId, { select: ["id"] }, sharedContext) const cart = await this.retrieve(cartId, { select: ["id"] }, sharedContext)
@@ -681,4 +688,198 @@ export default class CartModuleService implements ICartModuleService {
} }
await this.shippingMethodService_.delete(toDelete, sharedContext) await this.shippingMethodService_.delete(toDelete, sharedContext)
} }
@InjectManager("baseRepository_")
async listLineItemAdjustments(
filters: CartTypes.FilterableLineItemAdjustmentProps = {},
config: FindConfig<CartTypes.LineItemAdjustmentDTO> = {},
@MedusaContext() sharedContext: Context = {}
) {
const adjustments = await this.lineItemAdjustmentService_.list(
filters,
config,
sharedContext
)
return await this.baseRepository_.serialize<
CartTypes.LineItemAdjustmentDTO[]
>(adjustments, {
populate: true,
})
}
async addLineItemAdjustments(
adjustments: CartTypes.CreateLineItemAdjustmentDTO[]
): Promise<CartTypes.LineItemAdjustmentDTO[]>
async addLineItemAdjustments(
adjustment: CartTypes.CreateLineItemAdjustmentDTO
): Promise<CartTypes.LineItemAdjustmentDTO[]>
async addLineItemAdjustments(
cartId: string,
adjustments: CartTypes.CreateLineItemAdjustmentDTO[],
sharedContext?: Context
): Promise<CartTypes.LineItemAdjustmentDTO[]>
@InjectTransactionManager("baseRepository_")
async addLineItemAdjustments(
cartIdOrData:
| string
| CartTypes.CreateLineItemAdjustmentDTO[]
| CartTypes.CreateLineItemAdjustmentDTO,
adjustments?: CartTypes.CreateLineItemAdjustmentDTO[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.LineItemAdjustmentDTO[]> {
let addedAdjustments: LineItemAdjustment[] = []
if (isString(cartIdOrData)) {
const cart = await this.retrieve(
cartIdOrData,
{ select: ["id"], relations: ["items"] },
sharedContext
)
const lineIds = cart.items?.map((item) => item.id)
for (const adj of adjustments || []) {
if (!lineIds?.includes(adj.item_id)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Line item with id ${adj.item_id} does not exist on cart with id ${cartIdOrData}`
)
}
}
addedAdjustments = await this.lineItemAdjustmentService_.create(
adjustments as CartTypes.CreateLineItemAdjustmentDTO[],
sharedContext
)
} else {
const data = Array.isArray(cartIdOrData) ? cartIdOrData : [cartIdOrData]
addedAdjustments = await this.lineItemAdjustmentService_.create(
data as CartTypes.CreateLineItemAdjustmentDTO[],
sharedContext
)
}
return await this.baseRepository_.serialize<
CartTypes.LineItemAdjustmentDTO[]
>(addedAdjustments, {
populate: true,
})
}
@InjectTransactionManager("baseRepository_")
async setLineItemAdjustments(
cartId: string,
adjustments: (
| CartTypes.CreateLineItemAdjustmentDTO
| CartTypes.UpdateLineItemAdjustmentDTO
)[],
@MedusaContext() sharedContext: Context = {}
): Promise<CartTypes.LineItemAdjustmentDTO[]> {
const cart = await this.retrieve(
cartId,
{ select: ["id"], relations: ["items.adjustments"] },
sharedContext
)
const lineIds = cart.items?.map((item) => item.id)
const existingAdjustments = await this.listLineItemAdjustments(
{ item_id: lineIds },
{ select: ["id"] },
sharedContext
)
let toUpdate: CartTypes.UpdateLineItemAdjustmentDTO[] = []
let toCreate: CartTypes.CreateLineItemAdjustmentDTO[] = []
for (const adj of adjustments) {
if ("id" in adj) {
toUpdate.push(adj as CartTypes.UpdateLineItemAdjustmentDTO)
} else {
toCreate.push(adj as CartTypes.CreateLineItemAdjustmentDTO)
}
}
const adjustmentsSet = new Set(toUpdate.map((a) => a.id))
const toDelete: CartTypes.LineItemAdjustmentDTO[] = []
// From the existing adjustments, find the ones that are not passed in adjustments
existingAdjustments.forEach((adj: CartTypes.LineItemAdjustmentDTO) => {
if (!adjustmentsSet.has(adj.id)) {
toDelete.push(adj)
}
})
await this.lineItemAdjustmentService_.delete(
toDelete.map((adj) => adj!.id),
sharedContext
)
let result: LineItemAdjustment[] = []
// TODO: Replace the following two calls with a single bulk upsert call
if (toUpdate?.length) {
const updated = await this.lineItemAdjustmentService_.update(
toUpdate,
sharedContext
)
result.push(...updated)
}
if (toCreate?.length) {
const created = await this.lineItemAdjustmentService_.create(
toCreate,
sharedContext
)
result.push(...created)
}
return await this.baseRepository_.serialize<
CartTypes.LineItemAdjustmentDTO[]
>(result, {
populate: true,
})
}
async removeLineItemAdjustments(
adjustmentIds: string[],
sharedContext?: Context
): Promise<void>
async removeLineItemAdjustments(
adjustmentId: string,
sharedContext?: Context
): Promise<void>
async removeLineItemAdjustments(
selector: Partial<CartTypes.LineItemAdjustmentDTO>,
sharedContext?: Context
): Promise<void>
async removeLineItemAdjustments(
adjustmentIdsOrSelector:
| string
| string[]
| Partial<CartTypes.LineItemAdjustmentDTO>,
@MedusaContext() sharedContext: Context = {}
): Promise<void> {
let ids: string[] = []
if (isObject(adjustmentIdsOrSelector)) {
const adjustments = await this.listLineItemAdjustments(
{
...adjustmentIdsOrSelector,
} as Partial<CartTypes.LineItemAdjustmentDTO>,
{ select: ["id"] },
sharedContext
)
ids = adjustments.map((adj) => adj.id)
} else {
ids = Array.isArray(adjustmentIdsOrSelector)
? adjustmentIdsOrSelector
: [adjustmentIdsOrSelector]
}
await this.lineItemAdjustmentService_.delete(ids, sharedContext)
}
} }
+1
View File
@@ -2,5 +2,6 @@ export { default as AddressService } from "./address"
export { default as CartService } from "./cart" export { default as CartService } from "./cart"
export { default as CartModuleService } from "./cart-module" export { default as CartModuleService } from "./cart-module"
export { default as LineItemService } from "./line-item" export { default as LineItemService } from "./line-item"
export { default as LineItemAdjustmentService } from "./line-item-adjustment"
export { default as ShippingMethodService } from "./shipping-method" export { default as ShippingMethodService } from "./shipping-method"
@@ -0,0 +1,26 @@
import { DAL } from "@medusajs/types"
import { ModulesSdkUtils } from "@medusajs/utils"
import { LineItemAdjustment } from "@models"
import {
CreateLineItemAdjustmentDTO,
UpdateLineItemAdjustmentDTO,
} from "@types"
type InjectedDependencies = {
lineItemAdjustmentRepository: DAL.RepositoryService
}
export default class LineItemAdjustmentService<
TEntity extends LineItemAdjustment = LineItemAdjustment
> extends ModulesSdkUtils.abstractServiceFactory<
InjectedDependencies,
{
create: CreateLineItemAdjustmentDTO
update: UpdateLineItemAdjustmentDTO
}
>(LineItemAdjustment)<TEntity> {
constructor(container: InjectedDependencies) {
// @ts-ignore
super(...arguments)
}
}
+7
View File
@@ -1,3 +1,8 @@
import {
CreateLineItemAdjustmentDTO,
UpdateLineItemAdjustmentDTO,
} from "./line-item-adjustment"
export interface CreateCartDTO { export interface CreateCartDTO {
region_id?: string region_id?: string
customer_id?: string customer_id?: string
@@ -15,4 +20,6 @@ export interface UpdateCartDTO {
email?: string email?: string
currency_code?: string currency_code?: string
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
adjustments?: (CreateLineItemAdjustmentDTO | UpdateLineItemAdjustmentDTO)[]
} }
+1
View File
@@ -3,6 +3,7 @@ import { Logger } from "@medusajs/types"
export * from "./address" export * from "./address"
export * from "./cart" export * from "./cart"
export * from "./line-item" export * from "./line-item"
export * from "./line-item-adjustment"
export * from "./shipping-method" export * from "./shipping-method"
export * from "./repositories" export * from "./repositories"
@@ -0,0 +1,13 @@
export interface CreateLineItemAdjustmentDTO {
item_id: string
amount: number
code?: string
description?: string
promotion_id?: string
provider_id?: string
}
export interface UpdateLineItemAdjustmentDTO
extends Partial<CreateLineItemAdjustmentDTO> {
id: string
}
+15 -1
View File
@@ -1,8 +1,12 @@
import { DAL } from "@medusajs/types" import { DAL } from "@medusajs/types"
import { Cart, LineItem, ShippingMethod } from "@models" import { Cart, LineItem, LineItemAdjustment, ShippingMethod } from "@models"
import { CreateAddressDTO, UpdateAddressDTO } from "./address" import { CreateAddressDTO, UpdateAddressDTO } from "./address"
import { CreateCartDTO, UpdateCartDTO } from "./cart" import { CreateCartDTO, UpdateCartDTO } from "./cart"
import { CreateLineItemDTO, UpdateLineItemDTO } from "./line-item" import { CreateLineItemDTO, UpdateLineItemDTO } from "./line-item"
import {
CreateLineItemAdjustmentDTO,
UpdateLineItemAdjustmentDTO,
} from "./line-item-adjustment"
import { import {
CreateShippingMethodDTO, CreateShippingMethodDTO,
UpdateShippingMethodDTO, UpdateShippingMethodDTO,
@@ -48,3 +52,13 @@ export interface IShippingMethodRepository<
update: UpdateShippingMethodDTO update: UpdateShippingMethodDTO
} }
> {} > {}
export interface ILineItemAdjustmentRepository<
TEntity extends LineItemAdjustment = LineItemAdjustment
> extends DAL.RepositoryService<
TEntity,
{
create: CreateLineItemAdjustmentDTO
update: UpdateLineItemAdjustmentDTO
}
> {}
+20 -4
View File
@@ -9,11 +9,15 @@ export interface AdjustmentLineDTO {
/** /**
* The code of the adjustment line * The code of the adjustment line
*/ */
code: string code?: string
/** /**
* The amount of the adjustment line * The amount of the adjustment line
*/ */
amount: number amount: number
/**
* The ID of the associated cart
*/
cart_id: string
/** /**
* The description of the adjustment line * The description of the adjustment line
*/ */
@@ -43,11 +47,16 @@ export interface ShippingMethodAdjustmentLineDTO extends AdjustmentLineDTO {
shipping_method: CartShippingMethodDTO shipping_method: CartShippingMethodDTO
} }
export interface LineItemAdjustmentLineDTO extends AdjustmentLineDTO { export interface LineItemAdjustmentDTO extends AdjustmentLineDTO {
/**
* The associated line item
* @expandable
*/
item: CartLineItemDTO
/** /**
* The associated line item * The associated line item
*/ */
line_item: CartLineItemDTO item_id: string
} }
export interface TaxLineDTO { export interface TaxLineDTO {
@@ -340,7 +349,7 @@ export interface CartLineItemDTO {
* *
* @expandable * @expandable
*/ */
adjustments?: LineItemAdjustmentLineDTO[] adjustments?: LineItemAdjustmentDTO[]
/** /**
* The associated cart. * The associated cart.
* *
@@ -494,6 +503,13 @@ export interface FilterableLineItemProps
product_id?: string | string[] product_id?: string | string[]
} }
export interface FilterableLineItemAdjustmentProps
extends BaseFilterable<FilterableLineItemAdjustmentProps> {
id?: string | string[]
item_id?: string | string[]
promotion_id?: string | string[]
provider_id?: string | string[]
}
export interface FilterableShippingMethodProps export interface FilterableShippingMethodProps
extends BaseFilterable<FilterableShippingMethodProps> { extends BaseFilterable<FilterableShippingMethodProps> {
id?: string | string[] id?: string | string[]
+54 -13
View File
@@ -1,5 +1,6 @@
import { CartLineItemDTO } from "./common" import { CartLineItemDTO } from "./common"
/** ADDRESS START */
export interface UpsertAddressDTO { export interface UpsertAddressDTO {
customer_id?: string customer_id?: string
company?: string company?: string
@@ -21,6 +22,9 @@ export interface UpdateAddressDTO extends UpsertAddressDTO {
export interface CreateAddressDTO extends UpsertAddressDTO {} export interface CreateAddressDTO extends UpsertAddressDTO {}
/** ADDRESS END */
/** CART START */
export interface CreateCartDTO { export interface CreateCartDTO {
region_id?: string region_id?: string
customer_id?: string customer_id?: string
@@ -54,6 +58,9 @@ export interface UpdateCartDTO {
metadata?: Record<string, unknown> metadata?: Record<string, unknown>
} }
/** CART END */
/** TAXLINES START */
export interface CreateTaxLineDTO { export interface CreateTaxLineDTO {
description?: string description?: string
tax_rate_id?: string tax_rate_id?: string
@@ -62,14 +69,6 @@ export interface CreateTaxLineDTO {
provider_id?: string provider_id?: string
} }
export interface CreateAdjustmentDTO {
code: string
amount: number
description?: string
promotion_id?: string
provider_id?: string
}
export interface UpdateTaxLineDTO { export interface UpdateTaxLineDTO {
id: string id: string
description?: string description?: string
@@ -79,15 +78,48 @@ export interface UpdateTaxLineDTO {
provider_id?: string provider_id?: string
} }
/** TAXLINES END */
/** ADJUSTMENT START */
export interface CreateAdjustmentDTO {
item_id: string
code?: string
amount: number
description?: string
promotion_id?: string
provider_id?: string
}
export interface UpdateAdjustmentDTO { export interface UpdateAdjustmentDTO {
id: string id: string
code?: string code?: string
amount: number
description?: string
promotion_id?: string
provider_id?: string
}
export interface CreateLineItemAdjustmentDTO extends CreateAdjustmentDTO {
item_id: string
}
export interface UpdateLineItemAdjustmentDTO extends UpdateAdjustmentDTO {
item_id: string
}
export interface UpsertLineItemAdjustmentDTO {
id?: string
item_id: string
code?: string
amount?: number amount?: number
description?: string description?: string
promotion_id?: string promotion_id?: string
provider_id?: string provider_id?: string
} }
/** ADJUSTMENT START */
/** LINE ITEMS START */
export interface CreateLineItemDTO { export interface CreateLineItemDTO {
title: string title: string
subtitle?: string subtitle?: string
@@ -146,14 +178,23 @@ export interface UpdateLineItemDTO
adjustments?: UpdateAdjustmentDTO[] | CreateAdjustmentDTO[] adjustments?: UpdateAdjustmentDTO[] | CreateAdjustmentDTO[]
} }
/** LINE ITEMS END */
/** SHIPPING METHODS START */
export interface CreateShippingMethodDTO { export interface CreateShippingMethodDTO {
name: string name: string
cart_id: string cart_id: string
amount: number amount: number
data?: Record<string, unknown> data?: Record<string, unknown>
tax_lines?: CreateTaxLineDTO[]
adjustments?: CreateAdjustmentDTO[]
}
export interface CreateShippingMethodForSingleCartDTO {
name: string
amount: number
data?: Record<string, unknown>
tax_lines?: CreateTaxLineDTO[] tax_lines?: CreateTaxLineDTO[]
adjustments?: CreateAdjustmentDTO[] adjustments?: CreateAdjustmentDTO[]
} }
@@ -161,10 +202,10 @@ export interface CreateShippingMethodDTO {
export interface UpdateShippingMethodDTO { export interface UpdateShippingMethodDTO {
id: string id: string
name?: string name?: string
amount?: number amount?: number
data?: Record<string, unknown> data?: Record<string, unknown>
tax_lines?: UpdateTaxLineDTO[] | CreateTaxLineDTO[] tax_lines?: UpdateTaxLineDTO[] | CreateTaxLineDTO[]
adjustments?: UpdateAdjustmentDTO[] | CreateAdjustmentDTO[] adjustments?: CreateAdjustmentDTO[] | UpdateAdjustmentDTO[]
} }
/** SHIPPING METHODS END */
+70 -17
View File
@@ -2,24 +2,29 @@ import { FindConfig } from "../common"
import { IModuleService } from "../modules-sdk" import { IModuleService } from "../modules-sdk"
import { Context } from "../shared-context" import { Context } from "../shared-context"
import { import {
CartAddressDTO, CartAddressDTO,
CartDTO, CartDTO,
CartLineItemDTO, CartLineItemDTO,
CartShippingMethodDTO, CartShippingMethodDTO,
FilterableAddressProps, FilterableAddressProps,
FilterableCartProps, FilterableCartProps,
FilterableShippingMethodProps, FilterableLineItemAdjustmentProps,
FilterableLineItemProps,
FilterableShippingMethodProps,
LineItemAdjustmentDTO,
} from "./common" } from "./common"
import { import {
CreateAddressDTO, CreateAddressDTO,
CreateCartDTO, CreateAdjustmentDTO,
CreateLineItemDTO, CreateCartDTO,
CreateLineItemForCartDTO, CreateLineItemDTO,
CreateShippingMethodDTO, CreateLineItemForCartDTO,
UpdateAddressDTO, CreateShippingMethodDTO,
UpdateCartDTO, UpdateAddressDTO,
UpdateLineItemDTO, UpdateCartDTO,
UpdateLineItemWithSelectorDTO, UpdateLineItemDTO,
UpdateLineItemWithSelectorDTO,
UpsertLineItemAdjustmentDTO
} from "./mutations" } from "./mutations"
export interface ICartModuleService extends IModuleService { export interface ICartModuleService extends IModuleService {
@@ -77,7 +82,19 @@ export interface ICartModuleService extends IModuleService {
deleteAddresses(ids: string[], sharedContext?: Context): Promise<void> deleteAddresses(ids: string[], sharedContext?: Context): Promise<void>
deleteAddresses(ids: string, sharedContext?: Context): Promise<void> deleteAddresses(ids: string, sharedContext?: Context): Promise<void>
addLineItems(data: CreateLineItemForCartDTO): Promise<CartLineItemDTO> retrieveLineItem(
itemId: string,
config?: FindConfig<CartLineItemDTO>,
sharedContext?: Context
): Promise<CartLineItemDTO>
listLineItems(
filters: FilterableLineItemProps,
config?: FindConfig<CartLineItemDTO>,
sharedContext?: Context
): Promise<CartLineItemDTO[]>
addLineItems(data: CreateLineItemForCartDTO): Promise<CartLineItemDTO[]>
addLineItems(data: CreateLineItemForCartDTO[]): Promise<CartLineItemDTO[]> addLineItems(data: CreateLineItemForCartDTO[]): Promise<CartLineItemDTO[]>
addLineItems( addLineItems(
cartId: string, cartId: string,
@@ -136,4 +153,40 @@ export interface ICartModuleService extends IModuleService {
selector: Partial<CartShippingMethodDTO>, selector: Partial<CartShippingMethodDTO>,
sharedContext?: Context sharedContext?: Context
): Promise<void> ): Promise<void>
listLineItemAdjustments(
filters: FilterableLineItemAdjustmentProps,
config?: FindConfig<LineItemAdjustmentDTO>,
sharedContext?: Context
): Promise<LineItemAdjustmentDTO[]>
addLineItemAdjustments(
data: CreateAdjustmentDTO[]
): Promise<LineItemAdjustmentDTO[]>
addLineItemAdjustments(
data: CreateAdjustmentDTO
): Promise<LineItemAdjustmentDTO[]>
addLineItemAdjustments(
cartId: string,
data: CreateAdjustmentDTO[]
): Promise<LineItemAdjustmentDTO[]>
setLineItemAdjustments(
cartId: string,
data: UpsertLineItemAdjustmentDTO[],
sharedContext?: Context
): Promise<LineItemAdjustmentDTO[]>
removeLineItemAdjustments(
adjustmentIds: string[],
sharedContext?: Context
): Promise<void>
removeLineItemAdjustments(
adjustmentIds: string,
sharedContext?: Context
): Promise<void>
removeLineItemAdjustments(
selector: Partial<LineItemAdjustmentDTO>,
sharedContext?: Context
): Promise<void>
} }
@@ -1,16 +1,16 @@
import { import {
Context, Context,
FilterQuery as InternalFilterQuery,
FindConfig, FindConfig,
FilterQuery as InternalFilterQuery,
} from "@medusajs/types" } from "@medusajs/types"
import { EntitySchema } from "@mikro-orm/core" import { EntitySchema } from "@mikro-orm/core"
import { EntityClass } from "@mikro-orm/core/typings" import { EntityClass } from "@mikro-orm/core/typings"
import { import {
MedusaError,
doNotForceTransaction, doNotForceTransaction,
isDefined, isDefined,
isString, isString,
lowerCaseFirst, lowerCaseFirst,
MedusaError,
shouldForceTransaction, shouldForceTransaction,
upperCaseFirst, upperCaseFirst,
} from "../common" } from "../common"