Feat(medusa, medusa-js, medusa-react): order edit confirmation (#2264)
**what** Support confirm of an order edit: Upon confirmation, the items of the original order are detached and the items from the order edit are attached to the order. The order total is recomputed with the correct total which can defer from the paid_total and refundable_amount (based on the paid_total) **Tests** - Unit tests medusa-js and medusa-react as well as the core - Integration test of the confirmation flow which check that the order edit is properly confirmed and can be confirmed idempotently. Also validate the totals and that the order items correspond to the order edit items. Also validate the order totals. FIXES CORE-498
This commit is contained in:
committed by
GitHub
parent
87ad29dda4
commit
2be00007b2
@@ -1322,7 +1322,7 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/order-edits/:id", () => {
|
||||
describe("POST /admin/order-edits/:id/cancel", () => {
|
||||
const cancellableEditId = IdMap.getId("order-edit-1")
|
||||
const canceledEditId = IdMap.getId("order-edit-2")
|
||||
const confirmedEditId = IdMap.getId("order-edit-3")
|
||||
@@ -1387,7 +1387,6 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.order_edit).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -1419,6 +1418,210 @@ describe("[MEDUSA_FF_ORDER_EDITING] /admin/order-edits", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/order-edits/:id/confirm", () => {
|
||||
let product, product2
|
||||
const prodId1 = IdMap.getId("product-1")
|
||||
const prodId2 = IdMap.getId("product-2")
|
||||
const lineItemId1 = IdMap.getId("line-item-1")
|
||||
const lineItemId2 = IdMap.getId("line-item-2")
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
product = await simpleProductFactory(dbConnection, {
|
||||
id: prodId1,
|
||||
})
|
||||
|
||||
product2 = await simpleProductFactory(dbConnection, {
|
||||
id: prodId2,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
it("confirms an order edit", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const region = await simpleRegionFactory(dbConnection, { tax_rate: 10 })
|
||||
|
||||
const cart = await simpleCartFactory(dbConnection, {
|
||||
email: "adrien@test.com",
|
||||
region: region.id,
|
||||
line_items: [
|
||||
{
|
||||
id: lineItemId1,
|
||||
variant_id: product.variants[0].id,
|
||||
quantity: 1,
|
||||
unit_price: 1000,
|
||||
},
|
||||
{
|
||||
id: lineItemId2,
|
||||
variant_id: product2.variants[0].id,
|
||||
quantity: 1,
|
||||
unit_price: 1000,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||
|
||||
const completeRes = await api.post(`/store/carts/${cart.id}/complete`)
|
||||
|
||||
const order = completeRes.data.data
|
||||
|
||||
let response = await api.post(
|
||||
`/admin/order-edits/`,
|
||||
{
|
||||
order_id: order.id,
|
||||
internal_note: "This is an internal note",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const orderEditId = response.data.order_edit.id
|
||||
|
||||
const itemToUpdate = response.data.order_edit.items.find(
|
||||
(item) => item.original_item_id === lineItemId1
|
||||
)
|
||||
|
||||
response = await api.post(
|
||||
`/admin/order-edits/${orderEditId}/items/${itemToUpdate.id}`,
|
||||
{ quantity: 2 },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const orderEditItems = response.data.order_edit.items
|
||||
|
||||
response = await api.post(
|
||||
`/admin/order-edits/${orderEditId}/confirm`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.order_edit).toEqual(
|
||||
expect.objectContaining({
|
||||
id: orderEditId,
|
||||
created_by: "admin_user",
|
||||
confirmed_by: "admin_user",
|
||||
confirmed_at: expect.any(String),
|
||||
status: "confirmed",
|
||||
discount_total: 0,
|
||||
gift_card_total: 0,
|
||||
gift_card_tax_total: 0,
|
||||
shipping_total: 0,
|
||||
subtotal: 3000,
|
||||
tax_total: 300,
|
||||
total: 3300,
|
||||
})
|
||||
)
|
||||
|
||||
response = await api.get(`/admin/orders/${order.id}`, adminHeaders)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.order).toEqual(
|
||||
expect.objectContaining({
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: orderEditItems[0].id,
|
||||
original_item_id: orderEditItems[0].original_item_id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: orderEditItems[1].id,
|
||||
original_item_id: orderEditItems[1].original_item_id,
|
||||
}),
|
||||
]),
|
||||
shipping_total: 0,
|
||||
discount_total: 0,
|
||||
tax_total: 300,
|
||||
refunded_total: 0,
|
||||
gift_card_total: 0,
|
||||
gift_card_tax_total: 0,
|
||||
subtotal: 3000,
|
||||
total: 3300,
|
||||
paid_total: 2200,
|
||||
refundable_amount: 2200,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("confirms an already confirmed order edit", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const confirmedOrderEdit = await simpleOrderEditFactory(dbConnection, {
|
||||
created_by: "admin_user",
|
||||
confirmed_at: new Date(),
|
||||
confirmed_by: "admin_user",
|
||||
})
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/order-edits/${confirmedOrderEdit.id}/confirm`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.order_edit).toEqual(
|
||||
expect.objectContaining({
|
||||
id: confirmedOrderEdit.id,
|
||||
created_by: "admin_user",
|
||||
confirmed_by: "admin_user",
|
||||
confirmed_at: expect.any(String),
|
||||
status: "confirmed",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("confirms an already canceled order edit", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const canceledOrderEdit = await simpleOrderEditFactory(dbConnection, {
|
||||
created_by: "admin_user",
|
||||
canceled_at: new Date(),
|
||||
canceled_by: "admin_user",
|
||||
})
|
||||
|
||||
const err = await api
|
||||
.post(
|
||||
`/admin/order-edits/${canceledOrderEdit.id}/confirm`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toEqual(400)
|
||||
expect(err.response.data.message).toEqual(
|
||||
"Cannot confirm an order edit with status canceled"
|
||||
)
|
||||
})
|
||||
|
||||
it("confirms an already declined order edit", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const declinedOrderEdit = await simpleOrderEditFactory(dbConnection, {
|
||||
created_by: "admin_user",
|
||||
declined_at: new Date(),
|
||||
declined_by: "admin_user",
|
||||
})
|
||||
|
||||
const err = await api
|
||||
.post(
|
||||
`/admin/order-edits/${declinedOrderEdit.id}/confirm`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toEqual(400)
|
||||
expect(err.response.data.message).toEqual(
|
||||
"Cannot confirm an order edit with status declined"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/order-edits/:id/items/:item_id", () => {
|
||||
let product, product2
|
||||
const orderId = IdMap.getId("order-1")
|
||||
|
||||
@@ -78,6 +78,14 @@ class AdminOrderEditsResource extends BaseResource {
|
||||
return this.client.request("POST", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
confirm(
|
||||
id: string,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminOrderEditsRes> {
|
||||
const path = `/admin/order-edits/${id}/confirm`
|
||||
return this.client.request("POST", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
updateLineItem(
|
||||
orderEditId: string,
|
||||
itemId: string,
|
||||
|
||||
@@ -1695,12 +1695,29 @@ export const adminHandlers = [
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
|
||||
rest.post("/admin/order-edits/:id/cancel", (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
order_edit: { ...fixtures.get("order_edit"), canceled_at: new Date(), status: 'canceled' },
|
||||
order_edit: {
|
||||
...fixtures.get("order_edit"),
|
||||
canceled_at: new Date(),
|
||||
status: "canceled",
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
rest.post("/admin/order-edits/:id/confirm", (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
order_edit: {
|
||||
...fixtures.get("order_edit"),
|
||||
confirmed_at: new Date(),
|
||||
status: "confirmed",
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -153,24 +153,36 @@ export const useAdminRequestOrderEditConfirmation = (
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export const useAdminCancelOrderEdit = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
Response<AdminOrderEditsRes>,
|
||||
Error
|
||||
>
|
||||
options?: UseMutationOptions<Response<AdminOrderEditsRes>, Error>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(
|
||||
() =>
|
||||
client.admin.orderEdits.cancel(id),
|
||||
() => client.admin.orderEdits.cancel(id),
|
||||
buildOptions(
|
||||
queryClient,
|
||||
[adminOrderEditsKeys.lists(), adminOrderEditsKeys.detail(id)],
|
||||
options
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const useAdminConfirmOrderEdit = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<Response<AdminOrderEditsRes>, Error>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(
|
||||
() => client.admin.orderEdits.confirm(id),
|
||||
buildOptions(
|
||||
queryClient,
|
||||
[adminOrderEditsKeys.lists(), adminOrderEditsKeys.detail(id)],
|
||||
options
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { renderHook } from "@testing-library/react-hooks"
|
||||
import {
|
||||
useAdminCancelOrderEdit,
|
||||
useAdminConfirmOrderEdit,
|
||||
useAdminCreateOrderEdit,
|
||||
useAdminDeleteOrderEdit,
|
||||
useAdminDeleteOrderEditItemChange,
|
||||
@@ -220,3 +221,29 @@ describe("useAdminCancelOrderEdit hook", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("useAdminConfirmOrderEdit hook", () => {
|
||||
test("confirm an order edit", async () => {
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useAdminConfirmOrderEdit(fixtures.get("order_edit").id),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
result.current.mutate()
|
||||
|
||||
await waitFor(() => result.current.isSuccess)
|
||||
|
||||
expect(result.current.data.response.status).toEqual(200)
|
||||
expect(result.current.data).toEqual(
|
||||
expect.objectContaining({
|
||||
order_edit: {
|
||||
...fixtures.get("order_edit"),
|
||||
confirmed_at: expect.any(String),
|
||||
status: "confirmed",
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,14 +9,18 @@ describe("POST /admin/order-edits/:id/cancel", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", `/admin/order-edits/${orderEditId}/cancel`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/order-edits/${orderEditId}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
},
|
||||
flags: [OrderEditingFeatureFlag],
|
||||
})
|
||||
flags: [OrderEditingFeatureFlag],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@@ -25,7 +29,9 @@ describe("POST /admin/order-edits/:id/cancel", () => {
|
||||
|
||||
it("calls orderService cancel", () => {
|
||||
expect(orderEditServiceMock.cancel).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditServiceMock.cancel).toHaveBeenCalledWith(orderEditId, {loggedInUser: IdMap.getId("admin_user")})
|
||||
expect(orderEditServiceMock.cancel).toHaveBeenCalledWith(orderEditId, {
|
||||
loggedInUserId: IdMap.getId("admin_user"),
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
@@ -33,11 +39,13 @@ describe("POST /admin/order-edits/:id/cancel", () => {
|
||||
})
|
||||
|
||||
it("returns cancel result", () => {
|
||||
expect(subject.body.order_edit).toEqual(expect.objectContaining({
|
||||
id: orderEditId,
|
||||
canceled_at: expect.any(String),
|
||||
status: 'canceled'
|
||||
}))
|
||||
expect(subject.body.order_edit).toEqual(
|
||||
expect.objectContaining({
|
||||
id: orderEditId,
|
||||
canceled_at: expect.any(String),
|
||||
status: "canceled",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import OrderEditingFeatureFlag from "../../../../../loaders/feature-flags/order-editing"
|
||||
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
||||
|
||||
describe("POST /admin/order-edits/:id/confirm", () => {
|
||||
describe("confirms an order edit", () => {
|
||||
const orderEditId = IdMap.getId("testConfirmOrderEdit")
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/order-edits/${orderEditId}/confirm`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: "admin_user",
|
||||
},
|
||||
},
|
||||
flags: [OrderEditingFeatureFlag],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls orderService confirm", () => {
|
||||
expect(orderEditServiceMock.confirm).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditServiceMock.confirm).toHaveBeenCalledWith(orderEditId, {
|
||||
loggedInUserId: "admin_user",
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns confirm result", () => {
|
||||
expect(subject.body.order_edit).toEqual(
|
||||
expect.objectContaining({
|
||||
id: orderEditId,
|
||||
confirmed_at: expect.any(String),
|
||||
confirmed_by: "admin_user",
|
||||
status: "confirmed",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -31,7 +31,7 @@ describe("GET /admin/order-edits/:id", () => {
|
||||
expect(orderEditServiceMock.requestConfirmation).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditServiceMock.requestConfirmation).toHaveBeenCalledWith(
|
||||
orderEditId,
|
||||
{ loggedInUser: IdMap.getId("admin_user") }
|
||||
{ loggedInUserId: IdMap.getId("admin_user") }
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Request, Response } from "express"
|
||||
import { OrderEditService } from "../../../../services"
|
||||
import { IsOptional, IsString } from "class-validator"
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
defaultOrderEditFields,
|
||||
defaultOrderEditRelations,
|
||||
} from "../../../../types/order-edit"
|
||||
|
||||
/**
|
||||
* @oas [post] /order-edits/{id}/cancel
|
||||
@@ -64,10 +67,13 @@ export default async (req: Request, res: Response) => {
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await orderEditService
|
||||
.withTransaction(transactionManager)
|
||||
.cancel(id, { loggedInUser: userId })
|
||||
.cancel(id, { loggedInUserId: userId })
|
||||
})
|
||||
|
||||
const orderEdit = await orderEditService.retrieve(id)
|
||||
const orderEdit = await orderEditService.retrieve(id, {
|
||||
select: defaultOrderEditFields,
|
||||
relations: defaultOrderEditRelations,
|
||||
})
|
||||
|
||||
return res.json({ order_edit: orderEdit })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Request, Response } from "express"
|
||||
import { OrderEditService } from "../../../../services"
|
||||
import { EntityManager } from "typeorm"
|
||||
import {
|
||||
defaultOrderEditFields,
|
||||
defaultOrderEditRelations,
|
||||
} from "../../../../types/order-edit"
|
||||
|
||||
/**
|
||||
* @oas [post] /order-edits/{id}/confirm
|
||||
* operationId: "PostOrderEditsOrderEditConfirm"
|
||||
* summary: "Confirms an OrderEdit"
|
||||
* description: "Confirms an OrderEdit."
|
||||
* x-authenticated: true
|
||||
* parameters:
|
||||
* - (path) id=* {string} The ID of the order edit.
|
||||
* x-codeSamples:
|
||||
* - lang: JavaScript
|
||||
* label: JS Client
|
||||
* source: |
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.orderEdit.confirm(orderEditId)
|
||||
* .then(({ order_edit }) => {
|
||||
* console.log(order_edit.id)
|
||||
* })
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request POST 'https://medusa-url.com/admin/order-edits/:id/confirm' \
|
||||
* --header 'Authorization: Bearer {api_token}'
|
||||
* security:
|
||||
* - api_token: []
|
||||
* - cookie_auth: []
|
||||
* tags:
|
||||
* - OrderEdit
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* order_edit:
|
||||
* $ref: "#/components/schemas/order_edit"
|
||||
* "400":
|
||||
* $ref: "#/components/responses/400_error"
|
||||
* "401":
|
||||
* $ref: "#/components/responses/unauthorized"
|
||||
* "404":
|
||||
* $ref: "#/components/responses/not_found_error"
|
||||
* "500":
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req: Request, res: Response) => {
|
||||
const { id } = req.params
|
||||
|
||||
const orderEditService = req.scope.resolve(
|
||||
"orderEditService"
|
||||
) as OrderEditService
|
||||
|
||||
const manager = req.scope.resolve("manager") as EntityManager
|
||||
|
||||
const userId = req.user?.id ?? req.user?.userId
|
||||
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await orderEditService
|
||||
.withTransaction(transactionManager)
|
||||
.confirm(id, { loggedInUserId: userId })
|
||||
})
|
||||
|
||||
let orderEdit = await orderEditService.retrieve(id, {
|
||||
select: defaultOrderEditFields,
|
||||
relations: defaultOrderEditRelations,
|
||||
})
|
||||
orderEdit = await orderEditService.decorateTotals(orderEdit)
|
||||
|
||||
return res.json({ order_edit: orderEdit })
|
||||
}
|
||||
@@ -59,6 +59,11 @@ export default (app) => {
|
||||
middlewares.wrap(require("./add-line-item").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/confirm",
|
||||
middlewares.wrap(require("./confirm-order-edit").default)
|
||||
)
|
||||
|
||||
route.delete("/:id", middlewares.wrap(require("./delete-order-edit").default))
|
||||
|
||||
route.delete(
|
||||
|
||||
@@ -65,7 +65,7 @@ export default async (req, res) => {
|
||||
await manager.transaction(async (transactionManager) => {
|
||||
await orderEditService
|
||||
.withTransaction(transactionManager)
|
||||
.requestConfirmation(id, { loggedInUser })
|
||||
.requestConfirmation(id, { loggedInUserId: loggedInUser })
|
||||
})
|
||||
|
||||
const orderEdit = await orderEditService.retrieve(id, {
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("GET /store/order-edits/:id", () => {
|
||||
expect(orderEditServiceMock.decline).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditServiceMock.decline).toHaveBeenCalledWith(orderEditId, {
|
||||
declinedReason: "test",
|
||||
loggedInUser: undefined,
|
||||
loggedInUserId: undefined,
|
||||
})
|
||||
expect(orderEditServiceMock.decorateTotals).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -72,7 +72,7 @@ export default async (req: Request, res: Response) => {
|
||||
await manager.transaction(async (manager) => {
|
||||
await orderEditService.withTransaction(manager).decline(id, {
|
||||
declinedReason: validatedBody.declined_reason,
|
||||
loggedInUser: userId,
|
||||
loggedInUserId: userId,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export class LineItem extends BaseEntity {
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
order_id: string
|
||||
order_id: string | null
|
||||
|
||||
@ManyToOne(() => Order, (order) => order.items)
|
||||
@JoinColumn({ name: "order_id" })
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const orderEdits = {
|
||||
testCreatedOrder: {
|
||||
id: IdMap.getId("testCreatedOrder"),
|
||||
order_id: "empty-id",
|
||||
internal_note: "internal note",
|
||||
declined_reason: null,
|
||||
declined_at: null,
|
||||
declined_by: null,
|
||||
canceled_at: null,
|
||||
canceled_by: null,
|
||||
requested_at: null,
|
||||
requested_by: null,
|
||||
created_at: new Date(),
|
||||
created_by: "admin_user",
|
||||
confirmed_at: null,
|
||||
confirmed_by: null,
|
||||
},
|
||||
export const orderEdit = {
|
||||
id: IdMap.getId("testCreatedOrder"),
|
||||
order_id: "empty-id",
|
||||
internal_note: "internal note",
|
||||
declined_reason: null,
|
||||
declined_at: null,
|
||||
declined_by: null,
|
||||
canceled_at: null,
|
||||
canceled_by: null,
|
||||
requested_at: null,
|
||||
requested_by: null,
|
||||
created_at: new Date(),
|
||||
created_by: "admin_user",
|
||||
confirmed_at: null,
|
||||
confirmed_by: null,
|
||||
}
|
||||
|
||||
const computeLineItems = (orderEdit) => ({
|
||||
@@ -48,11 +46,20 @@ export const orderEditServiceMock = {
|
||||
},
|
||||
retrieve: jest.fn().mockImplementation((orderId) => {
|
||||
if (orderId === IdMap.getId("testCreatedOrder")) {
|
||||
return Promise.resolve(orderEdits.testCreatedOrder)
|
||||
return Promise.resolve(orderEdit)
|
||||
}
|
||||
if (orderId === IdMap.getId("testConfirmOrderEdit")) {
|
||||
return Promise.resolve({
|
||||
...orderEdit,
|
||||
id: IdMap.getId("testConfirmOrderEdit"),
|
||||
confirmed_at: new Date(),
|
||||
confirmed_by: "admin_user",
|
||||
status: "confirmed",
|
||||
})
|
||||
}
|
||||
if (orderId === IdMap.getId("testDeclineOrderEdit")) {
|
||||
return Promise.resolve({
|
||||
...orderEdits.testCreatedOrder,
|
||||
...orderEdit,
|
||||
id: IdMap.getId("testDeclineOrderEdit"),
|
||||
declined_reason: "Wrong size",
|
||||
declined_at: new Date(),
|
||||
@@ -60,7 +67,7 @@ export const orderEditServiceMock = {
|
||||
}
|
||||
if (orderId === IdMap.getId("testCancelOrderEdit")) {
|
||||
return Promise.resolve({
|
||||
...orderEdits.testCreatedOrder,
|
||||
...orderEdit,
|
||||
id: orderId,
|
||||
canceled_at: new Date(),
|
||||
status: "canceled",
|
||||
@@ -68,7 +75,7 @@ export const orderEditServiceMock = {
|
||||
}
|
||||
if (orderId === IdMap.getId("testRequestOrder")) {
|
||||
return Promise.resolve({
|
||||
...orderEdits.testCreatedOrder,
|
||||
...orderEdit,
|
||||
id: IdMap.getId("testRequestOrder"),
|
||||
requested_by: IdMap.getId("admin_user"),
|
||||
requested_at: new Date(),
|
||||
@@ -111,7 +118,7 @@ export const orderEditServiceMock = {
|
||||
}),
|
||||
requestConfirmation: jest.fn().mockImplementation((orderEditId, userId) => {
|
||||
return Promise.resolve({
|
||||
...orderEdits.testCreatedOrder,
|
||||
...orderEdit,
|
||||
id: orderEditId,
|
||||
requested_at: new Date(),
|
||||
requested_by: userId,
|
||||
@@ -120,6 +127,9 @@ export const orderEditServiceMock = {
|
||||
cancel: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({})
|
||||
}),
|
||||
confirm: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({})
|
||||
}),
|
||||
updateLineItem: jest.fn().mockImplementation((_) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
|
||||
@@ -4,6 +4,7 @@ import LineItemService from "../line-item"
|
||||
import { PricingServiceMock } from "../__mocks__/pricing"
|
||||
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
||||
import { RegionServiceMock } from "../__mocks__/region"
|
||||
|
||||
;[true, false].forEach((isTaxInclusiveEnabled) => {
|
||||
describe(`tax inclusive flag set to: ${isTaxInclusiveEnabled}`, () => {
|
||||
describe("LineItemService", () => {
|
||||
@@ -167,21 +168,23 @@ import { RegionServiceMock } from "../__mocks__/region"
|
||||
|
||||
describe("update", () => {
|
||||
const lineItemRepository = MockRepository({
|
||||
findOne: () =>
|
||||
Promise.resolve({
|
||||
id: IdMap.getId("test-line-item"),
|
||||
variant_id: IdMap.getId("test-variant"),
|
||||
variant: {
|
||||
id: IdMap.getId("test-variant"),
|
||||
title: "Test variant",
|
||||
find: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: IdMap.getId("test-line-item"),
|
||||
variant_id: IdMap.getId("test-variant"),
|
||||
variant: {
|
||||
id: IdMap.getId("test-variant"),
|
||||
title: "Test variant",
|
||||
},
|
||||
cart_id: IdMap.getId("test-cart"),
|
||||
title: "Test product",
|
||||
description: "Test variant",
|
||||
thumbnail: "",
|
||||
unit_price: 50,
|
||||
quantity: 1,
|
||||
},
|
||||
cart_id: IdMap.getId("test-cart"),
|
||||
title: "Test product",
|
||||
description: "Test variant",
|
||||
thumbnail: "",
|
||||
unit_price: 50,
|
||||
quantity: 1,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
|
||||
const lineItemService = new LineItemService({
|
||||
@@ -200,21 +203,23 @@ import { RegionServiceMock } from "../__mocks__/region"
|
||||
})
|
||||
|
||||
expect(lineItemRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemRepository.save).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("test-line-item"),
|
||||
variant_id: IdMap.getId("test-variant"),
|
||||
variant: {
|
||||
id: IdMap.getId("test-variant"),
|
||||
title: "Test variant",
|
||||
expect(lineItemRepository.save).toHaveBeenCalledWith([
|
||||
{
|
||||
id: IdMap.getId("test-line-item"),
|
||||
variant_id: IdMap.getId("test-variant"),
|
||||
variant: {
|
||||
id: IdMap.getId("test-variant"),
|
||||
title: "Test variant",
|
||||
},
|
||||
cart_id: IdMap.getId("test-cart"),
|
||||
title: "Test product",
|
||||
description: "Test variant",
|
||||
thumbnail: "",
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
has_shipping: true,
|
||||
},
|
||||
cart_id: IdMap.getId("test-cart"),
|
||||
title: "Test product",
|
||||
description: "Test variant",
|
||||
thumbnail: "",
|
||||
unit_price: 50,
|
||||
quantity: 2,
|
||||
has_shipping: true,
|
||||
})
|
||||
])
|
||||
})
|
||||
|
||||
it("successfully updates a line item with metadata", async () => {
|
||||
@@ -225,23 +230,25 @@ import { RegionServiceMock } from "../__mocks__/region"
|
||||
})
|
||||
|
||||
expect(lineItemRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemRepository.save).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("test-line-item"),
|
||||
variant_id: IdMap.getId("test-variant"),
|
||||
variant: {
|
||||
id: IdMap.getId("test-variant"),
|
||||
title: "Test variant",
|
||||
expect(lineItemRepository.save).toHaveBeenCalledWith([
|
||||
{
|
||||
id: IdMap.getId("test-line-item"),
|
||||
variant_id: IdMap.getId("test-variant"),
|
||||
variant: {
|
||||
id: IdMap.getId("test-variant"),
|
||||
title: "Test variant",
|
||||
},
|
||||
cart_id: IdMap.getId("test-cart"),
|
||||
title: "Test product",
|
||||
description: "Test variant",
|
||||
thumbnail: "",
|
||||
unit_price: 50,
|
||||
quantity: 1,
|
||||
metadata: {
|
||||
testKey: "testValue",
|
||||
},
|
||||
},
|
||||
cart_id: IdMap.getId("test-cart"),
|
||||
title: "Test product",
|
||||
description: "Test variant",
|
||||
thumbnail: "",
|
||||
unit_price: 50,
|
||||
quantity: 1,
|
||||
metadata: {
|
||||
testKey: "testValue",
|
||||
},
|
||||
})
|
||||
])
|
||||
})
|
||||
})
|
||||
describe("delete", () => {
|
||||
|
||||
@@ -258,7 +258,7 @@ describe("OrderEditService", () => {
|
||||
IdMap.getId("requested-order-edit"),
|
||||
{
|
||||
declinedReason: "I requested a different color for the new product",
|
||||
loggedInUser: "admin_user",
|
||||
loggedInUserId: "admin_user",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -276,7 +276,7 @@ describe("OrderEditService", () => {
|
||||
await expect(
|
||||
orderEditService.decline(IdMap.getId("confirmed-order-edit"), {
|
||||
declinedReason: "I requested a different color for the new product",
|
||||
loggedInUser: "admin_user",
|
||||
loggedInUserId: "admin_user",
|
||||
})
|
||||
).rejects.toThrowError(
|
||||
"Cannot decline an order edit with status confirmed."
|
||||
@@ -288,7 +288,7 @@ describe("OrderEditService", () => {
|
||||
IdMap.getId("declined-order-edit"),
|
||||
{
|
||||
declinedReason: "I requested a different color for the new product",
|
||||
loggedInUser: "admin_user",
|
||||
loggedInUserId: "admin_user",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -312,7 +312,7 @@ describe("OrderEditService", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
result = await orderEditService.requestConfirmation(orderEditId, {
|
||||
loggedInUser: userId,
|
||||
loggedInUserId: userId,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -344,7 +344,7 @@ describe("OrderEditService", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
result = await orderEditService.requestConfirmation(orderEditId, {
|
||||
loggedInUser: userId,
|
||||
loggedInUserId: userId,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -358,7 +358,7 @@ describe("OrderEditService", () => {
|
||||
const id = IdMap.getId("order-edit-with-changes")
|
||||
const userId = IdMap.getId("user-id")
|
||||
|
||||
await orderEditService.cancel(id, { loggedInUser: userId })
|
||||
await orderEditService.cancel(id, { loggedInUserId: userId })
|
||||
|
||||
expect(orderEditRepository.save).toHaveBeenCalledWith({
|
||||
...orderEditWithChanges,
|
||||
@@ -402,6 +402,39 @@ describe("OrderEditService", () => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe("confirm", () => {
|
||||
it("confirms an order edit", async () => {
|
||||
const id = IdMap.getId("order-edit-with-changes")
|
||||
const userId = IdMap.getId("user-id")
|
||||
|
||||
await orderEditService.confirm(id, { loggedInUserId: userId })
|
||||
|
||||
expect(orderEditRepository.save).toHaveBeenCalledWith({
|
||||
...orderEditWithChanges,
|
||||
confirmed_by: userId,
|
||||
confirmed_at: expect.any(Date),
|
||||
})
|
||||
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||
OrderEditService.Events.CONFIRMED,
|
||||
{ id }
|
||||
)
|
||||
})
|
||||
|
||||
it("Returns early in case of an already confirmed order edit", async () => {
|
||||
const id = IdMap.getId("confirmed-order-edit")
|
||||
const userId = IdMap.getId("user-id")
|
||||
|
||||
const result = await orderEditService.confirm(id, userId)
|
||||
|
||||
expect(result).toEqual(expect.objectContaining({ status: "confirmed" }))
|
||||
|
||||
expect(orderEditRepository.save).toHaveBeenCalledTimes(0)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should add a line item to an order edit", async () => {
|
||||
|
||||
@@ -1838,6 +1838,9 @@ class CartService extends TransactionBaseService {
|
||||
relations: ["countries"],
|
||||
})
|
||||
|
||||
const lineItemServiceTx =
|
||||
this.lineItemService_.withTransaction(transactionManager)
|
||||
|
||||
cart.items = (
|
||||
await Promise.all(
|
||||
cart.items.map(async (item) => {
|
||||
@@ -1856,21 +1859,19 @@ class CartService extends TransactionBaseService {
|
||||
availablePrice !== undefined &&
|
||||
availablePrice.calculatedPrice !== null
|
||||
) {
|
||||
return this.lineItemService_
|
||||
.withTransaction(transactionManager)
|
||||
.update(item.id, {
|
||||
has_shipping: false,
|
||||
unit_price: availablePrice.calculatedPrice,
|
||||
})
|
||||
return lineItemServiceTx.update(item.id, {
|
||||
has_shipping: false,
|
||||
unit_price: availablePrice.calculatedPrice,
|
||||
})
|
||||
} else {
|
||||
await this.lineItemService_
|
||||
.withTransaction(transactionManager)
|
||||
.delete(item.id)
|
||||
await lineItemServiceTx.delete(item.id)
|
||||
return
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter((item): item is LineItem => !!item)
|
||||
)
|
||||
.flat()
|
||||
.filter((item): item is LineItem => !!item)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DeepPartial } from "typeorm/common/DeepPartial"
|
||||
import { CartRepository } from "../repositories/cart"
|
||||
import { LineItemRepository } from "../repositories/line-item"
|
||||
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
|
||||
import { Cart, LineItemTaxLine, LineItem, LineItemAdjustment } from "../models"
|
||||
import { Cart, LineItem, LineItemAdjustment, LineItemTaxLine } from "../models"
|
||||
import { FindConfig, Selector } from "../types/common"
|
||||
import { FlagRouter } from "../utils/flag-router"
|
||||
import LineItemAdjustmentService from "./line-item-adjustment"
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ProductVariantService,
|
||||
RegionService,
|
||||
} from "./index"
|
||||
import { setMetadata } from "../utils"
|
||||
|
||||
type InjectedDependencies = {
|
||||
manager: EntityManager
|
||||
@@ -310,11 +311,14 @@ class LineItemService extends BaseService {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param idOrSelector - the id or selector of the line item(s) to update
|
||||
* @param data - the properties to update the line item(s)
|
||||
* @return the updated line item(s)
|
||||
*/
|
||||
async update(id: string, data: Partial<LineItem>): Promise<LineItem> {
|
||||
async update(
|
||||
idOrSelector: string | Selector<LineItem>,
|
||||
data: Partial<LineItem>
|
||||
): Promise<LineItem[]> {
|
||||
const { metadata, ...rest } = data
|
||||
|
||||
return await this.atomicPhase_(
|
||||
@@ -323,17 +327,34 @@ class LineItemService extends BaseService {
|
||||
this.lineItemRepository_
|
||||
)
|
||||
|
||||
const lineItem = await this.retrieve(id).then((lineItem) => {
|
||||
const lineItemMetadata = metadata
|
||||
? this.setMetadata_(lineItem, metadata)
|
||||
: lineItem.metadata
|
||||
const selector =
|
||||
typeof idOrSelector === "string" ? { id: idOrSelector } : idOrSelector
|
||||
|
||||
return Object.assign(lineItem, {
|
||||
let lineItems = await this.list(selector)
|
||||
|
||||
if (!lineItems.length) {
|
||||
const selectorConstraints = Object.entries(selector)
|
||||
.map(([key, value]) => `${key}: ${value}`)
|
||||
.join(", ")
|
||||
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Line item with ${selectorConstraints} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
lineItems = lineItems.map((item) => {
|
||||
const lineItemMetadata = metadata
|
||||
? setMetadata(item, metadata)
|
||||
: item.metadata
|
||||
|
||||
return Object.assign(item, {
|
||||
...rest,
|
||||
metadata: lineItemMetadata,
|
||||
})
|
||||
})
|
||||
return await lineItemRepository.save(lineItem)
|
||||
|
||||
return await lineItemRepository.save(lineItems)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EntityManager, IsNull } from "typeorm"
|
||||
import { DeepPartial, EntityManager, IsNull } from "typeorm"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
import { FindConfig } from "../types/common"
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
import {
|
||||
AddOrderEditLineItemInput,
|
||||
CreateOrderEditInput,
|
||||
UpdateOrderEditInput,
|
||||
} from "../types/order-edit"
|
||||
|
||||
type InjectedDependencies = {
|
||||
@@ -47,6 +46,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
DECLINED: "order-edit.declined",
|
||||
REQUESTED: "order-edit.requested",
|
||||
CANCELED: "order-edit.canceled",
|
||||
CONFIRMED: "order-edit.confirmed",
|
||||
}
|
||||
|
||||
protected readonly manager_: EntityManager
|
||||
@@ -109,27 +109,6 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
return orderEdit
|
||||
}
|
||||
|
||||
protected async retrieveActive(
|
||||
orderId: string,
|
||||
config: FindConfig<OrderEdit> = {}
|
||||
): Promise<OrderEdit | undefined> {
|
||||
const manager = this.transactionManager_ ?? this.manager_
|
||||
const orderEditRepository = manager.getCustomRepository(
|
||||
this.orderEditRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery(
|
||||
{
|
||||
order_id: orderId,
|
||||
confirmed_at: IsNull(),
|
||||
canceled_at: IsNull(),
|
||||
declined_at: IsNull(),
|
||||
},
|
||||
config
|
||||
)
|
||||
return await orderEditRepository.findOne(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute and return the different totals from the order edit id
|
||||
* @param orderEditId
|
||||
@@ -235,7 +214,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
|
||||
async update(
|
||||
orderEditId: string,
|
||||
data: UpdateOrderEditInput
|
||||
data: DeepPartial<OrderEdit>
|
||||
): Promise<OrderEdit> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const orderEditRepo = manager.getCustomRepository(
|
||||
@@ -290,7 +269,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
orderEditId: string,
|
||||
context: {
|
||||
declinedReason?: string
|
||||
loggedInUser?: string
|
||||
loggedInUserId?: string
|
||||
}
|
||||
): Promise<OrderEdit> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
@@ -298,7 +277,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
this.orderEditRepository_
|
||||
)
|
||||
|
||||
const { loggedInUser, declinedReason } = context
|
||||
const { loggedInUserId, declinedReason } = context
|
||||
|
||||
const orderEdit = await this.retrieve(orderEditId)
|
||||
|
||||
@@ -314,7 +293,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
orderEdit.declined_at = new Date()
|
||||
orderEdit.declined_by = loggedInUser
|
||||
orderEdit.declined_by = loggedInUserId
|
||||
orderEdit.declined_reason = declinedReason
|
||||
|
||||
const result = await orderEditRepo.save(orderEdit)
|
||||
@@ -573,7 +552,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
async requestConfirmation(
|
||||
orderEditId: string,
|
||||
context: {
|
||||
loggedInUser?: string
|
||||
loggedInUserId?: string
|
||||
} = {}
|
||||
): Promise<OrderEdit> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
@@ -598,7 +577,7 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
}
|
||||
|
||||
orderEdit.requested_at = new Date()
|
||||
orderEdit.requested_by = context.loggedInUser
|
||||
orderEdit.requested_by = context.loggedInUserId
|
||||
|
||||
orderEdit = await orderEditRepo.save(orderEdit)
|
||||
|
||||
@@ -610,6 +589,118 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async cancel(
|
||||
orderEditId: string,
|
||||
context: { loggedInUserId?: string } = {}
|
||||
): Promise<OrderEdit> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const orderEditRepository = manager.getCustomRepository(
|
||||
this.orderEditRepository_
|
||||
)
|
||||
|
||||
const orderEdit = await this.retrieve(orderEditId)
|
||||
|
||||
if (orderEdit.status === OrderEditStatus.CANCELED) {
|
||||
return orderEdit
|
||||
}
|
||||
|
||||
if (
|
||||
[OrderEditStatus.CONFIRMED, OrderEditStatus.DECLINED].includes(
|
||||
orderEdit.status
|
||||
)
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot cancel order edit with status ${orderEdit.status}`
|
||||
)
|
||||
}
|
||||
|
||||
orderEdit.canceled_at = new Date()
|
||||
orderEdit.canceled_by = context.loggedInUserId
|
||||
|
||||
const saved = await orderEditRepository.save(orderEdit)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(OrderEditService.Events.CANCELED, { id: orderEditId })
|
||||
|
||||
return saved
|
||||
})
|
||||
}
|
||||
|
||||
async confirm(
|
||||
orderEditId: string,
|
||||
context: { loggedInUserId?: string } = {}
|
||||
): Promise<OrderEdit> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const orderEditRepository = manager.getCustomRepository(
|
||||
this.orderEditRepository_
|
||||
)
|
||||
|
||||
let orderEdit = await this.retrieve(orderEditId)
|
||||
|
||||
if (
|
||||
[OrderEditStatus.CANCELED, OrderEditStatus.DECLINED].includes(
|
||||
orderEdit.status
|
||||
)
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot confirm an order edit with status ${orderEdit.status}`
|
||||
)
|
||||
}
|
||||
|
||||
if (orderEdit.status === OrderEditStatus.CONFIRMED) {
|
||||
return orderEdit
|
||||
}
|
||||
|
||||
const lineItemServiceTx = this.lineItemService_.withTransaction(manager)
|
||||
|
||||
await Promise.all([
|
||||
lineItemServiceTx.update(
|
||||
{ order_id: orderEdit.order_id },
|
||||
{ order_id: null }
|
||||
),
|
||||
lineItemServiceTx.update(
|
||||
{ order_edit_id: orderEditId },
|
||||
{ order_id: orderEdit.order_id }
|
||||
),
|
||||
])
|
||||
|
||||
orderEdit.confirmed_at = new Date()
|
||||
orderEdit.confirmed_by = context.loggedInUserId
|
||||
|
||||
orderEdit = await orderEditRepository.save(orderEdit)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(OrderEditService.Events.CONFIRMED, { id: orderEditId })
|
||||
|
||||
return orderEdit
|
||||
})
|
||||
}
|
||||
|
||||
protected async retrieveActive(
|
||||
orderId: string,
|
||||
config: FindConfig<OrderEdit> = {}
|
||||
): Promise<OrderEdit | undefined> {
|
||||
const manager = this.transactionManager_ ?? this.manager_
|
||||
const orderEditRepository = manager.getCustomRepository(
|
||||
this.orderEditRepository_
|
||||
)
|
||||
|
||||
const query = buildQuery(
|
||||
{
|
||||
order_id: orderId,
|
||||
confirmed_at: IsNull(),
|
||||
canceled_at: IsNull(),
|
||||
declined_at: IsNull(),
|
||||
},
|
||||
config
|
||||
)
|
||||
return await orderEditRepository.findOne(query)
|
||||
}
|
||||
|
||||
protected async deleteClonedItems(orderEditId: string): Promise<void> {
|
||||
const manager = this.transactionManager_ ?? this.manager_
|
||||
const lineItemServiceTx = this.lineItemService_.withTransaction(manager)
|
||||
@@ -647,45 +738,6 @@ export default class OrderEditService extends TransactionBaseService {
|
||||
)
|
||||
}
|
||||
|
||||
async cancel(
|
||||
orderEditId: string,
|
||||
context: { loggedInUser?: string } = {}
|
||||
): Promise<OrderEdit> {
|
||||
return await this.atomicPhase_(async (manager) => {
|
||||
const orderEditRepository = manager.getCustomRepository(
|
||||
this.orderEditRepository_
|
||||
)
|
||||
|
||||
const orderEdit = await this.retrieve(orderEditId)
|
||||
|
||||
if (orderEdit.status === OrderEditStatus.CANCELED) {
|
||||
return orderEdit
|
||||
}
|
||||
|
||||
if (
|
||||
[OrderEditStatus.CONFIRMED, OrderEditStatus.DECLINED].includes(
|
||||
orderEdit.status
|
||||
)
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot cancel order edit with status ${orderEdit.status}`
|
||||
)
|
||||
}
|
||||
|
||||
orderEdit.canceled_at = new Date()
|
||||
orderEdit.canceled_by = context.loggedInUser
|
||||
|
||||
const saved = await orderEditRepository.save(orderEdit)
|
||||
|
||||
await this.eventBusService_
|
||||
.withTransaction(manager)
|
||||
.emit(OrderEditService.Events.CANCELED, { id: orderEditId })
|
||||
|
||||
return saved
|
||||
})
|
||||
}
|
||||
|
||||
private static isOrderEditActive(orderEdit: OrderEdit): boolean {
|
||||
return !(
|
||||
orderEdit.status === OrderEditStatus.CONFIRMED ||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { OrderEdit, OrderEditItemChangeType } from "../models"
|
||||
|
||||
export type UpdateOrderEditInput = {
|
||||
internal_note?: string
|
||||
}
|
||||
|
||||
export type CreateOrderEditInput = {
|
||||
order_id: string
|
||||
internal_note?: string
|
||||
|
||||
Reference in New Issue
Block a user