feat(medusa, medusa-js, medusa-react): Implement store complete order… (#2275)
**What** Allow a customer to complete a requested order edit. **Test** - Unit tests complete flow - Unit tests medusa react - Integration tests of order edit completion FIXES CORE-501
This commit is contained in:
committed by
GitHub
parent
678a06752a
commit
95c0dc653a
@@ -313,4 +313,68 @@ describe("[MEDUSA_FF_ORDER_EDITING] /store/order-edits", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /store/order-edits/:id/complete", () => {
|
||||
let requestedOrderEdit
|
||||
let confirmedOrderEdit
|
||||
let createdOrderEdit
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
requestedOrderEdit = await simpleOrderEditFactory(dbConnection, {
|
||||
id: IdMap.getId("order-edit-1"),
|
||||
created_by: "admin_user",
|
||||
requested_at: new Date(),
|
||||
})
|
||||
|
||||
confirmedOrderEdit = await simpleOrderEditFactory(dbConnection, {
|
||||
id: IdMap.getId("order-edit-2"),
|
||||
created_by: "admin_user",
|
||||
confirmed_at: new Date(),
|
||||
confirmed_by: "admin_user",
|
||||
})
|
||||
|
||||
createdOrderEdit = await simpleOrderEditFactory(dbConnection, {
|
||||
id: IdMap.getId("order-edit-3"),
|
||||
created_by: "admin_user",
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
// TODO once payment collection is done
|
||||
/*it("complete an order edit", async () => {})*/
|
||||
|
||||
it("idempotently complete an already confirmed order edit", async () => {
|
||||
const api = useApi()
|
||||
const result = await api.post(
|
||||
`/store/order-edits/${confirmedOrderEdit.id}/complete`
|
||||
)
|
||||
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.order_edit).toEqual(
|
||||
expect.objectContaining({
|
||||
id: confirmedOrderEdit.id,
|
||||
status: "confirmed",
|
||||
confirmed_at: expect.any(String),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("fails to complete a non requested order edit", async () => {
|
||||
const api = useApi()
|
||||
const err = await api
|
||||
.post(`/store/order-edits/${createdOrderEdit.id}/complete`)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toEqual(400)
|
||||
expect(err.response.data.message).toBe(
|
||||
`Cannot complete an order edit with status created`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { StoreOrderEditsRes, StorePostOrderEditsOrderEditDecline } from "@medusajs/medusa"
|
||||
import {
|
||||
StoreOrderEditsRes,
|
||||
StorePostOrderEditsOrderEditDecline,
|
||||
} from "@medusajs/medusa"
|
||||
import { ResponsePromise } from "../typings"
|
||||
import BaseResource from "./base"
|
||||
|
||||
@@ -12,13 +15,18 @@ class OrderEditsResource extends BaseResource {
|
||||
}
|
||||
|
||||
decline(
|
||||
id: string,
|
||||
id: string,
|
||||
payload: StorePostOrderEditsOrderEditDecline,
|
||||
customHeaders: Record<string, any> = {}
|
||||
) {
|
||||
const path = `/store/order-edits/${id}/decline`
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
}
|
||||
|
||||
complete(id: string, customHeaders: Record<string, any> = {}) {
|
||||
const path = `/store/order-edits/${id}/complete`
|
||||
return this.client.request("POST", path, undefined, {}, customHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderEditsResource
|
||||
|
||||
@@ -73,7 +73,23 @@ export const storeHandlers = [
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
order_edit: {...fixtures.get("order_edit"), declined_reason: req.body.declined_reason, status: 'declined'},
|
||||
order_edit: {
|
||||
...fixtures.get("store_order_edit"),
|
||||
declined_reason: (req.body as any).declined_reason,
|
||||
status: "declined",
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
rest.post("/store/order-edits/:id/complete", (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
order_edit: {
|
||||
...fixtures.get("store_order_edit"),
|
||||
status: "confirmed",
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useMutation, UseMutationOptions, useQueryClient } from "react-query"
|
||||
import { Response } from "@medusajs/medusa-js"
|
||||
|
||||
import {
|
||||
StoreOrderEditsRes,
|
||||
StorePostOrderEditsOrderEditDecline,
|
||||
StoreOrderEditsRes
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
import { buildOptions } from "../../utils/buildOptions"
|
||||
@@ -31,3 +31,20 @@ export const useDeclineOrderEdit = (
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export const useCompleteOrderEdit = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<Response<StoreOrderEditsRes>, Error>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(
|
||||
() => client.orderEdits.complete(id),
|
||||
buildOptions(
|
||||
queryClient,
|
||||
[orderEditQueryKeys.lists(), orderEditQueryKeys.detail(id)],
|
||||
options
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useDeclineOrderEdit } from "../../../../src/"
|
||||
import { useCompleteOrderEdit, useDeclineOrderEdit } from "../../../../src/"
|
||||
import { renderHook } from "@testing-library/react-hooks"
|
||||
import { createWrapper } from "../../../utils"
|
||||
|
||||
describe("useCreateLineItem hook", () => {
|
||||
test("creates a line item", async () => {
|
||||
describe("useDeclineOrderEdit hook", () => {
|
||||
test("decline an order edit", async () => {
|
||||
const declineBody = {
|
||||
declined_reason: "Wrong color",
|
||||
}
|
||||
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useDeclineOrderEdit("test-cart"),
|
||||
() => useDeclineOrderEdit("store_order_edit"),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
@@ -28,3 +28,25 @@ describe("useCreateLineItem hook", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("useCompleteOrderEdit hook", () => {
|
||||
test("complete an order edit", async () => {
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useCompleteOrderEdit("store_order_edit"),
|
||||
{
|
||||
wrapper: createWrapper(),
|
||||
}
|
||||
)
|
||||
|
||||
result.current.mutate()
|
||||
|
||||
await waitFor(() => result.current.isSuccess)
|
||||
|
||||
expect(result.current.data.response.status).toEqual(200)
|
||||
expect(result.current.data.order_edit).toEqual(
|
||||
expect.objectContaining({
|
||||
status: "confirmed",
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
||||
import OrderEditingFeatureFlag from "../../../../../loaders/feature-flags/order-editing"
|
||||
|
||||
describe("GET /store/order-edits/:id/complete", () => {
|
||||
describe("successfully complete an order edit", () => {
|
||||
const orderEditId = IdMap.getId("testRequestOrder")
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/order-edits/${orderEditId}/complete`,
|
||||
{
|
||||
flags: [OrderEditingFeatureFlag],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls orderService confirm", () => {
|
||||
expect(orderEditServiceMock.confirm).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditServiceMock.confirm).toHaveBeenCalledWith(orderEditId, {
|
||||
loggedInUserId: undefined,
|
||||
})
|
||||
expect(orderEditServiceMock.decorateTotals).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("returns orderEdit", () => {
|
||||
expect(subject.body.order_edit.id).toEqual(orderEditId)
|
||||
})
|
||||
})
|
||||
|
||||
describe("idempotently complete an order edit", () => {
|
||||
const orderEditId = IdMap.getId("testConfirmOrderEdit")
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/order-edits/${orderEditId}/complete`,
|
||||
{
|
||||
flags: [OrderEditingFeatureFlag],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls orderService confirm", () => {
|
||||
expect(orderEditServiceMock.confirm).toHaveBeenCalledTimes(0)
|
||||
expect(orderEditServiceMock.decorateTotals).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("returns orderEdit", () => {
|
||||
expect(subject.body.order_edit.id).toEqual(orderEditId)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,8 +3,8 @@ import { request } from "../../../../../helpers/test-request"
|
||||
import { orderEditServiceMock } from "../../../../../services/__mocks__/order-edit"
|
||||
import OrderEditingFeatureFlag from "../../../../../loaders/feature-flags/order-editing"
|
||||
|
||||
describe("GET /store/order-edits/:id", () => {
|
||||
describe("successfully gets an order edit", () => {
|
||||
describe("GET /store/order-edits/:id/decline", () => {
|
||||
describe("successfully decline an order edit", () => {
|
||||
const orderEditId = IdMap.getId("testDeclineOrderEdit")
|
||||
let subject
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Request, Response } from "express"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { OrderEditService } from "../../../../services"
|
||||
import {
|
||||
defaultStoreOrderEditFields,
|
||||
defaultStoreOrderEditRelations,
|
||||
} from "../../../../types/order-edit"
|
||||
import { OrderEditStatus } from "../../../../models"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
/**
|
||||
* @oas [post] /order-edits/{id}/complete
|
||||
* operationId: "PostOrderEditsOrderEditComplete"
|
||||
* summary: "Completes an OrderEdit"
|
||||
* description: "Completes an OrderEdit."
|
||||
* 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 })
|
||||
* medusa.orderEdit.complete(orderEditId)
|
||||
* .then(({ order_edit }) => {
|
||||
* console.log(order_edit.id)
|
||||
* })
|
||||
* - lang: Shell
|
||||
* label: cURL
|
||||
* source: |
|
||||
* curl --location --request POST 'https://medusa-url.com/store/order-edits/{id}/complete'
|
||||
* 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: OrderEditService =
|
||||
req.scope.resolve("orderEditService")
|
||||
|
||||
const manager: EntityManager = req.scope.resolve("manager")
|
||||
|
||||
const userId = req.user?.customer_id ?? req.user?.id ?? req.user?.userId
|
||||
|
||||
await manager.transaction(async (manager) => {
|
||||
const orderEditServiceTx = orderEditService.withTransaction(manager)
|
||||
const orderEdit = await orderEditServiceTx.retrieve(id)
|
||||
|
||||
if (orderEdit.status === OrderEditStatus.CONFIRMED) {
|
||||
return orderEdit
|
||||
}
|
||||
|
||||
if (orderEdit.status !== OrderEditStatus.REQUESTED) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot complete an order edit with status ${orderEdit.status}`
|
||||
)
|
||||
}
|
||||
|
||||
// TODO once payment collection is done
|
||||
/*const paymentCollection = await this.paymentCollectionService_.withTransaction(manager).retrieve(orderEdit.payment_collection_id)
|
||||
if (!paymentCollection.authorized_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Unable to complete an order edit if the payment is not authorized"
|
||||
)
|
||||
}*/
|
||||
|
||||
return await orderEditServiceTx.confirm(id, {
|
||||
loggedInUserId: userId,
|
||||
})
|
||||
})
|
||||
|
||||
let orderEdit = await orderEditService.retrieve(id, {
|
||||
select: defaultStoreOrderEditFields,
|
||||
relations: defaultStoreOrderEditRelations,
|
||||
})
|
||||
orderEdit = await orderEditService.decorateTotals(orderEdit)
|
||||
|
||||
res.status(200).json({ order_edit: orderEdit })
|
||||
}
|
||||
@@ -39,6 +39,11 @@ export default (app) => {
|
||||
middlewares.wrap(require("./decline-order-edit").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/complete",
|
||||
middlewares.wrap(require("./complete-order-edit").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,15 @@ export const orderEditServiceMock = {
|
||||
id: IdMap.getId("testDeclineOrderEdit"),
|
||||
declined_reason: "Wrong size",
|
||||
declined_at: new Date(),
|
||||
status: "declined",
|
||||
})
|
||||
}
|
||||
if (orderId === IdMap.getId("testCompleteOrderEdit")) {
|
||||
return Promise.resolve({
|
||||
...orderEdit,
|
||||
id: IdMap.getId("testCompleteOrderEdit"),
|
||||
confirmed_at: new Date(),
|
||||
status: "completed",
|
||||
})
|
||||
}
|
||||
if (orderId === IdMap.getId("testCancelOrderEdit")) {
|
||||
@@ -79,6 +88,7 @@ export const orderEditServiceMock = {
|
||||
id: IdMap.getId("testRequestOrder"),
|
||||
requested_by: IdMap.getId("admin_user"),
|
||||
requested_at: new Date(),
|
||||
status: "requested",
|
||||
})
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
@@ -130,6 +140,9 @@ export const orderEditServiceMock = {
|
||||
confirm: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({})
|
||||
}),
|
||||
complete: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({})
|
||||
}),
|
||||
updateLineItem: jest.fn().mockImplementation((_) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
|
||||
@@ -20,6 +20,8 @@ import LineItemAdjustmentService from "../line-item-adjustment"
|
||||
|
||||
const orderEditToUpdate = {
|
||||
id: IdMap.getId("order-edit-to-update"),
|
||||
created_at: new Date(),
|
||||
status: "created",
|
||||
}
|
||||
|
||||
const orderEditWithChanges = {
|
||||
@@ -208,10 +210,12 @@ describe("OrderEditService", () => {
|
||||
internal_note: "test note",
|
||||
})
|
||||
expect(orderEditRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditRepository.save).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("order-edit-to-update"),
|
||||
internal_note: "test note",
|
||||
})
|
||||
expect(orderEditRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: IdMap.getId("order-edit-to-update"),
|
||||
internal_note: "test note",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should create an order edit and call the repository with the right arguments as well as the event bus service", async () => {
|
||||
@@ -304,6 +308,22 @@ describe("OrderEditService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("should add a line item to an order edit", async () => {
|
||||
jest
|
||||
.spyOn(orderEditService, "refreshAdjustments")
|
||||
.mockImplementation(async () => {})
|
||||
|
||||
await orderEditService.addLineItem(IdMap.getId("order-edit-with-changes"), {
|
||||
variant_id: IdMap.getId("to-be-added-variant"),
|
||||
quantity: 3,
|
||||
})
|
||||
|
||||
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditService.refreshAdjustments).toHaveBeenCalledTimes(1)
|
||||
expect(taxProviderServiceMock.createTaxLines).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditItemChangeServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
describe("requestConfirmation", () => {
|
||||
describe("created edit", () => {
|
||||
const orderEditId = IdMap.getId("order-edit-with-changes")
|
||||
@@ -377,7 +397,9 @@ describe("OrderEditService", () => {
|
||||
const id = IdMap.getId("canceled-order-edit")
|
||||
const userId = IdMap.getId("user-id")
|
||||
|
||||
const result = await orderEditService.cancel(id, userId)
|
||||
const result = await orderEditService.cancel(id, {
|
||||
loggedInUserId: userId,
|
||||
})
|
||||
|
||||
expect(result).toEqual(expect.objectContaining({ status: "canceled" }))
|
||||
|
||||
@@ -393,7 +415,7 @@ describe("OrderEditService", () => {
|
||||
const userId = IdMap.getId("user-id")
|
||||
|
||||
try {
|
||||
await orderEditService.cancel(id, userId)
|
||||
await orderEditService.cancel(id, { loggedInUserId: userId })
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
`Cannot cancel order edit with status ${status}`
|
||||
@@ -423,11 +445,13 @@ describe("OrderEditService", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("Returns early in case of an already confirmed order edit", async () => {
|
||||
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)
|
||||
const result = await orderEditService.confirm(id, {
|
||||
loggedInUserId: userId,
|
||||
})
|
||||
|
||||
expect(result).toEqual(expect.objectContaining({ status: "confirmed" }))
|
||||
|
||||
@@ -436,20 +460,4 @@ describe("OrderEditService", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("should add a line item to an order edit", async () => {
|
||||
jest
|
||||
.spyOn(orderEditService, "refreshAdjustments")
|
||||
.mockImplementation(async () => {})
|
||||
|
||||
await orderEditService.addLineItem(IdMap.getId("order-edit-with-changes"), {
|
||||
variant_id: IdMap.getId("to-be-added-variant"),
|
||||
quantity: 3,
|
||||
})
|
||||
|
||||
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditService.refreshAdjustments).toHaveBeenCalledTimes(1)
|
||||
expect(taxProviderServiceMock.createTaxLines).toHaveBeenCalledTimes(1)
|
||||
expect(orderEditItemChangeServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user