refactor(medusa): Convert DraftOrderService to TS (#1259)
This commit is contained in:
committed by
GitHub
parent
28ddf10446
commit
9a3ff32b42
@@ -353,6 +353,84 @@ describe("/admin/draft-orders", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("creates a draft order with discount and free shipping along the line item", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const payload = {
|
||||
email: "oli@test.dk",
|
||||
shipping_address: "oli-shipping",
|
||||
discounts: [{ code: "TEST" }, { code: "free-shipping"}],
|
||||
items: [
|
||||
{
|
||||
variant_id: "test-variant",
|
||||
quantity: 2,
|
||||
metadata: {},
|
||||
},
|
||||
],
|
||||
region_id: "test-region",
|
||||
customer_id: "oli-test",
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id: "test-option",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api
|
||||
.post("/admin/draft-orders", payload, {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const created = await api
|
||||
.get(`/admin/draft-orders/${response.data.draft_order.id}`, {
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const draftOrder = created.data.draft_order
|
||||
const lineItemId = draftOrder.cart.items[0].id
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(draftOrder.cart.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
variant_id: "test-variant",
|
||||
unit_price: 8000,
|
||||
quantity: 2,
|
||||
adjustments: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
item_id: lineItemId,
|
||||
amount: 1600,
|
||||
description: "discount",
|
||||
discount_id: "test-discount",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
// Check that discounts are applied
|
||||
expect(draftOrder.cart.discounts).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: "TEST",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "free-shipping",
|
||||
})
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("creates a draft order with created shipping address", async () => {
|
||||
const api = useApi()
|
||||
|
||||
|
||||
@@ -119,7 +119,15 @@ module.exports = async (connection, data = {}) => {
|
||||
type: "percentage",
|
||||
})
|
||||
|
||||
const d = manager.create(Discount, {
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "free-shipping-rule",
|
||||
description: "Free shipping rule",
|
||||
type: "free_shipping",
|
||||
value: 100,
|
||||
allocation: "total",
|
||||
})
|
||||
|
||||
const testDiscount = manager.create(Discount, {
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
is_dynamic: false,
|
||||
@@ -127,7 +135,15 @@ module.exports = async (connection, data = {}) => {
|
||||
rule_id: "discount_rule_id",
|
||||
})
|
||||
|
||||
d.regions = [
|
||||
const freeShippingDiscount = manager.create(Discount, {
|
||||
id: "free-shipping-discount",
|
||||
code: "free-shipping",
|
||||
is_dynamic: false,
|
||||
is_disabled: false,
|
||||
rule_id: "free-shipping-rule",
|
||||
})
|
||||
|
||||
testDiscount.regions = [
|
||||
{
|
||||
id: "test-region",
|
||||
name: "Test Region",
|
||||
@@ -136,7 +152,17 @@ module.exports = async (connection, data = {}) => {
|
||||
},
|
||||
]
|
||||
|
||||
await manager.save(d)
|
||||
freeShippingDiscount.regions = [
|
||||
{
|
||||
id: "test-region",
|
||||
name: "Test Region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
},
|
||||
]
|
||||
|
||||
await manager.save(testDiscount)
|
||||
await manager.save(freeShippingDiscount)
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'`
|
||||
|
||||
@@ -77,7 +77,7 @@ export const defaultAdminDraftOrdersCartFields: (keyof Cart)[] = [
|
||||
"total",
|
||||
]
|
||||
|
||||
export const defaultAdminDraftOrdersFields = [
|
||||
export const defaultAdminDraftOrdersFields: (keyof DraftOrder)[] = [
|
||||
"id",
|
||||
"status",
|
||||
"display_id",
|
||||
|
||||
@@ -7,6 +7,8 @@ import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { Type } from "class-transformer"
|
||||
import { DraftOrderListSelector } from "../../../../types/draft-orders"
|
||||
import { FindConfig } from "../../../../types/common"
|
||||
import { DraftOrder } from "../../../../models"
|
||||
/**
|
||||
* @oas [get] /draft-orders
|
||||
* operationId: "GetDraftOrders"
|
||||
@@ -38,11 +40,11 @@ export default async (req, res) => {
|
||||
selector.q = validated.q
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
const listConfig: FindConfig<DraftOrder> = {
|
||||
select: defaultAdminDraftOrdersFields,
|
||||
relations: defaultAdminDraftOrdersRelations,
|
||||
skip: validated.offset,
|
||||
take: validated.limit,
|
||||
skip: validated.offset ?? 0,
|
||||
take: validated.limit ?? 50,
|
||||
order: { created_at: "DESC" },
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Cart } from "./cart"
|
||||
import { Order } from "./order"
|
||||
import { generateEntityId } from "../utils/generate-entity-id"
|
||||
|
||||
enum DraftOrderStatus {
|
||||
export enum DraftOrderStatus {
|
||||
OPEN = "open",
|
||||
COMPLETED = "completed",
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "lodash"
|
||||
import { MockRepository, MockManager } from "medusa-test-utils"
|
||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||
import DraftOrderService from "../draft-order"
|
||||
|
||||
@@ -15,12 +15,7 @@ import { CartRepository } from "../repositories/cart"
|
||||
import { LineItemRepository } from "../repositories/line-item"
|
||||
import { PaymentSessionRepository } from "../repositories/payment-session"
|
||||
import { ShippingMethodRepository } from "../repositories/shipping-method"
|
||||
import {
|
||||
CartCreateProps,
|
||||
CartUpdateProps,
|
||||
FilterableCartProps,
|
||||
LineItemUpdate,
|
||||
} from "../types/cart"
|
||||
import { CartCreateProps, CartUpdateProps, FilterableCartProps, LineItemUpdate } from "../types/cart"
|
||||
import { AddressPayload, FindConfig, TotalField } from "../types/common"
|
||||
import { buildQuery, setMetadata, validateId } from "../utils"
|
||||
import CustomShippingOptionService from "./custom-shipping-option"
|
||||
@@ -38,6 +33,7 @@ import RegionService from "./region"
|
||||
import ShippingOptionService from "./shipping-option"
|
||||
import TaxProviderService from "./tax-provider"
|
||||
import TotalsService from "./totals"
|
||||
import { DiscountRuleType } from "../models"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
@@ -1071,7 +1067,7 @@ class CartService extends TransactionBaseService<CartService> {
|
||||
let sawNotShipping = false
|
||||
const newDiscounts = toParse.map((discountToParse) => {
|
||||
switch (discountToParse.rule?.type) {
|
||||
case "free_shipping":
|
||||
case DiscountRuleType.FREE_SHIPPING:
|
||||
if (discountToParse.rule.type === rule.type) {
|
||||
return discount
|
||||
}
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
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 {
|
||||
shipping_methods,
|
||||
discounts,
|
||||
no_notification_order,
|
||||
items,
|
||||
...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,
|
||||
cart: createdCart,
|
||||
})
|
||||
|
||||
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
|
||||
* @return {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
|
||||
432
packages/medusa/src/services/draft-order.ts
Normal file
432
packages/medusa/src/services/draft-order.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
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> {
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const draftOrderRepo = transactionManager.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> {
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const draftOrderRepo = transactionManager.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]> {
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const draftOrderRepository = transactionManager.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[]> {
|
||||
return await this.atomicPhase_(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const draftOrderRepo = transactionManager.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
|
||||
|
||||
if (rawCart.discounts) {
|
||||
const { discounts } = rawCart
|
||||
rawCart.discounts = []
|
||||
|
||||
for (const { code } of discounts) {
|
||||
await this.cartService_
|
||||
.withTransaction(transactionManager)
|
||||
.applyDiscount(rawCart as Cart, code)
|
||||
}
|
||||
}
|
||||
|
||||
const createdCart = await this.cartService_
|
||||
.withTransaction(transactionManager)
|
||||
.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,
|
||||
})
|
||||
|
||||
for (const item of items) {
|
||||
if (item.variant_id) {
|
||||
const line = await this.lineItemService_
|
||||
.withTransaction(transactionManager)
|
||||
.generate(item.variant_id, data.region_id, item.quantity, {
|
||||
metadata: item?.metadata || {},
|
||||
unit_price: item.unit_price,
|
||||
cart: createdCart,
|
||||
})
|
||||
|
||||
await this.lineItemService_
|
||||
.withTransaction(transactionManager)
|
||||
.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 this.lineItemService_
|
||||
.withTransaction(transactionManager)
|
||||
.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(transactionManager)
|
||||
.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
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es5", "es6"],
|
||||
"lib": [
|
||||
"es5",
|
||||
"es6"
|
||||
],
|
||||
"target": "es5",
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
@@ -18,7 +21,10 @@
|
||||
"skipLibCheck": true,
|
||||
"downlevelIteration": true // to use ES5 specific tooling
|
||||
},
|
||||
"include": ["./src/**/*", "index.d.ts"],
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"index.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"./dist/**/*",
|
||||
"./src/**/__tests__",
|
||||
|
||||
Reference in New Issue
Block a user