Merge remote-tracking branch 'origin/develop' into feat/meilisearch-plugin
This commit is contained in:
commit
10c87e8d5a
271 files changed
+16116
-5647
No files matched your search
@@ -12,6 +12,9 @@ import { Validator, MedusaError } from "medusa-core-utils"
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* email:
|
||||
* type: string
|
||||
* description: The Customer's email. Only providable if user not registered.
|
||||
* first_name:
|
||||
* type: string
|
||||
* description: The Customer's first name.
|
||||
@@ -37,6 +40,7 @@ export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string().optional(),
|
||||
first_name: Validator.string().optional(),
|
||||
last_name: Validator.string().optional(),
|
||||
password: Validator.string().optional(),
|
||||
@@ -50,9 +54,19 @@ export default async (req, res) => {
|
||||
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
|
||||
let customer = await customerService.retrieve(id)
|
||||
|
||||
if (value.email && customer.has_account) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Email cannot be changed when the user has registered their account"
|
||||
)
|
||||
}
|
||||
|
||||
await customerService.update(id, value)
|
||||
|
||||
const customer = await customerService.retrieve(id, {
|
||||
customer = await customerService.retrieve(id, {
|
||||
relations: ["orders"],
|
||||
})
|
||||
res.status(200).json({ customer })
|
||||
|
||||
@@ -22,6 +22,7 @@ import variantRoutes from "./variants"
|
||||
import draftOrderRoutes from "./draft-orders"
|
||||
import collectionRoutes from "./collections"
|
||||
import notificationRoutes from "./notifications"
|
||||
import noteRoutes from "./notes"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -68,6 +69,7 @@ export default (app, container, config) => {
|
||||
collectionRoutes(route)
|
||||
notificationRoutes(route)
|
||||
returnReasonRoutes(route)
|
||||
noteRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
/**
|
||||
* @oas [post] /notes
|
||||
* operationId: "PostNotes"
|
||||
* summary: "Creates a Note"
|
||||
* description: "Creates a Note which can be associated with any resource as required."
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* resource_id:
|
||||
* type: string
|
||||
* description: The id of the resource which the Note relates to.
|
||||
* resource_type:
|
||||
* type: string
|
||||
* description: The type of resource which the Note relates to.
|
||||
* value:
|
||||
* type: string
|
||||
* description: The content of the Note to create.
|
||||
* tags:
|
||||
* - Note
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* note:
|
||||
* $ref: "#/components/schemas/note"
|
||||
*
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
resource_id: Validator.string(),
|
||||
resource_type: Validator.string(),
|
||||
value: Validator.string(),
|
||||
})
|
||||
|
||||
const userId = req.user.id || req.user.userId
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const noteService = req.scope.resolve("noteService")
|
||||
|
||||
const result = await noteService.create({
|
||||
resource_id: value.resource_id,
|
||||
resource_type: value.resource_type,
|
||||
value: value.value,
|
||||
author_id: userId,
|
||||
})
|
||||
|
||||
res.status(200).json({ note: result })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @oas [delete] /notes/{id}
|
||||
* operationId: "DeleteNotesNote"
|
||||
* summary: "Deletes a Note"
|
||||
* description: "Deletes a Note."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Note to delete.
|
||||
* tags:
|
||||
* - Note
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: The id of the deleted Note.
|
||||
* deleted:
|
||||
* type: boolean
|
||||
* description: Whether or not the Note was deleted.
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const noteService = req.scope.resolve("noteService")
|
||||
await noteService.delete(id)
|
||||
|
||||
res.status(200).json({ id, deleted: true })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @oas [get] /notes/{id}
|
||||
* operationId: "GetNoteNote"
|
||||
* summary: "Get Note"
|
||||
* description: "Retrieves a single note using its id"
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the note to retrieve.
|
||||
* tags:
|
||||
* - Note
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* note:
|
||||
* $ref: "#/components/schemas/note"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const noteService = req.scope.resolve("noteService")
|
||||
const note = await noteService.retrieve(id, { relations: ["author"] })
|
||||
|
||||
res.status(200).json({ note })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/notes", route)
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-note").default))
|
||||
|
||||
route.get("/", middlewares.wrap(require("./list-notes").default))
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-note").default))
|
||||
|
||||
route.post("/:id", middlewares.wrap(require("./update-note").default))
|
||||
|
||||
route.delete("/:id", middlewares.wrap(require("./delete-note").default))
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @oas [get] /notes
|
||||
* operationId: "GetNotes"
|
||||
* summary: "List Notes"
|
||||
* description: "Retrieves a list of notes"
|
||||
* tags:
|
||||
* - Note
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* notes:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/note"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const limit = parseInt(req.query.limit) || 50
|
||||
const offset = parseInt(req.query.offset) || 0
|
||||
|
||||
const selector = {}
|
||||
|
||||
if ("resource_id" in req.query) {
|
||||
selector.resource_id = req.query.resource_id
|
||||
}
|
||||
|
||||
const noteService = req.scope.resolve("noteService")
|
||||
const notes = await noteService.list(selector, {
|
||||
take: limit,
|
||||
skip: offset,
|
||||
relations: ["author"],
|
||||
})
|
||||
|
||||
res.status(200).json({ notes })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
/**
|
||||
* @oas [post] /notes/{id}
|
||||
* operationId: "PostNotesNote"
|
||||
* summary: "Updates a Note"
|
||||
* description: "Updates a Note associated with some resource"
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Note to update
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* value:
|
||||
* type: string
|
||||
* description: The updated description of the Note.
|
||||
* tags:
|
||||
* - Note
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* note:
|
||||
* $ref: "#/components/schemas/note"
|
||||
*
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
value: Validator.string(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const noteService = req.scope.resolve("noteService")
|
||||
const result = await noteService.update(id, value.value)
|
||||
|
||||
res.status(200).json({ note: result })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,9 @@ import { defaultFields, defaultRelations } from "./"
|
||||
* no_notification:
|
||||
* description: If set to true no notification will be send related to this Swap.
|
||||
* type: boolean
|
||||
* allow_backorder:
|
||||
* description: If true, swaps can be completed with items out of stock
|
||||
* type: boolean
|
||||
* tags:
|
||||
* - Order
|
||||
* responses:
|
||||
@@ -83,6 +86,7 @@ export default async (req, res) => {
|
||||
quantity: Validator.number().required(),
|
||||
}),
|
||||
no_notification: Validator.boolean().optional(),
|
||||
allow_backorder: Validator.boolean().default(true),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
@@ -141,6 +145,7 @@ export default async (req, res) => {
|
||||
{
|
||||
idempotency_key: idempotencyKey.idempotency_key,
|
||||
no_notification: value.no_notification,
|
||||
allow_backorder: value.allow_backorder,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,10 +1,80 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
|
||||
|
||||
describe("POST /admin/products", () => {
|
||||
describe("successful creation", () => {
|
||||
describe("successful creation with variants", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/products", {
|
||||
payload: {
|
||||
title: "Test Product with variants",
|
||||
description: "Test Description",
|
||||
tags: [{ id: "test", value: "test" }],
|
||||
handle: "test-product",
|
||||
options: [{ title: "Test" }],
|
||||
variants: [
|
||||
{
|
||||
title: "Test",
|
||||
prices: [
|
||||
{
|
||||
currency_code: "USD",
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
value: "100",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("assigns invokes productVariantService with ranked variants", () => {
|
||||
expect(ProductVariantServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.create).toHaveBeenCalledWith(
|
||||
IdMap.getId("productWithOptions"),
|
||||
{
|
||||
title: "Test",
|
||||
variant_rank: 0,
|
||||
prices: [
|
||||
{
|
||||
currency_code: "USD",
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("option1"),
|
||||
value: "100",
|
||||
},
|
||||
],
|
||||
inventory_quantity: 0,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successful creation test", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -14,6 +84,7 @@ describe("POST /admin/products", () => {
|
||||
description: "Test Description",
|
||||
tags: [{ id: "test", value: "test" }],
|
||||
handle: "test-product",
|
||||
options: [{ title: "Denominations" }],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
@@ -39,7 +110,9 @@ describe("POST /admin/products", () => {
|
||||
description: "Test Description",
|
||||
tags: [{ id: "test", value: "test" }],
|
||||
handle: "test-product",
|
||||
status: "draft",
|
||||
is_giftcard: false,
|
||||
options: [{ title: "Denominations" }],
|
||||
profile_id: IdMap.getId("default_shipping_profile"),
|
||||
})
|
||||
})
|
||||
@@ -98,6 +171,7 @@ describe("POST /admin/products", () => {
|
||||
options: [{ title: "Denominations" }],
|
||||
handle: "test-gift-card",
|
||||
is_giftcard: true,
|
||||
status: "draft",
|
||||
profile_id: IdMap.getId("giftCardProfile"),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -193,6 +193,9 @@ export default async (req, res) => {
|
||||
.optional(),
|
||||
thumbnail: Validator.string().optional(),
|
||||
handle: Validator.string().optional(),
|
||||
status: Validator.string()
|
||||
.valid("proposed", "draft", "published", "rejected")
|
||||
.default("draft"),
|
||||
type: Validator.object()
|
||||
.keys({
|
||||
id: Validator.string().optional(),
|
||||
@@ -328,6 +331,8 @@ export default async (req, res) => {
|
||||
.create({ ...value, profile_id: shippingProfile.id })
|
||||
|
||||
if (variants) {
|
||||
for (const [i, variant] of variants.entries()) variant.variant_rank = i
|
||||
|
||||
const optionIds = value.options.map(
|
||||
o => newProduct.options.find(newO => newO.title === o.title).id
|
||||
)
|
||||
@@ -341,6 +346,7 @@ export default async (req, res) => {
|
||||
option_id: optionIds[index],
|
||||
})),
|
||||
}
|
||||
|
||||
await productVariantService
|
||||
.withTransaction(manager)
|
||||
.create(newProduct.id, variant)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import _ from "lodash"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,20 @@ export default async (req, res) => {
|
||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||
}
|
||||
|
||||
if ("status" in req.query) {
|
||||
const schema = Validator.array()
|
||||
.items(
|
||||
Validator.string().valid("proposed", "draft", "published", "rejected")
|
||||
)
|
||||
.single()
|
||||
|
||||
const { value, error } = schema.validate(req.query.status)
|
||||
|
||||
if (value && !error) {
|
||||
selector.status = value
|
||||
}
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
select: includeFields.length ? includeFields : defaultFields,
|
||||
relations: expandFields.length ? expandFields : defaultRelations,
|
||||
|
||||
@@ -193,6 +193,12 @@ export default async (req, res) => {
|
||||
.allow(null, ""),
|
||||
description: Validator.string().optional(),
|
||||
discountable: Validator.boolean().optional(),
|
||||
status: Validator.string().valid(
|
||||
"proposed",
|
||||
"draft",
|
||||
"published",
|
||||
"rejected"
|
||||
),
|
||||
type: Validator.object()
|
||||
.keys({
|
||||
id: Validator.string().optional(),
|
||||
|
||||
@@ -39,6 +39,7 @@ export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
value: Validator.string().required(),
|
||||
label: Validator.string().required(),
|
||||
parent_return_reason_id: Validator.string().optional(),
|
||||
description: Validator.string()
|
||||
.optional()
|
||||
.allow(""),
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @oas [delete] /return-reason/{id}
|
||||
* operationId: "DeleteReturnReason"
|
||||
* summary: "Delete a return reason"
|
||||
* description: "Deletes a return reason."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the return reason
|
||||
* tags:
|
||||
* - Return Reason
|
||||
* responses:
|
||||
* 200:
|
||||
* description: OK
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* properties:
|
||||
* id:
|
||||
* type: string
|
||||
* description: The id of the deleted return reason
|
||||
* object:
|
||||
* type: string
|
||||
* description: The type of the object that was deleted.
|
||||
* deleted:
|
||||
* type: boolean
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const returnReasonService = req.scope.resolve("returnReasonService")
|
||||
await returnReasonService.delete(id)
|
||||
|
||||
res.json({
|
||||
id: id,
|
||||
object: "return_reason",
|
||||
deleted: true,
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,11 @@ export default app => {
|
||||
*/
|
||||
route.post("/:id", middlewares.wrap(require("./update-reason").default))
|
||||
|
||||
/**
|
||||
* Delete a reason
|
||||
*/
|
||||
route.delete("/:id", middlewares.wrap(require("./delete-reason").default))
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -33,10 +38,14 @@ export const defaultFields = [
|
||||
"id",
|
||||
"value",
|
||||
"label",
|
||||
"parent_return_reason_id",
|
||||
"description",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const defaultRelations = []
|
||||
export const defaultRelations = [
|
||||
"parent_return_reason",
|
||||
"return_reason_children",
|
||||
]
|
||||
@@ -24,7 +24,7 @@ export default async (req, res) => {
|
||||
try {
|
||||
const returnReasonService = req.scope.resolve("returnReasonService")
|
||||
|
||||
const query = {}
|
||||
const query = { parent_return_reason_id: null }
|
||||
const data = await returnReasonService.list(query, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
|
||||
@@ -42,6 +42,7 @@ export default async (req, res) => {
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
label: Validator.string().optional(),
|
||||
parent_return_reason_id: Validator.string().optional(),
|
||||
description: Validator.string()
|
||||
.optional()
|
||||
.allow(""),
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -56,7 +56,7 @@ export default async (req, res) => {
|
||||
requirements: Validator.array()
|
||||
.items(
|
||||
Validator.object({
|
||||
id: Validator.string().required(),
|
||||
id: Validator.string().optional(),
|
||||
type: Validator.string().required(),
|
||||
amount: Validator.number()
|
||||
.integer()
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -138,18 +138,36 @@ export default async (req, res) => {
|
||||
// If cart is part of swap, we register swap as complete
|
||||
switch (cart.type) {
|
||||
case "swap": {
|
||||
const swapId = cart.metadata?.swap_id
|
||||
let swap = await swapService
|
||||
.withTransaction(manager)
|
||||
.registerCartCompletion(swapId)
|
||||
try {
|
||||
const swapId = cart.metadata?.swap_id
|
||||
let swap = await swapService
|
||||
.withTransaction(manager)
|
||||
.registerCartCompletion(swapId)
|
||||
|
||||
swap = await swapService
|
||||
.withTransaction(manager)
|
||||
.retrieve(swap.id, { relations: ["shipping_address"] })
|
||||
swap = await swapService
|
||||
.withTransaction(manager)
|
||||
.retrieve(swap.id, { relations: ["shipping_address"] })
|
||||
|
||||
return {
|
||||
response_code: 200,
|
||||
response_body: { data: swap, type: "swap" },
|
||||
return {
|
||||
response_code: 200,
|
||||
response_body: { data: swap, type: "swap" },
|
||||
}
|
||||
} catch (error) {
|
||||
if (
|
||||
error &&
|
||||
error.code === MedusaError.Codes.INSUFFICIENT_INVENTORY
|
||||
) {
|
||||
return {
|
||||
response_code: 409,
|
||||
response_body: {
|
||||
message: error.message,
|
||||
type: error.type,
|
||||
code: error.code,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
// case "payment_link":
|
||||
|
||||
@@ -7,21 +7,18 @@ describe("POST /store/customers/:id", () => {
|
||||
describe("successfully updates a customer", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("lebron")}`,
|
||||
{
|
||||
payload: {
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
subject = await request("POST", `/store/customers/me`, {
|
||||
payload: {
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
email: "test@email.com",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@@ -35,6 +32,7 @@ describe("POST /store/customers/:id", () => {
|
||||
{
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
email: "test@email.com",
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -59,20 +57,16 @@ describe("POST /store/customers/:id", () => {
|
||||
describe("successfully updates a customer with billing address id", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("lebron")}`,
|
||||
{
|
||||
payload: {
|
||||
billing_address: "test",
|
||||
subject = await request("POST", `/store/customers/me`, {
|
||||
payload: {
|
||||
billing_address: "test",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@@ -97,28 +91,24 @@ describe("POST /store/customers/:id", () => {
|
||||
describe("successfully updates a customer with billing address object", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("lebron")}`,
|
||||
{
|
||||
payload: {
|
||||
billing_address: {
|
||||
first_name: "Olli",
|
||||
last_name: "Juhl",
|
||||
address_1: "Laksegade",
|
||||
city: "Copenhagen",
|
||||
country_code: "dk",
|
||||
postal_code: "2100",
|
||||
phone: "+1 (222) 333 4444",
|
||||
},
|
||||
subject = await request("POST", `/store/customers/me`, {
|
||||
payload: {
|
||||
billing_address: {
|
||||
first_name: "Olli",
|
||||
last_name: "Juhl",
|
||||
address_1: "Laksegade",
|
||||
city: "Copenhagen",
|
||||
country_code: "dk",
|
||||
postal_code: "2100",
|
||||
phone: "+1 (222) 333 4444",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@@ -147,33 +137,4 @@ describe("POST /store/customers/:id", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails if not authenticated", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/customers/${IdMap.getId("customer1")}`,
|
||||
{
|
||||
payload: {
|
||||
first_name: "LeBron",
|
||||
last_name: "James",
|
||||
},
|
||||
clientSession: {
|
||||
jwt: {
|
||||
customer_id: IdMap.getId("lebron"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("status code 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res, next, id) => {
|
||||
if (!(req.user && req.user.customer_id === id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"You must be logged in to update"
|
||||
)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import { defaultRelations, defaultFields } from "./"
|
||||
* $ref: "#/components/schemas/customer"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const id = req.user.customer_id
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
address: Validator.address().required(),
|
||||
|
||||
@@ -21,7 +21,8 @@ import { defaultRelations, defaultFields } from "./"
|
||||
* $ref: "#/components/schemas/customer"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, address_id } = req.params
|
||||
const id = req.user.customer_id
|
||||
const { address_id } = req.params
|
||||
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
try {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { defaultRelations, defaultFields } from "./"
|
||||
|
||||
/**
|
||||
* @oas [get] /customers/{id}
|
||||
* @oas [get] /customers/me
|
||||
* operationId: GetCustomersCustomer
|
||||
* summary: Retrieves a Customer
|
||||
* description: "Retrieves a Customer - the Customer must be logged in to retrieve their details."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Customer.
|
||||
* tags:
|
||||
* - Customer
|
||||
* responses:
|
||||
@@ -20,7 +18,7 @@ import { defaultRelations, defaultFields } from "./"
|
||||
* $ref: "#/components/schemas/customer"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const id = req.user.customer_id
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
const customer = await customerService.retrieve(id, {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @oas [get] /customers/{id}/payment-methods
|
||||
* @oas [get] /customers/me/payment-methods
|
||||
* operationId: GetCustomersCustomerPaymentMethods
|
||||
* summary: Retrieve saved payment methods
|
||||
* description: "Retrieves a list of a Customer's saved payment methods. Payment methods are saved with Payment Providers and it is their responsibility to fetch saved methods."
|
||||
@@ -26,7 +26,7 @@
|
||||
* description: The data needed for the Payment Provider to use the saved payment method.
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const id = req.user.customer_id
|
||||
try {
|
||||
const storeService = req.scope.resolve("storeService")
|
||||
const paymentProviderService = req.scope.resolve("paymentProviderService")
|
||||
@@ -37,11 +37,11 @@ export default async (req, res) => {
|
||||
const store = await storeService.retrieve(["payment_providers"])
|
||||
|
||||
const methods = await Promise.all(
|
||||
store.payment_providers.map(async next => {
|
||||
store.payment_providers.map(async (next) => {
|
||||
const provider = paymentProviderService.retrieveProvider(next)
|
||||
|
||||
const pMethods = await provider.retrieveSavedMethods(customer)
|
||||
return pMethods.map(m => ({
|
||||
return pMethods.map((m) => ({
|
||||
provider_id: next,
|
||||
data: m,
|
||||
}))
|
||||
|
||||
@@ -7,7 +7,6 @@ export default (app, container) => {
|
||||
const middlewareService = container.resolve("middlewareService")
|
||||
|
||||
app.use("/customers", route)
|
||||
route.param("id", middlewares.wrap(require("./authorize-customer").default))
|
||||
|
||||
// Inject plugin routes
|
||||
const routers = middlewareService.getRouters("store/customers")
|
||||
@@ -30,28 +29,28 @@ export default (app, container) => {
|
||||
// Authenticated endpoints
|
||||
route.use(middlewares.authenticate())
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-customer").default))
|
||||
route.post("/:id", middlewares.wrap(require("./update-customer").default))
|
||||
route.get("/me", middlewares.wrap(require("./get-customer").default))
|
||||
route.post("/me", middlewares.wrap(require("./update-customer").default))
|
||||
|
||||
route.get("/:id/orders", middlewares.wrap(require("./list-orders").default))
|
||||
route.get("/me/orders", middlewares.wrap(require("./list-orders").default))
|
||||
|
||||
route.post(
|
||||
"/:id/addresses",
|
||||
"/me/addresses",
|
||||
middlewares.wrap(require("./create-address").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/addresses/:address_id",
|
||||
"/me/addresses/:address_id",
|
||||
middlewares.wrap(require("./update-address").default)
|
||||
)
|
||||
|
||||
route.delete(
|
||||
"/:id/addresses/:address_id",
|
||||
"/me/addresses/:address_id",
|
||||
middlewares.wrap(require("./delete-address").default)
|
||||
)
|
||||
|
||||
route.get(
|
||||
"/:id/payment-methods",
|
||||
"/me/payment-methods",
|
||||
middlewares.wrap(require("./get-payment-methods").default)
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../orders"
|
||||
|
||||
/**
|
||||
* @oas [get] /customers/{id}/orders
|
||||
* @oas [get] /customers/me/orders
|
||||
* operationId: GetCustomersCustomerOrders
|
||||
* summary: Retrieve Customer Orders
|
||||
* description: "Retrieves a list of a Customer's Orders."
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
* $ref: "#/components/schemas/order"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const id = req.user.customer_id
|
||||
try {
|
||||
const orderService = req.scope.resolve("orderService")
|
||||
|
||||
@@ -42,13 +42,13 @@ export default async (req, res) => {
|
||||
let includeFields = []
|
||||
if ("fields" in req.query) {
|
||||
includeFields = req.query.fields.split(",")
|
||||
includeFields = includeFields.filter(f => allowedFields.includes(f))
|
||||
includeFields = includeFields.filter((f) => allowedFields.includes(f))
|
||||
}
|
||||
|
||||
let expandFields = []
|
||||
if ("expand" in req.query) {
|
||||
expandFields = req.query.expand.split(",")
|
||||
expandFields = expandFields.filter(f => allowedRelations.includes(f))
|
||||
expandFields = expandFields.filter((f) => allowedRelations.includes(f))
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
/**
|
||||
* @oas [post] /customers/{id}/password-token
|
||||
* @oas [post] /customers/password-token
|
||||
* operationId: PostCustomersCustomerPasswordToken
|
||||
* summary: Creates a reset password token
|
||||
* description: "Creates a reset password token to be used in a subsequent /reset-password request. The password token should be sent out of band e.g. via email and will not be returned."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Customer.
|
||||
* tags:
|
||||
* - Customer
|
||||
* responses:
|
||||
@@ -15,9 +13,7 @@ import { MedusaError, Validator } from "medusa-core-utils"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string()
|
||||
.email()
|
||||
.required(),
|
||||
email: Validator.string().email().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
|
||||
@@ -2,12 +2,11 @@ import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
/**
|
||||
* @oas [post] /customers/{id}/reset-password
|
||||
* operationId: PostCustomersCustomerResetPassword
|
||||
* @oas [post] /customers/reset-password
|
||||
* operationId: PostCustomersResetPassword
|
||||
* summary: Resets Customer password
|
||||
* description: "Resets a Customer's password using a password token created by a previous /password-token request."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Customer.
|
||||
* - (body) email=* {string} The Customer's email.
|
||||
* - (body) token=* {string} The password token created by a /password-token request.
|
||||
* - (body) password=* {string} The new password to set for the Customer.
|
||||
@@ -25,9 +24,7 @@ import jwt from "jsonwebtoken"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string()
|
||||
.email()
|
||||
.required(),
|
||||
email: Validator.string().email().required(),
|
||||
token: Validator.string().required(),
|
||||
password: Validator.string().required(),
|
||||
})
|
||||
|
||||
@@ -2,12 +2,11 @@ import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "./"
|
||||
|
||||
/**
|
||||
* @oas [post] /customers/{id}/addresses/{address_id}
|
||||
* @oas [post] /customers/me/addresses/{address_id}
|
||||
* operationId: PostCustomersCustomerAddressesAddress
|
||||
* summary: "Update a Shipping Address"
|
||||
* description: "Updates a Customer's saved Shipping Address."
|
||||
* parameters:
|
||||
* - (path) id=* {String} The Customer id.
|
||||
* - (path) address_id=* {String} The id of the Address to update.
|
||||
* requestBody:
|
||||
* content:
|
||||
@@ -31,7 +30,8 @@ import { defaultRelations, defaultFields } from "./"
|
||||
* $ref: "#/components/schemas/customer"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id, address_id } = req.params
|
||||
const id = req.user.customer_id
|
||||
const { address_id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
address: Validator.address().required(),
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { optional } from "joi"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { defaultRelations, defaultFields } from "./"
|
||||
|
||||
/**
|
||||
* @oas [post] /customers/{id}
|
||||
* @oas [post] /customers/me
|
||||
* operationId: PostCustomersCustomer
|
||||
* summary: Update Customer details
|
||||
* description: "Updates a Customer's saved details."
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Customer.
|
||||
* requestBody:
|
||||
* content:
|
||||
* application/json:
|
||||
@@ -29,6 +28,9 @@ import { defaultRelations, defaultFields } from "./"
|
||||
* phone:
|
||||
* description: "The Customer's phone number."
|
||||
* type: string
|
||||
* email:
|
||||
* description: "The email of the customer."
|
||||
* type: string
|
||||
* metadata:
|
||||
* description: "Metadata about the customer."
|
||||
* type: object
|
||||
@@ -45,7 +47,7 @@ import { defaultRelations, defaultFields } from "./"
|
||||
* $ref: "#/components/schemas/customer"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const id = req.user.customer_id
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
billing_address: Validator.address().optional(),
|
||||
@@ -53,6 +55,7 @@ export default async (req, res) => {
|
||||
last_name: Validator.string().optional(),
|
||||
password: Validator.string().optional(),
|
||||
phone: Validator.string().optional(),
|
||||
email: Validator.string().optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||
import { defaultRelations } from ".."
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("Get product by id", () => {
|
||||
describe("get product by id successfull", () => {
|
||||
@@ -20,7 +22,7 @@ describe("Get product by id", () => {
|
||||
expect(ProductServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("product1"),
|
||||
{ relations: ["images", "variants", "options"] }
|
||||
{ relations: defaultRelations }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -28,4 +30,37 @@ describe("Get product by id", () => {
|
||||
expect(subject.body.product.id).toEqual(IdMap.getId("product1"))
|
||||
})
|
||||
})
|
||||
|
||||
describe("Query products with relations", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"GET",
|
||||
`/store/products/${IdMap.getId("variantsWithPrices")}`
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls retrieve() once", () => {
|
||||
expect(ProductServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("endpoint called with defaultRelations", () => {
|
||||
expect(ProductServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("variantsWithPrices"),
|
||||
{ relations: defaultRelations }
|
||||
)
|
||||
})
|
||||
|
||||
it("returns product with variant prices", () => {
|
||||
expect(
|
||||
subject.body.product.variants.some(variant => variant.prices)
|
||||
).toEqual(true)
|
||||
expect(subject.body.product.variants[0].prices[0].amount).toEqual(100)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { defaultRelations } from ".."
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||
|
||||
@@ -17,8 +18,8 @@ describe("GET /store/products", () => {
|
||||
it("calls get product from productSerice", () => {
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledWith(
|
||||
{},
|
||||
{ relations: ["variants", "options", "images"], skip: 0, take: 100 }
|
||||
{ status: ["published"] },
|
||||
{ relations: defaultRelations, skip: 0, take: 100 }
|
||||
)
|
||||
})
|
||||
|
||||
@@ -42,8 +43,8 @@ describe("GET /store/products", () => {
|
||||
it("calls list from productSerice", () => {
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.list).toHaveBeenCalledWith(
|
||||
{ is_giftcard: true },
|
||||
{ relations: ["variants", "options", "images"], skip: 0, take: 100 }
|
||||
{ is_giftcard: true, status: ["published"] },
|
||||
{ relations: defaultRelations, skip: 0, take: 100 }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { defaultRelations } from "."
|
||||
|
||||
/**
|
||||
* @oas [get] /products/{id}
|
||||
* operationId: GetProductsProduct
|
||||
@@ -22,7 +24,7 @@ export default async (req, res) => {
|
||||
|
||||
const productService = req.scope.resolve("productService")
|
||||
let product = await productService.retrieve(id, {
|
||||
relations: ["images", "variants", "options"],
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ product })
|
||||
|
||||
@@ -12,3 +12,14 @@ export default app => {
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultRelations = [
|
||||
"variants",
|
||||
"variants.prices",
|
||||
"options",
|
||||
"options.values",
|
||||
"images",
|
||||
"tags",
|
||||
"collection",
|
||||
"type",
|
||||
]
|
||||
@@ -1,3 +1,6 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultRelations } from "."
|
||||
|
||||
/**
|
||||
* @oas [get] /products
|
||||
* operationId: GetProducts
|
||||
@@ -39,8 +42,10 @@ export default async (req, res) => {
|
||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||
}
|
||||
|
||||
selector.status = ["published"]
|
||||
|
||||
const listConfig = {
|
||||
relations: ["variants", "options", "images"],
|
||||
relations: defaultRelations,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
}
|
||||
|
||||
@@ -23,10 +23,14 @@ export const defaultFields = [
|
||||
"id",
|
||||
"value",
|
||||
"label",
|
||||
"parent_return_reason_id",
|
||||
"description",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
|
||||
export const defaultRelations = []
|
||||
export const defaultRelations = [
|
||||
"parent_return_reason",
|
||||
"return_reason_children",
|
||||
]
|
||||
@@ -24,7 +24,7 @@ export default async (req, res) => {
|
||||
try {
|
||||
const returnReasonService = req.scope.resolve("returnReasonService")
|
||||
|
||||
const query = {}
|
||||
const query = { parent_return_reason_id: null}
|
||||
const data = await returnReasonService.list(query, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { IdMap } from "../../../../../../../medusa-test-utils/dist"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
@@ -23,4 +24,15 @@ describe("Get variant by id", () => {
|
||||
expect(subject.body.variant.id).toEqual("1")
|
||||
})
|
||||
})
|
||||
|
||||
describe("get variant with prices", () => {
|
||||
let subject
|
||||
beforeAll(async () => {
|
||||
subject = await request("GET", `/store/variants/variant_with_prices`)
|
||||
})
|
||||
it("successfully retrieves variants with prices", async () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
expect(subject.body.variant.prices[0].amount).toEqual(100)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
import { defaultRelations } from "."
|
||||
|
||||
/**
|
||||
* @oas [get] /variants/{variant_id}
|
||||
* operationId: GetVariantsVariant
|
||||
@@ -22,7 +24,10 @@ export default async (req, res) => {
|
||||
|
||||
try {
|
||||
const variantService = req.scope.resolve("productVariantService")
|
||||
let variant = await variantService.retrieve(id, { relations: ["prices"] })
|
||||
let variant = await variantService.retrieve(id, {
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.json({ variant })
|
||||
} catch (error) {
|
||||
throw error
|
||||
|
||||
@@ -11,3 +11,5 @@ export default app => {
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultRelations = ["prices"]
|
||||
@@ -1,3 +1,5 @@
|
||||
import { defaultRelations } from "."
|
||||
|
||||
/**
|
||||
* @oas [get] /variants
|
||||
* operationId: GetVariants
|
||||
@@ -23,10 +25,14 @@ export default async (req, res) => {
|
||||
const limit = parseInt(req.query.limit) || 100
|
||||
const offset = parseInt(req.query.offset) || 0
|
||||
|
||||
let selector = {}
|
||||
let expandFields = []
|
||||
if ("expand" in req.query) {
|
||||
expandFields = req.query.expand.split(",")
|
||||
}
|
||||
|
||||
let selector = {}
|
||||
const listConfig = {
|
||||
relations: [],
|
||||
relations: expandFields.length ? expandFields : defaultRelations,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
}
|
||||
|
||||
@@ -114,6 +114,9 @@ const t = async function({ directory, migrate, seedFile }) {
|
||||
const variants = p.variants
|
||||
delete p.variants
|
||||
|
||||
// default to the products being visible
|
||||
p.status = p.status || "published"
|
||||
|
||||
p.profile_id = defaultProfile.id
|
||||
if (p.is_giftcard) {
|
||||
p.profile_id = gcProfile.id
|
||||
|
||||
@@ -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,4 @@ export { Swap } from "./models/swap"
|
||||
export { User } from "./models/user"
|
||||
export { DraftOrder } from "./models/draft-order"
|
||||
export { ReturnReason } from "./models/return-reason"
|
||||
|
||||
export { Note } from "./models/note"
|
||||
@@ -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"`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class allowBackorderSwaps1630505790603 implements MigrationInterface {
|
||||
name = 'allowBackorderSwaps1630505790603'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "swap" ADD "allow_backorder" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ADD "payment_authorized_at" TIMESTAMP WITH TIME ZONE`);
|
||||
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum" RENAME TO "swap_payment_status_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum" AS ENUM('not_paid', 'awaiting', 'captured', 'confirmed', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
|
||||
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum" USING "payment_status"::"text"::"swap_payment_status_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "swap_payment_status_enum_old"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TYPE "swap_payment_status_enum_old" AS ENUM('not_paid', 'awaiting', 'captured', 'canceled', 'difference_refunded', 'partially_refunded', 'refunded', 'requires_action')`);
|
||||
await queryRunner.query(`ALTER TABLE "swap" ALTER COLUMN "payment_status" TYPE "swap_payment_status_enum_old" USING "payment_status"::"text"::"swap_payment_status_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "swap_payment_status_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "swap_payment_status_enum_old" RENAME TO "swap_payment_status_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" DROP COLUMN "payment_authorized_at"`);
|
||||
await queryRunner.query(`ALTER TABLE "swap" DROP COLUMN "allow_backorder"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class RankColumnWithDefaultValue1631104895519 implements MigrationInterface {
|
||||
name = 'RankColumnWithDefaultValue1631104895519'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "product_variant" ADD "variant_rank" integer DEFAULT '0'`);
|
||||
await queryRunner.query(`ALTER TABLE "product_option_value" DROP CONSTRAINT "FK_7234ed737ff4eb1b6ae6e6d7b01"`);
|
||||
await queryRunner.query(`ALTER TABLE "product_option_value" ADD CONSTRAINT "FK_7234ed737ff4eb1b6ae6e6d7b01" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE cascade ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "money_amount" DROP CONSTRAINT "FK_17a06d728e4cfbc5bd2ddb70af0"`);
|
||||
await queryRunner.query(`ALTER TABLE "money_amount" ADD CONSTRAINT "FK_17a06d728e4cfbc5bd2ddb70af0" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE cascade ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "product_variant" DROP COLUMN "variant_rank"`);
|
||||
await queryRunner.query(`ALTER TABLE "product_option_value" DROP CONSTRAINT "FK_7234ed737ff4eb1b6ae6e6d7b01"`);
|
||||
await queryRunner.query(`ALTER TABLE "product_option_value" ADD CONSTRAINT "FK_7234ed737ff4eb1b6ae6e6d7b01" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "money_amount" DROP CONSTRAINT "FK_17a06d728e4cfbc5bd2ddb70af0"`);
|
||||
await queryRunner.query(`ALTER TABLE "money_amount" ADD CONSTRAINT "FK_17a06d728e4cfbc5bd2ddb70af0" FOREIGN KEY ("variant_id") REFERENCES "product_variant"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class enforceUniqueness1631261634964 implements MigrationInterface {
|
||||
name = 'enforceUniqueness1631261634964'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_e08af711f3493df1e921c4c9ef"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_77c4073c30ea7793f484750529"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_0683952543d7d3f4fffc427034"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_410649600ce31c10c4b667ca10"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_5248fda27b9f16ef818604bb6f"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_832f86daf8103491d634a967da"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_ae3e22c67d7c7a969a363533c0"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_6f234f058bbbea810dce1d04d0" ON "product_collection" ("handle") WHERE deleted_at IS NULL`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_cf9cc6c3f2e6414b992223fff1" ON "product" ("handle") WHERE deleted_at IS NULL`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_2ca8cfbdafb998ecfd6d340389" ON "product_variant" ("sku") WHERE deleted_at IS NULL`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_045d4a149c09f4704e0bc08dd4" ON "product_variant" ("barcode") WHERE deleted_at IS NULL`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_b5b6225539ee8501082fbc0714" ON "product_variant" ("ean") WHERE deleted_at IS NULL`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_aa16f61348be02dd07ce3fc54e" ON "product_variant" ("upc") WHERE deleted_at IS NULL`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f65bf52e2239ace276ece2b2f4" ON "discount" ("code") WHERE deleted_at IS NULL`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
|
||||
await queryRunner.query(`DROP INDEX "IDX_f65bf52e2239ace276ece2b2f4"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_aa16f61348be02dd07ce3fc54e"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_b5b6225539ee8501082fbc0714"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_045d4a149c09f4704e0bc08dd4"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_2ca8cfbdafb998ecfd6d340389"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_cf9cc6c3f2e6414b992223fff1"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_6f234f058bbbea810dce1d04d0"`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ae3e22c67d7c7a969a363533c0" ON "discount" ("code") WHERE (deleted_at IS NOT NULL)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_832f86daf8103491d634a967da" ON "product_variant" ("upc") WHERE (deleted_at IS NOT NULL)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_5248fda27b9f16ef818604bb6f" ON "product_variant" ("ean") WHERE (deleted_at IS NOT NULL)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_410649600ce31c10c4b667ca10" ON "product_variant" ("barcode") WHERE (deleted_at IS NOT NULL)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0683952543d7d3f4fffc427034" ON "product_variant" ("sku") WHERE (deleted_at IS NOT NULL)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_77c4073c30ea7793f484750529" ON "product" ("handle") WHERE (deleted_at IS NOT NULL)`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e08af711f3493df1e921c4c9ef" ON "product_collection" ("handle") WHERE (deleted_at IS NOT NULL)`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class nestedReturnReasons1631800727788 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return_reason" ADD "parent_return_reason_id" character varying`
|
||||
|
||||
)
|
||||
await queryRunner.query(`ALTER TABLE "return_reason" ADD CONSTRAINT "FK_2250c5d9e975987ab212f61a657" FOREIGN KEY ("parent_return_reason_id") REFERENCES "return_reason"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "return_reason" DROP COLUMN "parent_return_reason_id"`
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class statusOnProduct1631864388026 implements MigrationInterface {
|
||||
name = "statusOnProduct1631864388026"
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TYPE "product_status_enum" AS ENUM('draft', 'proposed', 'published', 'rejected')`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "product" ADD "status" "product_status_enum" `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`UPDATE "product" SET "status" = 'published' WHERE "status" IS NULL`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "product" ALTER COLUMN "status" SET NOT NULL`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "product" ALTER COLUMN "status" SET DEFAULT 'draft'`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "product" DROP COLUMN "status"`)
|
||||
await queryRunner.query(`DROP TYPE "product_status_enum"`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class addNotes1632220294687 implements MigrationInterface {
|
||||
name = "addNotes1632220294687"
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "note" ("id" character varying NOT NULL, "value" character varying NOT NULL, "resource_type" character varying NOT NULL, "resource_id" character varying NOT NULL, "author_id" character varying, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "PK_96d0c172a4fba276b1bbed43058" PRIMARY KEY ("id"))`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_f74980b411cf94af523a72af7d" ON "note" ("resource_type") `
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_3287f98befad26c3a7dab088cf" ON "note" ("resource_id") `
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "IDX_3287f98befad26c3a7dab088cf"`)
|
||||
await queryRunner.query(`DROP INDEX "IDX_f74980b411cf94af523a72af7d"`)
|
||||
await queryRunner.query(`DROP TABLE "note"`)
|
||||
}
|
||||
}
|
||||
@@ -230,6 +230,9 @@ export class Cart {
|
||||
@Column({ type: resolveDbType("timestamptz"), nullable: true })
|
||||
completed_at: Date
|
||||
|
||||
@Column({ type: resolveDbType("timestamptz"), nullable: true })
|
||||
payment_authorized_at: Date
|
||||
|
||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
created_at: Date
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,7 @@ export class Discount {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Index({ unique: true, where: "deleted_at IS NOT NULL" })
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
@Column()
|
||||
code: string
|
||||
|
||||
|
||||
@@ -44,7 +44,11 @@ export class MoneyAmount {
|
||||
@Column({ nullable: true })
|
||||
variant_id: string
|
||||
|
||||
@ManyToOne(() => ProductVariant)
|
||||
@ManyToOne(
|
||||
() => ProductVariant,
|
||||
variant => variant.prices,
|
||||
{ onDelete: "cascade" }
|
||||
)
|
||||
@JoinColumn({ name: "variant_id" })
|
||||
variant: ProductVariant
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import {
|
||||
Entity,
|
||||
BeforeInsert,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Index,
|
||||
JoinColumn,
|
||||
PrimaryColumn,
|
||||
ManyToOne,
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
import { User } from "./user"
|
||||
import { resolveDbType, DbAwareColumn } from "../utils/db-aware-column"
|
||||
|
||||
@Entity()
|
||||
export class Note {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Column()
|
||||
value: string
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
resource_type: string
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
resource_id: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
author_id: string
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: "author_id" })
|
||||
author: User
|
||||
|
||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
updated_at: Date
|
||||
|
||||
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
|
||||
deleted_at: Date
|
||||
|
||||
@DbAwareColumn({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) return
|
||||
const id = ulid()
|
||||
this.id = `note_${id}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @schema note
|
||||
* title: "Note"
|
||||
* description: "Notes are elements which we can use in association with different resources to allow users to describe additional information in relation to these."
|
||||
* x-resourceId: note
|
||||
* properties:
|
||||
* id:
|
||||
* description: "The id of the Note. This value will be prefixed by `note_`."
|
||||
* type: string
|
||||
* resource_type:
|
||||
* description: "The type of resource that the Note refers to."
|
||||
* type: string
|
||||
* resource_id:
|
||||
* description: "The id of the resource that the Note refers to."
|
||||
* type: string
|
||||
* value:
|
||||
* description: "The contents of the note."
|
||||
* type: string
|
||||
* author:
|
||||
* description: "The author of the note."
|
||||
* type: User
|
||||
* created_at:
|
||||
* description: "The date with timezone at which the resource was created."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updated_at:
|
||||
* description: "The date with timezone at which the resource was last updated."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* deleted_at:
|
||||
* description: "The date with timezone at which the resource was deleted."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* metadata:
|
||||
* description: "An optional key-value map with additional information."
|
||||
* type: object
|
||||
*/
|
||||
@@ -24,7 +24,7 @@ export class ProductCollection {
|
||||
@Column()
|
||||
title: string
|
||||
|
||||
@Index({ unique: true, where: "deleted_at IS NOT NULL" })
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
@Column({ nullable: true })
|
||||
handle: string
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ export class ProductOptionValue {
|
||||
|
||||
@ManyToOne(
|
||||
() => ProductVariant,
|
||||
variant => variant.options
|
||||
variant => variant.options,
|
||||
{ onDelete: "cascade" }
|
||||
)
|
||||
@JoinColumn({ name: "variant_id" })
|
||||
variant: ProductVariant
|
||||
|
||||
@@ -43,26 +43,29 @@ export class ProductVariant {
|
||||
@OneToMany(
|
||||
() => MoneyAmount,
|
||||
ma => ma.variant,
|
||||
{ cascade: true }
|
||||
{ cascade: true, onDelete: "CASCADE" }
|
||||
)
|
||||
prices: MoneyAmount[]
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Index({ unique: true, where: "deleted_at IS NOT NULL" })
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
sku: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Index({ unique: true, where: "deleted_at IS NOT NULL" })
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
barcode: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Index({ unique: true, where: "deleted_at IS NOT NULL" })
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
ean: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Index({ unique: true, where: "deleted_at IS NOT NULL" })
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
upc: string
|
||||
|
||||
@Column({ nullable: true, default: 0, select: false })
|
||||
variant_rank: number
|
||||
|
||||
@Column({ type: "int" })
|
||||
inventory_quantity: number
|
||||
|
||||
|
||||
@@ -26,6 +26,13 @@ import { ProductVariant } from "./product-variant"
|
||||
import { ShippingProfile } from "./shipping-profile"
|
||||
import _ from "lodash"
|
||||
|
||||
export enum Status {
|
||||
DRAFT = "draft",
|
||||
PROPOSED = "proposed",
|
||||
PUBLISHED = "published",
|
||||
REJECTED = "rejected",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Product {
|
||||
@PrimaryColumn()
|
||||
@@ -40,13 +47,16 @@ export class Product {
|
||||
@Column({ nullable: true })
|
||||
description: string
|
||||
|
||||
@Index({ unique: true, where: "deleted_at IS NOT NULL" })
|
||||
@Index({ unique: true, where: "deleted_at IS NULL" })
|
||||
@Column({ nullable: true })
|
||||
handle: string
|
||||
|
||||
@Column({ default: false })
|
||||
is_giftcard: boolean
|
||||
|
||||
@DbAwareColumn({ type: "enum", enum: Status, default: "draft" })
|
||||
status: Status
|
||||
|
||||
@ManyToMany(() => Image, { cascade: ["insert"] })
|
||||
@JoinTable({
|
||||
name: "product_images",
|
||||
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
PrimaryColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
import { resolveDbType, DbAwareColumn } from "../utils/db-aware-column"
|
||||
@@ -26,6 +29,21 @@ export class ReturnReason {
|
||||
@Column({ nullable: true })
|
||||
description: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
parent_return_reason_id: string
|
||||
|
||||
@ManyToOne(() => ReturnReason, {cascade: ['soft-remove']}
|
||||
)
|
||||
@JoinColumn({ name: "parent_return_reason_id" })
|
||||
parent_return_reason: ReturnReason
|
||||
|
||||
@OneToMany(
|
||||
() => ReturnReason,
|
||||
return_reason => return_reason.parent_return_reason,
|
||||
{ cascade: ["insert", 'soft-remove'] }
|
||||
)
|
||||
return_reason_children: ReturnReason[]
|
||||
|
||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
created_at: Date
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export enum PaymentStatus {
|
||||
NOT_PAID = "not_paid",
|
||||
AWAITING = "awaiting",
|
||||
CAPTURED = "captured",
|
||||
CONFIRMED = "confirmed",
|
||||
CANCELED = "canceled",
|
||||
DIFFERENCE_REFUNDED = "difference_refunded",
|
||||
PARTIALLY_REFUNDED = "partially_refunded",
|
||||
@@ -131,9 +132,15 @@ 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
|
||||
|
||||
@Column({ type: "boolean", default: false })
|
||||
allow_backorder: Boolean
|
||||
|
||||
@DbAwareColumn({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@@ -216,6 +223,9 @@ export class Swap {
|
||||
* cart_id:
|
||||
* description: "The id of the Cart that the Customer will use to confirm the Swap."
|
||||
* type: string
|
||||
* allow_backorder:
|
||||
* description: "If true, swaps can be completed with items out of stock"
|
||||
* type: boolean
|
||||
* confirmed_at:
|
||||
* description: "The date with timezone at which the Swap was confirmed by the Customer."
|
||||
* type: string
|
||||
@@ -228,6 +238,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,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { Note } from "../models/note"
|
||||
|
||||
@EntityRepository(Note)
|
||||
export class NoteRepository extends Repository<Note> {}
|
||||
@@ -19,7 +19,12 @@ export class ProductRepository extends Repository<Product> {
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
const groupedRelations = {}
|
||||
if (entitiesIds.length === 0) {
|
||||
// no need to continue
|
||||
return []
|
||||
}
|
||||
|
||||
const groupedRelations: { [toplevel: string]: string[] } = {}
|
||||
for (const rel of relations) {
|
||||
const [topLevel] = rel.split(".")
|
||||
if (groupedRelations[topLevel]) {
|
||||
@@ -30,17 +35,51 @@ export class ProductRepository extends Repository<Product> {
|
||||
}
|
||||
|
||||
const entitiesIdsWithRelations = await Promise.all(
|
||||
Object.entries(groupedRelations).map(([_, rels]) => {
|
||||
return this.findByIds(entitiesIds, {
|
||||
select: ["id"],
|
||||
relations: rels as string[],
|
||||
})
|
||||
Object.entries(groupedRelations).map(([toplevel, rels]) => {
|
||||
let querybuilder = this.createQueryBuilder("products")
|
||||
|
||||
if (toplevel === "variants") {
|
||||
querybuilder = querybuilder
|
||||
.leftJoinAndSelect(
|
||||
`products.${toplevel}`,
|
||||
toplevel,
|
||||
"variants.deleted_at IS NULL"
|
||||
)
|
||||
.orderBy({
|
||||
"variants.variant_rank": "ASC",
|
||||
})
|
||||
} else {
|
||||
querybuilder = querybuilder.leftJoinAndSelect(
|
||||
`products.${toplevel}`,
|
||||
toplevel
|
||||
)
|
||||
}
|
||||
|
||||
for (const rel of rels) {
|
||||
const [_, rest] = rel.split(".")
|
||||
if (!rest) {
|
||||
continue
|
||||
}
|
||||
// Regex matches all '.' except the rightmost
|
||||
querybuilder = querybuilder.leftJoinAndSelect(
|
||||
rel.replace(/\.(?=[^.]*\.)/g, "__"),
|
||||
rel.replace(".", "__")
|
||||
)
|
||||
}
|
||||
|
||||
return querybuilder
|
||||
.where(
|
||||
"products.deleted_at IS NULL AND products.id IN (:...entitiesIds)",
|
||||
{ entitiesIds }
|
||||
)
|
||||
.getMany()
|
||||
})
|
||||
).then(flatten)
|
||||
|
||||
const entitiesAndRelations = entitiesIdsWithRelations.concat(entities)
|
||||
|
||||
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
|
||||
return map(entitiesAndRelationsById, entityAndRelations =>
|
||||
return map(entitiesAndRelationsById, (entityAndRelations) =>
|
||||
merge({}, ...entityAndRelations)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -90,6 +90,35 @@ const invalidVariant = {
|
||||
],
|
||||
}
|
||||
|
||||
const variantWithPrices = {
|
||||
id: "variant_with_prices",
|
||||
title: "Variant with prices",
|
||||
prices: [
|
||||
{
|
||||
id: "price_1",
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
sale_amount: null,
|
||||
variant_id: "variant_with_prices",
|
||||
region_id: null,
|
||||
created_at: "2021-03-16T21:24:13.657Z",
|
||||
updated_at: "2021-03-16T21:24:13.657Z",
|
||||
deleted_at: null,
|
||||
},
|
||||
{
|
||||
id: "price_2",
|
||||
currency_code: "dk",
|
||||
amount: 100,
|
||||
sale_amount: null,
|
||||
variant_id: "variant_with_prices",
|
||||
region_id: null,
|
||||
created_at: "2021-03-16T21:24:13.657Z",
|
||||
updated_at: "2021-03-16T21:24:13.657Z",
|
||||
deleted_at: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const testVariant = {
|
||||
id: IdMap.getId("testVariant"),
|
||||
title: "test variant",
|
||||
@@ -161,6 +190,7 @@ export const variants = {
|
||||
eur10us12: eur10us12,
|
||||
testVariant: testVariant,
|
||||
giftCard: giftCardVar,
|
||||
variantWithPrices: variantWithPrices,
|
||||
}
|
||||
|
||||
export const ProductVariantServiceMock = {
|
||||
@@ -193,6 +223,9 @@ export const ProductVariantServiceMock = {
|
||||
if (variantId === "4") {
|
||||
return Promise.resolve(variant4)
|
||||
}
|
||||
if (variantId === "variant_with_prices") {
|
||||
return Promise.resolve(variantWithPrices)
|
||||
}
|
||||
if (variantId === IdMap.getId("validId")) {
|
||||
return Promise.resolve(variant5)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,40 @@ export const products = {
|
||||
},
|
||||
],
|
||||
},
|
||||
variantsWithPrices: {
|
||||
id: IdMap.getId("variantsWithPrices"),
|
||||
title: "Variant with prices",
|
||||
variants: [
|
||||
{
|
||||
id: IdMap.getId("variant_with_prices"),
|
||||
title: "Variant with prices",
|
||||
prices: [
|
||||
{
|
||||
id: "price_1",
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
sale_amount: null,
|
||||
variant_id: "variant_with_prices",
|
||||
region_id: null,
|
||||
created_at: "2021-03-16T21:24:13.657Z",
|
||||
updated_at: "2021-03-16T21:24:13.657Z",
|
||||
deleted_at: null,
|
||||
},
|
||||
{
|
||||
id: "price_2",
|
||||
currency_code: "dk",
|
||||
amount: 100,
|
||||
sale_amount: null,
|
||||
variant_id: "variant_with_prices",
|
||||
region_id: null,
|
||||
created_at: "2021-03-16T21:24:13.657Z",
|
||||
updated_at: "2021-03-16T21:24:13.657Z",
|
||||
deleted_at: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const ProductServiceMock = {
|
||||
@@ -36,7 +70,9 @@ export const ProductServiceMock = {
|
||||
if (data.title === "Test Product") {
|
||||
return Promise.resolve(products.product1)
|
||||
}
|
||||
|
||||
if (data.title === "Test Product with variants") {
|
||||
return Promise.resolve(products.productWithOptions)
|
||||
}
|
||||
return Promise.resolve({ ...data })
|
||||
}),
|
||||
count: jest.fn().mockReturnValue(4),
|
||||
@@ -91,6 +127,9 @@ export const ProductServiceMock = {
|
||||
if (productId === IdMap.getId("productWithOptions")) {
|
||||
return Promise.resolve(products.productWithOptions)
|
||||
}
|
||||
if (productId === IdMap.getId("variantsWithPrices")) {
|
||||
return Promise.resolve(products.variantsWithPrices)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
update: jest.fn().mockImplementation((product, data) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -202,11 +202,23 @@ describe("CartService", () => {
|
||||
|
||||
const addressRepository = MockRepository({ create: c => c })
|
||||
const cartRepository = MockRepository()
|
||||
const customerService = {
|
||||
retrieveByEmail: jest.fn().mockReturnValue(
|
||||
Promise.resolve({
|
||||
id: IdMap.getId("customer"),
|
||||
email: "email@test.com",
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
const cartService = new CartService({
|
||||
manager: MockManager,
|
||||
addressRepository,
|
||||
totalsService,
|
||||
cartRepository,
|
||||
customerService,
|
||||
regionService,
|
||||
eventBusService,
|
||||
})
|
||||
@@ -218,6 +230,7 @@ describe("CartService", () => {
|
||||
it("successfully creates a cart", async () => {
|
||||
await cartService.create({
|
||||
region_id: IdMap.getId("testRegion"),
|
||||
email: "email@test.com",
|
||||
})
|
||||
|
||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
@@ -237,6 +250,9 @@ describe("CartService", () => {
|
||||
shipping_address: {
|
||||
country_code: "us",
|
||||
},
|
||||
customer_id: IdMap.getId("customer"),
|
||||
email: "email@test.com",
|
||||
customer: expect.any(Object),
|
||||
})
|
||||
|
||||
expect(cartRepository.save).toHaveBeenCalledTimes(1)
|
||||
@@ -734,11 +750,17 @@ describe("CartService", () => {
|
||||
if (email === "no@mail.com") {
|
||||
return Promise.reject()
|
||||
}
|
||||
return Promise.resolve({ id: IdMap.getId("existing") })
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("existing"),
|
||||
email,
|
||||
})
|
||||
}),
|
||||
create: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ id: IdMap.getId("newCus") })),
|
||||
create: jest.fn().mockImplementation(data =>
|
||||
Promise.resolve({
|
||||
id: IdMap.getId("newCus"),
|
||||
email: data.email,
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
@@ -774,6 +796,7 @@ describe("CartService", () => {
|
||||
customer_id: IdMap.getId("existing"),
|
||||
customer: {
|
||||
id: IdMap.getId("existing"),
|
||||
email: "test@testdom.com",
|
||||
},
|
||||
email: "test@testdom.com",
|
||||
discount_total: 0,
|
||||
@@ -798,7 +821,7 @@ describe("CartService", () => {
|
||||
expect(cartRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(cartRepository.save).toHaveBeenCalledWith({
|
||||
customer_id: IdMap.getId("newCus"),
|
||||
customer: { id: IdMap.getId("newCus") },
|
||||
customer: { id: IdMap.getId("newCus"), email: "no@mail.com" },
|
||||
email: "no@mail.com",
|
||||
discount_total: 0,
|
||||
shipping_total: 0,
|
||||
@@ -1060,6 +1083,9 @@ describe("CartService", () => {
|
||||
id: "region",
|
||||
countries: [{ iso_2: "us" }],
|
||||
},
|
||||
shipping_address: {
|
||||
country_code: "us",
|
||||
},
|
||||
items: [IdMap.getId("testitem"), null],
|
||||
payment_session: null,
|
||||
payment_sessions: [],
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,4 @@
|
||||
import { IdMap, MockManager, MockRepository } from "medusa-test-utils"
|
||||
import { add } from "winston"
|
||||
import CustomerService from "../customer"
|
||||
|
||||
const eventBusService = {
|
||||
|
||||
@@ -494,9 +494,6 @@ describe("DiscountService", () => {
|
||||
|
||||
expect(discountRepository.findAndCount).toHaveBeenCalledTimes(1)
|
||||
expect(discountRepository.findAndCount).toHaveBeenCalledWith({
|
||||
join: {
|
||||
alias: "discount",
|
||||
},
|
||||
where: expect.anything(),
|
||||
skip: 0,
|
||||
take: 50,
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,202 @@
|
||||
import NoteService from "../note"
|
||||
import { MockManager, MockRepository, IdMap } from "medusa-test-utils"
|
||||
import { EventBusServiceMock } from "../__mocks__/event-bus"
|
||||
|
||||
describe("NoteService", () => {
|
||||
describe("list", () => {
|
||||
const noteRepo = MockRepository({
|
||||
find: q => {
|
||||
return Promise.resolve([
|
||||
{ id: IdMap.getId("note"), value: "some note" },
|
||||
])
|
||||
},
|
||||
})
|
||||
|
||||
const noteService = new NoteService({
|
||||
manager: MockManager,
|
||||
noteRepository: noteRepo,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls note model functions", async () => {
|
||||
await noteService.list(
|
||||
{ resource_id: IdMap.getId("note") },
|
||||
{
|
||||
relations: ["author"],
|
||||
}
|
||||
)
|
||||
expect(noteRepo.find).toHaveBeenCalledTimes(1)
|
||||
expect(noteRepo.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
resource_id: IdMap.getId("note"),
|
||||
},
|
||||
relations: ["author"],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const noteRepo = MockRepository({
|
||||
findOne: q => {
|
||||
switch (q.where.id) {
|
||||
case IdMap.getId("note"):
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("note"),
|
||||
value: "some note",
|
||||
})
|
||||
default:
|
||||
return Promise.resolve()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const noteService = new NoteService({
|
||||
manager: MockManager,
|
||||
noteRepository: noteRepo,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls note model functions", async () => {
|
||||
await noteService.retrieve(IdMap.getId("note"), { relations: ["author"] })
|
||||
|
||||
expect(noteRepo.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(noteRepo.findOne).toHaveBeenCalledWith({
|
||||
where: { id: IdMap.getId("note") },
|
||||
relations: ["author"],
|
||||
})
|
||||
})
|
||||
|
||||
it("fails when note is not found", async () => {
|
||||
await expect(
|
||||
noteService.retrieve(IdMap.getId("not-existing"))
|
||||
).rejects.toThrow(
|
||||
`Note with id: ${IdMap.getId("not-existing")} was not found.`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
const note = {
|
||||
id: IdMap.getId("note"),
|
||||
author_id: IdMap.getId("user"),
|
||||
}
|
||||
|
||||
const noteRepo = MockRepository({
|
||||
create: f => Promise.resolve(note),
|
||||
save: f => Promise.resolve(note),
|
||||
})
|
||||
|
||||
const noteService = new NoteService({
|
||||
manager: MockManager,
|
||||
noteRepository: noteRepo,
|
||||
eventBusService: EventBusServiceMock,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls note model functions", async () => {
|
||||
await noteService.create({
|
||||
resource_id: IdMap.getId("resource-id"),
|
||||
resource_type: "type",
|
||||
value: "my note",
|
||||
author_id: IdMap.getId("user"),
|
||||
})
|
||||
|
||||
expect(noteRepo.create).toHaveBeenCalledTimes(1)
|
||||
expect(noteRepo.create).toHaveBeenCalledWith({
|
||||
resource_id: IdMap.getId("resource-id"),
|
||||
resource_type: "type",
|
||||
value: "my note",
|
||||
author_id: IdMap.getId("user"),
|
||||
metadata: {},
|
||||
})
|
||||
|
||||
expect(noteRepo.save).toHaveBeenCalledTimes(1)
|
||||
expect(noteRepo.save).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("note"),
|
||||
author_id: IdMap.getId("user"),
|
||||
})
|
||||
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||
NoteService.Events.CREATED,
|
||||
{ id: IdMap.getId("note") }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update", () => {
|
||||
const note = { id: IdMap.getId("note") }
|
||||
|
||||
const noteRepo = MockRepository({
|
||||
findOne: f => Promise.resolve(note),
|
||||
save: f => Promise.resolve(note),
|
||||
})
|
||||
|
||||
const noteService = new NoteService({
|
||||
manager: MockManager,
|
||||
noteRepository: noteRepo,
|
||||
eventBusService: EventBusServiceMock,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls note model functions", async () => {
|
||||
await noteService.update(IdMap.getId("note"), "new note")
|
||||
|
||||
expect(noteRepo.save).toHaveBeenCalledTimes(1)
|
||||
expect(noteRepo.save).toHaveBeenCalledWith({
|
||||
...note,
|
||||
value: "new note",
|
||||
})
|
||||
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||
NoteService.Events.UPDATED,
|
||||
{ id: IdMap.getId("note") }
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("delete", () => {
|
||||
const note = { id: IdMap.getId("note") }
|
||||
|
||||
const noteRepo = MockRepository({
|
||||
softRemove: f => Promise.resolve(),
|
||||
findOne: f => Promise.resolve(note),
|
||||
})
|
||||
|
||||
const noteService = new NoteService({
|
||||
manager: MockManager,
|
||||
noteRepository: noteRepo,
|
||||
eventBusService: EventBusServiceMock,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls note model functions", async () => {
|
||||
await noteService.delete(IdMap.getId("note"))
|
||||
|
||||
expect(noteRepo.softRemove).toHaveBeenCalledTimes(1)
|
||||
expect(noteRepo.softRemove).toHaveBeenCalledWith(note)
|
||||
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1)
|
||||
expect(EventBusServiceMock.emit).toHaveBeenCalledWith(
|
||||
NoteService.Events.DELETED,
|
||||
{ id: IdMap.getId("note") }
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -203,6 +203,7 @@ describe("OrderService", () => {
|
||||
}
|
||||
|
||||
orderService.cartService_.retrieve = jest.fn(() => Promise.resolve(cart))
|
||||
orderService.cartService_.update = jest.fn(() => Promise.resolve())
|
||||
|
||||
await orderService.createFromCart("cart_id")
|
||||
const order = {
|
||||
@@ -305,6 +306,7 @@ describe("OrderService", () => {
|
||||
orderService.cartService_.retrieve = () => {
|
||||
return Promise.resolve(cart)
|
||||
}
|
||||
orderService.cartService_.update = () => Promise.resolve()
|
||||
|
||||
await orderService.createFromCart("cart_id")
|
||||
const order = {
|
||||
@@ -453,6 +455,7 @@ describe("OrderService", () => {
|
||||
total: 100,
|
||||
}
|
||||
orderService.cartService_.retrieve = () => Promise.resolve(cart)
|
||||
orderService.cartService_.update = () => Promise.resolve()
|
||||
const res = orderService.createFromCart(cart)
|
||||
await expect(res).rejects.toThrow(
|
||||
"Variant with id: variant-1 does not have the required inventory"
|
||||
@@ -524,6 +527,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 +600,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 +625,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 +680,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 +695,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 +711,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 +722,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 +795,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 +839,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 +976,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 +1012,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 +1084,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 +1156,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 +1363,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 +1438,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 +1479,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 +1557,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],
|
||||
|
||||
@@ -148,6 +148,7 @@ describe("ProductVariantService", () => {
|
||||
expect(productVariantRepository.create).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("v2"),
|
||||
product_id: IdMap.getId("ironman"),
|
||||
variant_rank: 1,
|
||||
options: [
|
||||
{
|
||||
id: IdMap.getId("test"),
|
||||
|
||||
Loaded 100 of 271 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user