refactor: LineItemService migration to TS + refactoring + fix (#1254)

* refactor(medusa): LineItemService migration to TS + refactoring

* feat(medusa): Cleanup line-item service

* feat(medusa): Rebase develop

* test(medusa): Fix integration cart tests

* fix(medusa): Cart service updateUnitPrices
This commit is contained in:
Adrien de Peretti
2022-04-14 20:53:35 +02:00
committed by GitHub
parent 6830a12b46
commit 530bbd4cac
4 changed files with 326 additions and 4 deletions

View File

@@ -1739,7 +1739,7 @@ describe("/store/carts", () => {
.catch((err) => console.log(err))
// Ensure that the discount is only applied to the standard item
const itemId = cartWithGiftcard.data.cart.items[0].id
const itemId = cartWithGiftcard.data.cart.items.find(item => !item.is_giftcard).id
expect(cartWithGiftcard.data.cart.items).toEqual(
expect.arrayContaining([
expect.objectContaining({

View File

@@ -74,5 +74,5 @@ export class StorePostCartsCartLineItemsReq {
quantity: number
@IsOptional()
metadata?: object
metadata?: Record<string, unknown> | undefined
}

View File

@@ -1717,11 +1717,11 @@ class CartService extends BaseService {
await this.lineItemService_
.withTransaction(this.transactionManager_)
.delete(item.id)
return null
return
}
})
)
).filter((item): item is LineItem => item)
).filter((item): item is LineItem => !!item)
}
}

View File

@@ -0,0 +1,322 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { EntityManager } from "typeorm"
import { LineItemRepository } from "../repositories/line-item"
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
import { ProductService, RegionService, ProductVariantService } from "./index"
import { CartRepository } from "../repositories/cart"
import { LineItem } from "../models/line-item"
import LineItemAdjustmentService from "./line-item-adjustment"
import { Cart } from "../models/cart"
import { LineItemAdjustment } from "../models/line-item-adjustment"
type InjectedDependencies = {
manager: EntityManager
lineItemRepository: typeof LineItemRepository
lineItemTaxLineRepository: typeof LineItemTaxLineRepository
cartRepository: typeof CartRepository
productVariantService: ProductVariantService
productService: ProductService
regionService: RegionService
lineItemAdjustmentService: LineItemAdjustmentService
}
/**
* Provides layer to manipulate line items.
* @extends BaseService
*/
class LineItemService extends BaseService {
protected readonly manager_: EntityManager
protected readonly lineItemRepository_: typeof LineItemRepository
protected readonly itemTaxLineRepo_: typeof LineItemTaxLineRepository
protected readonly cartRepository_: typeof CartRepository
protected readonly productVariantService_: ProductVariantService
protected readonly productService_: ProductService
protected readonly regionService_: RegionService
protected readonly lineItemAdjustmentService_: LineItemAdjustmentService
constructor({
manager,
lineItemRepository,
lineItemTaxLineRepository,
productVariantService,
productService,
regionService,
cartRepository,
lineItemAdjustmentService
}: InjectedDependencies) {
super()
this.manager_ = manager
this.lineItemRepository_ = lineItemRepository
this.itemTaxLineRepo_ = lineItemTaxLineRepository
this.productVariantService_ = productVariantService
this.productService_ = productService
this.regionService_ = regionService
this.cartRepository_ = cartRepository
this.lineItemAdjustmentService_ = lineItemAdjustmentService
}
withTransaction(transactionManager: EntityManager): LineItemService {
if (!transactionManager) {
return this
}
const cloned = new LineItemService({
manager: transactionManager,
lineItemRepository: this.lineItemRepository_,
lineItemTaxLineRepository: this.itemTaxLineRepo_,
productVariantService: this.productVariantService_,
productService: this.productService_,
regionService: this.regionService_,
cartRepository: this.cartRepository_,
lineItemAdjustmentService: this.lineItemAdjustmentService_,
})
cloned.transactionManager_ = transactionManager
return cloned
}
async list(
selector,
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
): Promise<LineItem[]> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const lineItemRepo = transactionManager.getCustomRepository(
this.lineItemRepository_
)
const query = this.buildQuery_(selector, config)
return await lineItemRepo.find(query)
}
)
}
/**
* Retrieves a line item by its id.
* @param {string} id - the id of the line item to retrieve
* @param {object} config - the config to be used at query building
* @return {Promise<LineItem | never>} the line item
*/
async retrieve(id: string, config = {}): Promise<LineItem | never> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const lineItemRepository = transactionManager.getCustomRepository(
this.lineItemRepository_
)
const validatedId = this.validateId_(id)
const query = this.buildQuery_({ id: validatedId }, config)
const lineItem = await lineItemRepository.findOne(query)
if (!lineItem) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Line item with ${id} was not found`
)
}
return lineItem
}
)
}
/**
* Creates return line items for a given cart based on the return items in a
* return.
* @param {string} returnId - the id to generate return items from.
* @param {string} cartId - the cart to assign the return line items to.
* @return {Promise<LineItem[]>} the created line items
*/
async createReturnLines(
returnId: string,
cartId: string
): Promise<LineItem[]> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const lineItemRepo = transactionManager.getCustomRepository(
this.lineItemRepository_
)
const itemTaxLineRepo = transactionManager.getCustomRepository(
this.itemTaxLineRepo_
)
const returnLineItems = await lineItemRepo
.findByReturn(returnId)
.then((lineItems) => {
return lineItems.map((lineItem) =>
lineItemRepo.create({
cart_id: cartId,
thumbnail: lineItem.thumbnail,
is_return: true,
title: lineItem.title,
variant_id: lineItem.variant_id,
unit_price: -1 * lineItem.unit_price,
quantity: lineItem.return_item.quantity,
allow_discounts: lineItem.allow_discounts,
tax_lines: lineItem.tax_lines.map((taxLine) => {
return itemTaxLineRepo.create({
name: taxLine.name,
code: taxLine.code,
rate: taxLine.rate,
metadata: taxLine.metadata,
})
}),
metadata: lineItem.metadata,
adjustments: lineItem.adjustments.map((adjustment) => {
return {
amount: -1 * adjustment.amount,
description: adjustment.description,
discount_id: adjustment.discount_id,
metadata: adjustment.metadata,
}
}),
})
)
})
return await lineItemRepo.save(returnLineItems)
}
)
}
async generate(
variantId: string,
regionId: string,
quantity: number,
context: {
unit_price?: number
metadata?: Record<string, unknown>
customer_id?: string
cart?: Cart
} = {}
): Promise<LineItem> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const [variant, region] = await Promise.all([
this.productVariantService_
.withTransaction(transactionManager)
.retrieve(variantId, {
relations: ["product"],
include_discount_prices: true,
}),
this.regionService_
.withTransaction(transactionManager)
.retrieve(regionId),
])
let unit_price = Number(context.unit_price) < 0 ? 0 : context.unit_price
let shouldMerge = false
if (context.unit_price === undefined || context.unit_price === null) {
shouldMerge = true
unit_price = await this.productVariantService_
.withTransaction(transactionManager)
.getRegionPrice(variant.id, {
regionId: region.id,
quantity: quantity,
customer_id: context?.customer_id,
include_discount_prices: true,
})
}
const rawLineItem: Partial<LineItem> = {
unit_price: unit_price as number,
title: variant.product.title,
description: variant.title,
thumbnail: variant.product.thumbnail,
variant_id: variant.id,
quantity: quantity || 1,
allow_discounts: variant.product.discountable,
is_giftcard: variant.product.is_giftcard,
metadata: context?.metadata || {},
should_merge: shouldMerge
}
const lineLitemRepo = transactionManager.getCustomRepository(this.lineItemRepository_)
const lineItem = lineLitemRepo.create(rawLineItem)
if (context.cart) {
const adjustments = await this.lineItemAdjustmentService_
.withTransaction(transactionManager)
.generateAdjustments(context.cart, lineItem, { variant })
lineItem.adjustments = adjustments as unknown as LineItemAdjustment[]
}
return lineItem
}
)
}
/**
* Create a line item
* @param {Partial<LineItem>} data - the line item object to create
* @return {Promise<LineItem>} the created line item
*/
async create(data: Partial<LineItem>): Promise<LineItem> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const lineItemRepository = transactionManager.getCustomRepository(
this.lineItemRepository_
)
const lineItem = lineItemRepository.create(data)
return await lineItemRepository.save(lineItem)
}
)
}
/**
* Updates a line item
* @param {string} id - the id of the line item to update
* @param {Partial<LineItem>} data - the properties to update on line item
* @return {Promise<LineItem>} the update line item
*/
async update(id: string, data: Partial<LineItem>): Promise<LineItem> {
const { metadata, ...rest } = data
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const lineItemRepository = transactionManager.getCustomRepository(
this.lineItemRepository_
)
const lineItem = await this.retrieve(id).then((lineItem) => {
const lineItemMetadata = metadata
? this.setMetadata_(lineItem, metadata)
: lineItem.metadata
return Object.assign(lineItem, {
...rest,
metadata: lineItemMetadata,
})
})
return await lineItemRepository.save(lineItem)
}
)
}
/**
* Deletes a line item.
* @param {string} id - the id of the line item to delete
* @return {Promise<LineItem | undefined>} the result of the delete operation
*/
async delete(id: string): Promise<LineItem | undefined> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const lineItemRepository = transactionManager.getCustomRepository(
this.lineItemRepository_
)
return await lineItemRepository
.findOne({ where: { id } })
.then((lineItem) => lineItem && lineItemRepository.remove(lineItem))
}
)
}
}
export default LineItemService