feat: update and cancel swaps, claims, and returns (#310)
This commit is contained in:
1 parent
fab155c246
commit
cf66f97758
55 files changed
+3962
-1007
No files matched your search
@@ -0,0 +1,64 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ClaimServiceMock } from "../../../../../services/__mocks__/claim"
|
||||
|
||||
describe("POST /admin/orders/:id/claims/:claim_id/cancel", () => {
|
||||
describe("successfully cancels a claim", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order")}/claims/${IdMap.getId(
|
||||
"test-claim"
|
||||
)}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls ClaimService cancel", () => {
|
||||
expect(ClaimServiceMock.cancel).toHaveBeenCalledTimes(1)
|
||||
expect(ClaimServiceMock.cancel).toHaveBeenCalledWith(
|
||||
IdMap.getId("test-claim")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Trying to cancel a claim unrelated to the order fails", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order2")}/claims/${IdMap.getId(
|
||||
"test-claim"
|
||||
)}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,92 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ClaimServiceMock } from "../../../../../services/__mocks__/claim"
|
||||
|
||||
describe("POST /admin/orders/:id/claims/:claim_id/fulfillments/:fulfillment_id/cancel", () => {
|
||||
describe("successfully cancels a fulfillment", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order")}/claims/${IdMap.getId(
|
||||
"test-claim"
|
||||
)}/fulfillments/${IdMap.getId("claim-fulfillment")}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls claimService cancelFulfillment", () => {
|
||||
expect(ClaimServiceMock.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(ClaimServiceMock.cancelFulfillment).toHaveBeenCalledWith(
|
||||
IdMap.getId("claim-fulfillment")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Trying to cancel a fulfillment unrelated to the claim fails", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order")}/claims/${IdMap.getId(
|
||||
"claim-fulfillment2"
|
||||
)}/fulfillments/${IdMap.getId("claim-fulfillment")}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Trying to cancel a fulfillment, where claim and order are unrelated", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order2")}/claims/${IdMap.getId(
|
||||
"test-claim"
|
||||
)}/fulfillments/${IdMap.getId("claim-fulfillment")}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,92 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { SwapServiceMock } from "../../../../../services/__mocks__/swap"
|
||||
|
||||
describe("POST /admin/orders/:id/swaps/:swap_id/fulfillments/:fulfillment_id/cancel", () => {
|
||||
describe("successfully cancels a fulfillment", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order")}/swaps/${IdMap.getId(
|
||||
"test-swap"
|
||||
)}/fulfillments/${IdMap.getId("swap-fulfillment")}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls SwapService cancelFulfillment", () => {
|
||||
expect(SwapServiceMock.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(SwapServiceMock.cancelFulfillment).toHaveBeenCalledWith(
|
||||
IdMap.getId("swap-fulfillment")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Trying to cancel a fulfillment unrelated to the swap fails", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order")}/swaps/${IdMap.getId(
|
||||
"swap-fulfillment2"
|
||||
)}/fulfillments/${IdMap.getId("swap-fulfillment")}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Trying to cancel a fulfillment, where swap and order are unrelated", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order2")}/swaps/${IdMap.getId(
|
||||
"test-swap"
|
||||
)}/fulfillments/${IdMap.getId("swap-fulfillment")}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
|
||||
|
||||
describe("POST /admin/orders/:id/fulfillments/:fulfillment_id/cancel", () => {
|
||||
describe("successfully cancels a fulfillment", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order")}/fulfillments/${IdMap.getId(
|
||||
"order-fulfillment"
|
||||
)}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls OrderService cancelFulfillment", () => {
|
||||
expect(OrderServiceMock.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(OrderServiceMock.cancelFulfillment).toHaveBeenCalledWith(
|
||||
IdMap.getId("order-fulfillment")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Trying to cancel a fulfillment unrelated to the order fails", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order2")}/fulfillments/${IdMap.getId(
|
||||
"order-fulfillment"
|
||||
)}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,64 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { SwapServiceMock } from "../../../../../services/__mocks__/swap"
|
||||
|
||||
describe("POST /admin/orders/:id/swaps/:swap_id/cancel", () => {
|
||||
describe("successfully cancels a claim", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order")}/swaps/${IdMap.getId(
|
||||
"test-swap"
|
||||
)}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls SwapService cancel", () => {
|
||||
expect(SwapServiceMock.cancel).toHaveBeenCalledTimes(1)
|
||||
expect(SwapServiceMock.cancel).toHaveBeenCalledWith(
|
||||
IdMap.getId("test-swap")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Trying to cancel a claim unrelated to the order fails", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/orders/${IdMap.getId("test-order2")}/swaps/${IdMap.getId(
|
||||
"test-swap"
|
||||
)}/cancel`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -19,6 +19,7 @@ const defaultRelations = [
|
||||
"gift_card_transactions",
|
||||
"claims",
|
||||
"claims.return_order",
|
||||
"claims.return_order.shipping_method",
|
||||
"claims.shipping_methods",
|
||||
"claims.shipping_address",
|
||||
"claims.additional_items",
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] /orders/{id}/claims/{claim_id}/cancel
|
||||
* operationId: "PostOrdersClaimCancel"
|
||||
* summary: "Cancels a Claim"
|
||||
* description: "Cancels a Claim"
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Order.
|
||||
* . (path) claim_id=* {string} The id of the Claim.
|
||||
* tags:
|
||||
* - Claim
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* order:
|
||||
* $ref: "#/components/schemas/claim"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, claim_id } = req.params
|
||||
|
||||
try {
|
||||
const claimService = req.scope.resolve("claimService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const claim = await claimService.retrieve(claim_id)
|
||||
|
||||
if (claim.order_id !== id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`no claim was found with the id: ${claim_id} related to order: ${id}`
|
||||
)
|
||||
}
|
||||
|
||||
await claimService.cancel(claim_id)
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ order })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] orders//{id}/claims/{claim_id}/fulfillments/{fulfillment_id}/cancel
|
||||
* operationId: "PostOrdersClaimFulfillmentsCancel"
|
||||
* summary: "Cancels a fulfilmment related to a Claim"
|
||||
* description: "Registers a Fulfillment as canceled."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Order which the Claim relates to.
|
||||
* - (path) claim_id=* {string} The id of the Claim which the Fulfillment relates to.
|
||||
* - (path) fulfillment_id=* {string} The id of the Fulfillment.
|
||||
* tags:
|
||||
* - Fulfillment
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* fulfillment:
|
||||
* $ref: "#/components/schemas/fulfillment"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, claim_id, fulfillment_id } = req.params
|
||||
|
||||
try {
|
||||
const fulfillmentService = req.scope.resolve("fulfillmentService")
|
||||
const claimService = req.scope.resolve("claimService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
|
||||
|
||||
if (fulfillment.claim_order_id !== claim_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`no fulfillment was found with the id: ${fulfillment_id} related to claim: ${claim_id}`
|
||||
)
|
||||
}
|
||||
|
||||
const claim = await claimService.retrieve(claim_id)
|
||||
|
||||
if (claim.order_id !== id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`no claim was found with the id: ${claim_id} related to order: ${id}`
|
||||
)
|
||||
}
|
||||
|
||||
await claimService.cancelFulfillment(fulfillment_id)
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
res.json({ order })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] /orders/{id}/swaps/{swap_id}/fulfillments/{fulfillment_id}/cancel
|
||||
* operationId: "PostOrdersSwapFulfillmentsCancel"
|
||||
* summary: "Cancels a fulfilmment related to a Swap"
|
||||
* description: "Registers a Fulfillment as canceled."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Order which the Swap relates to.
|
||||
* - (path) swap_id=* {string} The id of the Swap which the Fulfillment relates to.
|
||||
* - (path) fulfillment_id=* {string} The id of the Fulfillment.
|
||||
* tags:
|
||||
* - Fulfillment
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* fulfillment:
|
||||
* $ref: "#/components/schemas/fulfillment"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, swap_id, fulfillment_id } = req.params
|
||||
|
||||
try {
|
||||
const fulfillmentService = req.scope.resolve("fulfillmentService")
|
||||
const swapService = req.scope.resolve("swapService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
|
||||
|
||||
if (fulfillment.swap_id !== swap_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`no fulfillment was found with the id: ${fulfillment_id} related to swap: ${id}`
|
||||
)
|
||||
}
|
||||
|
||||
const swap = await swapService.retrieve(swap_id)
|
||||
|
||||
if (swap.order_id !== id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`no swap was found with the id: ${swap_id} related to order: ${id}`
|
||||
)
|
||||
}
|
||||
|
||||
await swapService.cancelFulfillment(fulfillment_id)
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ order })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] /orders/{id}/fulfillments/{fulfillment_id}/cancel
|
||||
* operationId: "PostOrdersOrderFulfillmentsCancel"
|
||||
* summary: "Cancels a fulfilmment"
|
||||
* description: "Registers a Fulfillment as canceled."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Order which the Fulfillment relates to.
|
||||
* - (path) fulfillment_id=* {string} The id of the Fulfillment
|
||||
* tags:
|
||||
* - Fulfillment
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* fulfillment:
|
||||
* $ref: "#/components/schemas/fulfillment"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, fulfillment_id } = req.params
|
||||
|
||||
try {
|
||||
const fulfillmentService = req.scope.resolve("fulfillmentService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const fulfillment = await fulfillmentService.retrieve(fulfillment_id)
|
||||
|
||||
if (fulfillment.order_id !== id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`no fulfillment was found with the id: ${fulfillment_id} related to order: ${id}`
|
||||
)
|
||||
}
|
||||
|
||||
await orderService.cancelFulfillment(fulfillment_id)
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ order })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { defaultFields, defaultRelations } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] /orders/{id}/cancel
|
||||
* operationId: "PostOrdersOrderCancel"
|
||||
@@ -22,11 +24,11 @@ export default async (req, res) => {
|
||||
|
||||
try {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
await orderService.cancel(id)
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
relations: ["region", "customer", "swaps"],
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ order })
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] /orders/{id}/swaps/{swap_id}/cancel
|
||||
* operationId: "PostOrdersSwapCancel"
|
||||
* summary: "Cancels a Swap"
|
||||
* description: "Cancels a Swap"
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Order.
|
||||
* . (path) swap_id=* {string} The id of the Swap.
|
||||
* tags:
|
||||
* - Swap
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* order:
|
||||
* $ref: "#/components/schemas/swap"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, swap_id } = req.params
|
||||
|
||||
try {
|
||||
const swapService = req.scope.resolve("swapService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
const swap = await swapService.retrieve(swap_id)
|
||||
|
||||
if (swap.order_id !== id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`no swap was found with the id: ${swap_id} related to order: ${id}`
|
||||
)
|
||||
}
|
||||
|
||||
await swapService.cancel(swap_id)
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ order })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,30 @@ export default app => {
|
||||
middlewares.wrap(require("./create-fulfillment").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Cancel a fulfillment related to an order.
|
||||
*/
|
||||
route.post(
|
||||
"/:id/fulfillments/:fulfillment_id/cancel",
|
||||
middlewares.wrap(require("./cancel-fulfillment").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Cancel a fulfillment related to a swap.
|
||||
*/
|
||||
route.post(
|
||||
"/:id/swaps/:swap_id/fulfillments/:fulfillment_id/cancel",
|
||||
middlewares.wrap(require("./cancel-fulfillment-swap").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Cancel a fulfillment related to a claim.
|
||||
*/
|
||||
route.post(
|
||||
"/:id/claims/:claim_id/fulfillments/:fulfillment_id/cancel",
|
||||
middlewares.wrap(require("./cancel-fulfillment-claim").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a shipment.
|
||||
*/
|
||||
@@ -104,6 +128,14 @@ export default app => {
|
||||
*/
|
||||
route.post("/:id/swaps", middlewares.wrap(require("./create-swap").default))
|
||||
|
||||
/**
|
||||
* Cancels a swap.
|
||||
*/
|
||||
route.post(
|
||||
"/:id/swaps/:swap_id/cancel",
|
||||
middlewares.wrap(require("./cancel-swap").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Receives the inventory to return from a swap
|
||||
*/
|
||||
@@ -141,6 +173,14 @@ export default app => {
|
||||
*/
|
||||
route.post("/:id/claims", middlewares.wrap(require("./create-claim").default))
|
||||
|
||||
/**
|
||||
* Cancels a claim
|
||||
*/
|
||||
route.post(
|
||||
"/:id/claims/:claim_id/cancel",
|
||||
middlewares.wrap(require("./cancel-claim").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Updates a claim
|
||||
*/
|
||||
@@ -193,6 +233,7 @@ export const defaultRelations = [
|
||||
"gift_card_transactions",
|
||||
"claims",
|
||||
"claims.return_order",
|
||||
"claims.return_order.shipping_method",
|
||||
"claims.shipping_methods",
|
||||
"claims.shipping_address",
|
||||
"claims.additional_items",
|
||||
|
||||
@@ -140,7 +140,7 @@ export default async (req, res) => {
|
||||
if (typeof value.refund !== "undefined" && value.refund < 0) {
|
||||
returnObj.refund_amount = 0
|
||||
} else {
|
||||
if (value.refund) {
|
||||
if (value.refund >= 0) {
|
||||
returnObj.refund_amount = value.refund
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,10 @@ export default async (req, res) => {
|
||||
.withTransaction(manager)
|
||||
.retrieve(id)
|
||||
|
||||
const evaluatedNoNotification = value.no_notification !== undefined ? value.no_notification : order.no_notification
|
||||
const evaluatedNoNotification =
|
||||
value.no_notification !== undefined
|
||||
? value.no_notification
|
||||
: order.no_notification
|
||||
returnObj.no_notification = evaluatedNoNotification
|
||||
|
||||
const createdReturn = await returnService
|
||||
@@ -161,13 +164,13 @@ export default async (req, res) => {
|
||||
.withTransaction(manager)
|
||||
.fulfill(createdReturn.id)
|
||||
}
|
||||
|
||||
|
||||
await eventBus
|
||||
.withTransaction(manager)
|
||||
.emit("order.return_requested", {
|
||||
id,
|
||||
return_id: createdReturn.id,
|
||||
no_notification: evaluatedNoNotification
|
||||
no_notification: evaluatedNoNotification,
|
||||
})
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { result } from "lodash"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "../orders"
|
||||
|
||||
/**
|
||||
* @oas [post] /returns/{id}/cancel
|
||||
* operationId: "PostReturnsReturnCancel"
|
||||
* summary: "Cancel a Return"
|
||||
* description: "Registers a Return as canceled."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Return.
|
||||
* tags:
|
||||
* - Return
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* return:
|
||||
* $ref: "#/components/schemas/order"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const returnService = req.scope.resolve("returnService")
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
let result = await returnService.cancel(id)
|
||||
|
||||
if (result.swap_id) {
|
||||
const swapService = req.scope.resolve("swapService")
|
||||
result = await swapService.retrieve(result.swap_id)
|
||||
} else if (result.claim_order_id) {
|
||||
const claimService = req.scope.resolve("claimService")
|
||||
result = await claimService.retrieve(result.claim_order_id)
|
||||
}
|
||||
|
||||
const order = await orderService.retrieve(result.order_id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ order })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,10 @@ export default app => {
|
||||
middlewares.wrap(require("./receive-return").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/cancel",
|
||||
middlewares.wrap(require("./cancel-return").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
/**
|
||||
* @oas [post] /returns/{id}receive
|
||||
* @oas [post] /returns/{id}/receive
|
||||
* operationId: "PostReturnsReturnReceive"
|
||||
* summary: "Receive a Return"
|
||||
* description: "Registers a Return as received. Updates statuses on Orders and Swaps accordingly."
|
||||
|
||||
@@ -72,7 +72,7 @@ describe("GET /admin/swaps/:id", () => {
|
||||
|
||||
it("returns swap", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
expect(subject.body.swap.id).toEqual(IdMap.getId("test-swap"))
|
||||
expect(subject.body.swap.id).toEqual("test-swap")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -78,7 +78,7 @@ describe("POST /store/carts/:id", () => {
|
||||
})
|
||||
|
||||
it("returns the created order", () => {
|
||||
expect(subject.body.data.id).toEqual(IdMap.getId("test-swap"))
|
||||
expect(subject.body.data.id).toEqual("test-swap")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default (app) => {
|
||||
export default app => {
|
||||
app.use("/swaps", route)
|
||||
|
||||
route.get(
|
||||
@@ -19,6 +19,7 @@ export const defaultRelations = [
|
||||
"order",
|
||||
"additional_items",
|
||||
"return_order",
|
||||
"return_order.shipping_method",
|
||||
"fulfillments",
|
||||
"payment",
|
||||
"shipping_address",
|
||||
|
||||
@@ -4,11 +4,13 @@ export { Cart } from "./models/cart"
|
||||
export { ClaimImage } from "./models/claim-image"
|
||||
export { ClaimItem } from "./models/claim-item"
|
||||
export { ClaimTag } from "./models/claim-tag"
|
||||
export { ClaimOrder } from "./models/claim-order"
|
||||
export { Country } from "./models/country"
|
||||
export { Currency } from "./models/currency"
|
||||
export { Customer } from "./models/customer"
|
||||
export { DiscountRule } from "./models/discount-rule"
|
||||
export { Discount } from "./models/discount"
|
||||
export { Fulfillment } from "./models/fulfillment"
|
||||
export { FulfillmentItem } from "./models/fulfillment-item"
|
||||
export { FulfillmentProvider } from "./models/fulfillment-provider"
|
||||
export { GiftCardTransaction } from "./models/gift-card-transaction"
|
||||
@@ -43,4 +45,3 @@ export { Swap } from "./models/swap"
|
||||
export { User } from "./models/user"
|
||||
export { DraftOrder } from "./models/draft-order"
|
||||
export { ReturnReason } from "./models/return-reason"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class ensureCancellationFieldsExist1625560513367
|
||||
implements MigrationInterface {
|
||||
name = "ensureCancellationFieldsExist1625560513367"
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "swap" ADD "canceled_at" TIMESTAMP WITH TIME ZONE`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "return_status_enum" RENAME TO "return_status_enum_old"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "return_status_enum" AS ENUM('requested', 'received', 'requires_action', 'canceled')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ALTER COLUMN "status" DROP DEFAULT`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ALTER COLUMN "status" TYPE "return_status_enum" USING "status"::"text"::"return_status_enum"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ALTER COLUMN "status" SET DEFAULT 'requested'`
|
||||
)
|
||||
await queryRunner.query(`DROP TYPE "return_status_enum_old"`)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "return_status_enum_old" AS ENUM('requested', 'received', 'requires_action')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ALTER COLUMN "status" DROP DEFAULT`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ALTER COLUMN "status" TYPE "return_status_enum_old" USING "status"::"text"::"return_status_enum_old"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return" ALTER COLUMN "status" SET DEFAULT 'requested'`
|
||||
)
|
||||
await queryRunner.query(`DROP TYPE "return_status_enum"`)
|
||||
await queryRunner.query(
|
||||
`ALTER TYPE "return_status_enum_old" RENAME TO "return_status_enum"`
|
||||
)
|
||||
await queryRunner.query(`ALTER TABLE "swap" DROP COLUMN "canceled_at"`)
|
||||
}
|
||||
}
|
||||
@@ -218,6 +218,10 @@ export class ClaimOrder {
|
||||
* refund_amount:
|
||||
* description: "The amount that will be refunded in conjunction with the claim"
|
||||
* type: integer
|
||||
* canceled_at:
|
||||
* description: "The date with timezone at which the Swap was canceled."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* created_at:
|
||||
* type: string
|
||||
* format: date-time
|
||||
|
||||
@@ -28,6 +28,7 @@ export enum ReturnStatus {
|
||||
REQUESTED = "requested",
|
||||
RECEIVED = "received",
|
||||
REQUIRES_ACTION = "requires_action",
|
||||
CANCELED = "canceled",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
@@ -85,7 +86,7 @@ export class Return {
|
||||
@OneToOne(
|
||||
() => ShippingMethod,
|
||||
method => method.return_order,
|
||||
{ eager: true, cascade: true }
|
||||
{ cascade: true }
|
||||
)
|
||||
shipping_method: ShippingMethod
|
||||
|
||||
|
||||
@@ -131,6 +131,9 @@ export class Swap {
|
||||
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
|
||||
deleted_at: Date
|
||||
|
||||
@Column({ type: resolveDbType("timestamptz"), nullable: true })
|
||||
canceled_at: Date
|
||||
|
||||
@Column({ type: "boolean", nullable: true })
|
||||
no_notification: Boolean
|
||||
|
||||
@@ -228,6 +231,10 @@ export class Swap {
|
||||
* description: "The date with timezone at which the resource was last updated."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* canceled_at:
|
||||
* description: "The date with timezone at which the Swap was canceled."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* no_notification:
|
||||
* description: "If set to true, no notification will be sent related to this swap"
|
||||
* type: boolean
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
export const ClaimServiceMock = {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
retrieve: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve({ order_id: IdMap.getId("test-order") })
|
||||
}),
|
||||
|
||||
cancel: jest.fn().mockImplementation(f => {
|
||||
return Promise.resolve({ f })
|
||||
}),
|
||||
|
||||
cancelFulfillment: jest.fn().mockImplementation(f => {
|
||||
return Promise.resolve({ f })
|
||||
}),
|
||||
}
|
||||
|
||||
const mock = jest.fn().mockImplementation(() => {
|
||||
return ClaimServiceMock
|
||||
})
|
||||
|
||||
export default mock
|
||||
@@ -0,0 +1,26 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const FulfillmentServiceMock = {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
retrieve: jest.fn().mockImplementation(data => {
|
||||
switch (data) {
|
||||
case IdMap.getId("order-fulfillment"):
|
||||
return Promise.resolve({ order_id: IdMap.getId("test-order") })
|
||||
case IdMap.getId("swap-fulfillment"):
|
||||
return Promise.resolve({ swap_id: IdMap.getId("test-swap") })
|
||||
case IdMap.getId("claim-fulfillment"):
|
||||
return Promise.resolve({ claim_order_id: IdMap.getId("test-claim") })
|
||||
}
|
||||
}),
|
||||
cancelFulfillment: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve({ order_id: IdMap.getId("test-order") })
|
||||
}),
|
||||
}
|
||||
|
||||
const mock = jest.fn().mockImplementation(() => {
|
||||
return FulfillmentServiceMock
|
||||
})
|
||||
|
||||
export default mock
|
||||
@@ -182,6 +182,11 @@ export const OrderServiceMock = {
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
|
||||
cancelFulfillment: jest.fn().mockImplementation(f => {
|
||||
return Promise.resolve({ f })
|
||||
}),
|
||||
|
||||
archive: jest.fn().mockImplementation(order => {
|
||||
if (order === IdMap.getId("processed-order")) {
|
||||
orders.processedOrder.status = "archived"
|
||||
|
||||
@@ -11,6 +11,21 @@ export const SwapServiceMock = {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
retrieve: jest.fn().mockImplementation(data => {
|
||||
switch (data) {
|
||||
case IdMap.getId("test-swap"):
|
||||
return Promise.resolve({
|
||||
id: "test-swap",
|
||||
order_id: IdMap.getId("test-order"),
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({ id: "test-swap" })
|
||||
}
|
||||
}),
|
||||
cancel: jest.fn().mockImplementation(f => {
|
||||
return Promise.resolve({ f })
|
||||
}),
|
||||
|
||||
cancelFulfillment: jest.fn().mockImplementation(f => {
|
||||
return Promise.resolve({ id: IdMap.getId("test-swap") })
|
||||
}),
|
||||
list: jest.fn().mockImplementation((...args) => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import _ from "lodash"
|
||||
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
||||
import ClaimService from "../claim"
|
||||
import { InventoryServiceMock } from "../__mocks__/inventory"
|
||||
@@ -68,6 +67,7 @@ describe("ClaimService", () => {
|
||||
|
||||
const lineItemService = {
|
||||
generate: jest.fn((d, _, q) => ({ variant_id: d, quantity: q })),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("lineItem")
|
||||
return this
|
||||
@@ -338,6 +338,7 @@ describe("ClaimService", () => {
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("lineItem")
|
||||
return this
|
||||
@@ -407,6 +408,16 @@ describe("ClaimService", () => {
|
||||
).rejects.toThrow(`Cannot fulfill a claim without a shipping method.`)
|
||||
})
|
||||
|
||||
it("fails if claim is canceled", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({ ...claim, canceled_at: new Date() })
|
||||
)
|
||||
|
||||
await expect(
|
||||
claimService.createFulfillment("claim_id", {})
|
||||
).rejects.toThrow("Canceled claim cannot be fulfilled")
|
||||
})
|
||||
|
||||
it("fails if already fulfilled", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({ ...claim, fulfillment_status: "fulfilled" })
|
||||
@@ -426,6 +437,76 @@ describe("ClaimService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancelFulfillment", () => {
|
||||
const claimRepo = MockRepository({
|
||||
findOne: () => Promise.resolve({}),
|
||||
save: f => Promise.resolve(f),
|
||||
})
|
||||
|
||||
const fulfillmentService = {
|
||||
cancelFulfillment: jest.fn().mockImplementation(f => {
|
||||
switch (f) {
|
||||
case IdMap.getId("no-claim"):
|
||||
return Promise.resolve({})
|
||||
default:
|
||||
return Promise.resolve({
|
||||
claim_order_id: IdMap.getId("claim-id"),
|
||||
})
|
||||
}
|
||||
}),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
fulfillmentService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully cancels fulfillment and corrects claim status", async () => {
|
||||
await claimService.cancelFulfillment(IdMap.getId("claim"))
|
||||
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledWith(
|
||||
IdMap.getId("claim")
|
||||
)
|
||||
|
||||
expect(claimRepo.save).toHaveBeenCalledTimes(1)
|
||||
expect(claimRepo.save).toHaveBeenCalledWith({
|
||||
fulfillment_status: "canceled",
|
||||
})
|
||||
})
|
||||
|
||||
it("fails to cancel fulfillment when not related to a claim", async () => {
|
||||
await expect(
|
||||
claimService.cancelFulfillment(IdMap.getId("no-claim"))
|
||||
).rejects.toThrow(`Fufillment not related to a claim`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("processRefund", () => {
|
||||
const claimRepo = MockRepository({
|
||||
findOne: () => Promise.resolve({ canceled_at: new Date() }),
|
||||
})
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
})
|
||||
|
||||
it("fails when claim is canceled", async () => {
|
||||
await expect(
|
||||
claimService.processRefund(IdMap.getId("claim-id"))
|
||||
).rejects.toThrow("Canceled claim cannot be processed")
|
||||
})
|
||||
})
|
||||
|
||||
describe("createShipment", () => {
|
||||
const fulfillmentService = {
|
||||
createShipment: jest.fn(() => {
|
||||
@@ -446,6 +527,7 @@ describe("ClaimService", () => {
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
withTransactionMock("lineItem")
|
||||
return this
|
||||
@@ -507,6 +589,35 @@ describe("ClaimService", () => {
|
||||
shipped_quantity: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if claim is canceled", async () => {
|
||||
claimRepo.setFindOne(() => Promise.resolve({ canceled_at: new Date() }))
|
||||
|
||||
await expect(
|
||||
claimService.createShipment("claim", "ful_123", ["track1234"], {})
|
||||
).rejects.toThrow("Canceled claim cannot be fulfilled as shipped")
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const claimRepo = MockRepository({
|
||||
findOne: () => Promise.resolve({ canceled_at: new Date() }),
|
||||
})
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
claimRepository: claimRepo,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("fails when claim is canceled", async () => {
|
||||
await expect(
|
||||
claimService.update(IdMap.getId("claim-id"))
|
||||
).rejects.toThrow("Canceled claim cannot be updated")
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancel", () => {
|
||||
@@ -526,7 +637,31 @@ describe("ClaimService", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const claimRepo = MockRepository()
|
||||
const now = new Date()
|
||||
const ret_order = { id: "ret", status: "canceled" }
|
||||
const fulfillment = { id: "ful_21", canceled_at: now }
|
||||
|
||||
const claimRepo = MockRepository({
|
||||
findOne: q => {
|
||||
const claim = {
|
||||
return_order: { ...ret_order },
|
||||
fulfillments: [{ ...fulfillment }],
|
||||
}
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("fail-fulfillment"):
|
||||
claim.fulfillments[0].canceled_at = undefined
|
||||
return Promise.resolve(claim)
|
||||
case IdMap.getId("fail-return"):
|
||||
claim.return_order.status = "requested"
|
||||
return Promise.resolve(claim)
|
||||
case IdMap.getId("fail-refund"):
|
||||
claim.refund_amount = 123
|
||||
return Promise.resolve(claim)
|
||||
default:
|
||||
return Promise.resolve(claim)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const claimService = new ClaimService({
|
||||
manager: MockManager,
|
||||
@@ -540,62 +675,26 @@ describe("ClaimService", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls fulfillment service", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({
|
||||
return_order: {
|
||||
id: "ret",
|
||||
status: "requested",
|
||||
},
|
||||
fulfillments: [
|
||||
{
|
||||
id: "ful_21",
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
await claimService.cancel("claim")
|
||||
|
||||
expect(withTransactionMock).toHaveBeenCalledTimes(3)
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("fulfillment")
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("return")
|
||||
expect(withTransactionMock).toHaveBeenCalledWith("eventBus")
|
||||
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledWith({
|
||||
id: "ful_21",
|
||||
})
|
||||
|
||||
expect(returnService.cancel).toHaveBeenCalledTimes(1)
|
||||
expect(returnService.cancel).toHaveBeenCalledWith("ret")
|
||||
})
|
||||
|
||||
it("fails if fulfillment is shipped", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({
|
||||
fulfillment_status: "shipped",
|
||||
})
|
||||
)
|
||||
|
||||
await expect(claimService.cancel("id")).rejects.toThrow(
|
||||
"Cannot cancel a claim that has been shipped."
|
||||
it("fails if fulfillment isn't canceled", async () => {
|
||||
await expect(
|
||||
claimService.cancel(IdMap.getId("fail-fulfillment"))
|
||||
).rejects.toThrow(
|
||||
"All fulfillments must be canceled before the claim can be canceled"
|
||||
)
|
||||
})
|
||||
|
||||
it("fails if return is received", async () => {
|
||||
claimRepo.setFindOne(() =>
|
||||
Promise.resolve({
|
||||
return_order: {
|
||||
id: "ret",
|
||||
status: "received",
|
||||
},
|
||||
})
|
||||
it("fails if return isn't canceled", async () => {
|
||||
await expect(
|
||||
claimService.cancel(IdMap.getId("fail-return"))
|
||||
).rejects.toThrow(
|
||||
"Return must be canceled before the claim can be canceled"
|
||||
)
|
||||
})
|
||||
|
||||
await expect(claimService.cancel("id")).rejects.toThrow(
|
||||
"Cannot cancel a claim that has a received return."
|
||||
)
|
||||
it("fails if associated with a refund", async () => {
|
||||
await expect(
|
||||
claimService.cancel(IdMap.getId("fail-refund"))
|
||||
).rejects.toThrow("Claim with a refund cannot be canceled")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -94,10 +94,72 @@ describe("FulfillmentService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancelFulfillment", () => {
|
||||
const fulfillmentRepository = MockRepository({
|
||||
findOne: () =>
|
||||
Promise.resolve({
|
||||
canceled_at: new Date(),
|
||||
items: [{ item_id: 1, quantity: 2 }],
|
||||
}),
|
||||
save: f => f,
|
||||
})
|
||||
|
||||
const lineItemService = {
|
||||
retrieve: jest
|
||||
.fn()
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ id: 1, fulfilled_quantity: 2 })
|
||||
),
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const fulfillmentProviderService = {
|
||||
cancelFulfillment: f => f,
|
||||
}
|
||||
|
||||
const fulfillmentService = new FulfillmentService({
|
||||
manager: MockManager,
|
||||
fulfillmentProviderService,
|
||||
fulfillmentRepository,
|
||||
lineItemService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("correctly cancels fulfillment", async () => {
|
||||
await fulfillmentService.cancelFulfillment(IdMap.getId("fulfillment"))
|
||||
|
||||
expect(fulfillmentRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentRepository.save).toHaveBeenCalledWith({
|
||||
canceled_at: expect.any(Date),
|
||||
items: expect.any(Array),
|
||||
})
|
||||
|
||||
expect(lineItemService.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.retrieve).toHaveBeenCalledWith(1)
|
||||
expect(lineItemService.update).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.update).toHaveBeenCalledWith(1, {
|
||||
fulfilled_quantity: 0,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("createShipment", () => {
|
||||
const trackingLinkRepository = MockRepository({ create: c => c })
|
||||
const fulfillmentRepository = MockRepository({
|
||||
findOne: () => Promise.resolve({ id: IdMap.getId("fulfillment") }),
|
||||
findOne: q => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ canceled_at: new Date() })
|
||||
default:
|
||||
return Promise.resolve({ id: IdMap.getId("fulfillment") })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const fulfillmentService = new FulfillmentService({
|
||||
@@ -130,5 +192,15 @@ describe("FulfillmentService", () => {
|
||||
shipped_at: now,
|
||||
})
|
||||
})
|
||||
|
||||
it("fails when status is canceled", async () => {
|
||||
await expect(
|
||||
fulfillmentService.createShipment(
|
||||
IdMap.getId("canceled"),
|
||||
[({ tracking_number: "1234" }, { tracking_number: "2345" })],
|
||||
{}
|
||||
)
|
||||
).rejects.toThrow("Fulfillment has been canceled")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -524,6 +524,10 @@ describe("OrderService", () => {
|
||||
payment_status: "awaiting",
|
||||
status: "pending",
|
||||
})
|
||||
case IdMap.getId("canceled-order"):
|
||||
return Promise.resolve({
|
||||
status: "canceled",
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({
|
||||
fulfillment_status: "not_fulfilled",
|
||||
@@ -593,9 +597,17 @@ describe("OrderService", () => {
|
||||
"Can't update shipping, billing, items and payment method when order is processed"
|
||||
)
|
||||
})
|
||||
|
||||
it("throws if order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.update(IdMap.getId("canceled-order"), {})
|
||||
).rejects.toThrow("A canceled order cannot be updated")
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancel", () => {
|
||||
const now = new Date()
|
||||
|
||||
const orderRepo = MockRepository({
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
@@ -610,7 +622,7 @@ describe("OrderService", () => {
|
||||
fulfillment_status: "not_fulfilled",
|
||||
payment_status: "awaiting",
|
||||
status: "pending",
|
||||
fulfillments: [{ id: "fulfillment_test" }],
|
||||
fulfillments: [{ id: "fulfillment_test", canceled_at: now }],
|
||||
payments: [{ id: "payment_test" }],
|
||||
items: [
|
||||
{ id: "item_1", variant_id: "variant-1", quantity: 12 },
|
||||
@@ -665,11 +677,6 @@ describe("OrderService", () => {
|
||||
id: "payment_test",
|
||||
})
|
||||
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledWith({
|
||||
id: "fulfillment_test",
|
||||
})
|
||||
|
||||
expect(inventoryService.adjustInventory).toHaveBeenCalledTimes(2)
|
||||
expect(inventoryService.adjustInventory).toHaveBeenCalledWith(
|
||||
"variant-1",
|
||||
@@ -685,7 +692,7 @@ describe("OrderService", () => {
|
||||
fulfillment_status: "canceled",
|
||||
payment_status: "canceled",
|
||||
status: "canceled",
|
||||
fulfillments: [{ id: "fulfillment_test" }],
|
||||
fulfillments: [{ id: "fulfillment_test", canceled_at: now }],
|
||||
payments: [{ id: "payment_test" }],
|
||||
items: [
|
||||
{
|
||||
@@ -701,12 +708,6 @@ describe("OrderService", () => {
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("throws if order payment is captured", async () => {
|
||||
await expect(
|
||||
orderService.cancel(IdMap.getId("paid-order"))
|
||||
).rejects.toThrow("Can't cancel an order with a processed payment")
|
||||
})
|
||||
})
|
||||
|
||||
describe("capturePayment", () => {
|
||||
@@ -718,7 +719,8 @@ describe("OrderService", () => {
|
||||
payment_status: "awaiting",
|
||||
payments: [{ id: "payment_fail", captured_at: null }],
|
||||
})
|
||||
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ status: "canceled" })
|
||||
default:
|
||||
return Promise.resolve({
|
||||
fulfillment_status: "not_fulfilled",
|
||||
@@ -790,6 +792,12 @@ describe("OrderService", () => {
|
||||
payments: [{ id: "payment_fail", captured_at: null }],
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.capturePayment(IdMap.getId("canceled"))
|
||||
).rejects.toThrow("A canceled order cannot capture payment")
|
||||
})
|
||||
})
|
||||
|
||||
describe("createFulfillment", () => {
|
||||
@@ -828,6 +836,8 @@ describe("OrderService", () => {
|
||||
switch (q.where.id) {
|
||||
case "partial":
|
||||
return Promise.resolve(partialOrder)
|
||||
case "canceled":
|
||||
return Promise.resolve({ status: "canceled", ...order })
|
||||
default:
|
||||
return Promise.resolve(order)
|
||||
}
|
||||
@@ -963,6 +973,16 @@ describe("OrderService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.createFulfillment("canceled", [
|
||||
{
|
||||
item_id: "item_1",
|
||||
quantity: 1,
|
||||
},
|
||||
])
|
||||
).rejects.toThrow("A canceled order cannot be fulfilled")
|
||||
})
|
||||
it.each([
|
||||
[true, true],
|
||||
[false, false],
|
||||
@@ -989,6 +1009,60 @@ describe("OrderService", () => {
|
||||
)
|
||||
})
|
||||
|
||||
describe("cancelFulfillment", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOneWithRelations: () => Promise.resolve({}),
|
||||
save: f => Promise.resolve(f),
|
||||
})
|
||||
|
||||
const fulfillmentService = {
|
||||
cancelFulfillment: jest.fn().mockImplementation(f => {
|
||||
switch (f) {
|
||||
case IdMap.getId("no-order"):
|
||||
return Promise.resolve({})
|
||||
default:
|
||||
return Promise.resolve({
|
||||
order_id: IdMap.getId("order-id"),
|
||||
})
|
||||
}
|
||||
}),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const orderService = new OrderService({
|
||||
manager: MockManager,
|
||||
orderRepository: orderRepo,
|
||||
fulfillmentService,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully cancels fulfillment and corrects order status", async () => {
|
||||
await orderService.cancelFulfillment(IdMap.getId("order"))
|
||||
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledWith(
|
||||
IdMap.getId("order")
|
||||
)
|
||||
|
||||
expect(orderRepo.save).toHaveBeenCalledTimes(1)
|
||||
expect(orderRepo.save).toHaveBeenCalledWith({
|
||||
fulfillment_status: "canceled",
|
||||
})
|
||||
})
|
||||
|
||||
it("fails to cancel fulfillment when not related to an order", async () => {
|
||||
await expect(
|
||||
orderService.cancelFulfillment(IdMap.getId("no-order"))
|
||||
).rejects.toThrow(`Fufillment not related to an order`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("registerReturnReceived", () => {
|
||||
const order = {
|
||||
items: [
|
||||
@@ -1007,6 +1081,8 @@ describe("OrderService", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ status: "canceled", ...order })
|
||||
default:
|
||||
return Promise.resolve(order)
|
||||
}
|
||||
@@ -1077,6 +1153,179 @@ describe("OrderService", () => {
|
||||
"return"
|
||||
)
|
||||
})
|
||||
|
||||
it("fails when order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.registerReturnReceived(IdMap.getId("canceled"), {})
|
||||
).rejects.toThrow("A canceled order cannot be registered as received")
|
||||
})
|
||||
})
|
||||
|
||||
describe("completeOrder", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ status: "canceled" })
|
||||
default:
|
||||
return Promise.resolve({ id: IdMap.getId("order") })
|
||||
}
|
||||
},
|
||||
save: jest.fn().mockImplementation(f => f),
|
||||
})
|
||||
|
||||
const eventBus = {
|
||||
emit: () =>
|
||||
Promise.resolve({
|
||||
finished: () => Promise.resolve({}),
|
||||
}),
|
||||
}
|
||||
|
||||
const orderService = new OrderService({
|
||||
manager: MockManager,
|
||||
orderRepository: orderRepo,
|
||||
eventBusService: eventBus,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates order", async () => {
|
||||
await orderService.completeOrder(IdMap.getId("order"))
|
||||
|
||||
expect(orderRepo.save).toHaveBeenCalledTimes(1)
|
||||
expect(orderRepo.save).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("order"),
|
||||
status: "completed",
|
||||
})
|
||||
})
|
||||
|
||||
it("fails when order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.completeOrder(IdMap.getId("canceled"))
|
||||
).rejects.toThrow("A canceled order cannot be completed")
|
||||
})
|
||||
})
|
||||
|
||||
describe("addShippingMethod", () => {
|
||||
const orderRepo = MockRepository({
|
||||
findOneWithRelations: (rel, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ status: "canceled" })
|
||||
default:
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("order"),
|
||||
shipping_methods: [
|
||||
{ shipping_option: { profile_id: IdMap.getId("method1") } },
|
||||
],
|
||||
})
|
||||
}
|
||||
},
|
||||
save: jest.fn().mockImplementation(f => f),
|
||||
})
|
||||
|
||||
const optionService = {
|
||||
createShippingMethod: jest
|
||||
.fn()
|
||||
.mockImplementation((optionId, data, config) =>
|
||||
Promise.resolve({ shipping_option: { profile_id: optionId } })
|
||||
),
|
||||
deleteShippingMethod: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({})),
|
||||
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const orderService = new OrderService({
|
||||
manager: MockManager,
|
||||
orderRepository: orderRepo,
|
||||
eventBusService: eventBusService,
|
||||
shippingOptionService: optionService,
|
||||
totalsService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully adds shipping method", async () => {
|
||||
await orderService.addShippingMethod(
|
||||
IdMap.getId("order"),
|
||||
IdMap.getId("option"),
|
||||
{ some: "data" },
|
||||
{}
|
||||
)
|
||||
|
||||
expect(optionService.createShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(optionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("option"),
|
||||
{ some: "data" },
|
||||
{
|
||||
order: {
|
||||
id: IdMap.getId("order"),
|
||||
shipping_methods: [
|
||||
{
|
||||
shipping_option: {
|
||||
profile_id: IdMap.getId("method1"),
|
||||
},
|
||||
},
|
||||
],
|
||||
subtotal: 0,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(optionService.deleteShippingMethod).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("successfully removes shipping method if same option profile", async () => {
|
||||
await orderService.addShippingMethod(
|
||||
IdMap.getId("order"),
|
||||
IdMap.getId("method1"),
|
||||
{ some: "data" }
|
||||
)
|
||||
|
||||
expect(optionService.createShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(optionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("method1"),
|
||||
{ some: "data" },
|
||||
{
|
||||
order: {
|
||||
id: IdMap.getId("order"),
|
||||
shipping_methods: [
|
||||
{
|
||||
shipping_option: {
|
||||
profile_id: IdMap.getId("method1"),
|
||||
},
|
||||
},
|
||||
],
|
||||
subtotal: 0,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(optionService.deleteShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(optionService.deleteShippingMethod).toHaveBeenCalledWith({
|
||||
shipping_option: {
|
||||
profile_id: IdMap.getId("method1"),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("fails if order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.addShippingMethod(
|
||||
IdMap.getId("canceled"),
|
||||
IdMap.getId("option"),
|
||||
{ some: "data" }
|
||||
)
|
||||
).rejects.toThrow("A shipping method cannot be added to a canceled order")
|
||||
})
|
||||
})
|
||||
|
||||
describe("createShipment", () => {
|
||||
@@ -1111,6 +1360,8 @@ describe("OrderService", () => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("partial"):
|
||||
return Promise.resolve(partialOrder)
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ status: "canceled" })
|
||||
default:
|
||||
return Promise.resolve(order)
|
||||
}
|
||||
@@ -1184,6 +1435,18 @@ describe("OrderService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("fails when order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.createShipment(
|
||||
IdMap.getId(
|
||||
"canceled",
|
||||
IdMap.getId("fulfillment"),
|
||||
[{ tracking_number: "1234" }],
|
||||
{}
|
||||
)
|
||||
)
|
||||
).rejects.toThrow("A canceled order cannot be fulfilled as shipped")
|
||||
})
|
||||
it.each([
|
||||
[true, true],
|
||||
[false, false],
|
||||
@@ -1213,32 +1476,37 @@ describe("OrderService", () => {
|
||||
|
||||
const orderRepo = MockRepository({
|
||||
findOneWithRelations: (rel, q) => {
|
||||
if (q.where.id === IdMap.getId("cannot")) {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("order"),
|
||||
payments: [
|
||||
{
|
||||
id: "payment",
|
||||
},
|
||||
],
|
||||
total: 100,
|
||||
refunded_total: 100,
|
||||
})
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("cannot"):
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("order"),
|
||||
payments: [
|
||||
{
|
||||
id: "payment",
|
||||
},
|
||||
],
|
||||
total: 100,
|
||||
refunded_total: 100,
|
||||
})
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({
|
||||
status: "canceled",
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("order_123"),
|
||||
payments: [
|
||||
{
|
||||
id: "payment",
|
||||
},
|
||||
],
|
||||
total: 100,
|
||||
paid_total: 100,
|
||||
refundable_amount: 100,
|
||||
refunded_total: 0,
|
||||
no_notification: true,
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("order_123"),
|
||||
payments: [
|
||||
{
|
||||
id: "payment",
|
||||
},
|
||||
],
|
||||
total: 100,
|
||||
paid_total: 100,
|
||||
refundable_amount: 100,
|
||||
refunded_total: 0,
|
||||
no_notification: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1286,6 +1554,16 @@ describe("OrderService", () => {
|
||||
)
|
||||
).rejects.toThrow("Cannot refund more than the original order amount")
|
||||
})
|
||||
it("fails when order is canceled", async () => {
|
||||
await expect(
|
||||
orderService.createRefund(
|
||||
IdMap.getId("canceled"),
|
||||
100,
|
||||
"discount",
|
||||
"note"
|
||||
)
|
||||
).rejects.toThrow("A canceled order cannot be refunded")
|
||||
})
|
||||
|
||||
it.each([
|
||||
[false, false],
|
||||
|
||||
@@ -145,39 +145,45 @@ describe("ReturnService", () => {
|
||||
describe("receive", () => {
|
||||
const returnRepository = MockRepository({
|
||||
findOne: query => {
|
||||
if (query.where.id === IdMap.getId("test-return-2")) {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("test-return-2"),
|
||||
status: "requested",
|
||||
order: {
|
||||
id: IdMap.getId("test-order"),
|
||||
items: [
|
||||
{ id: IdMap.getId("test-line"), quantity: 10 },
|
||||
{ id: IdMap.getId("test-line-2"), quantity: 10 },
|
||||
],
|
||||
},
|
||||
items: [
|
||||
{
|
||||
item_id: IdMap.getId("test-line"),
|
||||
quantity: 10,
|
||||
switch (query.where.id) {
|
||||
case IdMap.getId("test-return-2"):
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("test-return-2"),
|
||||
status: "requested",
|
||||
order: {
|
||||
id: IdMap.getId("test-order"),
|
||||
items: [
|
||||
{ id: IdMap.getId("test-line"), quantity: 10 },
|
||||
{ id: IdMap.getId("test-line-2"), quantity: 10 },
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
items: [
|
||||
{
|
||||
item_id: IdMap.getId("test-line"),
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
case IdMap.getId("test-return-3"):
|
||||
return Promise.resolve({
|
||||
status: "canceled",
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("test-return"),
|
||||
status: "requested",
|
||||
order: {
|
||||
id: IdMap.getId("test-order"),
|
||||
items: [{ id: IdMap.getId("test-line"), quantity: 10 }],
|
||||
},
|
||||
items: [
|
||||
{
|
||||
item_id: IdMap.getId("test-line"),
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("test-return"),
|
||||
status: "requested",
|
||||
order: {
|
||||
id: IdMap.getId("test-order"),
|
||||
items: [{ id: IdMap.getId("test-line"), quantity: 10 }],
|
||||
},
|
||||
items: [
|
||||
{
|
||||
item_id: IdMap.getId("test-line"),
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -349,5 +355,121 @@ describe("ReturnService", () => {
|
||||
received_at: expect.anything(),
|
||||
})
|
||||
})
|
||||
|
||||
it("fails to receive a return which has been canceled", async () => {
|
||||
await expect(
|
||||
returnService.receive(IdMap.getId("test-return-3"), [
|
||||
{ item_id: IdMap.getId("test-line"), quantity: 10 },
|
||||
{ item_id: IdMap.getId("test-line-2"), quantity: 10 },
|
||||
])
|
||||
).rejects.toThrow("Cannot receive a canceled return")
|
||||
})
|
||||
})
|
||||
|
||||
describe("canceled", () => {
|
||||
const returnRepository = MockRepository({
|
||||
findOne: query => {
|
||||
switch (query.where.id) {
|
||||
case IdMap.getId("test-return"):
|
||||
return Promise.resolve({
|
||||
status: "pending",
|
||||
refund_amount: 0,
|
||||
})
|
||||
case IdMap.getId("test-return-2"):
|
||||
return Promise.resolve({
|
||||
status: "received",
|
||||
refund_amount: 0,
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({})
|
||||
}
|
||||
},
|
||||
save: f => f,
|
||||
})
|
||||
|
||||
const returnService = new ReturnService({
|
||||
manager: MockManager,
|
||||
returnRepository,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully cancels return", async () => {
|
||||
await returnService.cancel(IdMap.getId("test-return"))
|
||||
|
||||
expect(returnRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(returnRepository.save).toHaveBeenCalledWith({
|
||||
status: "canceled",
|
||||
refund_amount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it("fails to cancel return when already received", async () => {
|
||||
await expect(
|
||||
returnService.cancel(IdMap.getId("test-return-2"))
|
||||
).rejects.toThrow("Can't cancel a return which has been returned")
|
||||
})
|
||||
})
|
||||
|
||||
describe("fulfilled", () => {
|
||||
const returnRepository = MockRepository({
|
||||
findOne: query => {
|
||||
switch (query.where.id) {
|
||||
case IdMap.getId("test-return"):
|
||||
return Promise.resolve({
|
||||
status: "canceled",
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const returnService = new ReturnService({
|
||||
manager: MockManager,
|
||||
returnRepository,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("fails to fulfill when return is canceled", async () => {
|
||||
await expect(
|
||||
returnService.fulfill(IdMap.getId("test-return"))
|
||||
).rejects.toThrow("Cannot fulfill a canceled return")
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const returnRepository = MockRepository({
|
||||
findOne: query => {
|
||||
switch (query.where.id) {
|
||||
case IdMap.getId("test-return"):
|
||||
return Promise.resolve({
|
||||
status: "canceled",
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const returnService = new ReturnService({
|
||||
manager: MockManager,
|
||||
returnRepository,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("fails to update when return is canceled", async () => {
|
||||
await expect(
|
||||
returnService.update(IdMap.getId("test-return"), {})
|
||||
).rejects.toThrow("Cannot update a canceled return")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import paymentService from "medusa-interfaces/dist/payment-service"
|
||||
import { IdMap, MockRepository, MockManager } from "medusa-test-utils"
|
||||
import SwapService from "../swap"
|
||||
import { InventoryServiceMock } from "../__mocks__/inventory"
|
||||
@@ -172,6 +173,7 @@ describe("SwapService", () => {
|
||||
const lineItemService = {
|
||||
create: jest.fn().mockImplementation(d => Promise.resolve(d)),
|
||||
update: jest.fn().mockImplementation(d => Promise.resolve(d)),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
@@ -241,26 +243,42 @@ describe("SwapService", () => {
|
||||
other: "data",
|
||||
}
|
||||
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: (_, query) => {
|
||||
switch (query.where.id) {
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({
|
||||
canceled_at: new Date(),
|
||||
})
|
||||
default:
|
||||
return Promise.resolve({
|
||||
...existing,
|
||||
order_id: IdMap.getId("test"),
|
||||
cart_id: IdMap.getId("swap-cart"),
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
eventBusService,
|
||||
swapRepository: swapRepo,
|
||||
})
|
||||
|
||||
it("fails if cart already created", async () => {
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({
|
||||
...existing,
|
||||
order_id: IdMap.getId("test"),
|
||||
cart_id: IdMap.getId("swap-cart"),
|
||||
}),
|
||||
})
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
eventBusService,
|
||||
swapRepository: swapRepo,
|
||||
})
|
||||
const res = swapService.createCart(IdMap.getId("swap-1"))
|
||||
|
||||
await expect(res).rejects.toThrow(
|
||||
"A cart has already been created for the swap"
|
||||
)
|
||||
})
|
||||
|
||||
it("fails if swap is canceled", async () => {
|
||||
await expect(
|
||||
swapService.createCart(IdMap.getId("canceled"))
|
||||
).rejects.toThrow("Canceled swap cannot be used to create a cart")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -280,6 +298,7 @@ describe("SwapService", () => {
|
||||
quantity,
|
||||
}
|
||||
}),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
}
|
||||
const swapRepo = MockRepository()
|
||||
const returnService = {
|
||||
@@ -441,8 +460,16 @@ describe("SwapService", () => {
|
||||
}
|
||||
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: (rels, q) =>
|
||||
Promise.resolve(q.where.id === IdMap.getId("empty") ? {} : existing),
|
||||
findOneWithRelations: (rels, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ canceled_at: new Date() })
|
||||
case IdMap.getId("empty"):
|
||||
return Promise.resolve({})
|
||||
default:
|
||||
return Promise.resolve(existing)
|
||||
}
|
||||
},
|
||||
})
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
@@ -474,6 +501,12 @@ describe("SwapService", () => {
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it("fails to receive return when swap is canceled", async () => {
|
||||
await expect(
|
||||
swapService.receiveReturn(IdMap.getId("canceled"))
|
||||
).rejects.toThrow("Canceled swap cannot be registered as received")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -511,6 +544,7 @@ describe("SwapService", () => {
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
@@ -563,7 +597,77 @@ describe("SwapService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("failure", () => {})
|
||||
describe("failure", () => {
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({
|
||||
canceled_at: new Date(),
|
||||
}),
|
||||
})
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
swapRepository: swapRepo,
|
||||
})
|
||||
|
||||
it("fails when swap has been canceled", async () => {
|
||||
await expect(
|
||||
swapService.createFulfillment(IdMap.getId("swap"), {})
|
||||
).rejects.toThrow("Canceled swap cannot be fulfilled")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancelFulfillment", () => {
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: () => Promise.resolve({}),
|
||||
save: f => Promise.resolve(f),
|
||||
})
|
||||
|
||||
const fulfillmentService = {
|
||||
cancelFulfillment: jest.fn().mockImplementation(f => {
|
||||
switch (f) {
|
||||
case IdMap.getId("no-swap"):
|
||||
return Promise.resolve({})
|
||||
default:
|
||||
return Promise.resolve({
|
||||
swap_id: IdMap.getId("swap-id"),
|
||||
})
|
||||
}
|
||||
}),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
swapRepository: swapRepo,
|
||||
fulfillmentService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully cancels fulfillment and corrects swap status", async () => {
|
||||
await swapService.cancelFulfillment(IdMap.getId("swap"))
|
||||
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledTimes(1)
|
||||
expect(fulfillmentService.cancelFulfillment).toHaveBeenCalledWith(
|
||||
IdMap.getId("swap")
|
||||
)
|
||||
|
||||
expect(swapRepo.save).toHaveBeenCalledTimes(1)
|
||||
expect(swapRepo.save).toHaveBeenCalledWith({
|
||||
fulfillment_status: "canceled",
|
||||
})
|
||||
})
|
||||
|
||||
it("fails to cancel fulfillment when not related to a swap", async () => {
|
||||
await expect(
|
||||
swapService.cancelFulfillment(IdMap.getId("no-swap"))
|
||||
).rejects.toThrow(`Fufillment not related to a swap`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("createShipment", () => {
|
||||
@@ -631,6 +735,7 @@ describe("SwapService", () => {
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
@@ -677,7 +782,25 @@ describe("SwapService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("failure", () => {})
|
||||
describe("failure", () => {
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: () =>
|
||||
Promise.resolve({ canceled_at: new Date() }),
|
||||
})
|
||||
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
swapRepository: swapRepo,
|
||||
})
|
||||
|
||||
it("fails when swap is canceled", async () => {
|
||||
await expect(
|
||||
swapService.createShipment(
|
||||
IdMap.getId("swap", IdMap.getId("fulfillment"), [], {})
|
||||
)
|
||||
).rejects.toThrow("Canceled swap cannot be fulfilled as shipped")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("registerCartCompletion", () => {
|
||||
@@ -794,11 +917,19 @@ describe("SwapService", () => {
|
||||
}
|
||||
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: () => Promise.resolve(existing),
|
||||
findOneWithRelations: (rels, q) => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("canceled"):
|
||||
return Promise.resolve({ canceled_at: new Date() })
|
||||
default:
|
||||
return Promise.resolve(existing)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
swapRepo: swapRepo,
|
||||
eventBusService,
|
||||
swapRepository: swapRepo,
|
||||
totalsService,
|
||||
@@ -808,6 +939,11 @@ describe("SwapService", () => {
|
||||
inventoryService,
|
||||
})
|
||||
|
||||
it("fails to register cart completion when swap is canceled", async () => {
|
||||
await expect(
|
||||
swapService.registerCartCompletion(IdMap.getId("canceled"))
|
||||
).rejects.toThrow("Cart related to canceled swap cannot be completed")
|
||||
})
|
||||
it("throws an error because inventory is to low", async () => {
|
||||
try {
|
||||
await swapService.registerCartCompletion(IdMap.getId("swap"))
|
||||
@@ -867,6 +1003,8 @@ describe("SwapService", () => {
|
||||
return Promise.resolve(existing(0, false))
|
||||
case "not_conf":
|
||||
return Promise.resolve(existing(1, false, false))
|
||||
case "canceled":
|
||||
return Promise.resolve({ canceled_at: new Date() })
|
||||
default:
|
||||
return Promise.resolve(existing(1, false))
|
||||
}
|
||||
@@ -938,6 +1076,12 @@ describe("SwapService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("fails as swap is canceled", async () => {
|
||||
await expect(swapService.processDifference("canceled")).rejects.toThrow(
|
||||
"Canceled swap cannot be processed"
|
||||
)
|
||||
})
|
||||
|
||||
it("zero", async () => {
|
||||
await swapService.processDifference("0")
|
||||
expect(swapRepo.save).toHaveBeenCalledWith({
|
||||
@@ -980,6 +1124,10 @@ describe("SwapService", () => {
|
||||
order_id: IdMap.getId("order"),
|
||||
return_order: { status: "received" },
|
||||
})
|
||||
case "canceled":
|
||||
return Promise.resolve({
|
||||
canceled_at: new Date(),
|
||||
})
|
||||
default:
|
||||
return Promise.resolve()
|
||||
}
|
||||
@@ -1002,6 +1150,109 @@ describe("SwapService", () => {
|
||||
|
||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("fails to register as received when swap is canceled", async () => {
|
||||
await expect(swapService.registerReceived("canceled")).rejects.toThrow(
|
||||
"Canceled swap cannot be registered as received"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("cancel", () => {
|
||||
const now = new Date()
|
||||
const payment = { id: IdMap.getId("payment") }
|
||||
const return_order = { status: "canceled" }
|
||||
const fulfillment = { canceled_at: now }
|
||||
|
||||
const paymentProviderService = {
|
||||
cancelPayment: jest.fn(() => Promise.resolve({})),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: (_, q) => {
|
||||
const swap = {
|
||||
payment: { ...payment },
|
||||
return_order: { ...return_order },
|
||||
fulfillments: [{ ...fulfillment }, { ...fulfillment }],
|
||||
}
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("fail-fulfillment"):
|
||||
swap.fulfillments[1].canceled_at = undefined
|
||||
return Promise.resolve(swap)
|
||||
case IdMap.getId("fail-return"):
|
||||
swap.return_order.status = "received"
|
||||
return Promise.resolve(swap)
|
||||
case IdMap.getId("fail-refund-1"):
|
||||
swap.payment_status = "difference_refunded"
|
||||
return Promise.resolve(swap)
|
||||
case IdMap.getId("fail-refund-2"):
|
||||
swap.payment_status = "partially_refunded"
|
||||
return Promise.resolve(swap)
|
||||
case IdMap.getId("fail-refund-3"):
|
||||
swap.payment_status = "refunded"
|
||||
return Promise.resolve(swap)
|
||||
default:
|
||||
return Promise.resolve(swap)
|
||||
}
|
||||
},
|
||||
save: f => f,
|
||||
})
|
||||
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
swapRepository: swapRepo,
|
||||
paymentProviderService,
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully cancels valid swap", async () => {
|
||||
await swapService.cancel(IdMap.getId("complete"))
|
||||
|
||||
expect(swapRepo.save).toHaveBeenCalledTimes(1)
|
||||
expect(swapRepo.save).toHaveBeenCalledWith({
|
||||
payment_status: "canceled",
|
||||
fulfillment_status: "canceled",
|
||||
canceled_at: expect.any(Date),
|
||||
fulfillments: expect.anything(),
|
||||
payment: expect.anything(),
|
||||
return_order: expect.anything(),
|
||||
})
|
||||
expect(paymentProviderService.cancelPayment).toHaveBeenCalledTimes(1)
|
||||
expect(paymentProviderService.cancelPayment).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("payment"),
|
||||
})
|
||||
})
|
||||
|
||||
it("fails to cancel swap when fulfillment not canceled", async () => {
|
||||
await expect(
|
||||
swapService.cancel(IdMap.getId("fail-fulfillment"))
|
||||
).rejects.toThrow(
|
||||
"All fulfillments must be canceled before the swap can be canceled"
|
||||
)
|
||||
})
|
||||
|
||||
it("fails to cancel swap when return not canceled", async () => {
|
||||
await expect(
|
||||
swapService.cancel(IdMap.getId("fail-return"))
|
||||
).rejects.toThrow(
|
||||
"Return must be canceled before the swap can be cancele"
|
||||
)
|
||||
})
|
||||
|
||||
it.each([["fail-refund-1"], ["fail-refund-2"], ["fail-refund-3"]])(
|
||||
"fails to cancel swap when contains refund",
|
||||
async input => {
|
||||
await expect(swapService.cancel(IdMap.getId(input))).rejects.toThrow(
|
||||
"Swap with a refund cannot be canceled"
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -106,7 +106,20 @@ class ClaimService extends BaseService {
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const claim = await this.retrieve(id, { relations: ["shipping_methods"] })
|
||||
|
||||
const { claim_items, shipping_methods, metadata, no_notification } = data
|
||||
if (claim.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled claim cannot be updated"
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
claim_items,
|
||||
shipping_methods,
|
||||
metadata,
|
||||
fulfillment_status,
|
||||
no_notification,
|
||||
} = data
|
||||
|
||||
if (metadata) {
|
||||
claim.metadata = this.setMetadata_(claim, metadata)
|
||||
@@ -189,6 +202,23 @@ class ClaimService extends BaseService {
|
||||
...rest
|
||||
} = data
|
||||
|
||||
for (const item of claim_items) {
|
||||
const line = await this.lineItemService_.retrieve(item.item_id, {
|
||||
relations: ["order", "swap", "claim_order"],
|
||||
})
|
||||
|
||||
if (
|
||||
line.order?.canceled_at ||
|
||||
line.swap?.canceled_at ||
|
||||
line.claim_order?.canceled_at
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Cannot create a claim on a canceled item.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let addressId = shipping_address_id || order.shipping_address_id
|
||||
if (shipping_address) {
|
||||
const addressRepo = manager.getCustomRepository(this.addressRepo_)
|
||||
@@ -345,9 +375,19 @@ class ClaimService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
if (claim.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled claim cannot be fulfilled"
|
||||
)
|
||||
}
|
||||
|
||||
const order = claim.order
|
||||
|
||||
if (claim.fulfillment_status !== "not_fulfilled") {
|
||||
if (
|
||||
claim.fulfillment_status !== "not_fulfilled" &&
|
||||
claim.fulfillment_status !== "canceled"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"The claim has already been fulfilled."
|
||||
@@ -444,12 +484,42 @@ class ClaimService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async cancelFulfillment(fulfillmentId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const canceled = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.cancelFulfillment(fulfillmentId)
|
||||
|
||||
if (!canceled.claim_order_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Fufillment not related to a claim`
|
||||
)
|
||||
}
|
||||
|
||||
const claim = await this.retrieve(canceled.claim_order_id)
|
||||
|
||||
claim.fulfillment_status = "canceled"
|
||||
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const updated = await claimRepo.save(claim)
|
||||
return updated
|
||||
})
|
||||
}
|
||||
|
||||
async processRefund(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["order", "order.payments"],
|
||||
})
|
||||
|
||||
if (claim.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled claim cannot be processed"
|
||||
)
|
||||
}
|
||||
|
||||
if (claim.type !== "refund") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
@@ -495,6 +565,12 @@ class ClaimService extends BaseService {
|
||||
relations: ["additional_items"],
|
||||
})
|
||||
|
||||
if (claim.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled claim cannot be fulfilled as shipped"
|
||||
)
|
||||
}
|
||||
const evaluatedNoNotification =
|
||||
no_notification !== undefined ? no_notification : claim.no_notification
|
||||
|
||||
@@ -543,39 +619,35 @@ class ClaimService extends BaseService {
|
||||
async cancel(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["return_order", "fulfillments"],
|
||||
relations: ["return_order", "fulfillments", "order", "order.refunds"],
|
||||
})
|
||||
|
||||
if (
|
||||
claim.fulfillment_status === "shipped" ||
|
||||
claim.fulfillment_status === "partially_shipped"
|
||||
) {
|
||||
if (claim.refund_amount) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot cancel a claim that has been shipped.`
|
||||
"Claim with a refund cannot be canceled"
|
||||
)
|
||||
}
|
||||
|
||||
if (claim?.return_order?.status === "received") {
|
||||
if (claim.fulfillments) {
|
||||
for (const f of claim.fulfillments) {
|
||||
if (!f.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"All fulfillments must be canceled before the claim can be canceled"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (claim.return_order && claim.return_order.status !== "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Cannot cancel a claim that has a received return.`
|
||||
"Return must be canceled before the claim can be canceled"
|
||||
)
|
||||
}
|
||||
|
||||
if (claim.return_order) {
|
||||
await this.returnService_
|
||||
.withTransaction(manager)
|
||||
.cancel(claim.return_order.id)
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
claim.fulfillments.map(f =>
|
||||
this.fulfillmentService_.withTransaction(manager).cancelFulfillment(f)
|
||||
)
|
||||
)
|
||||
|
||||
claim.fulfillment_status = "canceled"
|
||||
claim.canceled_at = new Date()
|
||||
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const result = await claimRepo.save(claim)
|
||||
|
||||
@@ -195,6 +195,7 @@ class FulfillmentService extends BaseService {
|
||||
})
|
||||
|
||||
let result = await fulfillmentRepository.save(ful)
|
||||
|
||||
result.data = await this.fulfillmentProviderService_.createFulfillment(
|
||||
shipping_method,
|
||||
items,
|
||||
@@ -211,7 +212,9 @@ class FulfillmentService extends BaseService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a fulfillment with the fulfillment provider.
|
||||
* Cancels a fulfillment with the fulfillment provider. Will decrement the
|
||||
* fulfillment_quantity on the line items associated with the fulfillment.
|
||||
* Throws if the fulfillment has already been shipped.
|
||||
* @param {Fulfillment|string} fulfillmentOrId - the fulfillment object or id.
|
||||
* @return {Promise} the result of the save operation
|
||||
*
|
||||
@@ -222,17 +225,36 @@ class FulfillmentService extends BaseService {
|
||||
if (typeof fulfillmentOrId === "object") {
|
||||
id = fulfillmentOrId.id
|
||||
}
|
||||
const fulfillment = await this.retrieve(id)
|
||||
const fulfillment = await this.retrieve(id, {
|
||||
relations: ["items", "claim_order", "swap"],
|
||||
})
|
||||
|
||||
if (fulfillment.shipped_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`The fulfillment has already been shipped. Shipped fulfillments cannot be canceled`
|
||||
)
|
||||
}
|
||||
|
||||
await this.fulfillmentProviderService_.cancelFulfillment(fulfillment)
|
||||
|
||||
fulfillment.status = "canceled"
|
||||
fulfillment.canceled_at = new Date()
|
||||
|
||||
const lineItemService = this.lineItemService_.withTransaction(manager)
|
||||
|
||||
for (const fItem of fulfillment.items) {
|
||||
const item = await lineItemService.retrieve(fItem.item_id)
|
||||
const fulfilledQuantity = item.fulfilled_quantity - fItem.quantity
|
||||
await lineItemService.update(item.id, {
|
||||
fulfilled_quantity: fulfilledQuantity,
|
||||
})
|
||||
}
|
||||
|
||||
const fulfillmentRepo = manager.getCustomRepository(
|
||||
this.fulfillmentRepository_
|
||||
)
|
||||
const result = await fulfillmentRepo.save(fulfillment)
|
||||
return result
|
||||
const canceled = await fulfillmentRepo.save(fulfillment)
|
||||
return canceled
|
||||
})
|
||||
}
|
||||
|
||||
@@ -266,6 +288,13 @@ class FulfillmentService extends BaseService {
|
||||
relations: ["items"],
|
||||
})
|
||||
|
||||
if (fulfillment.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Fulfillment has been canceled"
|
||||
)
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
fulfillment.shipped_at = now
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
const fs = require("fs")
|
||||
|
||||
class InventoryService extends BaseService {
|
||||
constructor({ manager, productVariantService }) {
|
||||
|
||||
@@ -10,6 +10,7 @@ class OrderService extends BaseService {
|
||||
PAYMENT_CAPTURE_FAILED: "order.payment_capture_failed",
|
||||
SHIPMENT_CREATED: "order.shipment_created",
|
||||
FULFILLMENT_CREATED: "order.fulfillment_created",
|
||||
FULFILLMENT_CANCELED: "order.fulfillment_canceled",
|
||||
RETURN_REQUESTED: "order.return_requested",
|
||||
ITEMS_RETURNED: "order.items_returned",
|
||||
RETURN_ACTION_REQUIRED: "order.return_action_required",
|
||||
@@ -408,6 +409,13 @@ class OrderService extends BaseService {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const order = await this.retrieve(orderId)
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A canceled order cannot be completed"
|
||||
)
|
||||
}
|
||||
|
||||
// Run all other registered events
|
||||
const completeOrderJob = await this.eventBus_.emit(
|
||||
OrderService.Events.COMPLETED,
|
||||
@@ -618,6 +626,13 @@ class OrderService extends BaseService {
|
||||
const order = await this.retrieve(orderId, { relations: ["items"] })
|
||||
const shipment = await this.fulfillmentService_.retrieve(fulfillmentId)
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A canceled order cannot be fulfilled as shipped"
|
||||
)
|
||||
}
|
||||
|
||||
if (!shipment || shipment.order_id !== orderId) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
@@ -773,6 +788,13 @@ class OrderService extends BaseService {
|
||||
})
|
||||
const { shipping_methods } = order
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A shipping method cannot be added to a canceled order"
|
||||
)
|
||||
}
|
||||
|
||||
const newMethod = await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.createShippingMethod(optionId, data, { order, ...config })
|
||||
@@ -814,6 +836,13 @@ class OrderService extends BaseService {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const order = await this.retrieve(orderId)
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A canceled order cannot be updated"
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
(update.payment || update.items) &&
|
||||
(order.fulfillment_status !== "not_fulfilled" ||
|
||||
@@ -893,23 +922,36 @@ class OrderService extends BaseService {
|
||||
async cancel(orderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const order = await this.retrieve(orderId, {
|
||||
relations: ["fulfillments", "payments", "items"],
|
||||
relations: [
|
||||
"fulfillments",
|
||||
"payments",
|
||||
"returns",
|
||||
"claims",
|
||||
"swaps",
|
||||
"items",
|
||||
],
|
||||
})
|
||||
|
||||
if (order.payment_status !== "awaiting") {
|
||||
if (order.refunds?.length > 0) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Can't cancel an order with a processed payment"
|
||||
"Order with refund(s) cannot be canceled"
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
order.fulfillments.map(fulfillment =>
|
||||
this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.cancelFulfillment(fulfillment)
|
||||
)
|
||||
)
|
||||
const throwErrorIf = (arr, pred, type) =>
|
||||
arr?.filter(pred).find(_ => {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`All ${type} must be canceled before canceling an order`
|
||||
)
|
||||
})
|
||||
const notCanceled = o => !o.canceled_at
|
||||
|
||||
throwErrorIf(order.fulfillments, notCanceled, "fulfillments")
|
||||
throwErrorIf(order.returns, r => r.status !== "canceled", "returns")
|
||||
throwErrorIf(order.swaps, notCanceled, "swaps")
|
||||
throwErrorIf(order.claims, notCanceled, "claims")
|
||||
|
||||
for (const item of order.items) {
|
||||
await this.inventoryService_
|
||||
@@ -950,6 +992,13 @@ class OrderService extends BaseService {
|
||||
const orderRepo = manager.getCustomRepository(this.orderRepository_)
|
||||
const order = await this.retrieve(orderId, { relations: ["payments"] })
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A canceled order cannot capture payment"
|
||||
)
|
||||
}
|
||||
|
||||
const payments = []
|
||||
for (const p of order.payments) {
|
||||
if (p.captured_at === null) {
|
||||
@@ -1075,6 +1124,13 @@ class OrderService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A canceled order cannot be fulfilled"
|
||||
)
|
||||
}
|
||||
|
||||
if (!order.shipping_methods?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
@@ -1124,6 +1180,7 @@ class OrderService extends BaseService {
|
||||
const orderRepo = manager.getCustomRepository(this.orderRepository_)
|
||||
|
||||
order.fulfillments = [...order.fulfillments, ...fulfillments]
|
||||
|
||||
const result = await orderRepo.save(order)
|
||||
|
||||
const evaluatedNoNotification =
|
||||
@@ -1143,6 +1200,43 @@ class OrderService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a fulfillment (if related to an order)
|
||||
* @param {string} fulfillmentId - the ID of the fulfillment to cancel
|
||||
* @returns updated order
|
||||
*/
|
||||
async cancelFulfillment(fulfillmentId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const canceled = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.cancelFulfillment(fulfillmentId)
|
||||
|
||||
if (!canceled.order_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Fufillment not related to an order`
|
||||
)
|
||||
}
|
||||
|
||||
const order = await this.retrieve(canceled.order_id)
|
||||
|
||||
order.fulfillment_status = "canceled"
|
||||
|
||||
const orderRepo = manager.getCustomRepository(this.orderRepository_)
|
||||
const updated = await orderRepo.save(order)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(OrderService.Events.FULFILLMENT_CANCELED, {
|
||||
id: order.id,
|
||||
fulfillment_id: canceled.id,
|
||||
no_notification: canceled.no_notification,
|
||||
})
|
||||
|
||||
return updated
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the order line items, given an array of items.
|
||||
* @param {Order} order - the order to get line items from
|
||||
@@ -1208,6 +1302,13 @@ class OrderService extends BaseService {
|
||||
relations: ["payments"],
|
||||
})
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A canceled order cannot be refunded"
|
||||
)
|
||||
}
|
||||
|
||||
if (refundAmount > order.refundable_amount) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
@@ -1312,6 +1413,13 @@ class OrderService extends BaseService {
|
||||
relations: ["items", "returns", "payments"],
|
||||
})
|
||||
|
||||
if (order.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"A canceled order cannot be registered as received"
|
||||
)
|
||||
}
|
||||
|
||||
if (!receivedReturn || receivedReturn.order_id !== orderId) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
|
||||
@@ -321,7 +321,6 @@ class PaymentProviderService extends BaseService {
|
||||
async cancelPayment(paymentObj) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const payment = await this.retrievePayment(paymentObj.id)
|
||||
|
||||
const provider = this.retrieveProvider(payment.provider_id)
|
||||
payment.data = await provider.cancelPayment(payment)
|
||||
|
||||
|
||||
@@ -116,6 +116,31 @@ class ReturnService extends BaseService {
|
||||
return returnRepo.find(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a return if possible. Returns can be canceled if it has not been received.
|
||||
* @param {string} returnId - the id of the return to cancel.
|
||||
* @return {Promise<Return>} the updated Return
|
||||
*/
|
||||
async cancel(returnId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const ret = await this.retrieve(returnId)
|
||||
|
||||
if (ret.status === "received") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Can't cancel a return which has been returned"
|
||||
)
|
||||
}
|
||||
|
||||
const retRepo = manager.getCustomRepository(this.returnRepository_)
|
||||
|
||||
ret.status = "canceled"
|
||||
|
||||
const result = retRepo.save(ret)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that an order has the statuses necessary to complete a return.
|
||||
* fulfillment_status cannot be not_fulfilled or returned.
|
||||
@@ -236,6 +261,13 @@ class ReturnService extends BaseService {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const ret = await this.retrieve(returnId)
|
||||
|
||||
if (ret.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Cannot update a canceled return"
|
||||
)
|
||||
}
|
||||
|
||||
const { metadata, ...rest } = update
|
||||
|
||||
if ("metadata" in update) {
|
||||
@@ -272,6 +304,23 @@ class ReturnService extends BaseService {
|
||||
delete data.order_id
|
||||
}
|
||||
|
||||
for (const item of data.items) {
|
||||
const line = await this.lineItemService_.retrieve(item.item_id, {
|
||||
relations: ["order", "swap", "claim_order"],
|
||||
})
|
||||
|
||||
if (
|
||||
line.order?.canceled_at ||
|
||||
line.swap?.canceled_at ||
|
||||
line.claim_order?.canceled_at
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Cannot create a return for a canceled item.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const order = await this.orderService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(orderId, {
|
||||
@@ -370,6 +419,13 @@ class ReturnService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
if (returnOrder.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Cannot fulfill a canceled return"
|
||||
)
|
||||
}
|
||||
|
||||
let returnData = { ...returnOrder }
|
||||
|
||||
const items = await this.lineItemService_.list({
|
||||
@@ -429,6 +485,13 @@ class ReturnService extends BaseService {
|
||||
relations: ["items", "swap", "swap.additional_items"],
|
||||
})
|
||||
|
||||
if (returnObj.status === "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Cannot receive a canceled return"
|
||||
)
|
||||
}
|
||||
|
||||
let orderId = returnObj.order_id
|
||||
// check if return is requested on a swap
|
||||
if (returnObj.swap) {
|
||||
|
||||
@@ -314,6 +314,24 @@ class SwapService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
for (const item of returnItems) {
|
||||
const line = await this.lineItemService_.retrieve(item.item_id, {
|
||||
relations: ["order", "swap", "claim_order"],
|
||||
})
|
||||
console.log(line)
|
||||
|
||||
if (
|
||||
line.order?.canceled_at ||
|
||||
line.swap?.canceled_at ||
|
||||
line.claim_order?.canceled_at
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Cannot create a swap on a canceled item.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const newItems = await Promise.all(
|
||||
additionalItems.map(({ variant_id, quantity }) => {
|
||||
return this.lineItemService_.generate(
|
||||
@@ -364,6 +382,13 @@ class SwapService extends BaseService {
|
||||
relations: ["payment", "order", "order.payments"],
|
||||
})
|
||||
|
||||
if (swap.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled swap cannot be processed"
|
||||
)
|
||||
}
|
||||
|
||||
if (!swap.confirmed_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
@@ -515,6 +540,13 @@ class SwapService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
if (swap.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled swap cannot be used to create a cart"
|
||||
)
|
||||
}
|
||||
|
||||
if (swap.cart_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.DUPLICATE_ERROR,
|
||||
@@ -625,6 +657,13 @@ class SwapService extends BaseService {
|
||||
return swap
|
||||
}
|
||||
|
||||
if (swap.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Cart related to canceled swap cannot be completed"
|
||||
)
|
||||
}
|
||||
|
||||
const cart = swap.cart
|
||||
|
||||
const items = swap.cart.items
|
||||
@@ -716,6 +755,13 @@ class SwapService extends BaseService {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const swap = await this.retrieve(swapId, { relations: ["return_order"] })
|
||||
|
||||
if (swap.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled swap cannot be registered as received"
|
||||
)
|
||||
}
|
||||
|
||||
const returnId = swap.return_order && swap.return_order.id
|
||||
if (!returnId) {
|
||||
throw new MedusaError(
|
||||
@@ -739,6 +785,64 @@ class SwapService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a given swap if possible. A swap can only be canceled if all
|
||||
* related returns, fulfillments, and payments have been canceled. If a swap
|
||||
* is associated with a refund, it cannot be canceled.
|
||||
* @param {string} swapId - the id of the swap to cancel.
|
||||
* @returns {Promise<Swap>} the canceled swap.
|
||||
*/
|
||||
async cancel(swapId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const swap = await this.retrieve(swapId, {
|
||||
relations: ["payment", "fulfillments", "return_order"],
|
||||
})
|
||||
|
||||
if (
|
||||
swap.payment_status === "difference_refunded" ||
|
||||
swap.payment_status === "partially_refunded" ||
|
||||
swap.payment_status === "refunded"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Swap with a refund cannot be canceled"
|
||||
)
|
||||
}
|
||||
|
||||
if (swap.fulfillments) {
|
||||
for (const f of swap.fulfillments) {
|
||||
if (!f.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"All fulfillments must be canceled before the swap can be canceled"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (swap.return_order && swap.return_order.status !== "canceled") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Return must be canceled before the swap can be canceled"
|
||||
)
|
||||
}
|
||||
|
||||
swap.payment_status = "canceled"
|
||||
swap.fulfillment_status = "canceled"
|
||||
swap.canceled_at = new Date()
|
||||
|
||||
if (swap.payment) {
|
||||
await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.cancelPayment(swap.payment)
|
||||
}
|
||||
|
||||
const swapRepo = manager.getCustomRepository(this.swapRepository_)
|
||||
const result = await swapRepo.save(swap)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Fulfills the addtional items associated with the swap. Will call the
|
||||
* fulfillment providers associated with the shipping methods.
|
||||
@@ -770,7 +874,17 @@ class SwapService extends BaseService {
|
||||
})
|
||||
const order = swap.order
|
||||
|
||||
if (swap.fulfillment_status !== "not_fulfilled") {
|
||||
if (swap.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled swap cannot be fulfilled"
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
swap.fulfillment_status !== "not_fulfilled" &&
|
||||
swap.fulfillment_status !== "canceled"
|
||||
) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"The swap was already fulfilled"
|
||||
@@ -861,6 +975,34 @@ class SwapService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a fulfillment (if related to a swap)
|
||||
* @param {string} fulfillmentId - the ID of the fulfillment to cancel
|
||||
* @returns updated swap
|
||||
*/
|
||||
async cancelFulfillment(fulfillmentId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const canceled = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.cancelFulfillment(fulfillmentId)
|
||||
|
||||
if (!canceled.swap_id) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`Fufillment not related to a swap`
|
||||
)
|
||||
}
|
||||
|
||||
const swap = await this.retrieve(canceled.swap_id)
|
||||
|
||||
swap.fulfillment_status = "canceled"
|
||||
|
||||
const swapRepo = manager.getCustomRepository(this.swapRepository_)
|
||||
const updated = await swapRepo.save(swap)
|
||||
return updated
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a fulfillment as shipped and attaches tracking numbers.
|
||||
* @param {string} swapId - the id of the swap that has been shipped.
|
||||
@@ -887,6 +1029,12 @@ class SwapService extends BaseService {
|
||||
relations: ["additional_items"],
|
||||
})
|
||||
|
||||
if (swap.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled swap cannot be fulfilled as shipped"
|
||||
)
|
||||
}
|
||||
const evaluatedNoNotification =
|
||||
no_notification !== undefined ? no_notification : swap.no_notification
|
||||
|
||||
@@ -969,6 +1117,13 @@ class SwapService extends BaseService {
|
||||
relations: ["return_order", "return_order.items"],
|
||||
})
|
||||
|
||||
if (swap.canceled_at) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Canceled swap cannot be registered as received"
|
||||
)
|
||||
}
|
||||
|
||||
if (swap.return_order.status !== "received") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
|
||||
Reference in new issue
Block a user