diff --git a/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts b/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts index d07367ecb1523..fc6a2033a134e 100644 --- a/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts +++ b/packages/cart/integration-tests/__tests__/services/cart-module/index.spec.ts @@ -1200,42 +1200,6 @@ describe("Cart Module Service", () => { ]) ) }) - - 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", () => { @@ -1860,4 +1824,566 @@ describe("Cart Module Service", () => { expect(adjustments?.length).toBe(0) }) }) + + describe("setLineItemTaxLines", () => { + it("should set line item tax lines 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 taxLines = await service.setLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + { + item_id: itemTwo.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + expect.objectContaining({ + item_id: itemTwo.id, + rate: 20, + code: "TX", + }), + ]) + ) + }) + + it("should replace line item tax lines 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 taxLines = await service.setLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + ]) + ) + + await service.setLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 25, + code: "TX-2", + }, + ]) + + const cart = await service.retrieve(createdCart.id, { + relations: ["items.tax_lines"], + }) + + expect(cart.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: itemOne.id, + tax_lines: expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 25, + code: "TX-2", + }), + ]), + }), + ]) + ) + + expect(cart.items?.length).toBe(1) + expect(cart.items?.[0].tax_lines?.length).toBe(1) + }) + + it("should remove all line item tax lines 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 taxLines = await service.setLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + ]) + ) + + await service.setLineItemTaxLines(createdCart.id, []) + + const cart = await service.retrieve(createdCart.id, { + relations: ["items.tax_lines"], + }) + + expect(cart.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: itemOne.id, + tax_lines: [], + }), + ]) + ) + + expect(cart.items?.length).toBe(1) + expect(cart.items?.[0].tax_lines?.length).toBe(0) + }) + + it("should update line item tax lines 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 taxLines = await service.setLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + ]) + ) + + await service.setLineItemTaxLines(createdCart.id, [ + { + id: taxLines[0].id, + item_id: itemOne.id, + rate: 25, + code: "TX", + }, + ]) + + const cart = await service.retrieve(createdCart.id, { + relations: ["items.tax_lines"], + }) + + expect(cart.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: itemOne.id, + tax_lines: [ + expect.objectContaining({ + id: taxLines[0].id, + item_id: itemOne.id, + rate: 25, + code: "TX", + }), + ], + }), + ]) + ) + + expect(cart.items?.length).toBe(1) + expect(cart.items?.[0].tax_lines?.length).toBe(1) + }) + + it("should remove, update, and create line item tax lines 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 taxLines = await service.setLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + { + item_id: itemOne.id, + rate: 25, + code: "TX", + }, + ]) + + expect(taxLines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + expect.objectContaining({ + item_id: itemOne.id, + rate: 25, + code: "TX", + }), + ]) + ) + + const taxLine = taxLines.find((tx) => tx.item_id === itemOne.id) + + await service.setLineItemTaxLines(createdCart.id, [ + // update + { + id: taxLine.id, + rate: 40, + code: "TX", + }, + // create + { + item_id: itemOne.id, + rate: 25, + code: "TX-2", + }, + // remove: should remove the initial tax line for itemOne + ]) + + const cart = await service.retrieve(createdCart.id, { + relations: ["items.tax_lines"], + }) + + expect(cart.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: itemOne.id, + tax_lines: [ + expect.objectContaining({ + id: taxLine!.id, + item_id: itemOne.id, + rate: 40, + code: "TX", + }), + expect.objectContaining({ + item_id: itemOne.id, + rate: 25, + code: "TX-2", + }), + ], + }), + ]) + ) + + expect(cart.items?.length).toBe(1) + expect(cart.items?.[0].tax_lines?.length).toBe(2) + }) + }) + + describe("addLineItemAdjustments", () => { + it("should add line item tax lines 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 taxLines = await service.addLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + ]) + ) + }) + + it("should add multiple line item tax lines 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 taxLines = await service.addLineItemTaxLines(createdCart.id, [ + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + { + item_id: itemTwo.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLines).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + expect.objectContaining({ + item_id: itemTwo.id, + rate: 20, + code: "TX", + }), + ]) + ) + }) + + it("should add line item tax lines 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.addLineItemTaxLines([ + // item from cart one + { + item_id: itemOne.id, + rate: 20, + code: "TX", + }, + // item from cart two + { + item_id: itemTwo.id, + rate: 25, + code: "TX-2", + }, + ]) + + const cartOneItems = await service.listLineItems( + { cart_id: cartOne.id }, + { relations: ["tax_lines"] } + ) + const cartTwoItems = await service.listLineItems( + { cart_id: cartTwo.id }, + { relations: ["tax_lines"] } + ) + + expect(cartOneItems).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tax_lines: expect.arrayContaining([ + expect.objectContaining({ + item_id: itemOne.id, + rate: 20, + code: "TX", + }), + ]), + }), + ]) + ) + expect(cartTwoItems).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + tax_lines: expect.arrayContaining([ + expect.objectContaining({ + item_id: itemTwo.id, + rate: 25, + code: "TX-2", + }), + ]), + }), + ]) + ) + }) + }) + + describe("removeLineItemAdjustments", () => { + it("should remove line item tax line 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 [taxLine] = await service.addLineItemTaxLines(createdCart.id, [ + { + item_id: item.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLine.item_id).toBe(item.id) + + await service.removeLineItemTaxLines(taxLine.id) + + const taxLines = await service.listLineItemTaxLines({ + item_id: item.id, + }) + + expect(taxLines?.length).toBe(0) + }) + + it("should remove line item tax lines 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 [taxLine] = await service.addLineItemTaxLines(createdCart.id, [ + { + item_id: item.id, + rate: 20, + code: "TX", + }, + ]) + + expect(taxLine.item_id).toBe(item.id) + + await service.removeLineItemTaxLines({ item_id: item.id }) + + const taxLines = await service.listLineItemTaxLines({ + item_id: item.id, + }) + + expect(taxLines?.length).toBe(0) + }) + }) }) diff --git a/packages/cart/src/models/line-item-tax-line.ts b/packages/cart/src/models/line-item-tax-line.ts index 7fa461d87c204..149d767fec51a 100644 --- a/packages/cart/src/models/line-item-tax-line.ts +++ b/packages/cart/src/models/line-item-tax-line.ts @@ -1,15 +1,25 @@ import { generateEntityId } from "@medusajs/utils" -import { BeforeCreate, Entity, ManyToOne, OnInit } from "@mikro-orm/core" +import { + BeforeCreate, + Entity, + ManyToOne, + OnInit, + Property, +} 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", + onDelete: "cascade", + nullable: true, + index: "IDX_tax_line_item_id", }) - line_item: LineItem + item: LineItem | null + + @Property({ columnType: "text" }) + item_id: string @BeforeCreate() onCreate() { diff --git a/packages/cart/src/models/line-item.ts b/packages/cart/src/models/line-item.ts index 4ed2f897196be..ada1d29e3f782 100644 --- a/packages/cart/src/models/line-item.ts +++ b/packages/cart/src/models/line-item.ts @@ -110,18 +110,14 @@ export default class LineItem { @Check({ expression: "unit_price >= 0" }) // TODO: Validate that numeric types work with the expression unit_price: number - @OneToMany(() => LineItemTaxLine, (taxLine) => taxLine.line_item, { + @OneToMany(() => LineItemTaxLine, (taxLine) => taxLine.item, { cascade: [Cascade.REMOVE], }) tax_lines = new Collection(this) - @OneToMany( - () => LineItemAdjustment, - (adjustment) => adjustment.item, - { - cascade: [Cascade.REMOVE], - } - ) + @OneToMany(() => LineItemAdjustment, (adjustment) => adjustment.item, { + cascade: [Cascade.REMOVE], + }) adjustments = new Collection(this) /** COMPUTED PROPERTIES - START */ diff --git a/packages/cart/src/models/shipping-method-tax-line.ts b/packages/cart/src/models/shipping-method-tax-line.ts index 5d726936500ba..e5a022ab78daf 100644 --- a/packages/cart/src/models/shipping-method-tax-line.ts +++ b/packages/cart/src/models/shipping-method-tax-line.ts @@ -1,15 +1,25 @@ import { generateEntityId } from "@medusajs/utils" -import { BeforeCreate, Entity, ManyToOne, OnInit } from "@mikro-orm/core" +import { + BeforeCreate, + Entity, + ManyToOne, + OnInit, + Property, +} from "@mikro-orm/core" import ShippingMethod from "./shipping-method" import TaxLine from "./tax-line" @Entity({ tableName: "cart_shipping_method_tax_line" }) export default class ShippingMethodTaxLine extends TaxLine { @ManyToOne(() => ShippingMethod, { - joinColumn: "shipping_method", - fieldName: "shipping_method_id", + onDelete: "cascade", + nullable: true, + index: "IDX_tax_line_shipping_method_id", }) - shipping_method: ShippingMethod + shipping_method: ShippingMethod | null + + @Property({ columnType: "text" }) + shipping_method_id: string @BeforeCreate() onCreate() { diff --git a/packages/cart/src/models/tax-line.ts b/packages/cart/src/models/tax-line.ts index 7a55a930c7bdb..5425d2c84bb21 100644 --- a/packages/cart/src/models/tax-line.ts +++ b/packages/cart/src/models/tax-line.ts @@ -1,6 +1,6 @@ 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 */ @@ -17,7 +17,7 @@ export default abstract class TaxLine { @Property({ columnType: "text" }) code: string - @Property({ columnType: "numeric" }) + @Property({ columnType: "numeric", serializer: Number }) rate: number @Property({ columnType: "text", nullable: true }) diff --git a/packages/cart/src/services/cart-module.ts b/packages/cart/src/services/cart-module.ts index 255253c3ba0e7..e3eaff368aa6f 100644 --- a/packages/cart/src/services/cart-module.ts +++ b/packages/cart/src/services/cart-module.ts @@ -2,6 +2,7 @@ import { CartTypes, Context, DAL, + FilterableLineItemTaxLineProps, FindConfig, ICartModuleService, InternalModuleDeclaration, @@ -19,8 +20,10 @@ import { Cart, LineItem, LineItemAdjustment, + LineItemTaxLine, ShippingMethod, ShippingMethodAdjustment, + ShippingMethodTaxLine, } from "@models" import { CreateLineItemDTO, UpdateLineItemDTO } from "@types" import { joinerConfig } from "../joiner-config" @@ -34,6 +37,8 @@ type InjectedDependencies = { shippingMethodAdjustmentService: services.ShippingMethodAdjustmentService shippingMethodService: services.ShippingMethodService lineItemAdjustmentService: services.LineItemAdjustmentService + lineItemTaxLineService: services.LineItemTaxLineService + shippingMethodTaxLineService: services.ShippingMethodTaxLineService } export default class CartModuleService implements ICartModuleService { @@ -44,6 +49,8 @@ export default class CartModuleService implements ICartModuleService { protected shippingMethodAdjustmentService_: services.ShippingMethodAdjustmentService protected shippingMethodService_: services.ShippingMethodService protected lineItemAdjustmentService_: services.LineItemAdjustmentService + protected lineItemTaxLineService_: services.LineItemTaxLineService + protected shippingMethodTaxLineService_: services.ShippingMethodTaxLineService constructor( { @@ -54,6 +61,8 @@ export default class CartModuleService implements ICartModuleService { shippingMethodAdjustmentService, shippingMethodService, lineItemAdjustmentService, + shippingMethodTaxLineService, + lineItemTaxLineService, }: InjectedDependencies, protected readonly moduleDeclaration: InternalModuleDeclaration ) { @@ -64,6 +73,8 @@ export default class CartModuleService implements ICartModuleService { this.shippingMethodAdjustmentService_ = shippingMethodAdjustmentService this.shippingMethodService_ = shippingMethodService this.lineItemAdjustmentService_ = lineItemAdjustmentService + this.shippingMethodTaxLineService_ = shippingMethodTaxLineService + this.lineItemTaxLineService_ = lineItemTaxLineService } __joinerConfig(): ModuleJoinerConfig { @@ -791,25 +802,17 @@ export default class CartModuleService implements ICartModuleService { sharedContext ) - const lineIds = cart.items?.map((item) => item.id) - const existingAdjustments = await this.listLineItemAdjustments( - { item_id: lineIds }, + { item: { cart_id: cart.id } }, { 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 adjustmentsSet = new Set( + adjustments + .map((a) => (a as CartTypes.UpdateLineItemAdjustmentDTO).id) + .filter(Boolean) + ) const toDelete: CartTypes.LineItemAdjustmentDTO[] = [] @@ -820,29 +823,17 @@ export default class CartModuleService implements ICartModuleService { } }) - 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, + if (toDelete.length) { + await this.lineItemAdjustmentService_.delete( + toDelete.map((adj) => adj!.id), sharedContext ) - result.push(...updated) } - if (toCreate?.length) { - const created = await this.lineItemAdjustmentService_.create( - toCreate, - sharedContext - ) - result.push(...created) - } + let result = await this.lineItemAdjustmentService_.upsert( + adjustments, + sharedContext + ) return await this.baseRepository_.serialize< CartTypes.LineItemAdjustmentDTO[] @@ -925,25 +916,17 @@ export default class CartModuleService implements ICartModuleService { sharedContext ) - const methodIds = cart.shipping_methods?.map((method) => method.id) - const existingAdjustments = await this.listShippingMethodAdjustments( - { shipping_method_id: methodIds ?? [] }, + { shipping_method: { cart_id: cart.id } }, { select: ["id"] }, sharedContext ) - let toUpdate: CartTypes.UpdateShippingMethodAdjustmentDTO[] = [] - let toCreate: CartTypes.CreateShippingMethodAdjustmentDTO[] = [] - for (const adj of adjustments) { - if ("id" in adj) { - toUpdate.push(adj as CartTypes.UpdateShippingMethodAdjustmentDTO) - } else { - toCreate.push(adj as CartTypes.CreateShippingMethodAdjustmentDTO) - } - } - - const adjustmentsSet = new Set(toUpdate.map((a) => a.id)) + const adjustmentsSet = new Set( + adjustments + .map((a) => (a as CartTypes.UpdateShippingMethodAdjustmentDTO)?.id) + .filter(Boolean) + ) const toDelete: CartTypes.ShippingMethodAdjustmentDTO[] = [] @@ -963,24 +946,10 @@ export default class CartModuleService implements ICartModuleService { ) } - let result: ShippingMethodAdjustment[] = [] - - if (toCreate.length) { - const created = await this.shippingMethodAdjustmentService_.create( - toCreate, - sharedContext - ) - - result.push(...created) - } - - if (toUpdate.length) { - const updated = await this.shippingMethodAdjustmentService_.update( - toUpdate, - sharedContext - ) - result.push(...updated) - } + const result = await this.shippingMethodAdjustmentService_.upsert( + adjustments, + sharedContext + ) return await this.baseRepository_.serialize< CartTypes.ShippingMethodAdjustmentDTO[] @@ -1100,4 +1069,352 @@ export default class CartModuleService implements ICartModuleService { await this.shippingMethodAdjustmentService_.delete(ids, sharedContext) } + + @InjectManager("baseRepository_") + async listLineItemTaxLines( + filters: CartTypes.FilterableLineItemTaxLineProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ) { + const taxLines = await this.lineItemTaxLineService_.list( + filters, + config, + sharedContext + ) + + return await this.baseRepository_.serialize( + taxLines, + { + populate: true, + } + ) + } + + addLineItemTaxLines( + taxLines: CartTypes.CreateLineItemTaxLineDTO[] + ): Promise + addLineItemTaxLines( + taxLine: CartTypes.CreateLineItemTaxLineDTO + ): Promise + addLineItemTaxLines( + cartId: string, + taxLines: + | CartTypes.CreateLineItemTaxLineDTO[] + | CartTypes.CreateShippingMethodTaxLineDTO, + sharedContext?: Context + ): Promise + + @InjectTransactionManager("baseRepository_") + async addLineItemTaxLines( + cartIdOrData: + | string + | CartTypes.CreateLineItemTaxLineDTO[] + | CartTypes.CreateLineItemTaxLineDTO, + taxLines?: + | CartTypes.CreateLineItemTaxLineDTO[] + | CartTypes.CreateLineItemTaxLineDTO, + @MedusaContext() sharedContext: Context = {} + ): Promise { + let addedTaxLines: LineItemTaxLine[] = [] + if (isString(cartIdOrData)) { + // existence check + await this.retrieve(cartIdOrData, { select: ["id"] }, sharedContext) + + const lines = Array.isArray(taxLines) ? taxLines : [taxLines] + + addedTaxLines = await this.lineItemTaxLineService_.create( + lines as CartTypes.CreateLineItemTaxLineDTO[], + sharedContext + ) + } else { + const data = Array.isArray(cartIdOrData) ? cartIdOrData : [cartIdOrData] + + addedTaxLines = await this.lineItemTaxLineService_.create( + data as CartTypes.CreateLineItemTaxLineDTO[], + sharedContext + ) + } + + const serialized = await this.baseRepository_.serialize< + CartTypes.LineItemTaxLineDTO[] + >(addedTaxLines, { + populate: true, + }) + + if (isObject(cartIdOrData)) { + return serialized[0] + } + + return serialized + } + + @InjectTransactionManager("baseRepository_") + async setLineItemTaxLines( + cartId: string, + taxLines: ( + | CartTypes.CreateLineItemTaxLineDTO + | CartTypes.UpdateLineItemTaxLineDTO + )[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const cart = await this.retrieve( + cartId, + { select: ["id"], relations: ["items.tax_lines"] }, + sharedContext + ) + + const existingTaxLines = await this.listLineItemTaxLines( + { item: { cart_id: cart.id } }, + { select: ["id"] }, + sharedContext + ) + + const taxLinesSet = new Set( + taxLines + .map((taxLine) => (taxLine as CartTypes.UpdateLineItemTaxLineDTO)?.id) + .filter(Boolean) + ) + + const toDelete: CartTypes.LineItemTaxLineDTO[] = [] + + // From the existing tax lines, find the ones that are not passed in taxLines + existingTaxLines.forEach((taxLine: CartTypes.LineItemTaxLineDTO) => { + if (!taxLinesSet.has(taxLine.id)) { + toDelete.push(taxLine) + } + }) + + await this.lineItemTaxLineService_.delete( + toDelete.map((taxLine) => taxLine!.id), + sharedContext + ) + + const result = await this.lineItemTaxLineService_.upsert( + taxLines, + sharedContext + ) + + return await this.baseRepository_.serialize( + result, + { + populate: true, + } + ) + } + + removeLineItemTaxLines( + taxLineIds: string[], + sharedContext?: Context + ): Promise + removeLineItemTaxLines( + taxLineIds: string, + sharedContext?: Context + ): Promise + removeLineItemTaxLines( + selector: FilterableLineItemTaxLineProps, + sharedContext?: Context + ): Promise + + async removeLineItemTaxLines( + taxLineIdsOrSelector: + | string + | string[] + | CartTypes.FilterableShippingMethodTaxLineProps, + @MedusaContext() sharedContext: Context = {} + ): Promise { + let ids: string[] = [] + if (isObject(taxLineIdsOrSelector)) { + const taxLines = await this.listLineItemTaxLines( + { + ...(taxLineIdsOrSelector as CartTypes.FilterableLineItemTaxLineProps), + }, + { select: ["id"] }, + sharedContext + ) + + ids = taxLines.map((taxLine) => taxLine.id) + } else { + ids = Array.isArray(taxLineIdsOrSelector) + ? taxLineIdsOrSelector + : [taxLineIdsOrSelector] + } + + await this.lineItemTaxLineService_.delete(ids, sharedContext) + } + + @InjectManager("baseRepository_") + async listShippingMethodTaxLines( + filters: CartTypes.FilterableShippingMethodTaxLineProps = {}, + config: FindConfig = {}, + @MedusaContext() sharedContext: Context = {} + ) { + const taxLines = await this.shippingMethodTaxLineService_.list( + filters, + config, + sharedContext + ) + + return await this.baseRepository_.serialize< + CartTypes.ShippingMethodTaxLineDTO[] + >(taxLines, { + populate: true, + }) + } + + addShippingMethodTaxLines( + taxLines: CartTypes.CreateShippingMethodTaxLineDTO[] + ): Promise + addShippingMethodTaxLines( + taxLine: CartTypes.CreateShippingMethodTaxLineDTO + ): Promise + addShippingMethodTaxLines( + cartId: string, + taxLines: + | CartTypes.CreateShippingMethodTaxLineDTO[] + | CartTypes.CreateShippingMethodTaxLineDTO, + sharedContext?: Context + ): Promise + + @InjectTransactionManager("baseRepository_") + async addShippingMethodTaxLines( + cartIdOrData: + | string + | CartTypes.CreateShippingMethodTaxLineDTO[] + | CartTypes.CreateShippingMethodTaxLineDTO, + taxLines?: + | CartTypes.CreateShippingMethodTaxLineDTO[] + | CartTypes.CreateShippingMethodTaxLineDTO, + @MedusaContext() sharedContext: Context = {} + ): Promise< + CartTypes.ShippingMethodTaxLineDTO[] | CartTypes.ShippingMethodTaxLineDTO + > { + let addedTaxLines: ShippingMethodTaxLine[] = [] + if (isString(cartIdOrData)) { + // existence check + await this.retrieve(cartIdOrData, { select: ["id"] }, sharedContext) + + const lines = Array.isArray(taxLines) ? taxLines : [taxLines] + + addedTaxLines = await this.shippingMethodTaxLineService_.create( + lines as CartTypes.CreateShippingMethodTaxLineDTO[], + sharedContext + ) + } else { + addedTaxLines = await this.shippingMethodTaxLineService_.create( + taxLines as CartTypes.CreateShippingMethodTaxLineDTO[], + sharedContext + ) + } + + const serialized = + await this.baseRepository_.serialize( + addedTaxLines[0], + { + populate: true, + } + ) + + if (isObject(cartIdOrData)) { + return serialized[0] + } + + return serialized + } + + @InjectTransactionManager("baseRepository_") + async setShippingMethodTaxLines( + cartId: string, + taxLines: ( + | CartTypes.CreateShippingMethodTaxLineDTO + | CartTypes.UpdateShippingMethodTaxLineDTO + )[], + @MedusaContext() sharedContext: Context = {} + ): Promise { + const cart = await this.retrieve( + cartId, + { select: ["id"], relations: ["shipping_methods.tax_lines"] }, + sharedContext + ) + + const existingTaxLines = await this.listShippingMethodTaxLines( + { shipping_method: { cart_id: cart.id } }, + { select: ["id"] }, + sharedContext + ) + + const taxLinesSet = new Set( + taxLines + .map( + (taxLine) => (taxLine as CartTypes.UpdateShippingMethodTaxLineDTO)?.id + ) + .filter(Boolean) + ) + + const toDelete: CartTypes.ShippingMethodTaxLineDTO[] = [] + + // From the existing tax lines, find the ones that are not passed in taxLines + existingTaxLines.forEach((taxLine: CartTypes.ShippingMethodTaxLineDTO) => { + if (!taxLinesSet.has(taxLine.id)) { + toDelete.push(taxLine) + } + }) + + if (toDelete.length) { + await this.shippingMethodTaxLineService_.delete( + toDelete.map((taxLine) => taxLine!.id), + sharedContext + ) + } + + const result = await this.shippingMethodTaxLineService_.upsert( + taxLines, + sharedContext + ) + + return await this.baseRepository_.serialize< + CartTypes.ShippingMethodTaxLineDTO[] + >(result, { + populate: true, + }) + } + + removeShippingMethodTaxLines( + taxLineIds: string[], + sharedContext?: Context + ): Promise + removeShippingMethodTaxLines( + taxLineIds: string, + sharedContext?: Context + ): Promise + removeShippingMethodTaxLines( + selector: Partial, + sharedContext?: Context + ): Promise + + async removeShippingMethodTaxLines( + taxLineIdsOrSelector: + | string + | string[] + | CartTypes.FilterableShippingMethodTaxLineProps, + @MedusaContext() sharedContext: Context = {} + ): Promise { + let ids: string[] = [] + if (isObject(taxLineIdsOrSelector)) { + const taxLines = await this.listShippingMethodTaxLines( + { + ...(taxLineIdsOrSelector as CartTypes.FilterableShippingMethodTaxLineProps), + }, + { select: ["id"] }, + sharedContext + ) + + ids = taxLines.map((taxLine) => taxLine.id) + } else { + ids = Array.isArray(taxLineIdsOrSelector) + ? taxLineIdsOrSelector + : [taxLineIdsOrSelector] + } + + await this.shippingMethodTaxLineService_.delete(ids, sharedContext) + } } diff --git a/packages/cart/src/services/index.ts b/packages/cart/src/services/index.ts index cd2616864ad55..7120b701d7fee 100644 --- a/packages/cart/src/services/index.ts +++ b/packages/cart/src/services/index.ts @@ -3,6 +3,8 @@ export { default as CartService } from "./cart" export { default as CartModuleService } from "./cart-module" export { default as LineItemService } from "./line-item" export { default as LineItemAdjustmentService } from "./line-item-adjustment" +export { default as LineItemTaxLineService } from "./line-item-tax-line" export { default as ShippingMethodService } from "./shipping-method" export { default as ShippingMethodAdjustmentService } from "./shipping-method-adjustment" +export { default as ShippingMethodTaxLineService } from "./shipping-method-tax-line" diff --git a/packages/cart/src/services/line-item-tax-line.ts b/packages/cart/src/services/line-item-tax-line.ts new file mode 100644 index 0000000000000..a740fae4366d1 --- /dev/null +++ b/packages/cart/src/services/line-item-tax-line.ts @@ -0,0 +1,26 @@ +import { + CreateLineItemTaxLineDTO, + DAL, + UpdateLineItemTaxLineDTO, +} from "@medusajs/types" +import { ModulesSdkUtils } from "@medusajs/utils" +import { LineItemTaxLine } from "@models" + +type InjectedDependencies = { + lineItemTaxLineRepository: DAL.RepositoryService +} + +export default class LineItemTaxLineService< + TEntity extends LineItemTaxLine = LineItemTaxLine +> extends ModulesSdkUtils.abstractServiceFactory< + InjectedDependencies, + { + create: CreateLineItemTaxLineDTO + update: UpdateLineItemTaxLineDTO + } +>(LineItemTaxLine) { + constructor(container: InjectedDependencies) { + // @ts-ignore + super(...arguments) + } +} diff --git a/packages/cart/src/services/shipping-method-tax-line.ts b/packages/cart/src/services/shipping-method-tax-line.ts new file mode 100644 index 0000000000000..9229d6556717b --- /dev/null +++ b/packages/cart/src/services/shipping-method-tax-line.ts @@ -0,0 +1,22 @@ +import { CreateShippingMethodTaxLineDTO, DAL, UpdateShippingMethodTaxLineDTO } from "@medusajs/types" +import { ModulesSdkUtils } from "@medusajs/utils" +import { ShippingMethodTaxLine } from "@models" + +type InjectedDependencies = { + shippingMethodTaxLineRepository: DAL.RepositoryService +} + +export default class ShippingMethodTaxLineService< + TEntity extends ShippingMethodTaxLine = ShippingMethodTaxLine +> extends ModulesSdkUtils.abstractServiceFactory< + InjectedDependencies, + { + create: CreateShippingMethodTaxLineDTO + update: UpdateShippingMethodTaxLineDTO + } +>(ShippingMethodTaxLine) { + constructor(container: InjectedDependencies) { + // @ts-ignore + super(...arguments) + } +} diff --git a/packages/cart/src/types/index.ts b/packages/cart/src/types/index.ts index 98891da01912b..03f39f0916b24 100644 --- a/packages/cart/src/types/index.ts +++ b/packages/cart/src/types/index.ts @@ -4,9 +4,11 @@ export * from "./address" export * from "./cart" export * from "./line-item" export * from "./line-item-adjustment" +export * from "./line-item-tax-line" export * from "./repositories" export * from "./shipping-method" export * from "./shipping-method-adjustment" +export * from "./shipping-method-tax-line" export type InitializeModuleInjectableDependencies = { logger?: Logger diff --git a/packages/cart/src/types/line-item-tax-line.ts b/packages/cart/src/types/line-item-tax-line.ts new file mode 100644 index 0000000000000..e5ff65e1cdc55 --- /dev/null +++ b/packages/cart/src/types/line-item-tax-line.ts @@ -0,0 +1,9 @@ +import { CreateTaxLineDTO, UpdateTaxLineDTO } from "./tax-line" + +export interface CreateLineItemTaxLineDTO extends CreateTaxLineDTO { + item_id: string +} + +export interface UpdateLineItemTaxLineDTO extends UpdateTaxLineDTO { + item_id: string +} diff --git a/packages/cart/src/types/repositories.ts b/packages/cart/src/types/repositories.ts index 60d732c873b37..3d028f6c8e20a 100644 --- a/packages/cart/src/types/repositories.ts +++ b/packages/cart/src/types/repositories.ts @@ -3,8 +3,10 @@ import { Cart, LineItem, LineItemAdjustment, + LineItemTaxLine, ShippingMethod, ShippingMethodAdjustment, + ShippingMethodTaxLine, } from "@models" import { CreateAddressDTO, UpdateAddressDTO } from "./address" import { CreateCartDTO, UpdateCartDTO } from "./cart" @@ -13,6 +15,10 @@ import { CreateLineItemAdjustmentDTO, UpdateLineItemAdjustmentDTO, } from "./line-item-adjustment" +import { + CreateLineItemTaxLineDTO, + UpdateLineItemTaxLineDTO, +} from "./line-item-tax-line" import { CreateShippingMethodDTO, UpdateShippingMethodDTO, @@ -21,6 +27,10 @@ import { CreateShippingMethodAdjustmentDTO, UpdateShippingMethodAdjustmentDTO, } from "./shipping-method-adjustment" +import { + CreateShippingMethodTaxLineDTO, + UpdateShippingMethodTaxLineDTO, +} from "./shipping-method-tax-line" // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface IAddressRepository @@ -82,3 +92,23 @@ export interface IShippingMethodAdjustmentRepository< update: UpdateShippingMethodAdjustmentDTO } > {} + +export interface IShippingMethodTaxLineRepository< + TEntity extends ShippingMethodTaxLine = ShippingMethodTaxLine +> extends DAL.RepositoryService< + TEntity, + { + create: CreateShippingMethodTaxLineDTO + update: UpdateShippingMethodTaxLineDTO + } + > {} + +export interface ILineItemTaxLineRepository< + TEntity extends LineItemTaxLine = LineItemTaxLine +> extends DAL.RepositoryService< + TEntity, + { + create: CreateLineItemTaxLineDTO + update: UpdateLineItemTaxLineDTO + } + > {} diff --git a/packages/cart/src/types/shipping-method-tax-line.ts b/packages/cart/src/types/shipping-method-tax-line.ts new file mode 100644 index 0000000000000..c45f389cf4fae --- /dev/null +++ b/packages/cart/src/types/shipping-method-tax-line.ts @@ -0,0 +1,9 @@ +import { CreateTaxLineDTO, UpdateTaxLineDTO } from "./tax-line" + +export interface CreateShippingMethodTaxLineDTO extends CreateTaxLineDTO { + shipping_method_id: string +} + +export interface UpdateShippingMethodTaxLineDTO extends UpdateTaxLineDTO { + shipping_method_id: string +} diff --git a/packages/cart/src/types/tax-line.ts b/packages/cart/src/types/tax-line.ts new file mode 100644 index 0000000000000..8f54874e3a8c1 --- /dev/null +++ b/packages/cart/src/types/tax-line.ts @@ -0,0 +1,16 @@ +export interface UpdateTaxLineDTO { + id: string + description?: string + tax_rate_id?: string + code?: string + rate?: number + provider_id?: string +} + +export interface CreateTaxLineDTO { + description?: string + tax_rate_id?: string + code: string + rate: number + provider_id?: string +} diff --git a/packages/types/src/cart/common.ts b/packages/types/src/cart/common.ts index b163809a9a5f9..b83c2b7d6d514 100644 --- a/packages/types/src/cart/common.ts +++ b/packages/types/src/cart/common.ts @@ -103,13 +103,21 @@ export interface ShippingMethodTaxLineDTO extends TaxLineDTO { * The associated shipping method */ shipping_method: CartShippingMethodDTO + /** + * The ID of the associated shipping method + */ + shipping_method_id: string } export interface LineItemTaxLineDTO extends TaxLineDTO { /** * The associated line item */ - line_item: CartLineItemDTO + item: CartLineItemDTO + /** + * The ID of the associated line item + */ + item_id: string } export interface CartAddressDTO { @@ -513,6 +521,7 @@ export interface FilterableLineItemAdjustmentProps item_id?: string | string[] promotion_id?: string | string[] provider_id?: string | string[] + item?: FilterableLineItemProps } export interface FilterableShippingMethodProps extends BaseFilterable { @@ -528,6 +537,29 @@ export interface FilterableShippingMethodAdjustmentProps shipping_method_id?: string | string[] promotion_id?: string | string[] provider_id?: string | string[] + shipping_method?: FilterableShippingMethodProps +} + +export interface FilterableLineItemTaxLineProps + extends BaseFilterable { + id?: string | string[] + description?: string + code?: string | string[] + tax_rate_id?: string | string[] + provider_id?: string | string[] + item_id?: string | string[] + item?: FilterableLineItemProps +} + +export interface FilterableShippingMethodTaxLineProps + extends BaseFilterable { + id?: string | string[] + description?: string + code?: string | string[] + tax_rate_id?: string | string[] + provider_id?: string | string[] + shipping_method_id?: string | string[] + shipping_method?: FilterableShippingMethodProps } /** diff --git a/packages/types/src/cart/mutations.ts b/packages/types/src/cart/mutations.ts index ef9247c34957e..25d0dc71728d1 100644 --- a/packages/types/src/cart/mutations.ts +++ b/packages/types/src/cart/mutations.ts @@ -60,30 +60,9 @@ export interface UpdateCartDTO { /** CART END */ -/** TAXLINES START */ -export interface CreateTaxLineDTO { - description?: string - tax_rate_id?: string - code: string - rate: number - provider_id?: string -} - -export interface UpdateTaxLineDTO { - id: string - description?: string - tax_rate_id?: string - code?: string - rate?: number - provider_id?: string -} - -/** TAXLINES END */ - /** ADJUSTMENT START */ export interface CreateAdjustmentDTO { - item_id: string - code?: string + code: string amount: number description?: string promotion_id?: string @@ -117,7 +96,36 @@ export interface UpsertLineItemAdjustmentDTO { provider_id?: string } -/** ADJUSTMENT START */ +/** ADJUSTMENTS END */ + +/** TAX LINES START */ + +export interface CreateTaxLineDTO { + description?: string + tax_rate_id?: string + code: string + rate: number + provider_id?: string +} + +export interface UpdateTaxLineDTO { + id: string + description?: string + tax_rate_id?: string + code?: string + rate?: number + provider_id?: string +} + +export interface CreateShippingMethodTaxLineDTO extends CreateTaxLineDTO {} + +export interface UpdateShippingMethodTaxLineDTO extends UpdateTaxLineDTO {} + +export interface CreateLineItemTaxLineDTO extends CreateTaxLineDTO {} + +export interface UpdateLineItemTaxLineDTO extends UpdateTaxLineDTO {} + +/** TAX LINES END */ /** LINE ITEMS START */ export interface CreateLineItemDTO { diff --git a/packages/types/src/cart/service.ts b/packages/types/src/cart/service.ts index 58f7adcd22474..2af985f422c9c 100644 --- a/packages/types/src/cart/service.ts +++ b/packages/types/src/cart/service.ts @@ -10,10 +10,14 @@ import { FilterableCartProps, FilterableLineItemAdjustmentProps, FilterableLineItemProps, + FilterableLineItemTaxLineProps, FilterableShippingMethodAdjustmentProps, FilterableShippingMethodProps, + FilterableShippingMethodTaxLineProps, LineItemAdjustmentDTO, + LineItemTaxLineDTO, ShippingMethodAdjustmentDTO, + ShippingMethodTaxLineDTO, } from "./common" import { CreateAddressDTO, @@ -21,14 +25,18 @@ import { CreateCartDTO, CreateLineItemDTO, CreateLineItemForCartDTO, + CreateLineItemTaxLineDTO, CreateShippingMethodAdjustmentDTO, CreateShippingMethodDTO, CreateShippingMethodForSingleCartDTO, + CreateShippingMethodTaxLineDTO, UpdateAddressDTO, UpdateCartDTO, UpdateLineItemDTO, + UpdateLineItemTaxLineDTO, UpdateLineItemWithSelectorDTO, UpdateShippingMethodAdjustmentDTO, + UpdateShippingMethodTaxLineDTO, UpsertLineItemAdjustmentDTO, } from "./mutations" @@ -234,4 +242,81 @@ export interface ICartModuleService extends IModuleService { selector: Partial, sharedContext?: Context ): Promise + + listLineItemTaxLines( + filters: FilterableLineItemTaxLineProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + addLineItemTaxLines( + taxLines: CreateLineItemTaxLineDTO[] + ): Promise + addLineItemTaxLines( + taxLine: CreateLineItemTaxLineDTO + ): Promise + addLineItemTaxLines( + cartId: string, + taxLines: CreateLineItemTaxLineDTO[] | CreateLineItemTaxLineDTO, + sharedContext?: Context + ): Promise + + setLineItemTaxLines( + cartId: string, + taxLines: (CreateLineItemTaxLineDTO | UpdateLineItemTaxLineDTO)[], + sharedContext?: Context + ): Promise + + removeLineItemTaxLines( + taxLineIds: string[], + sharedContext?: Context + ): Promise + removeLineItemTaxLines( + taxLineIds: string, + sharedContext?: Context + ): Promise + removeLineItemTaxLines( + selector: FilterableLineItemTaxLineProps, + sharedContext?: Context + ): Promise + + listShippingMethodTaxLines( + filters: FilterableShippingMethodTaxLineProps, + config?: FindConfig, + sharedContext?: Context + ): Promise + + addShippingMethodTaxLines( + taxLines: CreateShippingMethodTaxLineDTO[] + ): Promise + addShippingMethodTaxLines( + taxLine: CreateShippingMethodTaxLineDTO + ): Promise + addShippingMethodTaxLines( + cartId: string, + taxLines: CreateShippingMethodTaxLineDTO[] | CreateShippingMethodTaxLineDTO, + sharedContext?: Context + ): Promise + + setShippingMethodTaxLines( + cartId: string, + taxLines: ( + | CreateShippingMethodTaxLineDTO + | UpdateShippingMethodTaxLineDTO + )[], + sharedContext?: Context + ): Promise + + removeShippingMethodTaxLines( + taxLineIds: string[], + sharedContext?: Context + ): Promise + removeShippingMethodTaxLines( + taxLineIds: string, + sharedContext?: Context + ): Promise + removeShippingMethodTaxLines( + selector: FilterableShippingMethodTaxLineProps, + sharedContext?: Context + ): Promise }