Files
medusa-store/packages/medusa/src/services/draft-order.js

388 lines
10 KiB
JavaScript

import _ from "lodash"
import { BaseService } from "medusa-interfaces"
import { MedusaError } from "medusa-core-utils"
import { Brackets } from "typeorm"
/**
* Handles draft orders
* @implements BaseService
*/
class DraftOrderService extends BaseService {
static Events = {
CREATED: "draft_order.created",
UPDATED: "draft_order.updated",
}
constructor({
manager,
draftOrderRepository,
paymentRepository,
orderRepository,
eventBusService,
cartService,
lineItemService,
productVariantService,
shippingOptionService,
}) {
super()
/** @private @const {EntityManager} */
this.manager_ = manager
/** @private @const {DraftOrderRepository} */
this.draftOrderRepository_ = draftOrderRepository
/** @private @const {PaymentRepository} */
this.paymentRepository_ = paymentRepository
/** @private @const {OrderRepository} */
this.orderRepository_ = orderRepository
/** @private @const {LineItemService} */
this.lineItemService_ = lineItemService
/** @private @const {CartService} */
this.cartService_ = cartService
/** @private @const {ProductVariantService} */
this.productVariantService_ = productVariantService
/** @private @const {ShippingOptionService} */
this.shippingOptionService_ = shippingOptionService
/** @private @const {EventBusService} */
this.eventBus_ = eventBusService
}
withTransaction(transactionManager) {
if (!transactionManager) {
return this
}
const cloned = new DraftOrderService({
manager: transactionManager,
draftOrderRepository: this.draftOrderRepository_,
paymentRepository: this.paymentRepository_,
orderRepository: this.orderRepository_,
lineItemService: this.lineItemService_,
cartService: this.cartService_,
productVariantService: this.productVariantService_,
shippingOptionService: this.shippingOptionService_,
eventBusService: this.eventBus_,
})
cloned.transactionManager_ = transactionManager
return cloned
}
/**
* Retrieves a draft order with the given id.
* @param {string} id - id of the draft order to retrieve
* @param {object} config - query object for findOne
* @return {Promise<DraftOrder>} the draft order
*/
async retrieve(id, config = {}) {
const draftOrderRepo = this.manager_.getCustomRepository(
this.draftOrderRepository_
)
const validatedId = this.validateId_(id)
const query = this.buildQuery_({ id: validatedId }, 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 {string} cartId - cart id that the draft orders's cart has
* * @param {object} config - query object for findOne
* @return {Promise<DraftOrder>} the draft order
*/
async retrieveByCartId(cartId, config = {}) {
const draftOrderRepo = this.manager_.getCustomRepository(
this.draftOrderRepository_
)
const query = this.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} empty promise
*/
async delete(draftOrderId) {
return this.atomicPhase_(async manager => {
const draftOrderRepo = manager.getCustomRepository(
this.draftOrderRepository_
)
const draftOrder = await draftOrderRepo.findOne({
where: { id: draftOrderId },
})
if (!draftOrder) return Promise.resolve()
await draftOrderRepo.remove(draftOrder)
return Promise.resolve()
})
}
/**
* Lists draft orders alongside the count
* @param {object} selector - query selector to filter draft orders
* @param {object} config - query config
* @return {Promise<DraftOrder[]>} draft orders
*/
async listAndCount(
selector,
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
) {
const draftOrderRepository = this.manager_.getCustomRepository(
this.draftOrderRepository_
)
let q
if ("q" in selector) {
q = selector.q
delete selector.q
}
const query = this.buildQuery_(selector, config)
if (q) {
const where = query.where
delete where.display_id
query.join = {
alias: "draft_order",
innerJoin: {
cart: "draft_order.cart",
},
}
query.where = qb => {
qb.where(where)
qb.andWhere(
new Brackets(qb => {
qb.where(`cart.email ILIKE :q`, {
q: `%${q}%`,
}).orWhere(`draft_order.display_id::varchar(255) ILIKE :dId`, {
dId: `${q}`,
})
})
)
}
}
const [draftOrders, count] = await draftOrderRepository.findAndCount(query)
return [draftOrders, count]
}
/**
* Lists draft orders
* @param {Object} selector - query object for find
* @param {Object} config - configurable attributes for find
* @return {Promise<DraftOrder>} list of draft orders
*/
async list(
selector,
config = { skip: 0, take: 50, order: { created_at: "DESC" } }
) {
const draftOrderRepo = this.manager_.getCustomRepository(
this.draftOrderRepository_
)
const query = this.buildQuery_(selector, config)
return draftOrderRepo.find(query)
}
/**
* Creates a draft order.
* @param {object} data - data to create draft order from
* @return {Promise<DraftOrder>} the created draft order
*/
async create(data) {
return this.atomicPhase_(async manager => {
const draftOrderRepo = manager.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 {
items,
shipping_methods,
discounts,
no_notification_order = undefined,
...rest
} = data
if (discounts) {
for (const { code } of discounts) {
rest.discounts = []
await this.cartService_
.withTransaction(manager)
.applyDiscount(rest, code)
}
}
const createdCart = await this.cartService_
.withTransaction(manager)
.create({ type: "draft_order", ...rest })
const draftOrder = draftOrderRepo.create({
cart_id: createdCart.id,
no_notification_order,
})
const result = await draftOrderRepo.save(draftOrder)
await this.eventBus_
.withTransaction(manager)
.emit(DraftOrderService.Events.CREATED, {
id: result.id,
})
for (const item of items) {
if (item.variant_id) {
const line = await this.lineItemService_
.withTransaction(manager)
.generate(item.variant_id, data.region_id, item.quantity, {
metadata: item?.metadata || {},
unit_price: item.unit_price,
})
await this.lineItemService_.withTransaction(manager).create({
cart_id: createdCart.id,
...line,
})
} 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 this.lineItemService_.withTransaction(manager).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 this.cartService_
.withTransaction(manager)
.addShippingMethod(createdCart.id, method.option_id, method.data)
}
return result
})
}
/**
* Registers a draft order as completed, when an order has been completed.
* @param {string} doId - id of draft order to complete
* @param {string} orderId - id of order completed from draft order cart
* @return {Promise} the created order
*/
async registerCartCompletion(doId, orderId) {
return this.atomicPhase_(async manager => {
const draftOrderRepo = manager.getCustomRepository(
this.draftOrderRepository_
)
const draftOrder = await this.retrieve(doId)
draftOrder.status = "completed"
draftOrder.completed_at = new Date()
draftOrder.order_id = orderId
await draftOrderRepo.save(draftOrder)
})
}
/**
* Updates a draft order with the given data
* @param {String} doId - id of the draft order
* @param {DraftOrder} data - values to update the order with
* @returns {Promise<DraftOrder>} the updated draft order
*/
async update(doId, data) {
return this.atomicPhase_(async manager => {
const doRepo = manager.getCustomRepository(this.draftOrderRepository_)
const draftOrder = await this.retrieve(doId)
let touched = false
if (draftOrder.status === "completed") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Can't update a draft order which is complete"
)
}
if (data.no_notification_order !== undefined) {
touched = true
draftOrder.no_notification_order = data.no_notification_order
}
if (touched) {
doRepo.save(draftOrder)
await this.eventBus_
.withTransaction(manager)
.emit(DraftOrderService.Events.UPDATED, {
id: draftOrder.id,
})
}
return draftOrder
})
}
}
export default DraftOrderService