merge develop
This commit is contained in:
1 parent
10c87e8d5a
commit
4fd361fddd
207 files changed
+8699
-4124
No files matched your search
@@ -0,0 +1,17 @@
|
||||
import _ from "lodash"
|
||||
|
||||
/**
|
||||
* @oas [get] /auth
|
||||
* operationId: "DeleteAuth"
|
||||
* summary: "Delete Session"
|
||||
* description: "Deletes the current session for the logged in user."
|
||||
* tags:
|
||||
* - Auth
|
||||
* responses:
|
||||
* "200":
|
||||
* description: OK
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
req.session.destroy()
|
||||
res.status(200).end()
|
||||
}
|
||||
@@ -13,5 +13,11 @@ export default app => {
|
||||
)
|
||||
route.post("/", middlewares.wrap(require("./create-session").default))
|
||||
|
||||
route.delete(
|
||||
"/",
|
||||
middlewares.authenticate(),
|
||||
middlewares.wrap(require("./delete-session").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
],
|
||||
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
],
|
||||
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ describe("POST /admin/discounts", () => {
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
ends_at: "03/14/2021 04:30",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
@@ -39,12 +41,99 @@ describe("POST /admin/discounts", () => {
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("02/02/2021 13:45"),
|
||||
ends_at: new Date("03/14/2021 04:30"),
|
||||
is_disabled: false,
|
||||
is_dynamic: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("unsuccessful creation with dynamic discount using an invalid iso8601 duration", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "PaMT2D",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"valid_duration" must be a valid ISO 8601 duration`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successful creation with dynamic discount", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service create", () => {
|
||||
expect(DiscountServiceMock.create).toHaveBeenCalledTimes(1)
|
||||
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("02/02/2021 13:45"),
|
||||
is_disabled: false,
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails on invalid data", () => {
|
||||
let subject
|
||||
|
||||
@@ -74,4 +163,84 @@ describe("POST /admin/discounts", () => {
|
||||
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails on invalid date intervals", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
ends_at: "02/02/2021",
|
||||
starts_at: "03/14/2021",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"ends_at" must be greater than "ref:starts_at"`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("succesfully creates a dynamic discount without setting valid duration", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request("POST", "/admin/discounts", {
|
||||
payload: {
|
||||
code: "TEST",
|
||||
is_dynamic: true,
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "03/14/2021 14:30",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
|
||||
code: "TEST",
|
||||
is_dynamic: true,
|
||||
is_disabled: false,
|
||||
rule: {
|
||||
description: "Test",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("03/14/2021 14:30"),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -17,6 +17,7 @@ const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
const defaultRelations = [
|
||||
|
||||
@@ -17,6 +17,7 @@ const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
const defaultRelations = [
|
||||
|
||||
@@ -17,6 +17,7 @@ const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
const defaultRelations = [
|
||||
|
||||
@@ -7,6 +7,7 @@ describe("POST /admin/discounts", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
@@ -50,4 +51,139 @@ describe("POST /admin/discounts", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("unsuccessful update with dynamic discount using an invalid iso8601 duration", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
{
|
||||
payload: {
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "PaMT2D",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"valid_duration" must be a valid ISO 8601 duration`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successful update with dynamic discount", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
{
|
||||
payload: {
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: "02/02/2021 13:45",
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service update", () => {
|
||||
expect(DiscountServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(DiscountServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("total10"),
|
||||
{
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
starts_at: new Date("02/02/2021 13:45"),
|
||||
is_dynamic: true,
|
||||
valid_duration: "P1Y2M03DT04H05M",
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fails on invalid date intervals", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/discounts/${IdMap.getId("total10")}`,
|
||||
{
|
||||
payload: {
|
||||
code: "10TOTALOFF",
|
||||
rule: {
|
||||
id: "1234",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
},
|
||||
ends_at: "02/02/2021",
|
||||
starts_at: "03/14/2021",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error", () => {
|
||||
expect(subject.body.message[0].message).toEqual(
|
||||
`"ends_at" must be greater than "ref:starts_at"`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultRelations } from "."
|
||||
|
||||
/**
|
||||
* @oas [post] /discounts
|
||||
@@ -71,7 +72,13 @@ export default async (req, res) => {
|
||||
.required(),
|
||||
is_disabled: Validator.boolean().default(false),
|
||||
starts_at: Validator.date().optional(),
|
||||
ends_at: Validator.date().optional(),
|
||||
ends_at: Validator.date()
|
||||
.greater(Validator.ref("starts_at"))
|
||||
.optional(),
|
||||
valid_duration: Validator.string()
|
||||
.isoDuration()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
usage_limit: Validator.number()
|
||||
.positive()
|
||||
.optional(),
|
||||
@@ -90,11 +97,10 @@ export default async (req, res) => {
|
||||
const discountService = req.scope.resolve("discountService")
|
||||
|
||||
const created = await discountService.create(value)
|
||||
const discount = await discountService.retrieve(created.id, [
|
||||
"rule",
|
||||
"rule.valid_for",
|
||||
"regions",
|
||||
])
|
||||
const discount = await discountService.retrieve(
|
||||
created.id,
|
||||
defaultRelations
|
||||
)
|
||||
|
||||
res.status(200).json({ discount })
|
||||
} catch (err) {
|
||||
|
||||
@@ -37,9 +37,9 @@ export default async (req, res) => {
|
||||
|
||||
try {
|
||||
const discountService = req.scope.resolve("discountService")
|
||||
await discountService.createDynamicCode(discount_id, value)
|
||||
const created = await discountService.createDynamicCode(discount_id, value)
|
||||
|
||||
const discount = await discountService.retrieve(discount_id, {
|
||||
const discount = await discountService.retrieve(created.id, {
|
||||
relations: ["rule", "rule.valid_for", "regions"],
|
||||
})
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ export const defaultFields = [
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
"valid_duration",
|
||||
]
|
||||
|
||||
export const defaultRelations = [
|
||||
|
||||
@@ -68,7 +68,16 @@ export default async (req, res) => {
|
||||
.optional(),
|
||||
is_disabled: Validator.boolean().optional(),
|
||||
starts_at: Validator.date().optional(),
|
||||
ends_at: Validator.date().optional(),
|
||||
ends_at: Validator.when("starts_at", {
|
||||
not: undefined,
|
||||
then: Validator.date()
|
||||
.greater(Validator.ref("starts_at"))
|
||||
.optional(),
|
||||
otherwise: Validator.date().optional(),
|
||||
}),
|
||||
valid_duration: Validator.string()
|
||||
.isoDuration().allow(null)
|
||||
.optional(),
|
||||
usage_limit: Validator.number()
|
||||
.positive()
|
||||
.optional(),
|
||||
@@ -78,6 +87,7 @@ export default async (req, res) => {
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export const defaultCartRelations = [
|
||||
"payment_sessions",
|
||||
"shipping_methods.shipping_option",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
]
|
||||
|
||||
export const defaultCartFields = [
|
||||
|
||||
@@ -43,7 +43,14 @@ export default async (req, res) => {
|
||||
.withTransaction(manager)
|
||||
.retrieve(draftOrder.cart_id, {
|
||||
select: ["total"],
|
||||
relations: ["discounts", "shipping_methods", "region", "items"],
|
||||
relations: [
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"region",
|
||||
"items",
|
||||
],
|
||||
})
|
||||
|
||||
await paymentProviderService
|
||||
|
||||
@@ -7,6 +7,8 @@ const defaultRelations = [
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
@@ -25,6 +27,7 @@ const defaultRelations = [
|
||||
"claims.additional_items",
|
||||
"claims.fulfillments",
|
||||
"claims.claim_items",
|
||||
"claims.claim_items.item",
|
||||
"claims.claim_items.images",
|
||||
"swaps",
|
||||
"swaps.return_order",
|
||||
@@ -54,6 +57,7 @@ const defaultFields = [
|
||||
"metadata",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
|
||||
@@ -202,7 +202,12 @@ export default async (req, res) => {
|
||||
const order = await orderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(id, {
|
||||
relations: ["items", "discounts"],
|
||||
relations: [
|
||||
"items",
|
||||
"cart",
|
||||
"cart.discounts",
|
||||
"cart.discounts.rule",
|
||||
],
|
||||
})
|
||||
|
||||
await claimService.withTransaction(manager).create({
|
||||
|
||||
@@ -45,6 +45,17 @@ import { defaultFields, defaultRelations } from "./"
|
||||
* quantity:
|
||||
* description: The quantity of the Product Variant to ship.
|
||||
* type: integer
|
||||
* custom_shipping_options:
|
||||
* description: The custom shipping options to potentially create a Shipping Method from.
|
||||
* type: array
|
||||
* items:
|
||||
* properties:
|
||||
* option_id:
|
||||
* description: The id of the Shipping Option to override with a custom price.
|
||||
* type: string
|
||||
* price:
|
||||
* description: The custom price of the Shipping Option.
|
||||
* type: integer
|
||||
* no_notification:
|
||||
* description: If set to true no notification will be send related to this Swap.
|
||||
* type: boolean
|
||||
@@ -85,6 +96,12 @@ export default async (req, res) => {
|
||||
variant_id: Validator.string().required(),
|
||||
quantity: Validator.number().required(),
|
||||
}),
|
||||
custom_shipping_options: Validator.array()
|
||||
.items({
|
||||
option_id: Validator.string().required(),
|
||||
price: Validator.number().required(),
|
||||
})
|
||||
.default([]),
|
||||
no_notification: Validator.boolean().optional(),
|
||||
allow_backorder: Validator.boolean().default(true),
|
||||
})
|
||||
@@ -149,7 +166,9 @@ export default async (req, res) => {
|
||||
}
|
||||
)
|
||||
|
||||
await swapService.withTransaction(manager).createCart(swap.id)
|
||||
await swapService
|
||||
.withTransaction(manager)
|
||||
.createCart(swap.id, value.custom_shipping_options)
|
||||
const returnOrder = await returnService
|
||||
.withTransaction(manager)
|
||||
.retrieveBySwap(swap.id)
|
||||
|
||||
@@ -221,6 +221,8 @@ export const defaultRelations = [
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
@@ -239,6 +241,7 @@ export const defaultRelations = [
|
||||
"claims.additional_items",
|
||||
"claims.fulfillments",
|
||||
"claims.claim_items",
|
||||
"claims.claim_items.item",
|
||||
"claims.claim_items.images",
|
||||
// "claims.claim_items.tags",
|
||||
"swaps",
|
||||
@@ -269,6 +272,7 @@ export const defaultFields = [
|
||||
"metadata",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
@@ -316,6 +320,8 @@ export const allowedRelations = [
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
"payments",
|
||||
"fulfillments",
|
||||
|
||||
@@ -46,7 +46,11 @@ export default app => {
|
||||
)
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-product").default))
|
||||
route.get("/", middlewares.wrap(require("./list-products").default))
|
||||
route.get(
|
||||
"/",
|
||||
middlewares.normalizeQuery(),
|
||||
middlewares.wrap(require("./list-products").default)
|
||||
)
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -121,3 +125,18 @@ export const allowedRelations = [
|
||||
"type",
|
||||
"collection",
|
||||
]
|
||||
|
||||
export const filterableFields = [
|
||||
"id",
|
||||
"status",
|
||||
"collection_id",
|
||||
"tags",
|
||||
"title",
|
||||
"description",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"type",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from "lodash"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
import { defaultFields, defaultRelations, filterableFields } from "./"
|
||||
|
||||
/**
|
||||
* @oas [get] /products
|
||||
@@ -31,6 +31,17 @@ import { defaultFields, defaultRelations } from "./"
|
||||
* $ref: "#/components/schemas/product"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.productFilter()
|
||||
|
||||
const { value, error } = schema.validate(req.query)
|
||||
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
JSON.stringify(error.details)
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
|
||||
@@ -53,21 +64,16 @@ export default async (req, res) => {
|
||||
expandFields = req.query.expand.split(",")
|
||||
}
|
||||
|
||||
if ("is_giftcard" in req.query) {
|
||||
selector.is_giftcard = req.query.is_giftcard === "true"
|
||||
for (const k of filterableFields) {
|
||||
if (k in value) {
|
||||
selector[k] = value[k]
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if (selector.status?.indexOf("null") > -1) {
|
||||
selector.status.splice(selector.status.indexOf("null"), 1)
|
||||
if (selector.status.length === 0) {
|
||||
delete selector.status
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShipping", () => {
|
||||
it("calls CartService addShippingMethod", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("fr-cart"),
|
||||
@@ -45,6 +45,50 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("successfully adds a shipping method", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const cartId = IdMap.getId("swap-cart")
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/store/carts/${cartId}/shipping-methods`,
|
||||
{
|
||||
payload: {
|
||||
option_id: IdMap.getId("freeShipping"),
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShippingMethod", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("swap-cart"),
|
||||
IdMap.getId("freeShipping"),
|
||||
{}
|
||||
)
|
||||
})
|
||||
|
||||
it("calls CartService retrieve", () => {
|
||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns the cart", () => {
|
||||
expect(subject.body.cart).toEqual(
|
||||
expect.objectContaining({ type: "swap", id: IdMap.getId("test-swap") })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successfully adds a shipping method with additional data", () => {
|
||||
let subject
|
||||
|
||||
@@ -68,7 +112,7 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShipping", () => {
|
||||
it("calls CartService addShippingMethod", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("fr-cart"),
|
||||
|
||||
@@ -44,7 +44,9 @@ export default async (req, res) => {
|
||||
|
||||
await manager.transaction(async m => {
|
||||
const txCartService = cartService.withTransaction(m)
|
||||
|
||||
await txCartService.addShippingMethod(id, value.option_id, value.data)
|
||||
|
||||
const updated = await txCartService.retrieve(id, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -54,12 +56,12 @@ export default async (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
const cart = await cartService.retrieve(id, {
|
||||
const updatedCart = await cartService.retrieve(id, {
|
||||
select: defaultFields,
|
||||
relations: defaultRelations,
|
||||
})
|
||||
|
||||
res.status(200).json({ cart })
|
||||
res.status(200).json({ cart: updatedCart })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
@@ -82,6 +82,14 @@ export default async (req, res) => {
|
||||
if (!value.region_id) {
|
||||
const regionService = req.scope.resolve("regionService")
|
||||
const regions = await regionService.withTransaction(manager).list({})
|
||||
|
||||
if (!regions?.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`A region is required to create a cart`
|
||||
)
|
||||
}
|
||||
|
||||
regionId = regions[0].id
|
||||
}
|
||||
|
||||
|
||||
@@ -116,4 +116,6 @@ export const defaultRelations = [
|
||||
"payment_sessions",
|
||||
"shipping_methods.shipping_option",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
]
|
||||
@@ -50,7 +50,7 @@ export default async (req, res) => {
|
||||
const id = req.user.customer_id
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
billing_address: Validator.address().optional(),
|
||||
billing_address: Validator.address().optional().allow(null),
|
||||
first_name: Validator.string().optional(),
|
||||
last_name: Validator.string().optional(),
|
||||
password: Validator.string().optional(),
|
||||
@@ -66,9 +66,9 @@ export default async (req, res) => {
|
||||
|
||||
try {
|
||||
const customerService = req.scope.resolve("customerService")
|
||||
let customer = await customerService.update(id, value)
|
||||
await customerService.update(id, value)
|
||||
|
||||
customer = await customerService.retrieve(customer.id, {
|
||||
const customer = await customerService.retrieve(id, {
|
||||
relations: defaultRelations,
|
||||
select: defaultFields,
|
||||
})
|
||||
|
||||
@@ -36,6 +36,8 @@ export const defaultRelations = [
|
||||
"items.variant.product",
|
||||
"shipping_methods",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"customer",
|
||||
"payments",
|
||||
"region",
|
||||
@@ -74,6 +76,8 @@ export const allowedRelations = [
|
||||
"items.variant.product",
|
||||
"shipping_methods",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"customer",
|
||||
"payments",
|
||||
"region",
|
||||
|
||||
@@ -108,7 +108,7 @@ export default async (req, res) => {
|
||||
case "started": {
|
||||
const { key, error } = await idempotencyKeyService.workStage(
|
||||
idempotencyKey.idempotency_key,
|
||||
async (manager) => {
|
||||
async manager => {
|
||||
const order = await orderService
|
||||
.withTransaction(manager)
|
||||
.retrieve(value.order_id, {
|
||||
@@ -163,7 +163,7 @@ export default async (req, res) => {
|
||||
case "swap_created": {
|
||||
const { key, error } = await idempotencyKeyService.workStage(
|
||||
idempotencyKey.idempotency_key,
|
||||
async (manager) => {
|
||||
async manager => {
|
||||
const swaps = await swapService.list({
|
||||
idempotency_key: idempotencyKey.idempotency_key,
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ describe("Get variant by id", () => {
|
||||
it("calls get variant from variantSerice", () => {
|
||||
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", {
|
||||
relations: ["prices"],
|
||||
relations: ["prices", "options"],
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -12,4 +12,4 @@ export default app => {
|
||||
return app
|
||||
}
|
||||
|
||||
export const defaultRelations = ["prices"]
|
||||
export const defaultRelations = ["prices", "options"]
|
||||
@@ -46,3 +46,4 @@ export { User } from "./models/user"
|
||||
export { DraftOrder } from "./models/draft-order"
|
||||
export { ReturnReason } from "./models/return-reason"
|
||||
export { Note } from "./models/note"
|
||||
export { CustomShippingOption } from "./models/custom-shipping-option"
|
||||
@@ -7,8 +7,8 @@ async function loadProductsIntoSearchEngine(container) {
|
||||
|
||||
const TAKE = 20
|
||||
const totalCount = await productService.count()
|
||||
let iterCount = 0,
|
||||
lastSeenId = ""
|
||||
let iterCount = 0
|
||||
let lastSeenId = ""
|
||||
|
||||
while (iterCount < totalCount) {
|
||||
const products = await productService.list(
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class validDurationForDiscount1631696624528 implements MigrationInterface {
|
||||
name = 'validDurationForDiscount1631696624528'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "discount" ADD "valid_duration" character varying`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "discount" DROP COLUMN "valid_duration"`);
|
||||
}
|
||||
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class deleteDateOnShippingOptionRequirements1632828114899
|
||||
implements MigrationInterface {
|
||||
name = "deleteDateOnShippingOptionRequirements1632828114899"
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_option_requirement" ADD "deleted_at" TIMESTAMP WITH TIME ZONE`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "shipping_option_requirement" DROP COLUMN "deleted_at"`
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class addCustomShippingOptions1633614437919 implements MigrationInterface {
|
||||
name = 'addCustomShippingOptions1633614437919'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "custom_shipping_option" ("id" character varying NOT NULL, "price" integer NOT NULL, "shipping_option_id" character varying NOT NULL, "cart_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 "UQ_0f838b122a9a01d921aa1cdb669" UNIQUE ("shipping_option_id", "cart_id"), CONSTRAINT "PK_8dfcb5c1172c29eec4a728420cc" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_44090cb11b06174cbcc667e91c" ON "custom_shipping_option" ("shipping_option_id") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_93caeb1bb70d37c1d36d6701a7" ON "custom_shipping_option" ("cart_id") `);
|
||||
await queryRunner.query(`ALTER TYPE "cart_type_enum" RENAME TO "cart_type_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "cart_type_enum" AS ENUM('default', 'swap', 'draft_order', 'payment_link', 'claim')`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" TYPE "cart_type_enum" USING "type"::"text"::"cart_type_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
|
||||
await queryRunner.query(`DROP TYPE "cart_type_enum_old"`);
|
||||
await queryRunner.query(`ALTER TABLE "custom_shipping_option" ADD CONSTRAINT "FK_44090cb11b06174cbcc667e91ca" FOREIGN KEY ("shipping_option_id") REFERENCES "shipping_option"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "custom_shipping_option" ADD CONSTRAINT "FK_93caeb1bb70d37c1d36d6701a7a" FOREIGN KEY ("cart_id") REFERENCES "cart"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "custom_shipping_option" DROP CONSTRAINT "FK_93caeb1bb70d37c1d36d6701a7a"`);
|
||||
await queryRunner.query(`ALTER TABLE "custom_shipping_option" DROP CONSTRAINT "FK_44090cb11b06174cbcc667e91ca"`);
|
||||
await queryRunner.query(`CREATE TYPE "cart_type_enum_old" AS ENUM('default', 'swap', 'draft_order', 'payment_link')`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" TYPE "cart_type_enum_old" USING "type"::"text"::"cart_type_enum_old"`);
|
||||
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
|
||||
await queryRunner.query(`DROP TYPE "cart_type_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "cart_type_enum_old" RENAME TO "cart_type_enum"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_93caeb1bb70d37c1d36d6701a7"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_44090cb11b06174cbcc667e91c"`);
|
||||
await queryRunner.query(`DROP TABLE "custom_shipping_option"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -113,12 +113,14 @@ import { PaymentSession } from "./payment-session"
|
||||
import { Payment } from "./payment"
|
||||
import { GiftCard } from "./gift-card"
|
||||
import { ShippingMethod } from "./shipping-method"
|
||||
import { CustomShippingOption } from "./custom-shipping-option"
|
||||
|
||||
export enum CartType {
|
||||
DEFAULT = "default",
|
||||
SWAP = "swap",
|
||||
DRAFT_ORDER = "draft_order",
|
||||
PAYMENT_LINK = "payment_link",
|
||||
CLAIM = "claim",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
BeforeInsert, Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
Unique,
|
||||
UpdateDateColumn
|
||||
} from "typeorm";
|
||||
import { ulid } from "ulid";
|
||||
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column";
|
||||
import { Cart } from './cart';
|
||||
import { ShippingOption } from "./shipping-option";
|
||||
|
||||
|
||||
@Entity()
|
||||
@Unique(['shipping_option_id', 'cart_id'])
|
||||
export class CustomShippingOption {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Column({ type: "int" })
|
||||
price: number
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
shipping_option_id: string;
|
||||
|
||||
@ManyToOne(() => ShippingOption)
|
||||
@JoinColumn({ name: "shipping_option_id" })
|
||||
shipping_option: ShippingOption
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
cart_id: string
|
||||
|
||||
@ManyToOne(() => Cart)
|
||||
@JoinColumn({ name: "cart_id" })
|
||||
cart: Cart
|
||||
|
||||
@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 = `cso_${id}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @schema Custom shipping_option
|
||||
* title: "Custom Shipping Option"
|
||||
* description: "Custom Shipping Options are 'overriden' Shipping Options. Store managers can attach a Custom Shipping Option to a cart in order to set a custom price for a particular Shipping Option"
|
||||
* x-resourceId: custom_shipping_option
|
||||
* properties:
|
||||
* id:
|
||||
* description: "The id of the Custom Shipping Option. This value will be prefixed with `cso_`."
|
||||
* type: string
|
||||
* price:
|
||||
* description: "The custom price set that will override the shipping option's original price"
|
||||
* type: integer
|
||||
* shipping_option_id:
|
||||
* description: "The id of the Shipping Option that the custom shipping option overrides"
|
||||
* anyOf:
|
||||
* - $ref: "#/components/schemas/shipping_option"
|
||||
* cart_id:
|
||||
* description: "The id of the Cart that the custom shipping option is attached to"
|
||||
* anyOf:
|
||||
* - $ref: "#/components/schemas/cart"
|
||||
* 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
|
||||
*/
|
||||
@@ -46,7 +46,7 @@ export class Customer {
|
||||
)
|
||||
shipping_addresses: Address[]
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, select: false })
|
||||
password_hash: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
|
||||
@@ -35,7 +35,7 @@ export class Discount {
|
||||
@Column({ nullable: true })
|
||||
rule_id: string
|
||||
|
||||
@ManyToOne(() => DiscountRule, { cascade: true, eager: true })
|
||||
@ManyToOne(() => DiscountRule, { cascade: true })
|
||||
@JoinColumn({ name: "rule_id" })
|
||||
rule: DiscountRule
|
||||
|
||||
@@ -44,7 +44,7 @@ export class Discount {
|
||||
|
||||
@Column({ nullable: true })
|
||||
parent_discount_id: string
|
||||
|
||||
|
||||
@ManyToOne(() => Discount)
|
||||
@JoinColumn({ name: "parent_discount_id" })
|
||||
parent_discount: Discount
|
||||
@@ -58,6 +58,9 @@ export class Discount {
|
||||
@Column({ type: resolveDbType("timestamptz"), nullable: true })
|
||||
ends_at: Date
|
||||
|
||||
@Column({ nullable: true })
|
||||
valid_duration: string
|
||||
|
||||
@ManyToMany(() => Region, { cascade: true })
|
||||
@JoinTable({
|
||||
name: "discount_regions",
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
JoinTable,
|
||||
} from "typeorm"
|
||||
import { ulid } from "ulid"
|
||||
import { DbAwareColumn } from "../utils/db-aware-column"
|
||||
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column"
|
||||
|
||||
import { ShippingOption } from "./shipping-option"
|
||||
|
||||
@@ -44,6 +44,9 @@ export class ShippingOptionRequirement {
|
||||
@Column({ type: "int" })
|
||||
amount: number
|
||||
|
||||
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
|
||||
deleted_at: Date
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) return
|
||||
|
||||
@@ -26,7 +26,7 @@ export class User {
|
||||
@Column({ nullable: true })
|
||||
last_name: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, select: false })
|
||||
password_hash: string
|
||||
|
||||
@Column({ nullable: true })
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { CustomShippingOption } from './../models/custom-shipping-option';
|
||||
|
||||
@EntityRepository(CustomShippingOption)
|
||||
export class CustomShippingOptionRepository extends Repository<CustomShippingOption> {}
|
||||
@@ -15,7 +15,27 @@ export class ProductRepository extends Repository<Product> {
|
||||
if (Array.isArray(idsOrOptionsWithoutRelations)) {
|
||||
entities = await this.findByIds(idsOrOptionsWithoutRelations)
|
||||
} else {
|
||||
entities = await this.find(idsOrOptionsWithoutRelations)
|
||||
// Since tags are in a one-to-many realtion they cant be included in a
|
||||
// regular query, to solve this add the join on tags seperately if
|
||||
// the query exists
|
||||
const tags = idsOrOptionsWithoutRelations.where.tags
|
||||
delete idsOrOptionsWithoutRelations.where.tags
|
||||
let qb = this.createQueryBuilder("product")
|
||||
.select(["product.id"])
|
||||
.where(idsOrOptionsWithoutRelations.where)
|
||||
.skip(idsOrOptionsWithoutRelations.skip)
|
||||
.take(idsOrOptionsWithoutRelations.take)
|
||||
|
||||
if (tags) {
|
||||
qb = qb
|
||||
.leftJoinAndSelect("product.tags", "tags")
|
||||
.andWhere(
|
||||
`tags.id IN (:...ids)`, { ids: tags._value}
|
||||
)
|
||||
}
|
||||
|
||||
entities = await qb
|
||||
.getMany()
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
|
||||
@@ -6,29 +6,29 @@ import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
describe("CartService", () => {
|
||||
const totalsService = {
|
||||
getTotal: o => {
|
||||
getTotal: (o) => {
|
||||
return o.total || 0
|
||||
},
|
||||
getSubtotal: o => {
|
||||
getSubtotal: (o) => {
|
||||
return o.subtotal || 0
|
||||
},
|
||||
getTaxTotal: o => {
|
||||
getTaxTotal: (o) => {
|
||||
return o.tax_total || 0
|
||||
},
|
||||
getDiscountTotal: o => {
|
||||
getDiscountTotal: (o) => {
|
||||
return o.discount_total || 0
|
||||
},
|
||||
getShippingTotal: o => {
|
||||
getShippingTotal: (o) => {
|
||||
return o.shipping_total || 0
|
||||
},
|
||||
getGiftCardTotal: o => {
|
||||
getGiftCardTotal: (o) => {
|
||||
return o.gift_card_total || 0
|
||||
},
|
||||
}
|
||||
@@ -117,7 +117,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("deleteMetadata", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOne: id => {
|
||||
findOne: (id) => {
|
||||
if (id === "empty") {
|
||||
return Promise.resolve({
|
||||
metadata: {},
|
||||
@@ -200,7 +200,7 @@ describe("CartService", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const addressRepository = MockRepository({ create: c => c })
|
||||
const addressRepository = MockRepository({ create: (c) => c })
|
||||
const cartRepository = MockRepository()
|
||||
const customerService = {
|
||||
retrieveByEmail: jest.fn().mockReturnValue(
|
||||
@@ -209,7 +209,7 @@ describe("CartService", () => {
|
||||
email: "email@test.com",
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -317,14 +317,14 @@ describe("CartService", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
create: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const shippingOptionService = {
|
||||
deleteShippingMethod: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -505,7 +505,7 @@ describe("CartService", () => {
|
||||
const lineItemService = {
|
||||
delete: jest.fn(),
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -547,7 +547,7 @@ describe("CartService", () => {
|
||||
|
||||
const shippingOptionService = {
|
||||
deleteShippingMethod: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -647,12 +647,13 @@ describe("CartService", () => {
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"gift_cards",
|
||||
"discounts",
|
||||
"customer",
|
||||
"region",
|
||||
"payment_sessions",
|
||||
"region.countries",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"discounts.regions",
|
||||
],
|
||||
{
|
||||
@@ -665,7 +666,7 @@ describe("CartService", () => {
|
||||
describe("updateLineItem", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -673,7 +674,7 @@ describe("CartService", () => {
|
||||
...InventoryServiceMock,
|
||||
confirmInventory: jest
|
||||
.fn()
|
||||
.mockImplementation(id => id !== IdMap.getId("cannot-cover")),
|
||||
.mockImplementation((id) => id !== IdMap.getId("cannot-cover")),
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
@@ -746,7 +747,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("updateEmail", () => {
|
||||
const customerService = {
|
||||
retrieveByEmail: jest.fn().mockImplementation(email => {
|
||||
retrieveByEmail: jest.fn().mockImplementation((email) => {
|
||||
if (email === "no@mail.com") {
|
||||
return Promise.reject()
|
||||
}
|
||||
@@ -755,13 +756,13 @@ describe("CartService", () => {
|
||||
email,
|
||||
})
|
||||
}),
|
||||
create: jest.fn().mockImplementation(data =>
|
||||
create: jest.fn().mockImplementation((data) =>
|
||||
Promise.resolve({
|
||||
id: IdMap.getId("newCus"),
|
||||
email: data.email,
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -846,7 +847,7 @@ describe("CartService", () => {
|
||||
}),
|
||||
})
|
||||
|
||||
const addressRepository = MockRepository({ create: c => c })
|
||||
const addressRepository = MockRepository({ create: (c) => c })
|
||||
|
||||
const cartService = new CartService({
|
||||
manager: MockManager,
|
||||
@@ -907,7 +908,7 @@ describe("CartService", () => {
|
||||
region: { countries: [{ iso_2: "us" }] },
|
||||
}),
|
||||
})
|
||||
const addressRepository = MockRepository({ create: c => c })
|
||||
const addressRepository = MockRepository({ create: (c) => c })
|
||||
|
||||
const cartService = new CartService({
|
||||
manager: MockManager,
|
||||
@@ -981,15 +982,15 @@ describe("CartService", () => {
|
||||
|
||||
describe("setRegion", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(r => r),
|
||||
update: jest.fn((r) => r),
|
||||
delete: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
const addressRepository = MockRepository({ create: c => c })
|
||||
const addressRepository = MockRepository({ create: (c) => c })
|
||||
const productVariantService = {
|
||||
getRegionPrice: jest.fn().mockImplementation(id => {
|
||||
getRegionPrice: jest.fn().mockImplementation((id) => {
|
||||
if (id === IdMap.getId("fail")) {
|
||||
return Promise.reject()
|
||||
}
|
||||
@@ -1033,7 +1034,7 @@ describe("CartService", () => {
|
||||
deleteSession: jest.fn(),
|
||||
updateSession: jest.fn(),
|
||||
createSession: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1237,7 +1238,7 @@ describe("CartService", () => {
|
||||
deleteSession: jest.fn(),
|
||||
updateSession: jest.fn(),
|
||||
createSession: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1308,11 +1309,50 @@ describe("CartService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("findCustomShippingOption", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
let cartService = new CartService({})
|
||||
|
||||
it("given a cart with custom shipping options and a shipping option id corresponding to a custom shipping option, then it should return a custom shipping option", async () => {
|
||||
const cartCSO = [
|
||||
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
|
||||
]
|
||||
const result = cartService.findCustomShippingOption(cartCSO, "test-so")
|
||||
|
||||
expect(result).toEqual({
|
||||
id: "cso-test",
|
||||
shipping_option_id: "test-so",
|
||||
price: 20,
|
||||
})
|
||||
})
|
||||
|
||||
it("given a cart with empty custom shipping options and shipping option id, then it should return undefined", async () => {
|
||||
const cartCSO = []
|
||||
|
||||
const result = cartService.findCustomShippingOption(cartCSO, "test-so")
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it("given a cart with custom shipping options and a shipping option id that does not belong to the cart, then it should throw an invalid error", async () => {
|
||||
const cartCSO = [
|
||||
{ id: "cso-test", shipping_option_id: "test-so", price: 500 },
|
||||
]
|
||||
|
||||
expect(() => {
|
||||
cartService.findCustomShippingOption(cartCSO, "some-other-so")
|
||||
}).toThrow(MedusaError)
|
||||
})
|
||||
})
|
||||
|
||||
describe("addShippingMethod", () => {
|
||||
const buildCart = (id, config = {}) => {
|
||||
return {
|
||||
id: IdMap.getId(id),
|
||||
items: (config.items || []).map(i => ({
|
||||
items: (config.items || []).map((i) => ({
|
||||
id: IdMap.getId(i.id),
|
||||
variant: {
|
||||
product: {
|
||||
@@ -1320,7 +1360,7 @@ describe("CartService", () => {
|
||||
},
|
||||
},
|
||||
})),
|
||||
shipping_methods: (config.shipping_methods || []).map(m => ({
|
||||
shipping_methods: (config.shipping_methods || []).map((m) => ({
|
||||
id: IdMap.getId(m.id),
|
||||
shipping_option: {
|
||||
profile_id: IdMap.getId(m.profile),
|
||||
@@ -1337,6 +1377,7 @@ describe("CartService", () => {
|
||||
const cart3 = buildCart("lines", {
|
||||
items: [{ id: "line", profile: "profile1" }],
|
||||
})
|
||||
const cartWithCustomSO = buildCart("cart-with-custom-so")
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOneWithRelations: (rels, q) => {
|
||||
@@ -1345,6 +1386,8 @@ describe("CartService", () => {
|
||||
return Promise.resolve(cart3)
|
||||
case IdMap.getId("existing"):
|
||||
return Promise.resolve(cart2)
|
||||
case IdMap.getId("cart-with-custom-so"):
|
||||
return Promise.resolve(cartWithCustomSO)
|
||||
default:
|
||||
return Promise.resolve(cart1)
|
||||
}
|
||||
@@ -1353,12 +1396,12 @@ describe("CartService", () => {
|
||||
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
const shippingOptionService = {
|
||||
createShippingMethod: jest.fn().mockImplementation(id => {
|
||||
createShippingMethod: jest.fn().mockImplementation((id) => {
|
||||
return Promise.resolve({
|
||||
shipping_option: {
|
||||
profile_id: id,
|
||||
@@ -1366,11 +1409,25 @@ describe("CartService", () => {
|
||||
})
|
||||
}),
|
||||
deleteShippingMethod: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const customShippingOptionService = {
|
||||
list: jest.fn().mockImplementation(({ cart_id }) => {
|
||||
if (cart_id === IdMap.getId("cart-with-custom-so")) {
|
||||
return [
|
||||
{
|
||||
id: "cso-test",
|
||||
shipping_profile_id: "test-so",
|
||||
cart_id: IdMap.getId("cart-with-custom-so"),
|
||||
},
|
||||
]
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
const cartService = new CartService({
|
||||
manager: MockManager,
|
||||
totalsService,
|
||||
@@ -1378,6 +1435,7 @@ describe("CartService", () => {
|
||||
shippingOptionService,
|
||||
lineItemService,
|
||||
eventBusService,
|
||||
customShippingOptionService,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1395,9 +1453,11 @@ describe("CartService", () => {
|
||||
IdMap.getId("option"),
|
||||
data
|
||||
)
|
||||
expect(
|
||||
shippingOptionService.createShippingMethod
|
||||
).toHaveBeenCalledWith(IdMap.getId("option"), data, { cart: cart1 })
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("option"),
|
||||
data,
|
||||
{ cart: cart1 }
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully overrides existing profile shipping method", async () => {
|
||||
@@ -1409,9 +1469,11 @@ describe("CartService", () => {
|
||||
IdMap.getId("profile1"),
|
||||
data
|
||||
)
|
||||
expect(
|
||||
shippingOptionService.createShippingMethod
|
||||
).toHaveBeenCalledWith(IdMap.getId("profile1"), data, { cart: cart2 })
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("profile1"),
|
||||
data,
|
||||
{ cart: cart2 }
|
||||
)
|
||||
expect(shippingOptionService.deleteShippingMethod).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("ship1"),
|
||||
shipping_option: {
|
||||
@@ -1437,9 +1499,11 @@ describe("CartService", () => {
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(
|
||||
shippingOptionService.createShippingMethod
|
||||
).toHaveBeenCalledWith(IdMap.getId("additional"), data, { cart: cart2 })
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("additional"),
|
||||
data,
|
||||
{ cart: cart2 }
|
||||
)
|
||||
})
|
||||
|
||||
it("updates item shipping", async () => {
|
||||
@@ -1459,18 +1523,55 @@ describe("CartService", () => {
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(
|
||||
shippingOptionService.createShippingMethod
|
||||
).toHaveBeenCalledWith(IdMap.getId("profile1"), data, { cart: cart3 })
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("profile1"),
|
||||
data,
|
||||
{ cart: cart3 }
|
||||
)
|
||||
|
||||
expect(lineItemService.update).toHaveBeenCalledTimes(1)
|
||||
expect(lineItemService.update).toHaveBeenCalledWith(IdMap.getId("line"), {
|
||||
has_shipping: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully adds a shipping method from a custom shipping option and custom price", async () => {
|
||||
const data = {
|
||||
id: "test",
|
||||
extra: "yes",
|
||||
}
|
||||
|
||||
cartService.findCustomShippingOption = jest
|
||||
.fn()
|
||||
.mockImplementation((cartCustomShippingOptions) => {
|
||||
return {
|
||||
price: 0,
|
||||
}
|
||||
})
|
||||
|
||||
await cartService.addShippingMethod(
|
||||
IdMap.getId("cart-with-custom-so"),
|
||||
IdMap.getId("test-so"),
|
||||
data
|
||||
)
|
||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("test-so"),
|
||||
data,
|
||||
{
|
||||
cart_id: IdMap.getId("cart-with-custom-so"),
|
||||
price: 0,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("applyDiscount", () => {
|
||||
const getOffsetDate = (offset) => {
|
||||
const date = new Date()
|
||||
date.setDate(date.getDate() + offset)
|
||||
return date
|
||||
}
|
||||
|
||||
const cartRepository = MockRepository({
|
||||
findOneWithRelations: (rels, q) => {
|
||||
if (q.where.id === IdMap.getId("with-d")) {
|
||||
@@ -1502,7 +1603,7 @@ describe("CartService", () => {
|
||||
})
|
||||
|
||||
const discountService = {
|
||||
retrieveByCode: jest.fn().mockImplementation(code => {
|
||||
retrieveByCode: jest.fn().mockImplementation((code) => {
|
||||
if (code === "US10") {
|
||||
return Promise.resolve({
|
||||
regions: [{ id: IdMap.getId("bad") }],
|
||||
@@ -1538,6 +1639,69 @@ describe("CartService", () => {
|
||||
},
|
||||
})
|
||||
}
|
||||
if (code === "EarlyDiscount") {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("10off"),
|
||||
code: "10%OFF",
|
||||
regions: [{ id: IdMap.getId("good") }],
|
||||
rule: {
|
||||
type: "percentage",
|
||||
},
|
||||
starts_at: getOffsetDate(1),
|
||||
ends_at: getOffsetDate(10),
|
||||
})
|
||||
}
|
||||
if (code === "ExpiredDiscount") {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("10off"),
|
||||
code: "10%OFF",
|
||||
regions: [{ id: IdMap.getId("good") }],
|
||||
rule: {
|
||||
type: "percentage",
|
||||
},
|
||||
ends_at: getOffsetDate(-1),
|
||||
starts_at: getOffsetDate(-10),
|
||||
})
|
||||
}
|
||||
if (code === "ExpiredDynamicDiscount") {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("10off"),
|
||||
code: "10%OFF",
|
||||
is_dynamic: true,
|
||||
regions: [{ id: IdMap.getId("good") }],
|
||||
rule: {
|
||||
type: "percentage",
|
||||
},
|
||||
starts_at: getOffsetDate(-10),
|
||||
ends_at: getOffsetDate(-1),
|
||||
})
|
||||
}
|
||||
if (code === "ExpiredDynamicDiscountEndDate") {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("10off"),
|
||||
is_dynamic: true,
|
||||
code: "10%OFF",
|
||||
regions: [{ id: IdMap.getId("good") }],
|
||||
rule: {
|
||||
type: "percentage",
|
||||
},
|
||||
starts_at: getOffsetDate(-10),
|
||||
ends_at: getOffsetDate(-3),
|
||||
valid_duration: "P0Y0M1D",
|
||||
})
|
||||
}
|
||||
if (code === "ValidDiscount") {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("10off"),
|
||||
code: "10%OFF",
|
||||
regions: [{ id: IdMap.getId("good") }],
|
||||
rule: {
|
||||
type: "percentage",
|
||||
},
|
||||
starts_at: getOffsetDate(-10),
|
||||
ends_at: getOffsetDate(10),
|
||||
})
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("10off"),
|
||||
code: "10%OFF",
|
||||
@@ -1688,6 +1852,76 @@ describe("CartService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully applies valid discount with expiration date to cart", async () => {
|
||||
await cartService.update(IdMap.getId("fr-cart"), {
|
||||
discounts: [
|
||||
{
|
||||
code: "ValidDiscount",
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusService.emit).toHaveBeenCalledWith(
|
||||
"cart.updated",
|
||||
expect.any(Object)
|
||||
)
|
||||
|
||||
expect(cartRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(cartRepository.save).toHaveBeenCalledWith({
|
||||
id: IdMap.getId("cart"),
|
||||
region_id: IdMap.getId("good"),
|
||||
discount_total: 0,
|
||||
shipping_total: 0,
|
||||
subtotal: 0,
|
||||
tax_total: 0,
|
||||
total: 0,
|
||||
discounts: [
|
||||
{
|
||||
id: IdMap.getId("10off"),
|
||||
code: "10%OFF",
|
||||
regions: [{ id: IdMap.getId("good") }],
|
||||
rule: {
|
||||
type: "percentage",
|
||||
},
|
||||
starts_at: expect.any(Date),
|
||||
ends_at: expect.any(Date),
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it("throws if discount is applied too before it's valid", async () => {
|
||||
await expect(
|
||||
cartService.update(IdMap.getId("cart"), {
|
||||
discounts: [{ code: "EarlyDiscount" }],
|
||||
})
|
||||
).rejects.toThrow("Discount is not valid yet")
|
||||
})
|
||||
|
||||
it("throws if expired discount is applied", async () => {
|
||||
await expect(
|
||||
cartService.update(IdMap.getId("cart"), {
|
||||
discounts: [{ code: "ExpiredDiscount" }],
|
||||
})
|
||||
).rejects.toThrow("Discount is expired")
|
||||
})
|
||||
|
||||
it("throws if expired dynamic discount is applied", async () => {
|
||||
await expect(
|
||||
cartService.update(IdMap.getId("cart"), {
|
||||
discounts: [{ code: "ExpiredDynamicDiscount" }],
|
||||
})
|
||||
).rejects.toThrow("Discount is expired")
|
||||
})
|
||||
|
||||
it("throws if expired dynamic discount is applied after ends_at", async () => {
|
||||
await expect(
|
||||
cartService.update(IdMap.getId("cart"), {
|
||||
discounts: [{ code: "ExpiredDynamicDiscountEndDate" }],
|
||||
})
|
||||
).rejects.toThrow("Discount is expired")
|
||||
})
|
||||
|
||||
it("throws if discount limit is reached", async () => {
|
||||
await expect(
|
||||
cartService.update(IdMap.getId("cart"), {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import CustomShippingOptionService from "../custom-shipping-option"
|
||||
import { MockManager, MockRepository, IdMap } from "medusa-test-utils"
|
||||
|
||||
describe("CustomShippingOptionService", () => {
|
||||
describe("list", () => {
|
||||
const customShippingOptionRepository = MockRepository({
|
||||
find: q => {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: "cso-test",
|
||||
shipping_option_id: "test-so",
|
||||
price: 0,
|
||||
cart_id: "test-cso-cart",
|
||||
},
|
||||
])
|
||||
},
|
||||
})
|
||||
|
||||
const customShippingOptionService = new CustomShippingOptionService({
|
||||
manager: MockManager,
|
||||
customShippingOptionRepository,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls customShippingOptionRepository find method", async () => {
|
||||
await customShippingOptionService.list(
|
||||
{ cart_id: "test-cso-cart" },
|
||||
{
|
||||
relations: ["shipping_option"],
|
||||
}
|
||||
)
|
||||
expect(customShippingOptionRepository.find).toHaveBeenCalledTimes(1)
|
||||
expect(customShippingOptionRepository.find).toHaveBeenCalledWith({
|
||||
where: {
|
||||
cart_id: "test-cso-cart",
|
||||
},
|
||||
relations: ["shipping_option"],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const customShippingOptionRepository = MockRepository({
|
||||
findOne: q => {
|
||||
if (q.where.id === "cso-test") {
|
||||
return Promise.resolve({
|
||||
id: "cso-test",
|
||||
shipping_option_id: "test-so",
|
||||
price: 0,
|
||||
cart_id: "test-cso-cart",
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const customShippingOptionService = new CustomShippingOptionService({
|
||||
manager: MockManager,
|
||||
customShippingOptionRepository,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls customShippingOptionRepository findOne method", async () => {
|
||||
await customShippingOptionService.retrieve("cso-test", {
|
||||
relations: ["shipping_option", "cart"],
|
||||
})
|
||||
|
||||
expect(customShippingOptionRepository.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(customShippingOptionRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { id: "cso-test" },
|
||||
relations: ["shipping_option", "cart"],
|
||||
})
|
||||
})
|
||||
|
||||
it("fails when custom shipping option is not found", async () => {
|
||||
expect(customShippingOptionService.retrieve("bad-cso")).rejects.toThrow(
|
||||
`Custom shipping option with id: bad-cso was not found.`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", () => {
|
||||
const customShippingOptionRepository = MockRepository({
|
||||
create: jest
|
||||
.fn()
|
||||
.mockImplementation(f => Promise.resolve({ id: "test-cso", ...f })),
|
||||
save: jest.fn().mockImplementation(f => Promise.resolve(f)),
|
||||
})
|
||||
|
||||
const customShippingOptionService = new CustomShippingOptionService({
|
||||
manager: MockManager,
|
||||
customShippingOptionRepository,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls customShippingOptionRepository create method", async () => {
|
||||
const customShippingOption = {
|
||||
cart_id: "test-cso-cart",
|
||||
shipping_option_id: "test-so",
|
||||
price: 30,
|
||||
}
|
||||
await customShippingOptionService.create(customShippingOption)
|
||||
|
||||
expect(customShippingOptionRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(customShippingOptionRepository.create).toHaveBeenCalledWith({
|
||||
cart_id: "test-cso-cart",
|
||||
shipping_option_id: "test-so",
|
||||
price: 30,
|
||||
metadata: {},
|
||||
})
|
||||
|
||||
expect(customShippingOptionRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(customShippingOptionRepository.save).toHaveBeenCalledWith({
|
||||
id: "test-cso",
|
||||
cart_id: "test-cso-cart",
|
||||
shipping_option_id: "test-so",
|
||||
price: 30,
|
||||
metadata: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -13,6 +13,9 @@ describe("DiscountService", () => {
|
||||
id: IdMap.getId("france"),
|
||||
}
|
||||
},
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const discountService = new DiscountService({
|
||||
@@ -55,6 +58,74 @@ describe("DiscountService", () => {
|
||||
|
||||
expect(discountRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully creates discount with start and end dates", async () => {
|
||||
await discountService.create({
|
||||
code: "test",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
allocation: "total",
|
||||
value: 20,
|
||||
},
|
||||
starts_at: new Date("03/14/2021"),
|
||||
ends_at: new Date("03/15/2021"),
|
||||
regions: [IdMap.getId("france")],
|
||||
})
|
||||
|
||||
expect(discountRuleRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(discountRuleRepository.create).toHaveBeenCalledWith({
|
||||
type: "percentage",
|
||||
allocation: "total",
|
||||
value: 20,
|
||||
})
|
||||
|
||||
expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(discountRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(discountRepository.create).toHaveBeenCalledWith({
|
||||
code: "TEST",
|
||||
rule: expect.anything(),
|
||||
regions: [{ id: IdMap.getId("france") }],
|
||||
starts_at: new Date("03/14/2021"),
|
||||
ends_at: new Date("03/15/2021"),
|
||||
})
|
||||
|
||||
expect(discountRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully creates discount with start date and a valid duration", async () => {
|
||||
await discountService.create({
|
||||
code: "test",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
allocation: "total",
|
||||
value: 20,
|
||||
},
|
||||
starts_at: new Date("03/14/2021"),
|
||||
valid_duration: "P0Y0M1D",
|
||||
regions: [IdMap.getId("france")],
|
||||
})
|
||||
|
||||
expect(discountRuleRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(discountRuleRepository.create).toHaveBeenCalledWith({
|
||||
type: "percentage",
|
||||
allocation: "total",
|
||||
value: 20,
|
||||
})
|
||||
|
||||
expect(discountRuleRepository.save).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(discountRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(discountRepository.create).toHaveBeenCalledWith({
|
||||
code: "TEST",
|
||||
rule: expect.anything(),
|
||||
regions: [{ id: IdMap.getId("france") }],
|
||||
starts_at: new Date("03/14/2021"),
|
||||
valid_duration: "P0Y0M1D",
|
||||
})
|
||||
|
||||
expect(discountRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
@@ -376,6 +447,7 @@ describe("DiscountService", () => {
|
||||
id: "parent",
|
||||
is_dynamic: true,
|
||||
rule_id: "parent_rule",
|
||||
valid_duration: "P1Y",
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -412,6 +484,8 @@ describe("DiscountService", () => {
|
||||
rule_id: "parent_rule",
|
||||
parent_discount_id: "parent",
|
||||
code: "HI",
|
||||
usage_limit: undefined,
|
||||
ends_at: expect.any(Date),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -231,6 +231,8 @@ describe("OrderService", () => {
|
||||
"payment",
|
||||
"items",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"gift_cards",
|
||||
"shipping_methods",
|
||||
],
|
||||
|
||||
@@ -415,6 +415,7 @@ describe("ProductService", () => {
|
||||
|
||||
const productService = new ProductService({
|
||||
manager: MockManager,
|
||||
eventBusService,
|
||||
productRepository,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
@@ -296,24 +296,19 @@ describe("ShippingOptionService", () => {
|
||||
})
|
||||
|
||||
describe("removeRequirement", () => {
|
||||
const shippingOptionRepository = MockRepository({
|
||||
findOne: q => {
|
||||
switch (q.where.id) {
|
||||
default:
|
||||
return Promise.resolve({
|
||||
requirements: [
|
||||
{
|
||||
id: IdMap.getId("requirement_id"),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
const shippingOptionRequirementRepository = MockRepository({
|
||||
softRemove: q => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
findOne: i =>
|
||||
i.where.id === IdMap.getId("requirement_id")
|
||||
? { id: IdMap.getId("requirement_id") }
|
||||
: null,
|
||||
})
|
||||
|
||||
const optionService = new ShippingOptionService({
|
||||
manager: MockManager,
|
||||
shippingOptionRepository,
|
||||
shippingOptionRequirementRepository,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -321,22 +316,19 @@ describe("ShippingOptionService", () => {
|
||||
})
|
||||
|
||||
it("remove requirement successfully", async () => {
|
||||
await optionService.removeRequirement(
|
||||
IdMap.getId("validId"),
|
||||
IdMap.getId("requirement_id")
|
||||
)
|
||||
await optionService.removeRequirement(IdMap.getId("requirement_id"))
|
||||
|
||||
expect(shippingOptionRepository.save).toBeCalledTimes(1)
|
||||
expect(shippingOptionRepository.save).toBeCalledWith({ requirements: [] })
|
||||
expect(shippingOptionRequirementRepository.findOne).toBeCalledTimes(1)
|
||||
expect(shippingOptionRequirementRepository.findOne).toBeCalledWith({
|
||||
where: { id: IdMap.getId("requirement_id") },
|
||||
})
|
||||
expect(shippingOptionRequirementRepository.softRemove).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
it("is idempotent", async () => {
|
||||
await optionService.removeRequirement(IdMap.getId("validId"), "something")
|
||||
|
||||
expect(shippingOptionRepository.save).toBeCalledTimes(1)
|
||||
expect(shippingOptionRepository.save).toBeCalledWith({
|
||||
requirements: [{ id: IdMap.getId("requirement_id") }],
|
||||
})
|
||||
expect(shippingOptionRequirementRepository.softRemove).toBeCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -176,8 +176,17 @@ describe("ShippingProfileService", () => {
|
||||
})
|
||||
|
||||
const shippingOptionService = {
|
||||
list: jest.fn().mockImplementation(() =>
|
||||
Promise.resolve([
|
||||
list: jest.fn().mockImplementation(({ id }) => {
|
||||
if (id && id.includes("test-option")) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: "test-option",
|
||||
amount: 1000,
|
||||
name: "Test option",
|
||||
},
|
||||
])
|
||||
}
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: "ship_1",
|
||||
},
|
||||
@@ -185,24 +194,74 @@ describe("ShippingProfileService", () => {
|
||||
id: "ship_2",
|
||||
},
|
||||
])
|
||||
),
|
||||
}),
|
||||
validateCartOption: jest.fn().mockImplementation(s => s),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const customShippingOptionService = {
|
||||
list: jest.fn().mockImplementation(({ cart_id }, config) => {
|
||||
if (cart_id === "cso-cart") {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: "cso_1",
|
||||
cart_id: "cso-cart",
|
||||
shipping_option_id: "test-option",
|
||||
price: 0,
|
||||
},
|
||||
])
|
||||
}
|
||||
return Promise.resolve([])
|
||||
}),
|
||||
}
|
||||
|
||||
const profileService = new ShippingProfileService({
|
||||
manager: MockManager,
|
||||
shippingProfileRepository: profRepo,
|
||||
shippingOptionService,
|
||||
customShippingOptionService,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("fetches correct options", async () => {
|
||||
it("given a cart with custom shipping options, should return correct custom shipping options ", async () => {
|
||||
const cart = {
|
||||
id: "cso-cart",
|
||||
items: [
|
||||
{
|
||||
variant: {
|
||||
product: {
|
||||
_id: IdMap.getId("product_1"),
|
||||
profile_id: IdMap.getId("profile"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
variant: {
|
||||
product: {
|
||||
_id: IdMap.getId("product_2"),
|
||||
profile_id: IdMap.getId("profile"),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
type: "swap",
|
||||
}
|
||||
|
||||
await expect(profileService.fetchCartOptions(cart)).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-option",
|
||||
amount: 0,
|
||||
name: "Test option",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("given a cart with no custom shipping options, should return normal shipping options", async () => {
|
||||
const cart = {
|
||||
items: [
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ import { InventoryServiceMock } from "../__mocks__/inventory"
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -161,7 +161,7 @@ describe("SwapService", () => {
|
||||
const cartService = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "cart" })),
|
||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -170,25 +170,36 @@ describe("SwapService", () => {
|
||||
findOneWithRelations: () => Promise.resolve(existing),
|
||||
})
|
||||
|
||||
const lineItemService = {
|
||||
create: jest.fn().mockImplementation(d => Promise.resolve(d)),
|
||||
update: jest.fn().mockImplementation(d => Promise.resolve(d)),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
const customShippingOptionService = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "cso-test" })),
|
||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const lineItemService = {
|
||||
create: jest.fn().mockImplementation((d) => Promise.resolve(d)),
|
||||
update: jest.fn().mockImplementation((d) => Promise.resolve(d)),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const swapService = new SwapService({
|
||||
manager: MockManager,
|
||||
eventBusService,
|
||||
swapRepository: swapRepo,
|
||||
cartService,
|
||||
lineItemService,
|
||||
customShippingOptionService,
|
||||
})
|
||||
|
||||
it("finds swap and calls return create cart", async () => {
|
||||
await swapService.createCart(IdMap.getId("swap-1"))
|
||||
await swapService.createCart(IdMap.getId("swap-1"), [
|
||||
{ option_id: "test-option", price: 10 },
|
||||
])
|
||||
|
||||
expect(swapRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
|
||||
expect(swapRepo.findOneWithRelations).toHaveBeenCalledWith(
|
||||
@@ -198,6 +209,9 @@ describe("SwapService", () => {
|
||||
"order.swaps",
|
||||
"order.swaps.additional_items",
|
||||
"order.discounts",
|
||||
"order.discounts.rule",
|
||||
"order.claims",
|
||||
"order.claims.additional_items",
|
||||
"additional_items",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
@@ -303,7 +317,7 @@ describe("SwapService", () => {
|
||||
const swapRepo = MockRepository()
|
||||
const returnService = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "ret" })),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -400,7 +414,7 @@ describe("SwapService", () => {
|
||||
receiveReturn: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ test: "received" })),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -446,7 +460,7 @@ describe("SwapService", () => {
|
||||
receiveReturn: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ status: "requires_action" })),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -524,7 +538,7 @@ describe("SwapService", () => {
|
||||
{ items: [{ item_id: "1234", quantity: 2 }], data: "new" },
|
||||
])
|
||||
),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -545,7 +559,7 @@ describe("SwapService", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -620,11 +634,11 @@ describe("SwapService", () => {
|
||||
describe("cancelFulfillment", () => {
|
||||
const swapRepo = MockRepository({
|
||||
findOneWithRelations: () => Promise.resolve({}),
|
||||
save: f => Promise.resolve(f),
|
||||
save: (f) => Promise.resolve(f),
|
||||
})
|
||||
|
||||
const fulfillmentService = {
|
||||
cancelFulfillment: jest.fn().mockImplementation(f => {
|
||||
cancelFulfillment: jest.fn().mockImplementation((f) => {
|
||||
switch (f) {
|
||||
case IdMap.getId("no-swap"):
|
||||
return Promise.resolve({})
|
||||
@@ -634,7 +648,7 @@ describe("SwapService", () => {
|
||||
})
|
||||
}
|
||||
}),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -688,14 +702,14 @@ describe("SwapService", () => {
|
||||
data: "new",
|
||||
})
|
||||
}),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -736,14 +750,14 @@ describe("SwapService", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const cartService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -819,7 +833,7 @@ describe("SwapService", () => {
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -834,7 +848,7 @@ describe("SwapService", () => {
|
||||
updateShippingMethod: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -843,7 +857,7 @@ describe("SwapService", () => {
|
||||
update: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -858,14 +872,14 @@ describe("SwapService", () => {
|
||||
cancelPayment: jest.fn(() => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const inventoryService = {
|
||||
...InventoryServiceMock,
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -986,19 +1000,19 @@ describe("SwapService", () => {
|
||||
describe("success", () => {
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const paymentProviderService = {
|
||||
capturePayment: jest.fn(g =>
|
||||
capturePayment: jest.fn((g) =>
|
||||
g.id === "good" ? Promise.resolve() : Promise.reject()
|
||||
),
|
||||
refundPayment: jest.fn(g =>
|
||||
refundPayment: jest.fn((g) =>
|
||||
g[0].id === "good" ? Promise.resolve() : Promise.reject()
|
||||
),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1126,7 +1140,7 @@ describe("SwapService", () => {
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1189,7 +1203,7 @@ describe("SwapService", () => {
|
||||
|
||||
const paymentProviderService = {
|
||||
cancelPayment: jest.fn(() => Promise.resolve({})),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1221,7 +1235,7 @@ describe("SwapService", () => {
|
||||
return Promise.resolve(swap)
|
||||
}
|
||||
},
|
||||
save: f => f,
|
||||
save: (f) => f,
|
||||
})
|
||||
|
||||
const swapService = new SwapService({
|
||||
@@ -1270,7 +1284,7 @@ describe("SwapService", () => {
|
||||
|
||||
it.each([["fail-refund-1"], ["fail-refund-2"], ["fail-refund-3"]])(
|
||||
"fails to cancel swap when contains refund",
|
||||
async input => {
|
||||
async (input) => {
|
||||
await expect(swapService.cancel(IdMap.getId(input))).rejects.toThrow(
|
||||
"Swap with a refund cannot be canceled"
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { BaseService } from "medusa-interfaces"
|
||||
|
||||
/**
|
||||
* Can authenticate a user based on email password combination
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class AuthService extends BaseService {
|
||||
constructor({ userService, customerService }) {
|
||||
@@ -47,7 +47,9 @@ class AuthService extends BaseService {
|
||||
success: true,
|
||||
user,
|
||||
}
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -76,12 +78,17 @@ class AuthService extends BaseService {
|
||||
*/
|
||||
async authenticate(email, password) {
|
||||
try {
|
||||
const user = await this.userService_.retrieveByEmail(email)
|
||||
const userPasswordHash = await this.userService_.retrieveByEmail(email, {
|
||||
select: ["password_hash"],
|
||||
})
|
||||
|
||||
const passwordsMatch = await this.comparePassword_(
|
||||
password,
|
||||
user.password_hash
|
||||
userPasswordHash.password_hash
|
||||
)
|
||||
|
||||
if (passwordsMatch) {
|
||||
const user = await this.userService_.retrieveByEmail(email)
|
||||
return {
|
||||
success: true,
|
||||
user,
|
||||
@@ -113,8 +120,13 @@ class AuthService extends BaseService {
|
||||
*/
|
||||
async authenticateCustomer(email, password) {
|
||||
try {
|
||||
const customer = await this.customerService_.retrieveByEmail(email)
|
||||
if (!customer.password_hash) {
|
||||
const customerPasswordHash = await this.customerService_.retrieveByEmail(
|
||||
email,
|
||||
{
|
||||
select: ["password_hash"],
|
||||
}
|
||||
)
|
||||
if (!customerPasswordHash.password_hash) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Invalid email or password",
|
||||
@@ -123,9 +135,11 @@ class AuthService extends BaseService {
|
||||
|
||||
const passwordsMatch = await this.comparePassword_(
|
||||
password,
|
||||
customer.password_hash
|
||||
customerPasswordHash.password_hash
|
||||
)
|
||||
|
||||
if (passwordsMatch) {
|
||||
const customer = await this.customerService_.retrieveByEmail(email)
|
||||
return {
|
||||
success: true,
|
||||
customer,
|
||||
|
||||
@@ -24,7 +24,6 @@ class CartService extends BaseService {
|
||||
regionService,
|
||||
lineItemService,
|
||||
shippingOptionService,
|
||||
shippingProfileService,
|
||||
customerService,
|
||||
discountService,
|
||||
giftCardService,
|
||||
@@ -32,6 +31,7 @@ class CartService extends BaseService {
|
||||
addressRepository,
|
||||
paymentSessionRepository,
|
||||
inventoryService,
|
||||
customShippingOptionService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -62,9 +62,6 @@ class CartService extends BaseService {
|
||||
/** @private @const {PaymentProviderService} */
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
|
||||
/** @private @const {ShippingProfileService} */
|
||||
this.shippingProfileService_ = shippingProfileService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
@@ -88,6 +85,9 @@ class CartService extends BaseService {
|
||||
|
||||
/** @private @const {InventoryService} */
|
||||
this.inventoryService_ = inventoryService
|
||||
|
||||
/** @private @const {CustomShippingOptionService} */
|
||||
this.customShippingOptionService_ = customShippingOptionService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -107,13 +107,13 @@ class CartService extends BaseService {
|
||||
regionService: this.regionService_,
|
||||
lineItemService: this.lineItemService_,
|
||||
shippingOptionService: this.shippingOptionService_,
|
||||
shippingProfileService: this.shippingProfileService_,
|
||||
customerService: this.customerService_,
|
||||
discountService: this.discountService_,
|
||||
totalsService: this.totalsService_,
|
||||
addressRepository: this.addressRepository_,
|
||||
giftCardService: this.giftCardService_,
|
||||
inventoryService: this.inventoryService_,
|
||||
customShippingOptionService: this.customShippingOptionService_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -160,20 +160,22 @@ class CartService extends BaseService {
|
||||
"total",
|
||||
]
|
||||
|
||||
const totalsToSelect = select.filter(v => totalFields.includes(v))
|
||||
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
||||
if (totalsToSelect.length > 0) {
|
||||
const relationSet = new Set(relations)
|
||||
relationSet.add("items")
|
||||
relationSet.add("gift_cards")
|
||||
relationSet.add("discounts")
|
||||
//relationSet.add("discounts.parent_discount")
|
||||
//relationSet.add("discounts.parent_discount.rule")
|
||||
//relationSet.add("discounts.parent_discount.regions")
|
||||
relationSet.add("discounts.rule")
|
||||
relationSet.add("discounts.rule.valid_for")
|
||||
// relationSet.add("discounts.parent_discount")
|
||||
// relationSet.add("discounts.parent_discount.rule")
|
||||
// relationSet.add("discounts.parent_discount.regions")
|
||||
relationSet.add("shipping_methods")
|
||||
relationSet.add("region")
|
||||
relations = [...relationSet]
|
||||
|
||||
select = select.filter(v => !totalFields.includes(v))
|
||||
select = select.filter((v) => !totalFields.includes(v))
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -236,9 +238,8 @@ class CartService extends BaseService {
|
||||
const cartRepo = this.manager_.getCustomRepository(this.cartRepository_)
|
||||
const validatedId = this.validateId_(cartId)
|
||||
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
options
|
||||
)
|
||||
const { select, relations, totalsToSelect } =
|
||||
this.transformQueryForTotals_(options)
|
||||
|
||||
const query = {
|
||||
where: { id: validatedId },
|
||||
@@ -273,7 +274,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the create operation
|
||||
*/
|
||||
async create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const addressRepo = manager.getCustomRepository(this.addressRepository_)
|
||||
const { region_id } = data
|
||||
@@ -342,7 +343,7 @@ class CartService extends BaseService {
|
||||
* @retur {Promise} the result of the update operation
|
||||
*/
|
||||
async removeLineItem(cartId, lineItemId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"items",
|
||||
@@ -352,7 +353,7 @@ class CartService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
const lineItem = cart.items.find(li => li.id === lineItemId)
|
||||
const lineItem = cart.items.find((li) => li.id === lineItemId)
|
||||
if (!lineItem) {
|
||||
return cart
|
||||
}
|
||||
@@ -421,7 +422,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addLineItem(cartId, lineItem) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"shipping_methods",
|
||||
@@ -434,7 +435,7 @@ class CartService extends BaseService {
|
||||
|
||||
let currentItem
|
||||
if (lineItem.should_merge) {
|
||||
currentItem = cart.items.find(line => {
|
||||
currentItem = cart.items.find((line) => {
|
||||
if (line.should_merge && line.variant_id === lineItem.variant_id) {
|
||||
return _.isEqual(line.metadata, lineItem.metadata)
|
||||
}
|
||||
@@ -500,13 +501,13 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateLineItem(cartId, lineItemId, lineItemUpdate) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["items", "payment_sessions"],
|
||||
})
|
||||
|
||||
// Ensure that the line item exists in the cart
|
||||
const lineItemExists = cart.items.find(i => i.id === lineItemId)
|
||||
const lineItemExists = cart.items.find((i) => i.id === lineItemId)
|
||||
if (!lineItemExists) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -553,7 +554,7 @@ class CartService extends BaseService {
|
||||
// if any free shipping discounts, we ensure to update shipping method amount
|
||||
if (shouldAdd) {
|
||||
await Promise.all(
|
||||
cart.shipping_methods.map(async sm => {
|
||||
cart.shipping_methods.map(async (sm) => {
|
||||
const smRepo = this.manager_.getCustomRepository(
|
||||
this.shippingMethodRepository_
|
||||
)
|
||||
@@ -568,7 +569,7 @@ class CartService extends BaseService {
|
||||
)
|
||||
} else {
|
||||
await Promise.all(
|
||||
cart.shipping_methods.map(async sm => {
|
||||
cart.shipping_methods.map(async (sm) => {
|
||||
const smRepo = this.manager_.getCustomRepository(
|
||||
this.shippingMethodRepository_
|
||||
)
|
||||
@@ -584,7 +585,7 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
async update(cartId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: [
|
||||
@@ -600,18 +601,21 @@ class CartService extends BaseService {
|
||||
"shipping_address",
|
||||
"billing_address",
|
||||
"gift_cards",
|
||||
"discounts",
|
||||
"customer",
|
||||
"region",
|
||||
"payment_sessions",
|
||||
"region.countries",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"discounts.regions",
|
||||
],
|
||||
})
|
||||
|
||||
if ("region_id" in update) {
|
||||
await this.setRegion_(cart, update.region_id, update.country_code)
|
||||
const countryCode =
|
||||
update.country_code || update.shipping_address?.country_code
|
||||
await this.setRegion_(cart, update.region_id, countryCode)
|
||||
}
|
||||
|
||||
if ("customer_id" in update) {
|
||||
@@ -728,9 +732,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the resultign customer object
|
||||
*/
|
||||
async createOrFetchUserFromEmail_(email) {
|
||||
const schema = Validator.string()
|
||||
.email()
|
||||
.required()
|
||||
const schema = Validator.string().email().required()
|
||||
const { value, error } = schema.validate(email.toLowerCase())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
@@ -871,6 +873,7 @@ class CartService extends BaseService {
|
||||
async applyDiscount(cart, discountCode) {
|
||||
const discount = await this.discountService_.retrieveByCode(discountCode, [
|
||||
"rule",
|
||||
"rule.valid_for",
|
||||
"regions",
|
||||
])
|
||||
|
||||
@@ -880,11 +883,27 @@ class CartService extends BaseService {
|
||||
if (discount.usage_limit) {
|
||||
discount.usage_count = discount.usage_count || 0
|
||||
|
||||
if (discount.usage_limit === discount.usage_count)
|
||||
if (discount.usage_limit === discount.usage_count) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Discount has been used maximum allowed times"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
if (discount.starts_at > today) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Discount is not valid yet"
|
||||
)
|
||||
}
|
||||
|
||||
if (discount.ends_at && discount.ends_at < today) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
"Discount is expired"
|
||||
)
|
||||
}
|
||||
|
||||
let regions = discount.regions
|
||||
@@ -921,7 +940,7 @@ class CartService extends BaseService {
|
||||
const toParse = [...cart.discounts, discount]
|
||||
|
||||
let sawNotShipping = false
|
||||
const newDiscounts = toParse.map(d => {
|
||||
const newDiscounts = toParse.map((d) => {
|
||||
const drule = d.rule
|
||||
switch (drule.type) {
|
||||
case "free_shipping":
|
||||
@@ -951,16 +970,22 @@ class CartService extends BaseService {
|
||||
* @return {Promise<Cart>} the resulting cart
|
||||
*/
|
||||
async removeDiscount(cartId, discountCode) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["discounts", "payment_sessions", "shipping_methods"],
|
||||
relations: [
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"payment_sessions",
|
||||
"shipping_methods",
|
||||
],
|
||||
})
|
||||
|
||||
if (cart.discounts.some(({ rule }) => rule.type === "free_shipping")) {
|
||||
await this.adjustFreeShipping_(cart, false)
|
||||
}
|
||||
|
||||
cart.discounts = cart.discounts.filter(d => d.code !== discountCode)
|
||||
cart.discounts = cart.discounts.filter((d) => d.code !== discountCode)
|
||||
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
@@ -991,7 +1016,7 @@ class CartService extends BaseService {
|
||||
* Updates the currently selected payment session.
|
||||
*/
|
||||
async updatePaymentSession(cartId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1025,7 +1050,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise<Cart>} the resulting cart
|
||||
*/
|
||||
async authorizePayment(cartId, context = {}) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepository = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
const cart = await this.retrieve(cartId, {
|
||||
@@ -1069,10 +1094,10 @@ class CartService extends BaseService {
|
||||
* Sets a payment method for a cart.
|
||||
* @param {string} cartId - the id of the cart to add payment method to
|
||||
* @param {PaymentMethod} paymentMethod - the method to be set to the cart
|
||||
* @returns {Promise} result of update operation
|
||||
* @return {Promise} result of update operation
|
||||
*/
|
||||
async setPaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const psRepo = manager.getCustomRepository(this.paymentSessionRepository_)
|
||||
|
||||
const cart = await this.retrieve(cartId, {
|
||||
@@ -1101,13 +1126,13 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
cart.payment_sessions.map(ps => {
|
||||
cart.payment_sessions.map((ps) => {
|
||||
return psRepo.save({ ...ps, is_selected: null })
|
||||
})
|
||||
)
|
||||
|
||||
const sess = cart.payment_sessions.find(
|
||||
ps => ps.provider_id === providerId
|
||||
(ps) => ps.provider_id === providerId
|
||||
)
|
||||
|
||||
sess.is_selected = true
|
||||
@@ -1130,13 +1155,13 @@ class CartService extends BaseService {
|
||||
* amounts, currencies, etc. as well as make sure to filter payment sessions
|
||||
* that are not available for the cart's region.
|
||||
* @param {string} cartId - the id of the cart to set payment session for
|
||||
* @returns {Promise} the result of the update operation.
|
||||
* @return {Promise} the result of the update operation.
|
||||
*/
|
||||
async setPaymentSessions(cartOrCartId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const psRepo = manager.getCustomRepository(this.paymentSessionRepository_)
|
||||
|
||||
let cartId =
|
||||
const cartId =
|
||||
typeof cartOrCartId === `string` ? cartOrCartId : cartOrCartId.id
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: [
|
||||
@@ -1150,6 +1175,8 @@ class CartService extends BaseService {
|
||||
relations: [
|
||||
"items",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"gift_cards",
|
||||
"billing_address",
|
||||
"shipping_address",
|
||||
@@ -1163,7 +1190,7 @@ class CartService extends BaseService {
|
||||
const region = cart.region
|
||||
|
||||
// If there are existing payment sessions ensure that these are up to date
|
||||
let seen = []
|
||||
const seen = []
|
||||
if (cart.payment_sessions && cart.payment_sessions.length) {
|
||||
for (const session of cart.payment_sessions) {
|
||||
if (
|
||||
@@ -1213,10 +1240,10 @@ class CartService extends BaseService {
|
||||
* @param {string} cartId - the id of the cart to remove from
|
||||
* @param {string} providerId - the id of the provider whoose payment session
|
||||
* should be removed.
|
||||
* @returns {Promise<Cart>} the resulting cart.
|
||||
* @return {Promise<Cart>} the resulting cart.
|
||||
*/
|
||||
async deletePaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1254,10 +1281,10 @@ class CartService extends BaseService {
|
||||
* @param {string} cartId - the id of the cart to remove from
|
||||
* @param {string} providerId - the id of the provider whoose payment session
|
||||
* should be removed.
|
||||
* @returns {Promise<Cart>} the resulting cart.
|
||||
* @return {Promise<Cart>} the resulting cart.
|
||||
*/
|
||||
async refreshPaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1296,12 +1323,14 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addShippingMethod(cartId, optionId, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
"shipping_methods",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods.shipping_option",
|
||||
"items",
|
||||
"items.variant",
|
||||
@@ -1309,11 +1338,32 @@ class CartService extends BaseService {
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
|
||||
const cartCustomShippingOptions =
|
||||
await this.customShippingOptionService_.list({ cart_id: cart.id })
|
||||
|
||||
const customShippingOption = this.findCustomShippingOption(
|
||||
cartCustomShippingOptions,
|
||||
optionId
|
||||
)
|
||||
|
||||
const { shipping_methods } = cart
|
||||
|
||||
/**
|
||||
* If we have a custom shipping option configured we want the price
|
||||
* override to take effect and do not want `validateCartOption` to check
|
||||
* if requirements are met, hence we are not passing the entire cart, but
|
||||
* just the id.
|
||||
*/
|
||||
const shippingMethodConfig = customShippingOption
|
||||
? { cart_id: cart.id, price: customShippingOption.price }
|
||||
: {
|
||||
cart,
|
||||
}
|
||||
|
||||
const newMethod = await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.createShippingMethod(optionId, data, { cart })
|
||||
.createShippingMethod(optionId, data, shippingMethodConfig)
|
||||
|
||||
const methods = [newMethod]
|
||||
if (shipping_methods.length) {
|
||||
@@ -1338,7 +1388,12 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
const result = await this.retrieve(cartId, {
|
||||
relations: ["discounts", "shipping_methods"],
|
||||
relations: [
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"shipping_methods",
|
||||
],
|
||||
})
|
||||
|
||||
// if cart has freeshipping, adjust price
|
||||
@@ -1353,6 +1408,29 @@ class CartService extends BaseService {
|
||||
}, "SERIALIZABLE")
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the cart's custom shipping options based on the passed option id.
|
||||
* throws if custom options is not empty and no shipping option corresponds to optionId
|
||||
* @param {Object} cartCustomShippingOptions - the cart's custom shipping options
|
||||
* @param {string} option - id of the normal or custom shipping option to find in the cartCustomShippingOptions
|
||||
* @return {CustomShippingOption | undefined}
|
||||
*/
|
||||
findCustomShippingOption(cartCustomShippingOptions, optionId) {
|
||||
const customOption = cartCustomShippingOptions?.find(
|
||||
(cso) => cso.shipping_option_id === optionId
|
||||
)
|
||||
const hasCustomOptions = cartCustomShippingOptions?.length
|
||||
|
||||
if (hasCustomOptions && !customOption) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Wrong shipping option"
|
||||
)
|
||||
}
|
||||
|
||||
return customOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Set's the region of a cart.
|
||||
* @param {string} cartId - the id of the cart to set region on
|
||||
@@ -1380,7 +1458,7 @@ class CartService extends BaseService {
|
||||
if (cart.items.length) {
|
||||
cart.items = await Promise.all(
|
||||
cart.items
|
||||
.map(async item => {
|
||||
.map(async (item) => {
|
||||
const availablePrice = await this.productVariantService_
|
||||
.getRegionPrice(item.variant_id, regionId)
|
||||
.catch(() => undefined)
|
||||
@@ -1459,20 +1537,20 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
if (cart.discounts && cart.discounts.length) {
|
||||
const newDiscounts = cart.discounts.map(d => {
|
||||
const newDiscounts = cart.discounts.map((d) => {
|
||||
if (d.regions.find(({ id }) => id === regionId)) {
|
||||
return d
|
||||
}
|
||||
})
|
||||
|
||||
cart.discounts = newDiscounts.filter(d => !!d)
|
||||
cart.discounts = newDiscounts.filter((d) => !!d)
|
||||
}
|
||||
|
||||
cart.gift_cards = []
|
||||
|
||||
if (cart.payment_sessions && cart.payment_sessions.length) {
|
||||
await Promise.all(
|
||||
cart.payment_sessions.map(ps =>
|
||||
cart.payment_sessions.map((ps) =>
|
||||
this.paymentProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.deleteSession(ps)
|
||||
@@ -1486,13 +1564,19 @@ class CartService extends BaseService {
|
||||
/**
|
||||
* Deletes a cart from the database. Completed carts cannot be deleted.
|
||||
* @param {string} cartId - the id of the cart to delete
|
||||
* @returns {Promise<Cart?>} the deleted cart or undefined if the cart was
|
||||
* @return {Promise<Cart?>} the deleted cart or undefined if the cart was
|
||||
* not found.
|
||||
*/
|
||||
async delete(cartId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["items", "discounts", "payment_sessions"],
|
||||
relations: [
|
||||
"items",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"payment_sessions",
|
||||
],
|
||||
})
|
||||
|
||||
if (cart.completed_at) {
|
||||
@@ -1524,7 +1608,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async setMetadata(cartId, key, value) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
const validatedId = this.validateId_(cartId)
|
||||
@@ -1558,7 +1642,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async deleteMetadata(cartId, key) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const validatedId = this.validateId_(cartId)
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Brackets } from "typeorm"
|
||||
|
||||
class ClaimService extends BaseService {
|
||||
static Events = {
|
||||
@@ -102,7 +100,7 @@ class ClaimService extends BaseService {
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
const claim = await this.retrieve(id, { relations: ["shipping_methods"] })
|
||||
|
||||
@@ -113,13 +111,7 @@ class ClaimService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
claim_items,
|
||||
shipping_methods,
|
||||
metadata,
|
||||
fulfillment_status,
|
||||
no_notification,
|
||||
} = data
|
||||
const { claim_items, shipping_methods, metadata, no_notification } = data
|
||||
|
||||
if (metadata) {
|
||||
claim.metadata = this.setMetadata_(claim, metadata)
|
||||
@@ -183,9 +175,11 @@ class ClaimService extends BaseService {
|
||||
* Creates a Claim on an Order. Claims consists of items that are claimed and
|
||||
* optionally items to be sent as replacement for the claimed items. The
|
||||
* shipping address that the new items will be shipped to
|
||||
* @param {Object} data - the object containing all data required to create a claim
|
||||
* @return {Object} created claim
|
||||
*/
|
||||
create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const claimRepo = manager.getCustomRepository(this.claimRepository_)
|
||||
|
||||
const {
|
||||
@@ -257,8 +251,8 @@ class ClaimService extends BaseService {
|
||||
|
||||
let toRefund = refund_amount
|
||||
if (type === "refund" && typeof refund_amount === "undefined") {
|
||||
const lines = claim_items.map(ci => {
|
||||
const orderItem = order.items.find(oi => oi.id === ci.item_id)
|
||||
const lines = claim_items.map((ci) => {
|
||||
const orderItem = order.items.find((oi) => oi.id === ci.item_id)
|
||||
return {
|
||||
...orderItem,
|
||||
quantity: ci.quantity,
|
||||
@@ -274,7 +268,7 @@ class ClaimService extends BaseService {
|
||||
}
|
||||
|
||||
const newItems = await Promise.all(
|
||||
additional_items.map(i =>
|
||||
additional_items.map((i) =>
|
||||
this.lineItemService_
|
||||
.withTransaction(manager)
|
||||
.generate(i.variant_id, order.region_id, i.quantity)
|
||||
@@ -332,7 +326,7 @@ class ClaimService extends BaseService {
|
||||
await this.returnService_.withTransaction(manager).create({
|
||||
order_id: order.id,
|
||||
claim_order_id: result.id,
|
||||
items: claim_items.map(ci => ({
|
||||
items: claim_items.map((ci) => ({
|
||||
item_id: ci.item_id,
|
||||
quantity: ci.quantity,
|
||||
metadata: ci.metadata,
|
||||
@@ -362,7 +356,7 @@ class ClaimService extends BaseService {
|
||||
) {
|
||||
const { metadata, no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: [
|
||||
"additional_items",
|
||||
@@ -429,7 +423,7 @@ class ClaimService extends BaseService {
|
||||
is_claim: true,
|
||||
no_notification: evaluatedNoNotification,
|
||||
},
|
||||
claim.additional_items.map(i => ({
|
||||
claim.additional_items.map((i) => ({
|
||||
item_id: i.id,
|
||||
quantity: i.quantity,
|
||||
})),
|
||||
@@ -445,7 +439,7 @@ class ClaimService extends BaseService {
|
||||
|
||||
for (const item of claim.additional_items) {
|
||||
const fulfillmentItem = successfullyFulfilled.find(
|
||||
f => item.id === f.item_id
|
||||
(f) => item.id === f.item_id
|
||||
)
|
||||
|
||||
if (fulfillmentItem) {
|
||||
@@ -485,7 +479,7 @@ class ClaimService extends BaseService {
|
||||
}
|
||||
|
||||
async cancelFulfillment(fulfillmentId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const canceled = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.cancelFulfillment(fulfillmentId)
|
||||
@@ -508,7 +502,7 @@ class ClaimService extends BaseService {
|
||||
}
|
||||
|
||||
async processRefund(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["order", "order.payments"],
|
||||
})
|
||||
@@ -560,7 +554,7 @@ class ClaimService extends BaseService {
|
||||
) {
|
||||
const { metadata, no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["additional_items"],
|
||||
})
|
||||
@@ -584,7 +578,7 @@ class ClaimService extends BaseService {
|
||||
claim.fulfillment_status = "shipped"
|
||||
|
||||
for (const i of claim.additional_items) {
|
||||
const shipped = shipment.items.find(si => si.item_id === i.id)
|
||||
const shipped = shipment.items.find((si) => si.item_id === i.id)
|
||||
if (shipped) {
|
||||
const shippedQty = (i.shipped_quantity || 0) + shipped.quantity
|
||||
await this.lineItemService_.withTransaction(manager).update(i.id, {
|
||||
@@ -617,7 +611,7 @@ class ClaimService extends BaseService {
|
||||
}
|
||||
|
||||
async cancel(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const claim = await this.retrieve(id, {
|
||||
relations: ["return_order", "fulfillments", "order", "order.refunds"],
|
||||
})
|
||||
@@ -665,6 +659,7 @@ class ClaimService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(
|
||||
@@ -678,7 +673,8 @@ class ClaimService extends BaseService {
|
||||
|
||||
/**
|
||||
* Gets an order by id.
|
||||
* @param {string} orderId - id of order to retrieve
|
||||
* @param {string} claimId - id of order to retrieve
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async retrieve(claimId, config = {}) {
|
||||
@@ -717,7 +713,7 @@ class ClaimService extends BaseService {
|
||||
const keyPath = `metadata.${key}`
|
||||
return this.orderModel_
|
||||
.updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } })
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
|
||||
class CustomShippingOptionService extends BaseService {
|
||||
constructor({ manager, customShippingOptionRepository }) {
|
||||
super()
|
||||
|
||||
/** @private @const {EntityManager} */
|
||||
this.manager_ = manager
|
||||
|
||||
/** @private @const {CustomShippingOptionRepository} */
|
||||
this.customShippingOptionRepository_ = customShippingOptionRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the service's manager to a given transaction manager
|
||||
* @param {EntityManager} manager - the manager to use
|
||||
* @return {CustomShippingOptionService} a cloned CustomShippingOption service
|
||||
*/
|
||||
withTransaction(manager) {
|
||||
if (!manager) {
|
||||
return this
|
||||
}
|
||||
|
||||
const cloned = new CustomShippingOptionService({
|
||||
manager,
|
||||
customShippingOptionRepository: this.customShippingOptionRepository_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = manager
|
||||
return cloned
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific shipping option.
|
||||
* @param {string} id - the id of the custom shipping option to retrieve.
|
||||
* @param {*} config - any options needed to query for the result.
|
||||
* @return {Promise<CustomShippingOption>} which resolves to the requested custom shipping option.
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
const customShippingOptionRepo = this.manager_.getCustomRepository(
|
||||
this.customShippingOptionRepository_
|
||||
)
|
||||
|
||||
const validatedId = this.validateId_(id)
|
||||
const query = this.buildQuery_({ id: validatedId }, config)
|
||||
|
||||
const customShippingOption = await customShippingOptionRepo.findOne(query)
|
||||
|
||||
if (!customShippingOption) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Custom shipping option with id: ${id} was not found.`
|
||||
)
|
||||
}
|
||||
|
||||
return customShippingOption
|
||||
}
|
||||
|
||||
/** Fetches all custom shipping options related to the given selector
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the configuration used to find the objects. contains relations, skip, and take.
|
||||
* @return {Promise<CustomShippingOption[]>} custom shipping options matching the query
|
||||
*/
|
||||
async list(
|
||||
selector,
|
||||
config = {
|
||||
skip: 0,
|
||||
take: 50,
|
||||
relations: [],
|
||||
}
|
||||
) {
|
||||
const customShippingOptionRepo = this.manager_.getCustomRepository(
|
||||
this.customShippingOptionRepository_
|
||||
)
|
||||
|
||||
const query = this.buildQuery_(selector, config)
|
||||
|
||||
return customShippingOptionRepo.find(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom shipping option associated with a given author
|
||||
* @param {object} data - the custom shipping option to create
|
||||
* @param {*} config - any configurations if needed, including meta data
|
||||
* @return {Promise<CustomShippingOption>} resolves to the creation result
|
||||
*/
|
||||
async create(data, config = { metadata: {} }) {
|
||||
const { metadata } = config
|
||||
|
||||
const { cart_id, shipping_option_id, price } = data
|
||||
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customShippingOptionRepo = manager.getCustomRepository(
|
||||
this.customShippingOptionRepository_
|
||||
)
|
||||
|
||||
const customShippingOption = await customShippingOptionRepo.create({
|
||||
cart_id,
|
||||
shipping_option_id,
|
||||
price,
|
||||
metadata,
|
||||
})
|
||||
const result = await customShippingOptionRepo.save(customShippingOption)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomShippingOptionService
|
||||
@@ -7,7 +7,7 @@ import { Brackets, ILike } from "typeorm"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate customers.
|
||||
* @implements BaseService
|
||||
* @implements {BaseService}
|
||||
*/
|
||||
class CustomerService extends BaseService {
|
||||
static Events = {
|
||||
@@ -60,9 +60,7 @@ class CustomerService extends BaseService {
|
||||
* @return {string} the validated email
|
||||
*/
|
||||
validateEmail_(email) {
|
||||
const schema = Validator.string()
|
||||
.email()
|
||||
.required()
|
||||
const schema = Validator.string().email().required()
|
||||
const { value, error } = schema.validate(email)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
@@ -93,7 +91,7 @@ class CustomerService extends BaseService {
|
||||
* secret a long side a payload with userId and the expiry time for the token,
|
||||
* which is always 15 minutes.
|
||||
* @param {string} customerId - the customer to reset the password for
|
||||
* @returns {string} the generated JSON web token
|
||||
* @return {string} the generated JSON web token
|
||||
*/
|
||||
async generateResetPasswordToken(customerId) {
|
||||
const customer = await this.retrieve(customerId)
|
||||
@@ -122,6 +120,7 @@ class CustomerService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(selector = {}, config = { relations: [], skip: 0, take: 50 }) {
|
||||
@@ -144,11 +143,11 @@ class CustomerService extends BaseService {
|
||||
delete where.first_name
|
||||
delete where.last_name
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
qb.where({ email: ILike(`%${q}%`) })
|
||||
.orWhere({ first_name: ILike(`%${q}%`) })
|
||||
.orWhere({ last_name: ILike(`%${q}%`) })
|
||||
@@ -183,11 +182,11 @@ class CustomerService extends BaseService {
|
||||
delete where.first_name
|
||||
delete where.last_name
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
qb.where({ email: ILike(`%${q}%`) })
|
||||
.orWhere({ first_name: ILike(`%${q}%`) })
|
||||
.orWhere({ last_name: ILike(`%${q}%`) })
|
||||
@@ -214,6 +213,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Gets a customer by id.
|
||||
* @param {string} customerId - the id of the customer to get.
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Customer>} the customer document.
|
||||
*/
|
||||
async retrieve(customerId, config = {}) {
|
||||
@@ -224,7 +224,6 @@ class CustomerService extends BaseService {
|
||||
const query = this.buildQuery_({ id: validatedId }, config)
|
||||
|
||||
const customer = await customerRepo.findOne(query)
|
||||
|
||||
if (!customer) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
@@ -238,6 +237,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Gets a customer by email.
|
||||
* @param {string} email - the email of the customer to get.
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Customer>} the customer document.
|
||||
*/
|
||||
async retrieveByEmail(email, config = {}) {
|
||||
@@ -261,6 +261,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Gets a customer by phone.
|
||||
* @param {string} phone - the phone of the customer to get.
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Customer>} the customer document.
|
||||
*/
|
||||
async retrieveByPhone(phone, config = {}) {
|
||||
@@ -284,7 +285,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Hashes a password
|
||||
* @param {string} password - the value to hash
|
||||
* @return hashed password
|
||||
* @return {Promise<string>} hashed password
|
||||
*/
|
||||
async hashPassword_(password) {
|
||||
const buf = await Scrypt.kdf(password, { logN: 1, r: 1, p: 1 })
|
||||
@@ -300,7 +301,7 @@ class CustomerService extends BaseService {
|
||||
* @return {Promise} the result of create
|
||||
*/
|
||||
async create(customer) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customerRepository = manager.getCustomRepository(
|
||||
this.customerRepository_
|
||||
)
|
||||
@@ -312,7 +313,9 @@ class CustomerService extends BaseService {
|
||||
customer.billing_address = this.validateBillingAddress_(billing_address)
|
||||
}
|
||||
|
||||
const existing = await this.retrieveByEmail(email).catch(err => undefined)
|
||||
const existing = await this.retrieveByEmail(email).catch(
|
||||
(err) => undefined
|
||||
)
|
||||
|
||||
if (existing && existing.has_account) {
|
||||
throw new MedusaError(
|
||||
@@ -353,13 +356,13 @@ class CustomerService extends BaseService {
|
||||
|
||||
/**
|
||||
* Updates a customer.
|
||||
* @param {string} variantId - the id of the variant. Must be a string that
|
||||
* @param {string} customerId - the id of the variant. Must be a string that
|
||||
* can be casted to an ObjectId
|
||||
* @param {object} update - an object with the update values.
|
||||
* @return {Promise} resolves to the update result.
|
||||
*/
|
||||
async update(customerId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customerRepository = manager.getCustomRepository(
|
||||
this.customerRepository_
|
||||
)
|
||||
@@ -370,10 +373,9 @@ class CustomerService extends BaseService {
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
password_hash,
|
||||
metadata,
|
||||
billing_address,
|
||||
billing_address_id,
|
||||
metadata,
|
||||
...rest
|
||||
} = update
|
||||
|
||||
@@ -386,7 +388,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
if ("billing_address_id" in update || "billing_address" in update) {
|
||||
const address = update.billing_address_id || update.billing_address
|
||||
const address = billing_address_id || billing_address
|
||||
await this.updateBillingAddress_(customer, address, addrRepo)
|
||||
}
|
||||
|
||||
@@ -408,12 +410,18 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the customers's billing address.
|
||||
* Updates the customers' billing address.
|
||||
* @param {Customer} customer - the Customer to update
|
||||
* @param {object} address - the value to set the billing address to
|
||||
* @param {Object|string} addressOrId - the value to set the billing address to
|
||||
* @param {Object} addrRepo - address repository
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateBillingAddress_(customer, addressOrId, addrRepo) {
|
||||
if (addressOrId === null) {
|
||||
customer.billing_address_id = null
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof addressOrId === `string`) {
|
||||
addressOrId = await addrRepo.findOne({
|
||||
where: { id: addressOrId },
|
||||
@@ -424,7 +432,6 @@ class CustomerService extends BaseService {
|
||||
|
||||
if (addressOrId.id) {
|
||||
customer.billing_address_id = addressOrId.id
|
||||
customer.billing_address = addressOrId
|
||||
} else {
|
||||
if (customer.billing_address_id) {
|
||||
const addr = await addrRepo.findOne({
|
||||
@@ -443,7 +450,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
async updateAddress(customerId, addressId, address) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const addressRepo = manager.getCustomRepository(this.addressRepository_)
|
||||
|
||||
address.country_code = address.country_code.toLowerCase()
|
||||
@@ -464,7 +471,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
async removeAddress(customerId, addressId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const addressRepo = manager.getCustomRepository(this.addressRepository_)
|
||||
|
||||
// Should not fail, if user does not exist, since delete is idempotent
|
||||
@@ -472,7 +479,9 @@ class CustomerService extends BaseService {
|
||||
where: { id: addressId, customer_id: customerId },
|
||||
})
|
||||
|
||||
if (!address) return Promise.resolve()
|
||||
if (!address) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await addressRepo.softRemove(address)
|
||||
|
||||
@@ -481,7 +490,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
async addAddress(customerId, address) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const addressRepository = manager.getCustomRepository(
|
||||
this.addressRepository_
|
||||
)
|
||||
@@ -493,8 +502,8 @@ class CustomerService extends BaseService {
|
||||
})
|
||||
this.validateBillingAddress_(address)
|
||||
|
||||
let shouldAdd = !customer.shipping_addresses.find(
|
||||
a =>
|
||||
const shouldAdd = !customer.shipping_addresses.find(
|
||||
(a) =>
|
||||
a.country_code.toLowerCase() === address.country_code.toLowerCase() &&
|
||||
a.address_1 === address.address_1 &&
|
||||
a.address_2 === address.address_2 &&
|
||||
@@ -526,13 +535,15 @@ class CustomerService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation.
|
||||
*/
|
||||
async delete(customerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customerRepo = manager.getCustomRepository(this.customerRepository_)
|
||||
|
||||
// Should not fail, if user does not exist, since delete is idempotent
|
||||
const customer = await customerRepo.findOne({ where: { id: customerId } })
|
||||
|
||||
if (!customer) return Promise.resolve()
|
||||
if (!customer) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await customerRepo.softRemove(customer)
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import _ from "lodash"
|
||||
import randomize from "randomatic"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { parse, toSeconds } from "iso8601-duration"
|
||||
import { Brackets, ILike } from "typeorm"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate discounts.
|
||||
* @implements BaseService
|
||||
* @implements {BaseService}
|
||||
*/
|
||||
class DiscountService extends BaseService {
|
||||
constructor({
|
||||
@@ -15,7 +14,6 @@ class DiscountService extends BaseService {
|
||||
discountRuleRepository,
|
||||
giftCardRepository,
|
||||
totalsService,
|
||||
productVariantService,
|
||||
productService,
|
||||
regionService,
|
||||
eventBusService,
|
||||
@@ -78,21 +76,13 @@ class DiscountService extends BaseService {
|
||||
id: Validator.string().optional(),
|
||||
description: Validator.string().optional(),
|
||||
type: Validator.string().required(),
|
||||
value: Validator.number()
|
||||
.min(0)
|
||||
.required(),
|
||||
value: Validator.number().min(0).required(),
|
||||
allocation: Validator.string().required(),
|
||||
valid_for: Validator.array().optional(),
|
||||
created_at: Validator.date().optional(),
|
||||
updated_at: Validator.date()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
deleted_at: Validator.date()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
metadata: Validator.object()
|
||||
.allow(null)
|
||||
.optional(),
|
||||
updated_at: Validator.date().allow(null).optional(),
|
||||
deleted_at: Validator.date().allow(null).optional(),
|
||||
metadata: Validator.object().allow(null).optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(discountRule)
|
||||
@@ -115,6 +105,7 @@ class DiscountService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) {
|
||||
@@ -128,6 +119,7 @@ class DiscountService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async listAndCount(
|
||||
@@ -151,11 +143,11 @@ class DiscountService extends BaseService {
|
||||
|
||||
delete where.code
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
qb.where({ code: ILike(`%${q}%`) })
|
||||
})
|
||||
)
|
||||
@@ -174,16 +166,20 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the result of the create operation
|
||||
*/
|
||||
async create(discount) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||
const ruleRepo = manager.getCustomRepository(this.discountRuleRepository_)
|
||||
|
||||
if (discount.rule?.valid_for) {
|
||||
discount.rule.valid_for = discount.rule.valid_for.map((id) => ({ id }))
|
||||
}
|
||||
|
||||
const validatedRule = this.validateDiscountRule_(discount.rule)
|
||||
|
||||
if (discount.regions) {
|
||||
discount.regions = await Promise.all(
|
||||
discount.regions.map(regionId =>
|
||||
this.regionService_.retrieve(regionId)
|
||||
discount.regions.map((regionId) =>
|
||||
this.regionService_.withTransaction(manager).retrieve(regionId)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -203,6 +199,7 @@ class DiscountService extends BaseService {
|
||||
/**
|
||||
* Gets a discount by id.
|
||||
* @param {string} discountId - id of discount to retrieve
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Discount>} the discount
|
||||
*/
|
||||
async retrieve(discountId, config = {}) {
|
||||
@@ -227,6 +224,7 @@ class DiscountService extends BaseService {
|
||||
/**
|
||||
* Gets a discount by discount code.
|
||||
* @param {string} discountCode - discount code of discount to retrieve
|
||||
* @param {array} relations - list of relations
|
||||
* @return {Promise<Discount>} the discount document
|
||||
*/
|
||||
async retrieveByCode(discountCode, relations = []) {
|
||||
@@ -263,16 +261,25 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async update(discountId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||
|
||||
const discount = await this.retrieve(discountId)
|
||||
|
||||
const { rule, metadata, regions, ...rest } = update
|
||||
|
||||
if (rest.ends_at) {
|
||||
if (discount.starts_at >= new Date(update.ends_at)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`"ends_at" must be greater than "starts_at"`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (regions) {
|
||||
discount.regions = await Promise.all(
|
||||
regions.map(regionId => this.regionService_.retrieve(regionId))
|
||||
regions.map((regionId) => this.regionService_.retrieve(regionId))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -282,6 +289,11 @@ class DiscountService extends BaseService {
|
||||
|
||||
if (rule) {
|
||||
discount.rule = this.validateDiscountRule_(rule)
|
||||
if (rule.valid_for) {
|
||||
discount.rule.valid_for = discount.rule.valid_for.map((id) => ({
|
||||
id,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(rest)) {
|
||||
@@ -296,11 +308,11 @@ class DiscountService extends BaseService {
|
||||
/**
|
||||
* Creates a dynamic code for a discount id.
|
||||
* @param {string} discountId - the id of the discount to create a code for
|
||||
* @param {string} code - the code to identify the discount by
|
||||
* @param {Object} data - the object containing a code to identify the discount by
|
||||
* @return {Promise} the newly created dynamic code
|
||||
*/
|
||||
async createDynamicCode(discountId, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||
|
||||
const discount = await this.retrieve(discountId)
|
||||
@@ -329,6 +341,13 @@ class DiscountService extends BaseService {
|
||||
usage_limit: discount.usage_limit,
|
||||
}
|
||||
|
||||
if (discount.valid_duration) {
|
||||
const lastValidDate = new Date()
|
||||
lastValidDate.setSeconds(
|
||||
lastValidDate.getSeconds() + toSeconds(parse(discount.valid_duration))
|
||||
)
|
||||
toCreate.ends_at = lastValidDate
|
||||
}
|
||||
const created = await discountRepo.create(toCreate)
|
||||
const result = await discountRepo.save(created)
|
||||
return result
|
||||
@@ -342,13 +361,15 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the newly created dynamic code
|
||||
*/
|
||||
async deleteDynamicCode(discountId, code) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||
const discount = await discountRepo.findOne({
|
||||
where: { parent_discount_id: discountId, code },
|
||||
})
|
||||
|
||||
if (!discount) return Promise.resolve()
|
||||
if (!discount) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await discountRepo.softRemove(discount)
|
||||
|
||||
@@ -363,7 +384,7 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addValidProduct(discountId, productId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRuleRepo = manager.getCustomRepository(
|
||||
this.discountRuleRepository_
|
||||
)
|
||||
@@ -374,7 +395,7 @@ class DiscountService extends BaseService {
|
||||
|
||||
const { rule } = discount
|
||||
|
||||
const exists = rule.valid_for.find(p => p.id === productId)
|
||||
const exists = rule.valid_for.find((p) => p.id === productId)
|
||||
// If product is already present, we return early
|
||||
if (exists) {
|
||||
return rule
|
||||
@@ -396,7 +417,7 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removeValidProduct(discountId, productId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRuleRepo = manager.getCustomRepository(
|
||||
this.discountRuleRepository_
|
||||
)
|
||||
@@ -407,13 +428,13 @@ class DiscountService extends BaseService {
|
||||
|
||||
const { rule } = discount
|
||||
|
||||
const exists = rule.valid_for.find(p => p.id === productId)
|
||||
const exists = rule.valid_for.find((p) => p.id === productId)
|
||||
// If product is not present, we return early
|
||||
if (!exists) {
|
||||
return rule
|
||||
}
|
||||
|
||||
rule.valid_for = rule.valid_for.filter(p => p.id !== productId)
|
||||
rule.valid_for = rule.valid_for.filter((p) => p.id !== productId)
|
||||
|
||||
const updated = await discountRuleRepo.save(rule)
|
||||
return updated
|
||||
@@ -427,14 +448,14 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addRegion(discountId, regionId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||
|
||||
const discount = await this.retrieve(discountId, {
|
||||
relations: ["regions"],
|
||||
})
|
||||
|
||||
const exists = discount.regions.find(r => r.id === regionId)
|
||||
const exists = discount.regions.find((r) => r.id === regionId)
|
||||
// If region is already present, we return early
|
||||
if (exists) {
|
||||
return discount
|
||||
@@ -456,20 +477,20 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removeRegion(discountId, regionId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||
|
||||
const discount = await this.retrieve(discountId, {
|
||||
relations: ["regions"],
|
||||
})
|
||||
|
||||
const exists = discount.regions.find(r => r.id === regionId)
|
||||
const exists = discount.regions.find((r) => r.id === regionId)
|
||||
// If region is not present, we return early
|
||||
if (!exists) {
|
||||
return discount
|
||||
}
|
||||
|
||||
discount.regions = discount.regions.filter(r => r.id !== regionId)
|
||||
discount.regions = discount.regions.filter((r) => r.id !== regionId)
|
||||
|
||||
const updated = await discountRepo.save(discount)
|
||||
return updated
|
||||
@@ -482,12 +503,14 @@ class DiscountService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation
|
||||
*/
|
||||
async delete(discountId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const discountRepo = manager.getCustomRepository(this.discountRepository_)
|
||||
|
||||
const discount = await discountRepo.findOne({ where: { id: discountId } })
|
||||
|
||||
if (!discount) return Promise.resolve()
|
||||
if (!discount) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await discountRepo.softRemove(discount)
|
||||
|
||||
@@ -497,7 +520,7 @@ class DiscountService extends BaseService {
|
||||
|
||||
/**
|
||||
* Decorates a discount.
|
||||
* @param {Discount} discount - the discount to decorate.
|
||||
* @param {string} discountId - id of discount to decorate
|
||||
* @param {string[]} fields - the fields to include.
|
||||
* @param {string[]} expandFields - fields to expand.
|
||||
* @return {Discount} return the decorated discount.
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import _ from "lodash"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { Brackets, ILike } from "typeorm"
|
||||
import { Brackets } from "typeorm"
|
||||
|
||||
/**
|
||||
* Handles draft orders
|
||||
* @implements BaseService
|
||||
* @implements {BaseService}
|
||||
*/
|
||||
class DraftOrderService extends BaseService {
|
||||
static Events = {
|
||||
@@ -106,7 +105,7 @@ class DraftOrderService extends BaseService {
|
||||
/**
|
||||
* Retrieves a draft order based on its associated cart id
|
||||
* @param {string} cartId - cart id that the draft orders's cart has
|
||||
* * @param {object} config - query object for findOne
|
||||
* @param {object} config - query object for findOne
|
||||
* @return {Promise<DraftOrder>} the draft order
|
||||
*/
|
||||
async retrieveByCartId(cartId, config = {}) {
|
||||
@@ -134,7 +133,7 @@ class DraftOrderService extends BaseService {
|
||||
* @return {Promise} empty promise
|
||||
*/
|
||||
async delete(draftOrderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const draftOrderRepo = manager.getCustomRepository(
|
||||
this.draftOrderRepository_
|
||||
)
|
||||
@@ -143,7 +142,9 @@ class DraftOrderService extends BaseService {
|
||||
where: { id: draftOrderId },
|
||||
})
|
||||
|
||||
if (!draftOrder) return Promise.resolve()
|
||||
if (!draftOrder) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await draftOrderRepo.remove(draftOrder)
|
||||
|
||||
@@ -185,11 +186,11 @@ class DraftOrderService extends BaseService {
|
||||
},
|
||||
}
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
qb.where(`cart.email ILIKE :q`, {
|
||||
q: `%${q}%`,
|
||||
}).orWhere(`draft_order.display_id::varchar(255) ILIKE :dId`, {
|
||||
@@ -230,7 +231,7 @@ class DraftOrderService extends BaseService {
|
||||
* @return {Promise<DraftOrder>} the created draft order
|
||||
*/
|
||||
async create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const draftOrderRepo = manager.getCustomRepository(
|
||||
this.draftOrderRepository_
|
||||
)
|
||||
@@ -250,10 +251,10 @@ class DraftOrderService extends BaseService {
|
||||
}
|
||||
|
||||
const {
|
||||
items,
|
||||
shipping_methods,
|
||||
discounts,
|
||||
no_notification_order,
|
||||
items,
|
||||
...rest
|
||||
} = data
|
||||
|
||||
@@ -332,7 +333,7 @@ class DraftOrderService extends BaseService {
|
||||
* @return {Promise} the created order
|
||||
*/
|
||||
async registerCartCompletion(doId, orderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const draftOrderRepo = manager.getCustomRepository(
|
||||
this.draftOrderRepository_
|
||||
)
|
||||
@@ -350,10 +351,10 @@ class DraftOrderService extends BaseService {
|
||||
* Updates a draft order with the given data
|
||||
* @param {String} doId - id of the draft order
|
||||
* @param {DraftOrder} data - values to update the order with
|
||||
* @returns {Promise<DraftOrder>} the updated draft order
|
||||
* @return {Promise<DraftOrder>} the updated draft order
|
||||
*/
|
||||
async update(doId, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const doRepo = manager.getCustomRepository(this.draftOrderRepository_)
|
||||
const draftOrder = await this.retrieve(doId)
|
||||
let touched = false
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import _ from "lodash"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
/**
|
||||
* Handles Fulfillments
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class FulfillmentService extends BaseService {
|
||||
constructor({
|
||||
@@ -61,7 +60,7 @@ class FulfillmentService extends BaseService {
|
||||
}
|
||||
|
||||
partitionItems_(shippingMethods, items) {
|
||||
let partitioned = []
|
||||
const partitioned = []
|
||||
// partition order items to their dedicated shipping method
|
||||
for (const method of shippingMethods) {
|
||||
const temp = { shipping_method: method }
|
||||
@@ -95,12 +94,12 @@ class FulfillmentService extends BaseService {
|
||||
async getFulfillmentItems_(order, items, transformer) {
|
||||
const toReturn = await Promise.all(
|
||||
items.map(async ({ item_id, quantity }) => {
|
||||
const item = order.items.find(i => i.id === item_id)
|
||||
const item = order.items.find((i) => i.id === item_id)
|
||||
return transformer(item, quantity)
|
||||
})
|
||||
)
|
||||
|
||||
return toReturn.filter(i => !!i)
|
||||
return toReturn.filter((i) => !!i)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,6 +136,7 @@ class FulfillmentService extends BaseService {
|
||||
/**
|
||||
* Retrieves a fulfillment by its id.
|
||||
* @param {string} id - the id of the fulfillment to retrieve
|
||||
* @param {object} config - optional values to include with fulfillmentRepository query
|
||||
* @return {Fulfillment} the fulfillment
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
@@ -165,11 +165,11 @@ class FulfillmentService extends BaseService {
|
||||
* those partitions.
|
||||
* @param {Order} order - order to create fulfillment for
|
||||
* @param {{ item_id: string, quantity: number}[]} itemsToFulfill - the items in the order to fulfill
|
||||
* @param {object} metadata - potential metadata to add
|
||||
* @param {object} custom - potential custom values to add
|
||||
* @return {Fulfillment[]} the created fulfillments
|
||||
*/
|
||||
async createFulfillment(order, itemsToFulfill, custom = {}) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const fulfillmentRepository = manager.getCustomRepository(
|
||||
this.fulfillmentRepository_
|
||||
)
|
||||
@@ -190,18 +190,19 @@ class FulfillmentService extends BaseService {
|
||||
const ful = fulfillmentRepository.create({
|
||||
...custom,
|
||||
provider_id: shipping_method.shipping_option.provider_id,
|
||||
items: items.map(i => ({ item_id: i.id, quantity: i.quantity })),
|
||||
items: items.map((i) => ({ item_id: i.id, quantity: i.quantity })),
|
||||
data: {},
|
||||
})
|
||||
|
||||
let result = await fulfillmentRepository.save(ful)
|
||||
const result = await fulfillmentRepository.save(ful)
|
||||
|
||||
result.data = await this.fulfillmentProviderService_.createFulfillment(
|
||||
shipping_method,
|
||||
items,
|
||||
{ ...order },
|
||||
{ ...result }
|
||||
)
|
||||
result.data =
|
||||
await this.fulfillmentProviderService_.createFulfillment(
|
||||
shipping_method,
|
||||
items,
|
||||
{ ...order },
|
||||
{ ...result }
|
||||
)
|
||||
|
||||
return fulfillmentRepository.save(result)
|
||||
})
|
||||
@@ -220,7 +221,7 @@ class FulfillmentService extends BaseService {
|
||||
*
|
||||
*/
|
||||
cancelFulfillment(fulfillmentOrId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
let id = fulfillmentOrId
|
||||
if (typeof fulfillmentOrId === "object") {
|
||||
id = fulfillmentOrId.id
|
||||
@@ -260,9 +261,9 @@ class FulfillmentService extends BaseService {
|
||||
|
||||
/**
|
||||
* Creates a shipment by marking a fulfillment as shipped. Adds
|
||||
* tracking numbers and potentially more metadata.
|
||||
* tracking links and potentially more metadata.
|
||||
* @param {Order} fulfillmentId - the fulfillment to ship
|
||||
* @param {TrackingLink[]} trackingNumbers - tracking numbers for the shipment
|
||||
* @param {TrackingLink[]} trackingLinks - tracking links for the shipment
|
||||
* @param {object} config - potential configuration settings, such as no_notification and metadata
|
||||
* @return {Fulfillment} the shipped fulfillment
|
||||
*/
|
||||
@@ -276,7 +277,7 @@ class FulfillmentService extends BaseService {
|
||||
) {
|
||||
const { metadata, no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const fulfillmentRepository = manager.getCustomRepository(
|
||||
this.fulfillmentRepository_
|
||||
)
|
||||
@@ -298,7 +299,7 @@ class FulfillmentService extends BaseService {
|
||||
const now = new Date()
|
||||
fulfillment.shipped_at = now
|
||||
|
||||
fulfillment.tracking_links = trackingLinks.map(tl =>
|
||||
fulfillment.tracking_links = trackingLinks.map((tl) =>
|
||||
trackingLinkRepo.create(tl)
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Brackets } from "typeorm"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate gift cards.
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class GiftCardService extends BaseService {
|
||||
static Events = {
|
||||
@@ -101,7 +101,7 @@ class GiftCardService extends BaseService {
|
||||
.select(["gift_card.id"])
|
||||
.where(where)
|
||||
.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
return qb
|
||||
.where(`gift_card.code ILIKE :q`, { q: `%${q}%` })
|
||||
.orWhere(`display_id::varchar(255) ILIKE :dId`, { dId: `${q}` })
|
||||
@@ -111,14 +111,14 @@ class GiftCardService extends BaseService {
|
||||
|
||||
return giftCardRepo.findWithRelations(
|
||||
rels,
|
||||
raw.map(i => i.id)
|
||||
raw.map((i) => i.id)
|
||||
)
|
||||
}
|
||||
return giftCardRepo.findWithRelations(rels, query)
|
||||
}
|
||||
|
||||
async createTransaction(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const gctRepo = manager.getCustomRepository(this.giftCardTransactionRepo_)
|
||||
const created = gctRepo.create(data)
|
||||
const saved = await gctRepo.save(created)
|
||||
@@ -132,7 +132,7 @@ class GiftCardService extends BaseService {
|
||||
* @return {Promise<GiftCard>} the result of the create operation
|
||||
*/
|
||||
async create(giftCard) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||
|
||||
if (!giftCard.region_id) {
|
||||
@@ -169,6 +169,7 @@ class GiftCardService extends BaseService {
|
||||
/**
|
||||
* Gets a gift card by id.
|
||||
* @param {string} giftCardId - id of gift card to retrieve
|
||||
* @param {object} config - optional values to include with gift card query
|
||||
* @return {Promise<GiftCard>} the gift card
|
||||
*/
|
||||
async retrieve(giftCardId, config = {}) {
|
||||
@@ -244,7 +245,7 @@ class GiftCardService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async update(giftCardId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||
|
||||
const giftCard = await this.retrieve(giftCardId)
|
||||
@@ -285,12 +286,14 @@ class GiftCardService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation
|
||||
*/
|
||||
async delete(giftCardId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const giftCardRepo = manager.getCustomRepository(this.giftCardRepository_)
|
||||
|
||||
const giftCard = await giftCardRepo.findOne({ where: { id: giftCardId } })
|
||||
|
||||
if (!giftCard) return Promise.resolve()
|
||||
if (!giftCard) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await giftCardRepo.softRemove(giftCard)
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import _ from "lodash"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate line items.
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class LineItemService extends BaseService {
|
||||
constructor({
|
||||
@@ -67,6 +66,7 @@ class LineItemService extends BaseService {
|
||||
/**
|
||||
* Retrieves a line item by its id.
|
||||
* @param {string} id - the id of the line item to retrieve
|
||||
* @param {object} config - the config to be used at query building
|
||||
* @return {LineItem} the line item
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
@@ -90,7 +90,7 @@ class LineItemService extends BaseService {
|
||||
}
|
||||
|
||||
async generate(variantId, regionId, quantity, config = {}) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const variant = await this.productVariantService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(variantId, {
|
||||
@@ -142,7 +142,7 @@ class LineItemService extends BaseService {
|
||||
* @return {LineItem} the created line item
|
||||
*/
|
||||
async create(lineItem) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const lineItemRepository = manager.getCustomRepository(
|
||||
this.lineItemRepository_
|
||||
)
|
||||
@@ -160,7 +160,7 @@ class LineItemService extends BaseService {
|
||||
* @return {LineItem} the update line item
|
||||
*/
|
||||
async update(id, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const lineItemRepository = manager.getCustomRepository(
|
||||
this.lineItemRepository_
|
||||
)
|
||||
@@ -188,14 +188,16 @@ class LineItemService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation
|
||||
*/
|
||||
async delete(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const lineItemRepository = manager.getCustomRepository(
|
||||
this.lineItemRepository_
|
||||
)
|
||||
|
||||
const lineItem = await lineItemRepository.findOne({ where: { id } })
|
||||
|
||||
if (!lineItem) return Promise.resolve()
|
||||
if (!lineItem) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await lineItemRepository.remove(lineItem)
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import _ from "lodash"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate orchestrate notifications.
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class NotificationService extends BaseService {
|
||||
constructor(container) {
|
||||
@@ -34,6 +33,7 @@ class NotificationService extends BaseService {
|
||||
/**
|
||||
* Registers an attachment generator to the service. The generator can be
|
||||
* used to generate on demand invoices or other documents.
|
||||
* @param {object} service
|
||||
*/
|
||||
registerAttachmentGenerator(service) {
|
||||
this.attachmentGenerator_ = service
|
||||
@@ -42,16 +42,18 @@ class NotificationService extends BaseService {
|
||||
/**
|
||||
* Sets the service's manager to a given transaction manager.
|
||||
* @param {EntityManager} transactionManager - the manager to use
|
||||
* return {NotificationService} a cloned notification service
|
||||
* @return {NotificationService} a cloned notification service
|
||||
*/
|
||||
withTransaction(transactionManager) {
|
||||
if (!transactionManager) {
|
||||
return this
|
||||
}
|
||||
|
||||
const cloned = new LineItemService({
|
||||
const cloned = new NotificationService({
|
||||
manager: transactionManager,
|
||||
notificationProviderRepository: this.notificationProviderRepository_,
|
||||
notificationRepository: this.notificationRepository_,
|
||||
logger: this.logger_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -93,6 +95,7 @@ class NotificationService extends BaseService {
|
||||
/**
|
||||
* Retrieves a notification with a given id
|
||||
* @param {string} id - the id of the notification
|
||||
* @param {object} config - the configuration to apply to the query
|
||||
* @return {Notification} the notification
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
@@ -158,6 +161,7 @@ class NotificationService extends BaseService {
|
||||
* order to allow for resends. Will log any errors that are encountered.
|
||||
* @param {string} eventName - the event to handle
|
||||
* @param {object} data - the data the event was sent with
|
||||
* @return {Promise} - the result of notification subscribed
|
||||
*/
|
||||
handleEvent(eventName, data) {
|
||||
const subs = this.subscribers_[eventName]
|
||||
@@ -169,8 +173,8 @@ class NotificationService extends BaseService {
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
subs.map(async providerId => {
|
||||
return this.send(eventName, data, providerId).catch(err => {
|
||||
subs.map(async (providerId) => {
|
||||
return this.send(eventName, data, providerId).catch((err) => {
|
||||
console.log(err)
|
||||
this.logger_.warn(
|
||||
`An error occured while ${providerId} was processing a notification for ${eventName}: ${err.message}`
|
||||
|
||||
@@ -178,6 +178,7 @@ class OrderService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the config to be used for find
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(
|
||||
@@ -187,9 +188,8 @@ class OrderService extends BaseService {
|
||||
const orderRepo = this.manager_.getCustomRepository(this.orderRepository_)
|
||||
const query = this.buildQuery_(selector, config)
|
||||
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
config
|
||||
)
|
||||
const { select, relations, totalsToSelect } =
|
||||
this.transformQueryForTotals_(config)
|
||||
|
||||
if (select && select.length) {
|
||||
query.select = select
|
||||
@@ -201,7 +201,7 @@ class OrderService extends BaseService {
|
||||
|
||||
const raw = await orderRepo.find(query)
|
||||
|
||||
return raw.map(r => this.decorateTotals_(r, totalsToSelect))
|
||||
return raw.map((r) => this.decorateTotals_(r, totalsToSelect))
|
||||
}
|
||||
|
||||
async listAndCount(
|
||||
@@ -231,11 +231,11 @@ class OrderService extends BaseService {
|
||||
},
|
||||
}
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
qb.where(`shipping_address.first_name ILIKE :qfn`, {
|
||||
qfn: `%${q}%`,
|
||||
})
|
||||
@@ -246,20 +246,19 @@ class OrderService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
config
|
||||
)
|
||||
const { select, relations, totalsToSelect } =
|
||||
this.transformQueryForTotals_(config)
|
||||
|
||||
if (select && select.length) {
|
||||
query.select = select
|
||||
}
|
||||
|
||||
let rels = relations
|
||||
const rels = relations
|
||||
delete query.relations
|
||||
|
||||
const raw = await orderRepo.findWithRelations(rels, query)
|
||||
const count = await orderRepo.count(query)
|
||||
const orders = raw.map(r => this.decorateTotals_(r, totalsToSelect))
|
||||
const orders = raw.map((r) => this.decorateTotals_(r, totalsToSelect))
|
||||
|
||||
return [orders, count]
|
||||
}
|
||||
@@ -287,15 +286,20 @@ class OrderService extends BaseService {
|
||||
"refundable_amount",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
]
|
||||
|
||||
const totalsToSelect = select.filter(v => totalFields.includes(v))
|
||||
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
||||
if (totalsToSelect.length > 0) {
|
||||
const relationSet = new Set(relations)
|
||||
relationSet.add("items")
|
||||
relationSet.add("swaps")
|
||||
relationSet.add("swaps.additional_items")
|
||||
relationSet.add("claims")
|
||||
relationSet.add("claims.additional_items")
|
||||
relationSet.add("discounts")
|
||||
relationSet.add("discounts.rule")
|
||||
relationSet.add("discounts.rule.valid_for")
|
||||
relationSet.add("gift_cards")
|
||||
relationSet.add("gift_card_transactions")
|
||||
relationSet.add("refunds")
|
||||
@@ -303,7 +307,7 @@ class OrderService extends BaseService {
|
||||
relationSet.add("region")
|
||||
relations = [...relationSet]
|
||||
|
||||
select = select.filter(v => !totalFields.includes(v))
|
||||
select = select.filter((v) => !totalFields.includes(v))
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -316,15 +320,15 @@ class OrderService extends BaseService {
|
||||
/**
|
||||
* Gets an order by id.
|
||||
* @param {string} orderId - id of order to retrieve
|
||||
* @param {Object} config - config of order to retrieve
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async retrieve(orderId, config = {}) {
|
||||
const orderRepo = this.manager_.getCustomRepository(this.orderRepository_)
|
||||
const validatedId = this.validateId_(orderId)
|
||||
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
config
|
||||
)
|
||||
const { select, relations, totalsToSelect } =
|
||||
this.transformQueryForTotals_(config)
|
||||
|
||||
const query = {
|
||||
where: { id: validatedId },
|
||||
@@ -355,14 +359,14 @@ class OrderService extends BaseService {
|
||||
/**
|
||||
* Gets an order by cart id.
|
||||
* @param {string} cartId - cart id to find order
|
||||
* @param {Object} config - the config to be used to find order
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async retrieveByCartId(cartId, config = {}) {
|
||||
const orderRepo = this.manager_.getCustomRepository(this.orderRepository_)
|
||||
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
config
|
||||
)
|
||||
const { select, relations, totalsToSelect } =
|
||||
this.transformQueryForTotals_(config)
|
||||
|
||||
const query = {
|
||||
where: { cart_id: cartId },
|
||||
@@ -395,7 +399,7 @@ class OrderService extends BaseService {
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async existsByCartId(cartId) {
|
||||
const order = await this.retrieveByCartId(cartId).catch(_ => undefined)
|
||||
const order = await this.retrieveByCartId(cartId).catch((_) => undefined)
|
||||
if (!order) {
|
||||
return false
|
||||
}
|
||||
@@ -407,7 +411,7 @@ class OrderService extends BaseService {
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async completeOrder(orderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId)
|
||||
|
||||
if (order.status === "canceled") {
|
||||
@@ -426,7 +430,7 @@ class OrderService extends BaseService {
|
||||
}
|
||||
)
|
||||
|
||||
await completeOrderJob.finished().catch(error => {
|
||||
await completeOrderJob.finished().catch((error) => {
|
||||
throw error
|
||||
})
|
||||
|
||||
@@ -443,7 +447,7 @@ class OrderService extends BaseService {
|
||||
* @return {Promise} resolves to the creation result.
|
||||
*/
|
||||
async createFromCart(cartId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.cartService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(cartId, {
|
||||
@@ -453,6 +457,8 @@ class OrderService extends BaseService {
|
||||
"payment",
|
||||
"items",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"gift_cards",
|
||||
"shipping_methods",
|
||||
],
|
||||
@@ -615,6 +621,7 @@ class OrderService extends BaseService {
|
||||
* @param {string} fulfillmentId - the fulfillment that has now been shipped
|
||||
* @param {TrackingLink[]} trackingLinks - array of tracking numebers
|
||||
* associated with the shipment
|
||||
* @param {Object} config - the config of the order that has been shipped
|
||||
* @param {Dictionary<String, String>} metadata - optional metadata to add to
|
||||
* the fulfillment
|
||||
* @return {order} the resulting order following the update.
|
||||
@@ -630,7 +637,7 @@ class OrderService extends BaseService {
|
||||
) {
|
||||
const { metadata, no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId, { relations: ["items"] })
|
||||
const shipment = await this.fulfillmentService_.retrieve(fulfillmentId)
|
||||
|
||||
@@ -662,7 +669,7 @@ class OrderService extends BaseService {
|
||||
|
||||
order.fulfillment_status = "shipped"
|
||||
for (const item of order.items) {
|
||||
const shipped = shipmentRes.items.find(si => si.item_id === item.id)
|
||||
const shipped = shipmentRes.items.find((si) => si.item_id === item.id)
|
||||
if (shipped) {
|
||||
const shippedQty = (item.shipped_quantity || 0) + shipped.quantity
|
||||
if (shippedQty !== item.quantity) {
|
||||
@@ -696,11 +703,11 @@ class OrderService extends BaseService {
|
||||
|
||||
/**
|
||||
* Creates an order
|
||||
* @param {object} order - the order to create
|
||||
* @param {object} data - the data to create an order
|
||||
* @return {Promise} resolves to the creation result.
|
||||
*/
|
||||
async create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const orderRepo = manager.getCustomRepository(this.orderRepository_)
|
||||
const order = orderRepo.create(data)
|
||||
const result = await orderRepo.save(order)
|
||||
@@ -716,7 +723,7 @@ class OrderService extends BaseService {
|
||||
|
||||
/**
|
||||
* Updates the order's billing address.
|
||||
* @param {string} orderId - the id of the order to update
|
||||
* @param {object} order - the order to update
|
||||
* @param {object} address - the value to set the billing address to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
@@ -751,7 +758,7 @@ class OrderService extends BaseService {
|
||||
|
||||
/**
|
||||
* Updates the order's shipping address.
|
||||
* @param {string} orderId - the id of the order to update
|
||||
* @param {object} order - the order to update
|
||||
* @param {object} address - the value to set the shipping address to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
@@ -783,7 +790,7 @@ class OrderService extends BaseService {
|
||||
}
|
||||
|
||||
async addShippingMethod(orderId, optionId, data, config = {}) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId, {
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
@@ -841,7 +848,7 @@ class OrderService extends BaseService {
|
||||
* @return {Promise} resolves to the update result.
|
||||
*/
|
||||
async update(orderId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId)
|
||||
|
||||
if (order.status === "canceled") {
|
||||
@@ -870,13 +877,7 @@ class OrderService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
metadata,
|
||||
items,
|
||||
billing_address,
|
||||
shipping_address,
|
||||
...rest
|
||||
} = update
|
||||
const { ...rest } = update
|
||||
|
||||
if ("metadata" in update) {
|
||||
order.metadata = this.setMetadata_(order, update.metadata)
|
||||
@@ -928,7 +929,7 @@ class OrderService extends BaseService {
|
||||
* @return {Promise} result of the update operation.
|
||||
*/
|
||||
async cancel(orderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId, {
|
||||
relations: [
|
||||
"fulfillments",
|
||||
@@ -948,16 +949,16 @@ class OrderService extends BaseService {
|
||||
}
|
||||
|
||||
const throwErrorIf = (arr, pred, type) =>
|
||||
arr?.filter(pred).find(_ => {
|
||||
arr?.filter(pred).find((_) => {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_ALLOWED,
|
||||
`All ${type} must be canceled before canceling an order`
|
||||
)
|
||||
})
|
||||
const notCanceled = o => !o.canceled_at
|
||||
const notCanceled = (o) => !o.canceled_at
|
||||
|
||||
throwErrorIf(order.fulfillments, notCanceled, "fulfillments")
|
||||
throwErrorIf(order.returns, r => r.status !== "canceled", "returns")
|
||||
throwErrorIf(order.returns, (r) => r.status !== "canceled", "returns")
|
||||
throwErrorIf(order.swaps, notCanceled, "swaps")
|
||||
throwErrorIf(order.claims, notCanceled, "claims")
|
||||
|
||||
@@ -996,7 +997,7 @@ class OrderService extends BaseService {
|
||||
* @return {Promise} result of the update operation.
|
||||
*/
|
||||
async capturePayment(orderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const orderRepo = manager.getCustomRepository(this.orderRepository_)
|
||||
const order = await this.retrieve(orderId, { relations: ["payments"] })
|
||||
|
||||
@@ -1013,7 +1014,7 @@ class OrderService extends BaseService {
|
||||
const result = await this.paymentProviderService_
|
||||
.withTransaction(manager)
|
||||
.capturePayment(p)
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(OrderService.Events.PAYMENT_CAPTURE_FAILED, {
|
||||
@@ -1035,7 +1036,7 @@ class OrderService extends BaseService {
|
||||
}
|
||||
|
||||
order.payments = payments
|
||||
order.payment_status = payments.every(p => p.captured_at !== null)
|
||||
order.payment_status = payments.every((p) => p.captured_at !== null)
|
||||
? "captured"
|
||||
: "requires_action"
|
||||
|
||||
@@ -1091,6 +1092,8 @@ class OrderService extends BaseService {
|
||||
* we need to partition the order items, such that they can be sent
|
||||
* to their respective fulfillment provider.
|
||||
* @param {string} orderId - id of order to cancel.
|
||||
* @param {Object} itemsToFulfill - items to fulfil.
|
||||
* @param {Object} config - the config to cancel.
|
||||
* @return {Promise} result of the update operation.
|
||||
*/
|
||||
async createFulfillment(
|
||||
@@ -1103,7 +1106,7 @@ class OrderService extends BaseService {
|
||||
) {
|
||||
const { metadata, no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
// NOTE: we are telling the service to calculate all totals for us which
|
||||
// will add to what is fetched from the database. We want this to happen
|
||||
// so that we get all order details. These will thereafter be forwarded
|
||||
@@ -1119,6 +1122,8 @@ class OrderService extends BaseService {
|
||||
],
|
||||
relations: [
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"region",
|
||||
"fulfillments",
|
||||
"shipping_address",
|
||||
@@ -1163,7 +1168,7 @@ class OrderService extends BaseService {
|
||||
// Update all line items to reflect fulfillment
|
||||
for (const item of order.items) {
|
||||
const fulfillmentItem = successfullyFulfilled.find(
|
||||
f => item.id === f.item_id
|
||||
(f) => item.id === f.item_id
|
||||
)
|
||||
|
||||
if (fulfillmentItem) {
|
||||
@@ -1211,10 +1216,10 @@ class OrderService extends BaseService {
|
||||
/**
|
||||
* Cancels a fulfillment (if related to an order)
|
||||
* @param {string} fulfillmentId - the ID of the fulfillment to cancel
|
||||
* @returns updated order
|
||||
* @return {Promise} updated order
|
||||
*/
|
||||
async cancelFulfillment(fulfillmentId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const canceled = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.cancelFulfillment(fulfillmentId)
|
||||
@@ -1258,12 +1263,12 @@ class OrderService extends BaseService {
|
||||
async getFulfillmentItems_(order, items, transformer) {
|
||||
const toReturn = await Promise.all(
|
||||
items.map(async ({ item_id, quantity }) => {
|
||||
const item = order.items.find(i => i.id.equals(item_id))
|
||||
const item = order.items.find((i) => i.id.equals(item_id))
|
||||
return transformer(item, quantity)
|
||||
})
|
||||
)
|
||||
|
||||
return toReturn.filter(i => !!i)
|
||||
return toReturn.filter((i) => !!i)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1273,7 +1278,7 @@ class OrderService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async archive(orderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId)
|
||||
|
||||
if (order.status !== ("completed" || "refunded")) {
|
||||
@@ -1292,6 +1297,12 @@ class OrderService extends BaseService {
|
||||
|
||||
/**
|
||||
* Refunds a given amount back to the customer.
|
||||
* @param {string} orderId - id of the order to refund.
|
||||
* @param {float} refundAmount - the amount to refund.
|
||||
* @param {string} reason - the reason to refund.
|
||||
* @param {string} note - note for refund.
|
||||
* @param {Object} config - the config for refund.
|
||||
* @return {Promise} the result of the refund operation.
|
||||
*/
|
||||
async createRefund(
|
||||
orderId,
|
||||
@@ -1304,7 +1315,7 @@ class OrderService extends BaseService {
|
||||
) {
|
||||
const { no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId, {
|
||||
select: ["refundable_amount", "total", "refunded_total"],
|
||||
relations: ["payments"],
|
||||
@@ -1374,7 +1385,7 @@ class OrderService extends BaseService {
|
||||
}
|
||||
|
||||
if (totalsFields.includes("items.refundable")) {
|
||||
order.items = order.items.map(i => ({
|
||||
order.items = order.items.map((i) => ({
|
||||
...i,
|
||||
refundable: this.totalsService_.getLineItemRefund(order, {
|
||||
...i,
|
||||
@@ -1389,7 +1400,23 @@ class OrderService extends BaseService {
|
||||
order.swaps.length
|
||||
) {
|
||||
for (const s of order.swaps) {
|
||||
s.additional_items = s.additional_items.map(i => ({
|
||||
s.additional_items = s.additional_items.map((i) => ({
|
||||
...i,
|
||||
refundable: this.totalsService_.getLineItemRefund(order, {
|
||||
...i,
|
||||
quantity: i.quantity - (i.returned_quantity || 0),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
totalsFields.includes("claims.additional_items.refundable") &&
|
||||
order.claims &&
|
||||
order.claims.length
|
||||
) {
|
||||
for (const c of order.claims) {
|
||||
c.additional_items = c.additional_items.map((i) => ({
|
||||
...i,
|
||||
refundable: this.totalsService_.getLineItemRefund(order, {
|
||||
...i,
|
||||
@@ -1412,10 +1439,11 @@ class OrderService extends BaseService {
|
||||
* mismatches.
|
||||
* @param {string} orderId - the order to return.
|
||||
* @param {object} receivedReturn - the received return
|
||||
* @param {float} customRefundAmount - the custom refund amount return
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async registerReturnReceived(orderId, receivedReturn, customRefundAmount) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const order = await this.retrieve(orderId, {
|
||||
select: ["total", "refunded_total", "refundable_amount"],
|
||||
relations: ["items", "returns", "payments"],
|
||||
@@ -1435,7 +1463,7 @@ class OrderService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
let refundAmount = customRefundAmount || receivedReturn.refund_amount
|
||||
const refundAmount = customRefundAmount || receivedReturn.refund_amount
|
||||
|
||||
const orderRepo = manager.getCustomRepository(this.orderRepository_)
|
||||
|
||||
@@ -1504,7 +1532,7 @@ class OrderService extends BaseService {
|
||||
const keyPath = `metadata.${key}`
|
||||
return this.orderModel_
|
||||
.updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } })
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} resolves to the creation result.
|
||||
*/
|
||||
async create(productOrProductId, variant) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const productRepo = manager.getCustomRepository(this.productRepository_)
|
||||
const variantRepo = manager.getCustomRepository(
|
||||
this.productVariantRepository_
|
||||
@@ -148,8 +148,8 @@ class ProductVariantService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
product.options.forEach(option => {
|
||||
if (!variant.options.find(vo => option.id === vo.option_id)) {
|
||||
product.options.forEach((option) => {
|
||||
if (!variant.options.find((vo) => option.id === vo.option_id)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Variant options do not contain value for ${option.title}`
|
||||
@@ -158,10 +158,10 @@ class ProductVariantService extends BaseService {
|
||||
})
|
||||
|
||||
let variantExists = undefined
|
||||
variantExists = product.variants.find(v => {
|
||||
return v.options.every(option => {
|
||||
variantExists = product.variants.find((v) => {
|
||||
return v.options.every((option) => {
|
||||
const variantOption = variant.options.find(
|
||||
o => option.option_id === o.option_id
|
||||
(o) => option.option_id === o.option_id
|
||||
)
|
||||
|
||||
return option.value === variantOption.value
|
||||
@@ -220,7 +220,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise}
|
||||
*/
|
||||
async publish(variantId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const variantRepo = manager.getCustomRepository(
|
||||
this.productVariantRepository_
|
||||
)
|
||||
@@ -252,7 +252,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} resolves to the update result.
|
||||
*/
|
||||
async update(variantOrVariantId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const variantRepo = manager.getCustomRepository(
|
||||
this.productVariantRepository_
|
||||
)
|
||||
@@ -328,7 +328,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async setCurrencyPrice(variantId, price) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const moneyAmountRepo = manager.getCustomRepository(
|
||||
this.moneyAmountRepository_
|
||||
)
|
||||
@@ -367,7 +367,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {number} the price specific to the region
|
||||
*/
|
||||
async getRegionPrice(variantId, regionId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const moneyAmountRepo = manager.getCustomRepository(
|
||||
this.moneyAmountRepository_
|
||||
)
|
||||
@@ -415,7 +415,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async setRegionPrice(variantId, price) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const moneyAmountRepo = manager.getCustomRepository(
|
||||
this.moneyAmountRepository_
|
||||
)
|
||||
@@ -452,7 +452,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} the result of the update operation.
|
||||
*/
|
||||
async updateOptionValue(variantId, optionId, optionValue) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const productOptionValueRepo = manager.getCustomRepository(
|
||||
this.productOptionValueRepository_
|
||||
)
|
||||
@@ -487,7 +487,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} the result of the update operation.
|
||||
*/
|
||||
async addOptionValue(variantId, optionId, optionValue) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const productOptionValueRepo = manager.getCustomRepository(
|
||||
this.productOptionValueRepository_
|
||||
)
|
||||
@@ -511,7 +511,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} empty promise
|
||||
*/
|
||||
async deleteOptionValue(variantId, optionId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const productOptionValueRepo = manager.getCustomRepository(
|
||||
this.productOptionValueRepository_
|
||||
)
|
||||
@@ -561,7 +561,7 @@ class ProductVariantService extends BaseService {
|
||||
},
|
||||
}
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where).andWhere([
|
||||
{ sku: ILike(`%${q}%`) },
|
||||
{ title: ILike(`%${q}%`) },
|
||||
@@ -581,7 +581,7 @@ class ProductVariantService extends BaseService {
|
||||
* @return {Promise} empty promise
|
||||
*/
|
||||
async delete(variantId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const variantRepo = manager.getCustomRepository(
|
||||
this.productVariantRepository_
|
||||
)
|
||||
@@ -592,10 +592,12 @@ class ProductVariantService extends BaseService {
|
||||
|
||||
await variantRepo.softRemove(variant)
|
||||
|
||||
await this.eventBus_.emit(ProductVariantService.Events.DELETED, {
|
||||
id: variant.id,
|
||||
product_id: variant.product_id,
|
||||
})
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(ProductVariantService.Events.DELETED, {
|
||||
id: variant.id,
|
||||
product_id: variant.product_id,
|
||||
})
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
@@ -301,7 +301,7 @@ class ProductService extends BaseService {
|
||||
|
||||
let product = productRepo.create(rest)
|
||||
|
||||
if (images && images.length) {
|
||||
if (images) {
|
||||
product.images = await this.upsertImages_(images)
|
||||
}
|
||||
|
||||
@@ -386,11 +386,11 @@ class ProductService extends BaseService {
|
||||
...rest
|
||||
} = update
|
||||
|
||||
if (!product.thumbnail && !update.thumbnail && images && images.length) {
|
||||
if (!product.thumbnail && !update.thumbnail && images?.length) {
|
||||
product.thumbnail = images[0]
|
||||
}
|
||||
|
||||
if (images && images.length) {
|
||||
if (images) {
|
||||
product.images = await this.upsertImages_(images)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { countries } from "../utils/countries"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate regions.
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class RegionService extends BaseService {
|
||||
static Events = {
|
||||
UPDATED: "region.updated",
|
||||
CREATED: "region.created",
|
||||
DELETED: "region.deleted",
|
||||
}
|
||||
|
||||
constructor({
|
||||
@@ -83,11 +83,11 @@ class RegionService extends BaseService {
|
||||
|
||||
/**
|
||||
* Creates a region.
|
||||
* @param {Region} rawRegion - the unvalidated region
|
||||
* @param {Region} regionObject - the unvalidated region
|
||||
* @return {Region} the newly created region
|
||||
*/
|
||||
async create(regionObject) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepository = manager.getCustomRepository(
|
||||
this.regionRepository_
|
||||
)
|
||||
@@ -118,7 +118,7 @@ class RegionService extends BaseService {
|
||||
}
|
||||
|
||||
if (metadata) {
|
||||
regionObject.metadata = this.setMetadata_(region, metadata)
|
||||
regionObject.metadata = this.setMetadata_(regionObject, metadata)
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(validated)) {
|
||||
@@ -145,7 +145,7 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async update(regionId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepository = manager.getCustomRepository(
|
||||
this.regionRepository_
|
||||
)
|
||||
@@ -218,17 +218,17 @@ class RegionService extends BaseService {
|
||||
|
||||
if (region.countries) {
|
||||
region.countries = await Promise.all(
|
||||
region.countries.map(countryCode =>
|
||||
region.countries.map((countryCode) =>
|
||||
this.validateCountry_(countryCode, id)
|
||||
)
|
||||
).catch(err => {
|
||||
).catch((err) => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
if (region.payment_providers) {
|
||||
region.payment_providers = await Promise.all(
|
||||
region.payment_providers.map(async pId => {
|
||||
region.payment_providers.map(async (pId) => {
|
||||
const pp = await ppRepository.findOne({ where: { id: pId } })
|
||||
if (!pp) {
|
||||
throw new MedusaError(
|
||||
@@ -244,7 +244,7 @@ class RegionService extends BaseService {
|
||||
|
||||
if (region.fulfillment_providers) {
|
||||
region.fulfillment_providers = await Promise.all(
|
||||
region.fulfillment_providers.map(async fId => {
|
||||
region.fulfillment_providers.map(async (fId) => {
|
||||
const fp = await fpRepository.findOne({ where: { id: fId } })
|
||||
if (!fp) {
|
||||
throw new MedusaError(
|
||||
@@ -283,7 +283,7 @@ class RegionService extends BaseService {
|
||||
.withTransaction(this.transactionManager_)
|
||||
.retrieve(["currencies"])
|
||||
|
||||
const storeCurrencies = store.currencies.map(curr => curr.code)
|
||||
const storeCurrencies = store.currencies.map((curr) => curr.code)
|
||||
|
||||
if (!storeCurrencies.includes(currencyCode.toLowerCase())) {
|
||||
throw new MedusaError(
|
||||
@@ -305,7 +305,7 @@ class RegionService extends BaseService {
|
||||
)
|
||||
|
||||
const countryCode = code.toUpperCase()
|
||||
const validCountry = countries.find(c => c.alpha2 === countryCode)
|
||||
const validCountry = countries.find((c) => c.alpha2 === countryCode)
|
||||
if (!validCountry) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -339,6 +339,7 @@ class RegionService extends BaseService {
|
||||
/**
|
||||
* Retrieves a region by its id.
|
||||
* @param {string} regionId - the id of the region to retrieve
|
||||
* @param {object} config - configuration settings
|
||||
* @return {Region} the region
|
||||
*/
|
||||
async retrieve(regionId, config = {}) {
|
||||
@@ -361,7 +362,8 @@ class RegionService extends BaseService {
|
||||
|
||||
/**
|
||||
* Lists all regions based on a query
|
||||
* @param {object} listOptions - query object for find
|
||||
* @param {object} selector - query object for find
|
||||
* @param {object} config - configuration settings
|
||||
* @return {Promise} result of the find operation
|
||||
*/
|
||||
async list(selector = {}, config = { relations: [], skip: 0, take: 10 }) {
|
||||
@@ -377,15 +379,23 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation
|
||||
*/
|
||||
async delete(regionId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepo = manager.getCustomRepository(this.regionRepository_)
|
||||
|
||||
const region = await regionRepo.findOne({ where: { id: regionId } })
|
||||
|
||||
if (!region) return Promise.resolve()
|
||||
if (!region) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await regionRepo.softRemove(region)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(RegionService.Events.DELETED, {
|
||||
id: regionId,
|
||||
})
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
@@ -397,7 +407,7 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addCountry(regionId, code) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepo = manager.getCustomRepository(this.regionRepository_)
|
||||
|
||||
const country = await this.validateCountry_(code, regionId)
|
||||
@@ -407,7 +417,7 @@ class RegionService extends BaseService {
|
||||
// Check if region already has country
|
||||
if (
|
||||
region.countries &&
|
||||
region.countries.map(c => c.iso_2).includes(country.iso_2)
|
||||
region.countries.map((c) => c.iso_2).includes(country.iso_2)
|
||||
) {
|
||||
return region
|
||||
}
|
||||
@@ -434,7 +444,7 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removeCountry(regionId, code) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepo = manager.getCustomRepository(this.regionRepository_)
|
||||
|
||||
const region = await this.retrieve(regionId, { relations: ["countries"] })
|
||||
@@ -442,13 +452,13 @@ class RegionService extends BaseService {
|
||||
// Check if region contains country. If not, we simpy resolve
|
||||
if (
|
||||
region.countries &&
|
||||
!region.countries.map(c => c.iso_2).includes(code)
|
||||
!region.countries.map((c) => c.iso_2).includes(code)
|
||||
) {
|
||||
return region
|
||||
}
|
||||
|
||||
region.countries = region.countries.filter(
|
||||
country => country.iso_2 !== code
|
||||
(country) => country.iso_2 !== code
|
||||
)
|
||||
|
||||
const updated = await regionRepo.save(region)
|
||||
@@ -470,7 +480,7 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addPaymentProvider(regionId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepo = manager.getCustomRepository(this.regionRepository_)
|
||||
const ppRepo = manager.getCustomRepository(
|
||||
this.paymentProviderRepository_
|
||||
@@ -517,7 +527,7 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addFulfillmentProvider(regionId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepo = manager.getCustomRepository(this.regionRepository_)
|
||||
const fpRepo = manager.getCustomRepository(
|
||||
this.fulfillmentProviderRepository_
|
||||
@@ -561,7 +571,7 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removePaymentProvider(regionId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepo = manager.getCustomRepository(this.regionRepository_)
|
||||
|
||||
const region = await this.retrieve(regionId, {
|
||||
@@ -595,7 +605,7 @@ class RegionService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async removeFulfillmentProvider(regionId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const regionRepo = manager.getCustomRepository(this.regionRepository_)
|
||||
|
||||
const region = await this.retrieve(regionId, {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { In } from "typeorm"
|
||||
|
||||
class ReturnReasonService extends BaseService {
|
||||
constructor({ manager, returnReasonRepository }) {
|
||||
@@ -29,7 +28,7 @@ class ReturnReasonService extends BaseService {
|
||||
}
|
||||
|
||||
create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
|
||||
if (data.parent_return_reason_id && data.parent_return_reason_id !== "") {
|
||||
@@ -51,7 +50,7 @@ class ReturnReasonService extends BaseService {
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
const reason = await this.retrieve(id)
|
||||
|
||||
@@ -77,6 +76,7 @@ class ReturnReasonService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - config object
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(
|
||||
@@ -90,7 +90,8 @@ class ReturnReasonService extends BaseService {
|
||||
|
||||
/**
|
||||
* Gets an order by id.
|
||||
* @param {string} orderId - id of order to retrieve
|
||||
* @param {string} id - id of order to retrieve
|
||||
* @param {Object} config - config object
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
@@ -111,7 +112,7 @@ class ReturnReasonService extends BaseService {
|
||||
}
|
||||
|
||||
async delete(returnReasonId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
|
||||
// We include the relation 'return_reason_children' to enable cascading deletes of return reasons if a parent is removed
|
||||
|
||||
@@ -93,6 +93,12 @@ class ReturnService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
if (order.claims && order.claims.length) {
|
||||
for (const c of order.claims) {
|
||||
merged = [...merged, ...c.additional_items]
|
||||
}
|
||||
}
|
||||
|
||||
const toReturn = await Promise.all(
|
||||
items.map(async data => {
|
||||
const item = merged.find(i => i.id === data.item_id)
|
||||
@@ -325,7 +331,13 @@ class ReturnService extends BaseService {
|
||||
.withTransaction(manager)
|
||||
.retrieve(orderId, {
|
||||
select: ["refunded_total", "total", "refundable_amount"],
|
||||
relations: ["swaps", "swaps.additional_items", "items"],
|
||||
relations: [
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
"claims",
|
||||
"claims.additional_items",
|
||||
"items",
|
||||
],
|
||||
})
|
||||
|
||||
const returnLines = await this.getFulfillmentItems_(
|
||||
@@ -487,13 +499,18 @@ class ReturnService extends BaseService {
|
||||
* @param {string[]} lineItems - the line items to return
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async receive(returnId, receivedItems, refundAmount, allowMismatch = false) {
|
||||
async receive(
|
||||
return_id,
|
||||
received_items,
|
||||
refund_amount,
|
||||
allow_mismatch = false
|
||||
) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const returnRepository = manager.getCustomRepository(
|
||||
this.returnRepository_
|
||||
)
|
||||
|
||||
const returnObj = await this.retrieve(returnId, {
|
||||
const returnObj = await this.retrieve(return_id, {
|
||||
relations: ["items", "swap", "swap.additional_items"],
|
||||
})
|
||||
|
||||
@@ -518,6 +535,8 @@ class ReturnService extends BaseService {
|
||||
"returns",
|
||||
"payments",
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"refunds",
|
||||
"shipping_methods",
|
||||
"region",
|
||||
@@ -535,7 +554,7 @@ class ReturnService extends BaseService {
|
||||
|
||||
const returnLines = await this.getFulfillmentItems_(
|
||||
order,
|
||||
receivedItems,
|
||||
received_items,
|
||||
this.validateReturnLineItem_
|
||||
)
|
||||
|
||||
@@ -564,12 +583,12 @@ class ReturnService extends BaseService {
|
||||
let returnStatus = "received"
|
||||
|
||||
const isMatching = newLines.every(l => l.is_requested)
|
||||
if (!isMatching && !allowMismatch) {
|
||||
if (!isMatching && !allow_mismatch) {
|
||||
// Should update status
|
||||
returnStatus = "requires_action"
|
||||
}
|
||||
|
||||
const totalRefundableAmount = refundAmount || returnObj.refund_amount
|
||||
const totalRefundableAmount = refund_amount || returnObj.refund_amount
|
||||
|
||||
const now = new Date()
|
||||
const updateObj = {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import _ from "lodash"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { In } from "typeorm"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate profiles.
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class ShippingOptionService extends BaseService {
|
||||
constructor({
|
||||
@@ -64,6 +62,7 @@ class ShippingOptionService extends BaseService {
|
||||
/**
|
||||
* Validates a requirement
|
||||
* @param {ShippingRequirement} requirement - the requirement to validate
|
||||
* @param {string} optionId - the id to validate the requirement
|
||||
* @return {ShippingRequirement} a validated shipping requirement
|
||||
*/
|
||||
async validateRequirement_(requirement, optionId) {
|
||||
@@ -123,6 +122,7 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {object} config - config object
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(selector, config = { skip: 0, take: 50 }) {
|
||||
@@ -136,6 +136,7 @@ class ShippingOptionService extends BaseService {
|
||||
* Gets a profile by id.
|
||||
* Throws in case of DB Error and if profile was not found.
|
||||
* @param {string} optionId - the id of the profile to get.
|
||||
* @param {object} options - the options to get a profile
|
||||
* @return {Promise<Product>} the profile document.
|
||||
*/
|
||||
async retrieve(optionId, options = {}) {
|
||||
@@ -171,10 +172,10 @@ class ShippingOptionService extends BaseService {
|
||||
* and its methods should be copied to an order/swap entity.
|
||||
* @param {string} id - the id of the shipping method to update
|
||||
* @param {object} update - the values to update the method with
|
||||
* @returns {Promise<ShippingMethod>} the resulting shipping method
|
||||
* @return {Promise<ShippingMethod>} the resulting shipping method
|
||||
*/
|
||||
async updateShippingMethod(id, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const methodRepo = manager.getCustomRepository(this.methodRepository_)
|
||||
const method = await methodRepo.findOne({ where: { id } })
|
||||
|
||||
@@ -200,10 +201,10 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
/**
|
||||
* Removes a given shipping method
|
||||
* @param {string} id - the id of the option to use for the method.
|
||||
* @param {string} sm - the shipping method to remove
|
||||
*/
|
||||
async deleteShippingMethod(sm) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const methodRepo = manager.getCustomRepository(this.methodRepository_)
|
||||
return methodRepo.remove(sm)
|
||||
})
|
||||
@@ -214,10 +215,10 @@ class ShippingOptionService extends BaseService {
|
||||
* @param {string} optionId - the id of the option to use for the method.
|
||||
* @param {object} data - the optional provider data to use.
|
||||
* @param {object} config - the cart to create the shipping method for.
|
||||
* @returns {ShippingMethod} the resulting shipping method.
|
||||
* @return {ShippingMethod} the resulting shipping method.
|
||||
*/
|
||||
async createShippingMethod(optionId, data, config) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const option = await this.retrieve(optionId, {
|
||||
relations: ["requirements"],
|
||||
})
|
||||
@@ -255,6 +256,10 @@ class ShippingOptionService extends BaseService {
|
||||
toCreate.cart_id = config.cart.id
|
||||
}
|
||||
|
||||
if (config.cart_id) {
|
||||
toCreate.cart_id = config.cart_id
|
||||
}
|
||||
|
||||
if (config.return_id) {
|
||||
toCreate.return_id = config.return_id
|
||||
}
|
||||
@@ -286,7 +291,7 @@ class ShippingOptionService extends BaseService {
|
||||
* Checks if a given option id is a valid option for a cart. If it is the
|
||||
* option is returned with the correct price. Throws when region_ids do not
|
||||
* match, or when the shipping option requirements are not satisfied.
|
||||
* @param {string} optionId - the id of the option to check
|
||||
* @param {object} option - the option object to check
|
||||
* @param {Cart} cart - the cart object to check against
|
||||
* @return {ShippingOption} the validated shipping option
|
||||
*/
|
||||
@@ -303,7 +308,7 @@ class ShippingOptionService extends BaseService {
|
||||
}
|
||||
|
||||
const subtotal = cart.subtotal
|
||||
const requirementResults = option.requirements.map(requirement => {
|
||||
const requirementResults = option.requirements.map((requirement) => {
|
||||
switch (requirement.type) {
|
||||
case "max_subtotal":
|
||||
return requirement.amount > subtotal
|
||||
@@ -328,11 +333,11 @@ class ShippingOptionService extends BaseService {
|
||||
* Creates a new shipping option. Used both for outbound and inbound shipping
|
||||
* options. The difference is registered by the `is_return` field which
|
||||
* defaults to false.
|
||||
* @param {ShippingOption} option - the shipping option to create
|
||||
* @param {ShippingOption} data - the data to create shipping options
|
||||
* @return {Promise<ShippingOption>} the result of the create operation
|
||||
*/
|
||||
async create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const optionRepo = manager.getCustomRepository(this.optionRepository_)
|
||||
const option = await optionRepo.create(data)
|
||||
|
||||
@@ -370,7 +375,7 @@ class ShippingOptionService extends BaseService {
|
||||
for (const r of data.requirements) {
|
||||
const validated = await this.validateRequirement_(r)
|
||||
|
||||
if (acc.find(raw => raw.type === validated.type)) {
|
||||
if (acc.find((raw) => raw.type === validated.type)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Only one requirement of each type is allowed"
|
||||
@@ -379,7 +384,7 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
if (
|
||||
acc.find(
|
||||
raw =>
|
||||
(raw) =>
|
||||
(raw.type === "max_subtotal" &&
|
||||
validated.amount > raw.amount) ||
|
||||
(raw.type === "min_subtotal" && validated.amount < raw.amount)
|
||||
@@ -408,7 +413,7 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
/**
|
||||
* Validates a shipping option price
|
||||
* @param {ShippingOptionPrice} price - the price to validate
|
||||
* @param {ShippingOptionPrice} priceType - the price to validate
|
||||
* @param {ShippingOption} option - the option to validate against
|
||||
* @return {Promise<ShippingOptionPrice>} the validated price
|
||||
*/
|
||||
@@ -449,8 +454,10 @@ class ShippingOptionService extends BaseService {
|
||||
* @return {Promise} resolves to the update result.
|
||||
*/
|
||||
async update(optionId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const option = await this.retrieve(optionId)
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const option = await this.retrieve(optionId, {
|
||||
relations: ["requirements"],
|
||||
})
|
||||
|
||||
if ("metadata" in update) {
|
||||
option.metadata = await this.setMetadata_(option, update.metadata)
|
||||
@@ -475,7 +482,7 @@ class ShippingOptionService extends BaseService {
|
||||
for (const r of update.requirements) {
|
||||
const validated = await this.validateRequirement_(r, optionId)
|
||||
|
||||
if (acc.find(raw => raw.type === validated.type)) {
|
||||
if (acc.find((raw) => raw.type === validated.type)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Only one requirement of each type is allowed"
|
||||
@@ -484,7 +491,7 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
if (
|
||||
acc.find(
|
||||
raw =>
|
||||
(raw) =>
|
||||
(raw.type === "max_subtotal" &&
|
||||
validated.amount > raw.amount) ||
|
||||
(raw.type === "min_subtotal" && validated.amount < raw.amount)
|
||||
@@ -498,6 +505,20 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
acc.push(validated)
|
||||
}
|
||||
|
||||
if (option.requirements) {
|
||||
const accReqs = acc.map((a) => a.id)
|
||||
const toRemove = option.requirements.filter(
|
||||
(r) => !accReqs.includes(r.id)
|
||||
)
|
||||
await Promise.all(
|
||||
toRemove.map(async (req) => {
|
||||
await this.removeRequirement(req.id)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
option.requirements = acc
|
||||
}
|
||||
|
||||
if ("price_type" in update) {
|
||||
@@ -536,7 +557,7 @@ class ShippingOptionService extends BaseService {
|
||||
*/
|
||||
async delete(optionId) {
|
||||
try {
|
||||
let option = await this.retrieve(optionId)
|
||||
const option = await this.retrieve(optionId)
|
||||
|
||||
const optionRepo = this.manager_.getCustomRepository(
|
||||
this.optionRepository_
|
||||
@@ -563,13 +584,13 @@ class ShippingOptionService extends BaseService {
|
||||
* @return {Promise} the result of update
|
||||
*/
|
||||
async addRequirement(optionId, requirement) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const option = await this.retrieve(optionId, {
|
||||
relations: ["requirements"],
|
||||
})
|
||||
const validatedReq = await this.validateRequirement_(requirement)
|
||||
|
||||
if (option.requirements.find(r => r.type === validatedReq.type)) {
|
||||
if (option.requirements.find((r) => r.type === validatedReq.type)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.DUPLICATE_ERROR,
|
||||
`A requirement with type: ${validatedReq.type} already exists`
|
||||
@@ -585,34 +606,30 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
/**
|
||||
* Removes a requirement from a shipping option
|
||||
* @param {string} optionId - the shipping option to remove from
|
||||
* @param {string} requirementId - the id of the requirement to remove
|
||||
* @return {Promise} the result of update
|
||||
*/
|
||||
async removeRequirement(optionId, requirementId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const option = await this.retrieve(optionId, {
|
||||
relations: "requirements",
|
||||
})
|
||||
const newReqs = option.requirements.map(r => {
|
||||
if (r.id === requirementId) {
|
||||
return null
|
||||
} else {
|
||||
return r
|
||||
}
|
||||
})
|
||||
async removeRequirement(requirementId) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
try {
|
||||
const reqRepo = manager.getCustomRepository(this.requirementRepository_)
|
||||
const requirement = await reqRepo.findOne({
|
||||
where: { id: requirementId },
|
||||
})
|
||||
|
||||
option.requirements = newReqs.filter(Boolean)
|
||||
const result = await reqRepo.softRemove(requirement)
|
||||
|
||||
const optionRepo = manager.getCustomRepository(this.optionRepository_)
|
||||
const result = await optionRepo.save(option)
|
||||
return result
|
||||
return result
|
||||
} catch (error) {
|
||||
// Delete is idempotent, but we return a promise to allow then-chaining
|
||||
return Promise.resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a shipping option.
|
||||
* @param {ShippingOption} shippingOption - the shipping option to decorate.
|
||||
* @param {ShippingOption} optionId - the shipping option to decorate using optionId.
|
||||
* @param {string[]} fields - the fields to include.
|
||||
* @param {string[]} expandFields - fields to expand.
|
||||
* @return {ShippingOption} the decorated ShippingOption.
|
||||
@@ -632,9 +649,8 @@ class ShippingOptionService extends BaseService {
|
||||
|
||||
/**
|
||||
* Dedicated method to set metadata for a shipping option.
|
||||
* @param {string} optionId - the option to set metadata for.
|
||||
* @param {string} key - key for metadata field
|
||||
* @param {string} value - value for metadata field.
|
||||
* @param {object} option - the option to set metadata for.
|
||||
* @param {object} metadata - object for metadata field
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async setMetadata_(option, metadata) {
|
||||
@@ -665,9 +681,10 @@ class ShippingOptionService extends BaseService {
|
||||
* price type "calculated".
|
||||
* @param {ShippingOption} option - the shipping option to retrieve the price
|
||||
* for.
|
||||
* @param {Cart || Order} cart - the context in which the price should be
|
||||
* @param {ShippingData} data - the shipping data to retrieve the price.
|
||||
* @param {Cart | Order} cart - the context in which the price should be
|
||||
* retrieved.
|
||||
* @returns {Promise<Number>} the price of the shipping option.
|
||||
* @return {Promise<Number>} the price of the shipping option.
|
||||
*/
|
||||
async getPrice_(option, data, cart) {
|
||||
if (option.price_type === "calculated") {
|
||||
|
||||
@@ -14,6 +14,7 @@ class ShippingProfileService extends BaseService {
|
||||
productService,
|
||||
productRepository,
|
||||
shippingOptionService,
|
||||
customShippingOptionService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -31,6 +32,9 @@ class ShippingProfileService extends BaseService {
|
||||
|
||||
/** @private @const {ShippingOptionService} */
|
||||
this.shippingOptionService_ = shippingOptionService
|
||||
|
||||
/** @private @const {CustomShippingOptionService} */
|
||||
this.customShippingOptionService_ = customShippingOptionService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -43,6 +47,7 @@ class ShippingProfileService extends BaseService {
|
||||
shippingProfileRepository: this.shippingProfileRepository_,
|
||||
productService: this.productService_,
|
||||
shippingOptionService: this.shippingOptionService_,
|
||||
customShippingOptionService: this.customShippingOptionService_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -410,19 +415,47 @@ class ShippingProfileService extends BaseService {
|
||||
* Finds all the shipping profiles that cover the products in a cart, and
|
||||
* validates all options that are available for the cart.
|
||||
* @param {Cart} cart - the cart object to find shipping options for
|
||||
* @return {[ShippingOptions]} a list of the available shipping options
|
||||
* @return {[ShippingOption]} a list of the available shipping options
|
||||
*/
|
||||
async fetchCartOptions(cart) {
|
||||
const profileIds = this.getProfilesInCart_(cart)
|
||||
|
||||
const rawOpts = await this.shippingOptionService_.list(
|
||||
const selector = {
|
||||
profile_id: profileIds,
|
||||
admin_only: false,
|
||||
}
|
||||
|
||||
const customShippingOptions = await this.customShippingOptionService_.list(
|
||||
{
|
||||
profile_id: profileIds,
|
||||
admin_only: false,
|
||||
cart_id: cart.id,
|
||||
},
|
||||
{ relations: ["requirements", "profile"] }
|
||||
{ select: ["id", "shipping_option_id", "price"] }
|
||||
)
|
||||
|
||||
const hasCustomShippingOptions = customShippingOptions?.length
|
||||
// if there are custom shipping options associated with the cart, use those
|
||||
if (hasCustomShippingOptions) {
|
||||
selector.id = customShippingOptions.map(cso => cso.shipping_option_id)
|
||||
}
|
||||
|
||||
const rawOpts = await this.shippingOptionService_.list(selector, {
|
||||
relations: ["requirements", "profile"],
|
||||
})
|
||||
|
||||
// if there are custom shipping options associated with the cart, return cart shipping options with custom price
|
||||
if (hasCustomShippingOptions) {
|
||||
return rawOpts.map(so => {
|
||||
const customOption = customShippingOptions.find(
|
||||
cso => cso.shipping_option_id === so.id
|
||||
)
|
||||
|
||||
return {
|
||||
...so,
|
||||
amount: customOption?.price,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const options = []
|
||||
|
||||
for (const o of rawOpts) {
|
||||
|
||||
@@ -32,6 +32,7 @@ class SwapService extends BaseService {
|
||||
fulfillmentService,
|
||||
orderService,
|
||||
inventoryService,
|
||||
customShippingOptionService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -70,6 +71,9 @@ class SwapService extends BaseService {
|
||||
|
||||
/** @private @const {EventBusService} */
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
/** @private @const {CustomShippingOptionService} */
|
||||
this.customShippingOptionService_ = customShippingOptionService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -90,6 +94,7 @@ class SwapService extends BaseService {
|
||||
orderService: this.orderService_,
|
||||
inventoryService: this.inventoryService_,
|
||||
fulfillmentService: this.fulfillmentService_,
|
||||
customShippingOptionService: this.customShippingOptionService_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -116,7 +121,7 @@ class SwapService extends BaseService {
|
||||
"cart.total",
|
||||
]
|
||||
|
||||
const totalsToSelect = select.filter(v => totalFields.includes(v))
|
||||
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
||||
if (totalsToSelect.length > 0) {
|
||||
const relationSet = new Set(relations)
|
||||
relationSet.add("cart")
|
||||
@@ -127,7 +132,7 @@ class SwapService extends BaseService {
|
||||
relationSet.add("cart.region")
|
||||
relations = [...relationSet]
|
||||
|
||||
select = select.filter(v => !totalFields.includes(v))
|
||||
select = select.filter((v) => !totalFields.includes(v))
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -170,9 +175,8 @@ class SwapService extends BaseService {
|
||||
|
||||
const validatedId = this.validateId_(id)
|
||||
|
||||
const { totalsToSelect, ...newConfig } = this.transformQueryForTotals_(
|
||||
config
|
||||
)
|
||||
const { totalsToSelect, ...newConfig } =
|
||||
this.transformQueryForTotals_(config)
|
||||
|
||||
const query = this.buildQuery_({ id: validatedId }, newConfig)
|
||||
|
||||
@@ -251,7 +255,7 @@ class SwapService extends BaseService {
|
||||
*/
|
||||
validateReturnItems_(order, returnItems) {
|
||||
return returnItems.map(({ item_id, quantity }) => {
|
||||
const item = order.items.find(i => i.id === item_id)
|
||||
const item = order.items.find((i) => i.id === item_id)
|
||||
|
||||
// The item must exist in the order
|
||||
if (!item) {
|
||||
@@ -303,7 +307,7 @@ class SwapService extends BaseService {
|
||||
}
|
||||
) {
|
||||
const { no_notification, ...rest } = custom
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
if (
|
||||
order.fulfillment_status === "not_fulfilled" ||
|
||||
order.payment_status !== "captured"
|
||||
@@ -318,7 +322,6 @@ class SwapService extends BaseService {
|
||||
const line = await this.lineItemService_.retrieve(item.item_id, {
|
||||
relations: ["order", "swap", "claim_order"],
|
||||
})
|
||||
console.log(line)
|
||||
|
||||
if (
|
||||
line.order?.canceled_at ||
|
||||
@@ -377,7 +380,7 @@ class SwapService extends BaseService {
|
||||
}
|
||||
|
||||
async processDifference(swapId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId, {
|
||||
relations: ["payment", "order", "order.payments"],
|
||||
})
|
||||
@@ -493,7 +496,7 @@ class SwapService extends BaseService {
|
||||
}
|
||||
|
||||
async update(swapId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId)
|
||||
|
||||
if ("metadata" in update) {
|
||||
@@ -524,8 +527,8 @@ class SwapService extends BaseService {
|
||||
* @returns {Promise<Swap>} the swap with its cart_id prop set to the id of
|
||||
* the new cart.
|
||||
*/
|
||||
async createCart(swapId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
async createCart(swapId, customShippingOptions = []) {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId, {
|
||||
relations: [
|
||||
"order",
|
||||
@@ -533,6 +536,9 @@ class SwapService extends BaseService {
|
||||
"order.swaps",
|
||||
"order.swaps.additional_items",
|
||||
"order.discounts",
|
||||
"order.discounts.rule",
|
||||
"order.claims",
|
||||
"order.claims.additional_items",
|
||||
"additional_items",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
@@ -575,6 +581,16 @@ class SwapService extends BaseService {
|
||||
},
|
||||
})
|
||||
|
||||
for (const customShippingOption of customShippingOptions) {
|
||||
await this.customShippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.create({
|
||||
cart_id: cart.id,
|
||||
shipping_option_id: customShippingOption.option_id,
|
||||
price: customShippingOption.price,
|
||||
})
|
||||
}
|
||||
|
||||
for (const item of swap.additional_items) {
|
||||
await this.lineItemService_.withTransaction(manager).update(item.id, {
|
||||
cart_id: cart.id,
|
||||
@@ -606,7 +622,13 @@ class SwapService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
const lineItem = allItems.find(i => i.id === r.item_id)
|
||||
if (order.claims && order.claims.length) {
|
||||
for (const c of order.claims) {
|
||||
allItems = [...allItems, ...c.additional_items]
|
||||
}
|
||||
}
|
||||
|
||||
const lineItem = allItems.find((i) => i.id === r.item_id)
|
||||
|
||||
const toCreate = {
|
||||
cart_id: cart.id,
|
||||
@@ -637,7 +659,7 @@ class SwapService extends BaseService {
|
||||
*
|
||||
*/
|
||||
async registerCartCompletion(swapId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId, {
|
||||
relations: [
|
||||
"cart",
|
||||
@@ -769,7 +791,7 @@ class SwapService extends BaseService {
|
||||
* status.
|
||||
*/
|
||||
async receiveReturn(swapId, returnItems) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId, { relations: ["return_order"] })
|
||||
|
||||
if (swap.canceled_at) {
|
||||
@@ -810,7 +832,7 @@ class SwapService extends BaseService {
|
||||
* @returns {Promise<Swap>} the canceled swap.
|
||||
*/
|
||||
async cancel(swapId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId, {
|
||||
relations: ["payment", "fulfillments", "return_order"],
|
||||
})
|
||||
@@ -876,7 +898,7 @@ class SwapService extends BaseService {
|
||||
) {
|
||||
const { metadata, no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId, {
|
||||
relations: [
|
||||
"payment",
|
||||
@@ -936,7 +958,7 @@ class SwapService extends BaseService {
|
||||
is_swap: true,
|
||||
no_notification: evaluatedNoNotification,
|
||||
},
|
||||
swap.additional_items.map(i => ({
|
||||
swap.additional_items.map((i) => ({
|
||||
item_id: i.id,
|
||||
quantity: i.quantity,
|
||||
})),
|
||||
@@ -953,7 +975,7 @@ class SwapService extends BaseService {
|
||||
// Update all line items to reflect fulfillment
|
||||
for (const item of swap.additional_items) {
|
||||
const fulfillmentItem = successfullyFulfilled.find(
|
||||
f => item.id === f.item_id
|
||||
(f) => item.id === f.item_id
|
||||
)
|
||||
|
||||
if (fulfillmentItem) {
|
||||
@@ -998,7 +1020,7 @@ class SwapService extends BaseService {
|
||||
* @returns updated swap
|
||||
*/
|
||||
async cancelFulfillment(fulfillmentId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const canceled = await this.fulfillmentService_
|
||||
.withTransaction(manager)
|
||||
.cancelFulfillment(fulfillmentId)
|
||||
@@ -1041,7 +1063,7 @@ class SwapService extends BaseService {
|
||||
) {
|
||||
const { metadata, no_notification } = config
|
||||
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(swapId, {
|
||||
relations: ["additional_items"],
|
||||
})
|
||||
@@ -1067,7 +1089,7 @@ class SwapService extends BaseService {
|
||||
|
||||
// Go through all the additional items in the swap
|
||||
for (const i of swap.additional_items) {
|
||||
const shipped = shipment.items.find(si => si.item_id === i.id)
|
||||
const shipped = shipment.items.find((si) => si.item_id === i.id)
|
||||
if (shipped) {
|
||||
const shippedQty = (i.shipped_quantity || 0) + shipped.quantity
|
||||
await this.lineItemService_.withTransaction(manager).update(i.id, {
|
||||
@@ -1116,7 +1138,7 @@ class SwapService extends BaseService {
|
||||
const keyPath = `metadata.${key}`
|
||||
return this.swapModel_
|
||||
.updateOne({ _id: validatedId }, { $unset: { [keyPath]: "" } })
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
@@ -1129,7 +1151,7 @@ class SwapService extends BaseService {
|
||||
* @returns {Promise<Order>} the resulting order
|
||||
*/
|
||||
async registerReceived(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const swap = await this.retrieve(id, {
|
||||
relations: ["return_order", "return_order.items"],
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from "lodash"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import carts from "../api/routes/store/carts"
|
||||
|
||||
/**
|
||||
* A service that calculates total and subtotals for orders, carts etc..
|
||||
@@ -158,6 +159,13 @@ class TotalsService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
if (order.claims && order.claims.length) {
|
||||
for (const c of order.claims) {
|
||||
const claimItemIds = c.additional_items.map(el => el.id)
|
||||
itemIds = [...itemIds, ...claimItemIds]
|
||||
}
|
||||
}
|
||||
|
||||
const refunds = lineItems.map(i => {
|
||||
if (!itemIds.includes(i.id)) {
|
||||
throw new MedusaError(
|
||||
@@ -253,6 +261,12 @@ class TotalsService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
if (cart.claims && cart.claims.length) {
|
||||
for (const c of cart.claims) {
|
||||
merged = [...merged, ...c.additional_items]
|
||||
}
|
||||
}
|
||||
|
||||
const { type, allocation, value } = discount.rule
|
||||
if (allocation === "total") {
|
||||
let percentage = 0
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import mongoose from "mongoose"
|
||||
import _ from "lodash"
|
||||
|
||||
class TransactionService extends BaseService {
|
||||
constructor({}) {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { BaseService } from "medusa-interfaces"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate users.
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class UserService extends BaseService {
|
||||
static Events = {
|
||||
@@ -48,7 +48,16 @@ class UserService extends BaseService {
|
||||
* @return {string} the validated email
|
||||
*/
|
||||
validateEmail_(email) {
|
||||
return email
|
||||
const schema = Validator.string().email().required()
|
||||
const { value, error } = schema.validate(email)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"The email is not valid"
|
||||
)
|
||||
}
|
||||
|
||||
return value.toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +73,7 @@ class UserService extends BaseService {
|
||||
* Gets a user by id.
|
||||
* Throws in case of DB Error and if user was not found.
|
||||
* @param {string} userId - the id of the user to get.
|
||||
* @param {Object} config - query configs
|
||||
* @return {Promise<User>} the user document.
|
||||
*/
|
||||
async retrieve(userId, config = {}) {
|
||||
@@ -88,6 +98,7 @@ class UserService extends BaseService {
|
||||
* Gets a user by api token.
|
||||
* Throws in case of DB Error and if user was not found.
|
||||
* @param {string} apiToken - the token of the user to get.
|
||||
* @param {string[]} relations - relations to include with the user
|
||||
* @return {Promise<User>} the user document.
|
||||
*/
|
||||
async retrieveByApiToken(apiToken, relations = []) {
|
||||
@@ -112,15 +123,14 @@ class UserService extends BaseService {
|
||||
* Gets a user by email.
|
||||
* Throws in case of DB Error and if user was not found.
|
||||
* @param {string} email - the email of the user to get.
|
||||
* @param {Object} config - query config
|
||||
* @return {Promise<User>} the user document.
|
||||
*/
|
||||
async retrieveByEmail(email, relations = []) {
|
||||
async retrieveByEmail(email, config = {}) {
|
||||
const userRepo = this.manager_.getCustomRepository(this.userRepository_)
|
||||
|
||||
const user = await userRepo.findOne({
|
||||
where: { email },
|
||||
relations,
|
||||
})
|
||||
const query = this.buildQuery_({ email: email.toLowerCase() }, config)
|
||||
const user = await userRepo.findOne(query)
|
||||
|
||||
if (!user) {
|
||||
throw new MedusaError(
|
||||
@@ -135,7 +145,7 @@ class UserService extends BaseService {
|
||||
/**
|
||||
* Hashes a password
|
||||
* @param {string} password - the value to hash
|
||||
* @return hashed password
|
||||
* @return {string} hashed password
|
||||
*/
|
||||
async hashPassword_(password) {
|
||||
const buf = await Scrypt.kdf(password, { logN: 1, r: 1, p: 1 })
|
||||
@@ -146,10 +156,11 @@ class UserService extends BaseService {
|
||||
* Creates a user with username being validated.
|
||||
* Fails if email is not a valid format.
|
||||
* @param {object} user - the user to create
|
||||
* @param {string} password - user's password to hash
|
||||
* @return {Promise} the result of create
|
||||
*/
|
||||
async create(user, password) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||
|
||||
const validatedEmail = this.validateEmail_(user.email)
|
||||
@@ -168,11 +179,12 @@ class UserService extends BaseService {
|
||||
|
||||
/**
|
||||
* Updates a user.
|
||||
* @param {object} user - the user to create
|
||||
* @param {object} userId - id of the user to update
|
||||
* @param {object} update - the values to be updated on the user
|
||||
* @return {Promise} the result of create
|
||||
*/
|
||||
async update(userId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||
const validatedId = this.validateId_(userId)
|
||||
|
||||
@@ -213,13 +225,15 @@ class UserService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation.
|
||||
*/
|
||||
async delete(userId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||
|
||||
// Should not fail, if user does not exist, since delete is idempotent
|
||||
const user = await userRepo.findOne({ where: { id: userId } })
|
||||
|
||||
if (!user) return Promise.resolve()
|
||||
if (!user) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await userRepo.softRemove(user)
|
||||
|
||||
@@ -233,10 +247,10 @@ class UserService extends BaseService {
|
||||
* password does not work.
|
||||
* @param {string} userId - the userId to set password for
|
||||
* @param {string} password - the old password to set
|
||||
* @returns {Promise} the result of the update operation
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async setPassword_(userId, password) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const userRepo = manager.getCustomRepository(this.userRepository_)
|
||||
|
||||
const user = await this.retrieve(userId)
|
||||
@@ -261,8 +275,8 @@ class UserService extends BaseService {
|
||||
* The token will be signed with the users current password hash as a secret
|
||||
* a long side a payload with userId and the expiry time for the token, which
|
||||
* is always 15 minutes.
|
||||
* @param {User} user - the user to reset password for
|
||||
* @returns {string} the generated JSON web token
|
||||
* @param {string} userId - the id of the user to reset password for
|
||||
* @return {string} the generated JSON web token
|
||||
*/
|
||||
async generateResetPasswordToken(userId) {
|
||||
const user = await this.retrieve(userId)
|
||||
|
||||
@@ -32,7 +32,13 @@ class OrderSubscriber {
|
||||
handleOrderPlaced = async data => {
|
||||
const order = await this.orderService_.retrieve(data.id, {
|
||||
select: ["subtotal"],
|
||||
relations: ["discounts", "items", "gift_cards"],
|
||||
relations: [
|
||||
"discounts",
|
||||
"discounts.rule",
|
||||
"discounts.rule.valid_for",
|
||||
"items",
|
||||
"gift_cards",
|
||||
],
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
|
||||
Reference in new issue
Block a user