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:
@@ -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,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user