Files
medusa-store/packages/medusa/src/services/draft-order.ts
Adrien de Peretti cbe2b7f687 chore(medusa): Remove intepestive services re instanciation in loop (#2036)
* chore(medusa): Renove intepestive services re instanciation in loop

* test(medusa): Fix missing deps

* fix(medusa): Missing await
2022-08-11 22:18:11 +02:00

427 lines
12 KiB
TypeScript

import { MedusaError } from "medusa-core-utils"
import { Brackets, EntityManager, FindManyOptions, UpdateResult } from "typeorm"
import { DraftOrderRepository } from "../repositories/draft-order"
import { PaymentRepository } from "../repositories/payment"
import EventBusService from "./event-bus"
import CartService from "./cart"
import LineItemService from "./line-item"
import { OrderRepository } from "../repositories/order"
import ProductVariantService from "./product-variant"
import ShippingOptionService from "./shipping-option"
import { DraftOrder, DraftOrderStatus, Cart, CartType } from "../models"
import { AdminPostDraftOrdersReq } from "../api/routes/admin/draft-orders"
import { TransactionBaseService } from "../interfaces"
import { ExtendedFindConfig, FindConfig } from "../types/common"
import { buildQuery } from "../utils"
type InjectedDependencies = {
manager: EntityManager
draftOrderRepository: typeof DraftOrderRepository
paymentRepository: typeof PaymentRepository
orderRepository: typeof OrderRepository
eventBusService: EventBusService
cartService: CartService
lineItemService: LineItemService
productVariantService: ProductVariantService
shippingOptionService: ShippingOptionService
}
/**
* Handles draft orders
* @implements {BaseService}
*/
class DraftOrderService extends TransactionBaseService<DraftOrderService> {
static readonly Events = {
CREATED: "draft_order.created",
UPDATED: "draft_order.updated",
}
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly draftOrderRepository_: typeof DraftOrderRepository
protected readonly paymentRepository_: typeof PaymentRepository
protected readonly orderRepository_: typeof OrderRepository
protected readonly eventBus_: EventBusService
protected readonly cartService_: CartService
protected readonly lineItemService_: LineItemService
protected readonly productVariantService_: ProductVariantService
protected readonly shippingOptionService_: ShippingOptionService
constructor({
manager,
draftOrderRepository,
paymentRepository,
orderRepository,
eventBusService,
cartService,
lineItemService,
productVariantService,
shippingOptionService,
}: InjectedDependencies) {
super({
manager,
draftOrderRepository,
paymentRepository,
orderRepository,
eventBusService,
cartService,
lineItemService,
productVariantService,
shippingOptionService,
})
this.manager_ = manager
this.draftOrderRepository_ = draftOrderRepository
this.paymentRepository_ = paymentRepository
this.orderRepository_ = orderRepository
this.lineItemService_ = lineItemService
this.cartService_ = cartService
this.productVariantService_ = productVariantService
this.shippingOptionService_ = shippingOptionService
this.eventBus_ = eventBusService
}
/**
* Retrieves a draft order with the given id.
* @param id - id of the draft order to retrieve
* @param config - query object for findOne
* @return the draft order
*/
async retrieve(
id: string,
config: FindConfig<DraftOrder> = {}
): Promise<DraftOrder | never> {
const manager = this.manager_
const draftOrderRepo = manager.getCustomRepository(
this.draftOrderRepository_
)
const query = buildQuery({ id }, config)
const draftOrder = await draftOrderRepo.findOne(query)
if (!draftOrder) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Draft order with ${id} was not found`
)
}
return draftOrder
}
/**
* Retrieves a draft order based on its associated cart id
* @param cartId - cart id that the draft orders's cart has
* @param config - query object for findOne
* @return the draft order
*/
async retrieveByCartId(
cartId: string,
config: FindConfig<DraftOrder> = {}
): Promise<DraftOrder | never> {
const manager = this.manager_
const draftOrderRepo = manager.getCustomRepository(
this.draftOrderRepository_
)
const query = buildQuery({ cart_id: cartId }, config)
const draftOrder = await draftOrderRepo.findOne(query)
if (!draftOrder) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Draft order was not found`
)
}
return draftOrder
}
/**
* Deletes draft order idempotently.
* @param {string} draftOrderId - id of draft order to delete
* @return {Promise<DraftOrder | undefined>} empty promise
*/
async delete(draftOrderId: string): Promise<DraftOrder | undefined> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const draftOrderRepo = transactionManager.getCustomRepository(
this.draftOrderRepository_
)
const draftOrder = await draftOrderRepo.findOne({
where: { id: draftOrderId },
})
if (!draftOrder) {
return
}
return await draftOrderRepo.remove(draftOrder)
}
)
}
/**
* Lists draft orders alongside the count
* @param selector - query selector to filter draft orders
* @param config - query config
* @return draft orders
*/
async listAndCount(
selector,
config: FindConfig<DraftOrder> = {
skip: 0,
take: 50,
order: { created_at: "DESC" },
}
): Promise<[DraftOrder[], number]> {
const manager = this.manager_
const draftOrderRepository = manager.getCustomRepository(
this.draftOrderRepository_
)
const { q, ...restSelector } = selector
const query = buildQuery(
restSelector,
config
) as FindManyOptions<DraftOrder> & ExtendedFindConfig<DraftOrder>
if (q) {
const where = query.where
delete where?.display_id
query.join = {
alias: "draft_order",
innerJoin: {
cart: "draft_order.cart",
},
}
query.where = (qb): void => {
qb.where(where)
qb.andWhere(
new Brackets((qb) => {
qb.where(`cart.email ILIKE :q`, {
q: `%${q}%`,
}).orWhere(`draft_order.display_id::TEXT ILIKE :displayId`, {
displayId: `${q}`,
})
})
)
}
}
return await draftOrderRepository.findAndCount(query)
}
/**
* Lists draft orders
* @param selector - query object for find
* @param config - configurable attributes for find
* @return list of draft orders
*/
async list(
selector,
config: FindConfig<DraftOrder> = {
skip: 0,
take: 50,
order: { created_at: "DESC" },
}
): Promise<DraftOrder[]> {
const manager = this.manager_
const draftOrderRepo = manager.getCustomRepository(
this.draftOrderRepository_
)
const query = buildQuery(selector, config)
return await draftOrderRepo.find(query)
}
/**
* Creates a draft order.
* @param data - data to create draft order from
* @return the created draft order
*/
async create(data: AdminPostDraftOrdersReq): Promise<DraftOrder> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const draftOrderRepo = transactionManager.getCustomRepository(
this.draftOrderRepository_
)
if (!data.region_id) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`region_id is required to create a draft order`
)
}
if (!data.items || !data.items.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Items are required to create a draft order`
)
}
const { shipping_methods, no_notification_order, items, ...rawCart } =
data
const cartServiceTx =
this.cartService_.withTransaction(transactionManager)
if (rawCart.discounts) {
const { discounts } = rawCart
rawCart.discounts = []
for (const { code } of discounts) {
await cartServiceTx.applyDiscount(rawCart as Cart, code)
}
}
const createdCart = await cartServiceTx.create({
type: CartType.DRAFT_ORDER,
...rawCart,
})
const draftOrder = draftOrderRepo.create({
cart_id: createdCart.id,
no_notification_order,
})
const result = await draftOrderRepo.save(draftOrder)
await this.eventBus_
.withTransaction(transactionManager)
.emit(DraftOrderService.Events.CREATED, {
id: result.id,
})
const lineItemServiceTx =
this.lineItemService_.withTransaction(transactionManager)
for (const item of items) {
if (item.variant_id) {
const line = await lineItemServiceTx.generate(
item.variant_id,
data.region_id,
item.quantity,
{
metadata: item?.metadata || {},
unit_price: item.unit_price,
cart: createdCart,
}
)
await lineItemServiceTx.create({
...line,
cart_id: createdCart.id,
})
} else {
let price
if (typeof item.unit_price === `undefined` || item.unit_price < 0) {
price = 0
} else {
price = item.unit_price
}
// custom line items can be added to a draft order
await lineItemServiceTx.create({
cart_id: createdCart.id,
has_shipping: true,
title: item.title || "Custom item",
allow_discounts: false,
unit_price: price,
quantity: item.quantity,
})
}
}
for (const method of shipping_methods) {
await cartServiceTx.addShippingMethod(
createdCart.id,
method.option_id,
method.data
)
}
return result
}
)
}
/**
* Registers a draft order as completed, when an order has been completed.
* @param draftOrderId - id of draft order to complete
* @param orderId - id of order completed from draft order cart
* @return the created order
*/
async registerCartCompletion(
draftOrderId: string,
orderId: string
): Promise<UpdateResult> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const draftOrderRepo = transactionManager.getCustomRepository(
this.draftOrderRepository_
)
return await draftOrderRepo.update(
{
id: draftOrderId,
},
{
status: DraftOrderStatus.COMPLETED,
completed_at: new Date(),
order_id: orderId,
}
)
}
)
}
/**
* Updates a draft order with the given data
* @param id - id of the draft order
* @param data - values to update the order with
* @return the updated draft order
*/
async update(
id: string,
data: { no_notification_order: boolean }
): Promise<DraftOrder> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
const draftOrderRepo = transactionManager.getCustomRepository(
this.draftOrderRepository_
)
const draftOrder = await this.retrieve(id)
if (draftOrder.status === DraftOrderStatus.COMPLETED) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Can't update a draft order which is complete"
)
}
let touched = false
if (data.no_notification_order !== undefined) {
touched = true
draftOrder.no_notification_order = data.no_notification_order
}
if (touched) {
await draftOrderRepo.save(draftOrder)
await this.eventBus_
.withTransaction(transactionManager)
.emit(DraftOrderService.Events.UPDATED, {
id: draftOrder.id,
})
}
return draftOrder
}
)
}
}
export default DraftOrderService