fix: allow changing regions safely

This commit is contained in:
Sebastian Rindom committed 2021-10-19 18:56:52 +02:00
commit 06f5fe2670
278 files changed
+14657 -9233

No files matched your search

-9
View File
@@ -1,9 +0,0 @@
{
"plugins": ["prettier"],
"extends": ["prettier"],
"rules": {
"prettier/prettier": "error",
"semi": "error",
"no-unused-expressions": "true"
}
}
-7
View File
@@ -1,7 +0,0 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5"
}
File diff suppressed because it is too large. Load diff
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@medusajs/medusa",
"version": "1.1.41",
"version": "1.1.45",
"description": "E-commerce for JAMstack",
"main": "dist/index.js",
"bin": {
@@ -20,11 +20,11 @@
"@babel/cli": "^7.14.3",
"@babel/core": "^7.14.3",
"@babel/preset-typescript": "^7.13.0",
"babel-preset-medusa-package": "^1.1.15",
"babel-preset-medusa-package": "^1.1.17",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-interfaces": "^1.1.23",
"medusa-interfaces": "^1.1.27",
"nodemon": "^2.0.1",
"prettier": "^1.19.1",
"sqlite3": "^5.0.2",
@@ -45,7 +45,7 @@
},
"dependencies": {
"@hapi/joi": "^16.1.8",
"@medusajs/medusa-cli": "^1.1.18",
"@medusajs/medusa-cli": "^1.1.22",
"@types/lodash": "^4.14.168",
"awilix": "^4.2.3",
"body-parser": "^1.19.0",
@@ -67,8 +67,8 @@
"joi": "^17.3.0",
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",
"medusa-core-utils": "^1.1.22",
"medusa-test-utils": "^1.1.25",
"medusa-core-utils": "^1.1.26",
"medusa-test-utils": "^1.1.29",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"passport": "^0.4.0",
@@ -87,5 +87,5 @@
"uuid": "^8.3.1",
"winston": "^3.2.1"
},
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
"gitHead": "5afd0dfbabaebfb6b09f0e2f055386ac76059372"
}
@@ -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
}
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations } from "."
/**
* @oas [post] /discounts
@@ -96,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) {
@@ -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",
]
@@ -72,11 +72,9 @@ export default async (req, res) => {
const schema = Validator.object().keys({
region_id: Validator.string().optional(),
country_code: Validator.string().optional(),
email: Validator.string()
.email()
.optional(),
billing_address: Validator.address().optional(),
shipping_address: Validator.address().optional(),
email: Validator.string().email().optional(),
billing_address: Validator.address().optional().allow(null),
shipping_address: Validator.address().optional().allow(null),
gift_cards: Validator.array()
.items({
code: Validator.string(),
@@ -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",
@@ -7,6 +7,7 @@ export default app => {
app.use("/products", route)
route.get("/", middlewares.wrap(require("./list-products").default))
route.post("/search", middlewares.wrap(require("./search").default))
route.get("/:id", middlewares.wrap(require("./get-product").default))
return app
@@ -0,0 +1,35 @@
import { Validator, MedusaError } from "medusa-core-utils"
import ProductService from "../../../../services/product"
export default async (req, res) => {
const schema = Validator.object()
.keys({
q: Validator.string().required(),
offset: Validator.number().optional(),
limit: Validator.number().optional(),
filter: Validator.any(),
})
.options({ allowUnknown: true })
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const { q, offset, limit, filter, ...options } = value
const paginationOptions = { offset, limit }
const searchService = req.scope.resolve("searchService")
const results = await searchService.search(ProductService.IndexName, q, {
paginationOptions,
filter,
additionalOptions: options,
})
res.status(200).send(results)
} catch (error) {
throw error
}
}
@@ -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"]
+1
View File
@@ -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"
+13 -6
View File
@@ -16,6 +16,7 @@ import passportLoader from "./passport"
import pluginsLoader, { registerPluginModels } from "./plugins"
import defaultsLoader from "./defaults"
import Logger from "./logger"
import searchIndexLoader from "./search-index"
export default async ({ directory: rootDirectory, expressApp }) => {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`)
@@ -101,12 +102,6 @@ export default async ({ directory: rootDirectory, expressApp }) => {
const servAct = Logger.success(servicesActivity, "Services initialized") || {}
track("SERVICES_INIT_COMPLETED", { duration: servAct.duration })
const subActivity = Logger.activity("Initializing subscribers")
track("SUBSCRIBERS_INIT_STARTED")
subscribersLoader({ container, activityId: subActivity })
const subAct = Logger.success(subActivity, "Subscribers initialized") || {}
track("SUBSCRIBERS_INIT_COMPLETED", { duration: subAct.duration })
const expActivity = Logger.activity("Initializing express")
track("EXPRESS_INIT_STARTED")
await expressLoader({
@@ -138,6 +133,12 @@ export default async ({ directory: rootDirectory, expressApp }) => {
const pAct = Logger.success(pluginsActivity, "Plugins intialized") || {}
track("PLUGINS_INIT_COMPLETED", { duration: pAct.duration })
const subActivity = Logger.activity("Initializing subscribers")
track("SUBSCRIBERS_INIT_STARTED")
subscribersLoader({ container, activityId: subActivity })
const subAct = Logger.success(subActivity, "Subscribers initialized") || {}
track("SUBSCRIBERS_INIT_COMPLETED", { duration: subAct.duration })
const apiActivity = Logger.activity("Initializing API")
track("API_INIT_STARTED")
await apiLoader({
@@ -155,6 +156,12 @@ export default async ({ directory: rootDirectory, expressApp }) => {
const dAct = Logger.success(defaultsActivity, "Defaults initialized") || {}
track("DEFAULTS_INIT_COMPLETED", { duration: dAct.duration })
const searchActivity = Logger.activity("Initializing search engine indexing")
track("SEARCH_ENGINE_INDEXING_STARTED")
searchIndexLoader({ container, activityId: searchActivity })
const searchAct = Logger.success(searchActivity, "Indexing completed") || {}
track("SEARCH_ENGINE_INDEXING_COMPLETED", { duration: searchAct.duration })
return { container, dbConnection, app: expressApp }
}
+13 -3
View File
@@ -8,6 +8,7 @@ import {
NotificationService,
FileService,
OauthService,
SearchService,
} from "medusa-interfaces"
import { getConfigFile, createRequireFromPath } from "medusa-core-utils"
import _ from "lodash"
@@ -249,7 +250,7 @@ async function registerServices(pluginDetails, container) {
)
// Add the service directly to the container in order to make simple
// resolution if we already know which payment provider we need to use
// resolution if we already know which fulfillment provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
@@ -263,7 +264,7 @@ async function registerServices(pluginDetails, container) {
)
// Add the service directly to the container in order to make simple
// resolution if we already know which payment provider we need to use
// resolution if we already know which notification provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
@@ -272,13 +273,22 @@ async function registerServices(pluginDetails, container) {
})
} else if (loaded.prototype instanceof FileService) {
// Add the service directly to the container in order to make simple
// resolution if we already know which payment provider we need to use
// resolution if we already know which file storage provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
),
[`fileService`]: aliasTo(name),
})
} else if (loaded.prototype instanceof SearchService) {
// Add the service directly to the container in order to make simple
// resolution if we already know which search provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
),
[`searchService`]: aliasTo(name),
})
} else {
container.register({
[name]: asFunction(
@@ -0,0 +1,71 @@
import ProductService from "../services/product"
import { indexTypes } from "medusa-core-utils"
async function loadProductsIntoSearchEngine(container) {
const searchService = container.resolve("searchService")
const productService = container.resolve("productService")
const TAKE = 20
let hasMore = true
let lastSeenId = ""
while (hasMore) {
const products = await productService.list(
{ id: { gt: lastSeenId } },
{
select: [
"id",
"title",
"subtitle",
"description",
"handle",
"is_giftcard",
"discountable",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"origin_country",
"created_at",
"updated_at",
],
relations: [
"variants",
"tags",
"type",
"collection",
"variants.prices",
"variants.options",
"options",
],
take: TAKE,
order: { id: "ASC" },
}
)
if (products.length > 0) {
await searchService.addDocuments(
ProductService.IndexName,
products,
indexTypes.products
)
lastSeenId = products[products.length - 1].id
} else {
hasMore = false
}
}
}
export default async ({ container }) => {
const searchService = container.resolve("searchService")
const logger = container.resolve("logger")
if (searchService.isDefault) {
logger.warn(
"No search engine provider was found: make sure to include a search plugin to enable searching"
)
return
}
await loadProductsIntoSearchEngine(container)
}
@@ -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"`);
}
}
+12 -16
View File
@@ -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()
@@ -149,11 +151,9 @@ export class Cart {
@JoinColumn({ name: "shipping_address_id" })
shipping_address: Address
@OneToMany(
() => LineItem,
lineItem => lineItem.cart,
{ cascade: ["insert", "remove"] }
)
@OneToMany(() => LineItem, (lineItem) => lineItem.cart, {
cascade: ["insert", "remove"],
})
items: LineItem[]
@Index()
@@ -202,11 +202,9 @@ export class Cart {
payment_session: PaymentSession
@OneToMany(
() => PaymentSession,
paymentSession => paymentSession.cart,
{ cascade: true }
)
@OneToMany(() => PaymentSession, (paymentSession) => paymentSession.cart, {
cascade: true,
})
payment_sessions: PaymentSession[]
@Index()
@@ -217,11 +215,9 @@ export class Cart {
@JoinColumn({ name: "payment_id" })
payment: Payment
@OneToMany(
() => ShippingMethod,
method => method.cart,
{ cascade: ["soft-remove", "remove"] }
)
@OneToMany(() => ShippingMethod, (method) => method.cart, {
cascade: ["soft-remove", "remove"],
})
shipping_methods: ShippingMethod[]
@DbAwareColumn({ type: "enum", enum: CartType, default: "default" })
@@ -271,7 +267,7 @@ export class Cart {
@AfterLoad()
private afterLoad() {
if (this.payment_sessions) {
this.payment_session = this.payment_sessions.find(p => p.is_selected)
this.payment_session = this.payment_sessions.find((p) => p.is_selected)
}
}
}
@@ -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
*/
+1 -1
View File
@@ -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
@@ -0,0 +1,5 @@
import { EntityRepository, Repository } from "typeorm"
import { CustomShippingOption } from './../models/custom-shipping-option';
@EntityRepository(CustomShippingOption)
export class CustomShippingOptionRepository extends Repository<CustomShippingOption> {}
+1 -1
View File
@@ -34,7 +34,7 @@ export class OrderRepository extends Repository<Order> {
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entities, e => merge({}, ...entitiesAndRelationsById[e.id]))
return map(entities, (e) => merge({}, ...entitiesAndRelationsById[e.id]))
}
public async findOneWithRelations(
+38 -7
View File
@@ -1,21 +1,52 @@
import { flatten, groupBy, map, merge } from "lodash"
import { EntityRepository, FindManyOptions, Repository } from "typeorm"
import {
OrderByCondition,
EntityRepository,
FindManyOptions,
Repository,
} from "typeorm"
import { Product } from "../models/product"
type DefaultWithoutRelations = Omit<FindManyOptions<Product>, "relations">
type CustomOptions = {
where?: DefaultWithoutRelations["where"] & { tags?: string[] }
order?: OrderByCondition
skip?: number
take?: number
}
type FindWithRelationsOptions = CustomOptions
@EntityRepository(Product)
export class ProductRepository extends Repository<Product> {
public async findWithRelations(
relations: Array<keyof Product> = [],
idsOrOptionsWithoutRelations: Omit<
FindManyOptions<Product>,
"relations"
> = {}
idsOrOptionsWithoutRelations: FindWithRelationsOptions = {}
): Promise<Product[]> {
let entities
let entities: 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)
.orderBy(idsOrOptionsWithoutRelations.order)
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)
+142 -47
View File
@@ -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,19 +1523,50 @@ 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 getOffsetDate = (offset) => {
const date = new Date()
date.setDate(date.getDate() + offset)
return date
@@ -1508,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") }],
@@ -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({
@@ -231,6 +231,8 @@ describe("OrderService", () => {
"payment",
"items",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"gift_cards",
"shipping_methods",
],
@@ -802,6 +802,7 @@ describe("ProductVariantService", () => {
}
return Promise.resolve({
id: IdMap.getId("ironman"),
product_id: IdMap.getId("product-test"),
})
},
})
@@ -820,9 +821,20 @@ describe("ProductVariantService", () => {
await productVariantService.delete(IdMap.getId("ironman"))
expect(productVariantRepository.softRemove).toBeCalledTimes(1)
expect(productVariantRepository.softRemove).toBeCalledWith({
id: IdMap.getId("ironman"),
})
expect(productVariantRepository.softRemove).toBeCalledWith(
expect.objectContaining({
id: IdMap.getId("ironman"),
})
)
expect(eventBusService.emit).toHaveBeenCalledTimes(1)
expect(eventBusService.emit).toHaveBeenCalledWith(
"product-variant.deleted",
{
id: IdMap.getId("ironman"),
product_id: IdMap.getId("product-test"),
}
)
})
it("successfully resolves if variant does not exist", async () => {
@@ -415,7 +415,9 @@ describe("ProductService", () => {
const productService = new ProductService({
manager: MockManager,
eventBusService,
productRepository,
eventBusService,
})
beforeEach(() => {
@@ -428,6 +430,11 @@ describe("ProductService", () => {
expect(productRepository.softRemove).toBeCalledWith({
id: IdMap.getId("ironman"),
})
expect(eventBusService.emit).toBeCalledTimes(1)
expect(eventBusService.emit).toBeCalledWith("product.deleted", {
id: IdMap.getId("ironman"),
})
})
})
@@ -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: [
{
+46 -32
View File
@@ -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"
)
+4 -2
View File
@@ -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 {
+120 -23
View File
@@ -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
@@ -166,9 +166,11 @@ class CartService extends BaseService {
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]
@@ -599,12 +601,13 @@ 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",
],
})
@@ -794,13 +797,18 @@ class CartService extends BaseService {
* @return {Promise} the result of the update operation
*/
async updateShippingAddress_(cart, addressOrId, addrRepo) {
if (addressOrId === null) {
cart.shipping_address = null
return
}
if (typeof addressOrId === `string`) {
addressOrId = await addrRepo.findOne({
where: { id: addressOrId },
})
}
addressOrId.country_code = addressOrId.country_code?.toLowerCase()
addressOrId.country_code = addressOrId.country_code?.toLowerCase() ?? null
if (
addressOrId.country_code &&
@@ -815,7 +823,7 @@ class CartService extends BaseService {
}
if (addressOrId.id) {
cart.shipping_address = addressOrId
await addrRepo.save(addressOrId)
cart.shipping_address_id = addressOrId.id
} else {
if (cart.shipping_address_id) {
@@ -871,6 +879,7 @@ class CartService extends BaseService {
async applyDiscount(cart, discountCode) {
const discount = await this.discountService_.retrieveByCode(discountCode, [
"rule",
"rule.valid_for",
"regions",
])
@@ -880,11 +889,12 @@ 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()
@@ -968,7 +978,13 @@ class CartService extends BaseService {
async removeDiscount(cartId, discountCode) {
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")) {
@@ -1084,7 +1100,7 @@ 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) => {
@@ -1145,13 +1161,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) => {
const psRepo = manager.getCustomRepository(this.paymentSessionRepository_)
let cartId =
const cartId =
typeof cartOrCartId === `string` ? cartOrCartId : cartOrCartId.id
const cart = await this.retrieve(cartId, {
select: [
@@ -1165,6 +1181,8 @@ class CartService extends BaseService {
relations: [
"items",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"gift_cards",
"billing_address",
"shipping_address",
@@ -1178,7 +1196,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 (
@@ -1228,7 +1246,7 @@ 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) => {
@@ -1269,7 +1287,7 @@ 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) => {
@@ -1317,6 +1335,8 @@ class CartService extends BaseService {
relations: [
"shipping_methods",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods.shipping_option",
"items",
"items.variant",
@@ -1324,11 +1344,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) {
@@ -1353,7 +1394,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
@@ -1368,6 +1414,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
@@ -1418,6 +1487,14 @@ class CartService extends BaseService {
)
}
/*
* When changing the region you are changing the set of countries that your
* cart can be shipped to so we need to make sure that the current shipping
* address adheres to the new country set.
*
* First check if there is an existing shipping address on the cart if so
* fetch the entire thing so we can modify the shipping country
*/
let shippingAddress = {}
if (cart.shipping_address_id) {
shippingAddress = await addrRepo.findOne({
@@ -1425,6 +1502,10 @@ class CartService extends BaseService {
})
}
/*
* If the client has specified which country code we are updating to check
* that that country is in fact in the country and perform the update.
*/
if (countryCode !== undefined) {
if (
!region.countries.find(
@@ -1444,7 +1525,17 @@ class CartService extends BaseService {
await addrRepo.save(updated)
} else {
/*
* In the case where the country code is not specified we need to check
*
* 1. if the region we are switching to has only one country preselect
* that
* 2. if the region has multiple countries we need to unset the country
* and wait for client to decide which country to use
*/
let updated = { ...shippingAddress }
// If the country code of a shipping address is set we need to clear it
if (!_.isEmpty(shippingAddress) && shippingAddress.country_code) {
updated = {
@@ -1501,13 +1592,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) => {
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) {
+22 -26
View File
@@ -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
+40 -29
View File
@@ -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)
+46 -41
View File
@@ -1,14 +1,11 @@
import _ from "lodash"
import randomize from "randomatic"
import { BaseService } from "medusa-interfaces"
import { Validator, MedusaError } from "medusa-core-utils"
import { MedusaErrorCodes } from "medusa-core-utils/dist/errors"
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({
@@ -17,7 +14,6 @@ class DiscountService extends BaseService {
discountRuleRepository,
giftCardRepository,
totalsService,
productVariantService,
productService,
regionService,
eventBusService,
@@ -80,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)
@@ -117,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 }) {
@@ -130,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(
@@ -153,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}%`) })
})
)
@@ -176,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)
)
)
}
@@ -205,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 = {}) {
@@ -229,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 = []) {
@@ -265,7 +261,7 @@ 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)
@@ -283,7 +279,7 @@ class DiscountService extends BaseService {
if (regions) {
discount.regions = await Promise.all(
regions.map(regionId => this.regionService_.retrieve(regionId))
regions.map((regionId) => this.regionService_.retrieve(regionId))
)
}
@@ -293,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)) {
@@ -307,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)
@@ -360,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)
@@ -381,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_
)
@@ -392,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
@@ -414,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_
)
@@ -425,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
@@ -445,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
@@ -474,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
@@ -500,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)
@@ -515,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.
+14 -13
View File
@@ -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
+21 -20
View File
@@ -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)
)
+11 -8
View File
@@ -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,7 +1,6 @@
import { BaseService } from "medusa-interfaces"
import { MedusaError } from "medusa-core-utils"
import { v4 } from "uuid"
import IdempotencyKeyModel from "../models/idempotency-key"
const KEY_LOCKED_TIMEOUT = 1000
@@ -28,7 +27,7 @@ class IdempotencyKeyService extends BaseService {
* @return {Promise<IdempotencyKeyModel>} the existing or created idempotency key
*/
async initializeRequest(headerKey, reqMethod, reqParams, reqPath) {
return this.atomicPhase_(async _ => {
return this.atomicPhase_(async (_) => {
// If idempotency key exists, return it
let key = await this.retrieve(headerKey)
@@ -54,7 +53,7 @@ class IdempotencyKeyService extends BaseService {
* @return {Promise<IdempotencyKeyModel>} the created idempotency key
*/
async create(payload) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const idempotencyKeyRepo = manager.getCustomRepository(
this.idempotencyKeyRepository_
)
@@ -93,7 +92,7 @@ class IdempotencyKeyService extends BaseService {
* @return {Promise} result of the update operation
*/
async lock(idempotencyKey) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const idempotencyKeyRepo = manager.getCustomRepository(
this.idempotencyKeyRepository_
)
@@ -120,7 +119,7 @@ class IdempotencyKeyService extends BaseService {
* @return {Promise} result of the update operation
*/
async update(idempotencyKey, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const idempotencyKeyRepo = manager.getCustomRepository(
this.idempotencyKeyRepository_
)
@@ -148,7 +147,7 @@ class IdempotencyKeyService extends BaseService {
*/
async workStage(idempotencyKey, func) {
try {
return await this.atomicPhase_(async manager => {
return await this.atomicPhase_(async (manager) => {
let key
const { recovery_point, response_code, response_body } = await func(
+10 -8
View File
@@ -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)
+7 -8
View File
@@ -1,6 +1,5 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import _ from "lodash"
import { TransactionManager } from "typeorm"
class NoteService extends BaseService {
@@ -47,7 +46,7 @@ class NoteService extends BaseService {
* Retrieves a specific note.
* @param {*} id - the id of the note to retrieve.
* @param {*} config - any options needed to query for the result.
* @returns {Promise} which resolves to the requested note.
* @return {Promise} which resolves to the requested note.
*/
async retrieve(id, config = {}) {
const noteRepo = this.manager_.getCustomRepository(this.noteRepository_)
@@ -91,14 +90,14 @@ class NoteService extends BaseService {
* Creates a note associated with a given author
* @param {object} data - the note to create
* @param {*} config - any configurations if needed, including meta data
* @returns {Promise} resolves to the creation result
* @return {Promise} resolves to the creation result
*/
async create(data, config = { metadata: {} }) {
const { metadata } = config
const { resource_id, resource_type, value, author_id } = data
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const toCreate = {
@@ -124,10 +123,10 @@ class NoteService extends BaseService {
* Updates a given note with a new value
* @param {*} noteId - the id of the note to update
* @param {*} value - the new value
* @returns {Promise} resolves to the updated element
* @return {Promise} resolves to the updated element
*/
async update(noteId, value) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const note = await this.retrieve(noteId, { relations: ["author"] })
@@ -147,10 +146,10 @@ class NoteService extends BaseService {
/**
* Deletes a given note
* @param {*} noteId - id of the note to delete
* @returns {Promise}
* @return {Promise}
*/
async delete(noteId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const note = await this.retrieve(noteId)
+10 -6
View File
@@ -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}`
+86 -58
View File
@@ -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)
})
}
+27 -16
View File
@@ -11,6 +11,7 @@ class ProductVariantService extends BaseService {
static Events = {
UPDATED: "product-variant.updated",
CREATED: "product-variant.created",
DELETED: "product-variant.deleted",
}
/** @param { productVariantModel: (ProductVariantModel) } */
@@ -118,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_
@@ -147,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}`
@@ -157,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
@@ -206,6 +207,7 @@ class ProductVariantService extends BaseService {
.withTransaction(manager)
.emit(ProductVariantService.Events.CREATED, {
id: result.id,
product_id: result.product_id,
})
return result
@@ -218,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_
)
@@ -233,6 +235,7 @@ class ProductVariantService extends BaseService {
.withTransaction(manager)
.emit(ProductVariantService.Events.UPDATED, {
id: result.id,
product_id: result.product_id,
})
return result
@@ -249,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_
)
@@ -309,6 +312,7 @@ class ProductVariantService extends BaseService {
.withTransaction(manager)
.emit(ProductVariantService.Events.UPDATED, {
id: result.id,
product_id: result.product_id,
fields: Object.keys(update),
})
return result
@@ -324,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_
)
@@ -363,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_
)
@@ -411,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_
)
@@ -448,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_
)
@@ -483,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_
)
@@ -507,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_
)
@@ -557,7 +561,7 @@ class ProductVariantService extends BaseService {
},
}
query.where = qb => {
query.where = (qb) => {
qb.where(where).andWhere([
{ sku: ILike(`%${q}%`) },
{ title: ILike(`%${q}%`) },
@@ -577,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_
)
@@ -588,6 +592,13 @@ class ProductVariantService extends BaseService {
await variantRepo.softRemove(variant)
await this.eventBus_
.withTransaction(manager)
.emit(ProductVariantService.Events.DELETED, {
id: variant.id,
product_id: variant.product_id,
})
return Promise.resolve()
})
}
+41 -35
View File
@@ -8,9 +8,11 @@ import { Brackets } from "typeorm"
* @implements BaseService
*/
class ProductService extends BaseService {
static IndexName = `products`
static Events = {
UPDATED: "product.updated",
CREATED: "product.created",
DELETED: "product.deleted",
}
constructor({
@@ -24,6 +26,7 @@ class ProductService extends BaseService {
productTypeRepository,
productTagRepository,
imageRepository,
searchService,
}) {
super()
@@ -56,6 +59,9 @@ class ProductService extends BaseService {
/** @private @const {ImageRepository} */
this.imageRepository_ = imageRepository
/** @private @const {SearchService} */
this.searchService_ = searchService
}
withTransaction(transactionManager) {
@@ -122,7 +128,7 @@ class ProductService extends BaseService {
.select(["product.id"])
.where(where)
.andWhere(
new Brackets(qb => {
new Brackets((qb) => {
qb.where(`product.description ILIKE :q`, { q: `%${q}%` })
.orWhere(`product.title ILIKE :q`, { q: `%${q}%` })
.orWhere(`variant.title ILIKE :q`, { q: `%${q}%` })
@@ -134,7 +140,7 @@ class ProductService extends BaseService {
return productRepo.findWithRelations(
rels,
raw.map(i => i.id)
raw.map((i) => i.id)
)
}
@@ -276,7 +282,7 @@ class ProductService extends BaseService {
* @return {Promise} resolves to the creation result.
*/
async create(productObject) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productRepo = manager.getCustomRepository(this.productRepository_)
const optionRepo = manager.getCustomRepository(
this.productOptionRepository_
@@ -295,7 +301,7 @@ class ProductService extends BaseService {
let product = productRepo.create(rest)
if (images && images.length) {
if (images) {
product.images = await this.upsertImages_(images)
}
@@ -310,7 +316,7 @@ class ProductService extends BaseService {
product = await productRepo.save(product)
product.options = await Promise.all(
options.map(async o => {
options.map(async (o) => {
const res = optionRepo.create({ ...o, product_id: product.id })
await optionRepo.save(res)
return res
@@ -360,7 +366,7 @@ class ProductService extends BaseService {
* @return {Promise} resolves to the update result.
*/
async update(productId, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productRepo = manager.getCustomRepository(this.productRepository_)
const productVariantRepo = manager.getCustomRepository(
this.productVariantRepository_
@@ -370,21 +376,14 @@ class ProductService extends BaseService {
relations: ["variants", "tags", "images"],
})
const {
variants,
metadata,
options,
images,
tags,
type,
...rest
} = update
const { variants, metadata, options, images, tags, type, ...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)
}
@@ -403,7 +402,7 @@ class ProductService extends BaseService {
if (variants) {
// Iterate product variants and update their properties accordingly
for (const variant of product.variants) {
const exists = variants.find(v => v.id && variant.id === v.id)
const exists = variants.find((v) => v.id && variant.id === v.id)
if (!exists) {
await productVariantRepo.remove(variant)
}
@@ -414,7 +413,7 @@ class ProductService extends BaseService {
newVariant.variant_rank = i
if (newVariant.id) {
const variant = product.variants.find(v => v.id === newVariant.id)
const variant = product.variants.find((v) => v.id === newVariant.id)
if (!variant) {
throw new MedusaError(
@@ -465,7 +464,7 @@ class ProductService extends BaseService {
* @return {Promise} empty promise
*/
async delete(productId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productRepo = manager.getCustomRepository(this.productRepository_)
// Should not fail, if product does not exist, since delete is idempotent
@@ -475,6 +474,12 @@ class ProductService extends BaseService {
await productRepo.softRemove(product)
await this.eventBus_
.withTransaction(manager)
.emit(ProductService.Events.DELETED, {
id: productId,
})
return Promise.resolve()
})
}
@@ -488,7 +493,7 @@ class ProductService extends BaseService {
* @return {Promise} the result of the model update operation
*/
async addOption(productId, optionTitle) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productOptionRepo = manager.getCustomRepository(
this.productOptionRepository_
)
@@ -497,7 +502,7 @@ class ProductService extends BaseService {
relations: ["options", "variants"],
})
if (product.options.find(o => o.title === optionTitle)) {
if (product.options.find((o) => o.title === optionTitle)) {
throw new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
`An option with the title: ${optionTitle} already exists`
@@ -527,7 +532,7 @@ class ProductService extends BaseService {
}
async reorderVariants(productId, variantOrder) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productRepo = manager.getCustomRepository(this.productRepository_)
const product = await this.retrieve(productId, {
@@ -541,8 +546,8 @@ class ProductService extends BaseService {
)
}
product.variants = variantOrder.map(vId => {
const variant = product.variants.find(v => v.id === vId)
product.variants = variantOrder.map((vId) => {
const variant = product.variants.find((v) => v.id === vId)
if (!variant) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
@@ -571,7 +576,7 @@ class ProductService extends BaseService {
* @return {Promise} the result of the update operation
*/
async reorderOptions(productId, optionOrder) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productRepo = manager.getCustomRepository(this.productRepository_)
const product = await this.retrieve(productId, { relations: ["options"] })
@@ -583,8 +588,8 @@ class ProductService extends BaseService {
)
}
product.options = optionOrder.map(oId => {
const option = product.options.find(o => o.id === oId)
product.options = optionOrder.map((oId) => {
const option = product.options.find((o) => o.id === oId)
if (!option) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
@@ -612,7 +617,7 @@ class ProductService extends BaseService {
* @return {Promise} the updated product
*/
async updateOption(productId, optionId, data) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productOptionRepo = manager.getCustomRepository(
this.productOptionRepository_
)
@@ -622,7 +627,8 @@ class ProductService extends BaseService {
const { title, values } = data
const optionExists = product.options.some(
o => o.title.toUpperCase() === title.toUpperCase() && o.id !== optionId
(o) =>
o.title.toUpperCase() === title.toUpperCase() && o.id !== optionId
)
if (optionExists) {
throw new MedusaError(
@@ -661,7 +667,7 @@ class ProductService extends BaseService {
* @return {Promise} the updated product
*/
async deleteOption(productId, optionId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productOptionRepo = manager.getCustomRepository(
this.productOptionRepository_
)
@@ -689,17 +695,17 @@ class ProductService extends BaseService {
const firstVariant = product.variants[0]
const valueToMatch = firstVariant.options.find(
o => o.option_id === optionId
(o) => o.option_id === optionId
).value
const equalsFirst = await Promise.all(
product.variants.map(async v => {
const option = v.options.find(o => o.option_id === optionId)
product.variants.map(async (v) => {
const option = v.options.find((o) => o.option_id === optionId)
return option.value === valueToMatch
})
)
if (!equalsFirst.every(v => v)) {
if (!equalsFirst.every((v) => v)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`To delete an option, first delete all variants, such that when option is deleted, no duplicate variants will exist.`
@@ -23,11 +23,13 @@ class QueryBuilderService extends BaseService {
}
buildFilterQuery(filters) {
if (_.isEmpty(filters)) return
if (_.isEmpty(filters)) {
return
}
const filterQuery = {}
Object.keys(filters).map(filter => {
Object.keys(filters).map((filter) => {
filterQuery[filter] = filters[filter]
})
@@ -35,9 +37,11 @@ class QueryBuilderService extends BaseService {
}
buildTextSearchQuery(search, searchProperties) {
if (_.isEmpty(search)) return
if (_.isEmpty(search)) {
return
}
const searchQuery = searchProperties.map(s => ({
const searchQuery = searchProperties.map((s) => ({
[s]: new RegExp(search.q, "i"),
}))
+35 -25
View File
@@ -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
+25 -6
View File
@@ -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 = {
+66
View File
@@ -0,0 +1,66 @@
import { SearchService } from "medusa-interfaces"
/**
* Default class that implements SearchService but provides stuv implementation for all methods
* @extends SearchService
*/
class DefaultSearchService extends SearchService {
constructor(container) {
super()
this.isDefault = true
this.logger_ = container.logger
}
createIndex(indexName, options) {
this.logger_.warn(
"This is an empty method: createIndex must be overridden by a child class"
)
}
getIndex(indexName) {
this.logger_.warn(
"This is an empty method: getIndex must be overridden by a child class"
)
}
addDocuments(indexName, documents, type) {
this.logger_.warn(
"This is an empty method: addDocuments must be overridden by a child class"
)
}
replaceDocuments(indexName, documents, type) {
this.logger_.warn(
"This is an empty method: replaceDocuments must be overridden by a child class"
)
}
deleteDocument(indexName, document_id) {
this.logger_.warn(
"This is an empty method: deleteDocument must be overridden by a child class"
)
}
deleteAllDocuments(indexName) {
this.logger_.warn(
"This is an empty method: deleteAllDocuments must be overridden by a child class"
)
}
search(indexName, query, options) {
this.logger_.warn(
"This is an empty method: search must be overridden a the child class"
)
return { hits: [] }
}
updateSettings(indexName, settings) {
this.logger_.warn(
"This is an empty method: updateSettings must be overridden by a child class"
)
}
}
export default DefaultSearchService
+37 -32
View File
@@ -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,7 +454,7 @@ class ShippingOptionService extends BaseService {
* @return {Promise} resolves to the update result.
*/
async update(optionId, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const option = await this.retrieve(optionId, {
relations: ["requirements"],
})
@@ -477,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"
@@ -486,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)
@@ -502,12 +507,12 @@ class ShippingOptionService extends BaseService {
}
if (option.requirements) {
const accReqs = acc.map(a => a.id)
const accReqs = acc.map((a) => a.id)
const toRemove = option.requirements.filter(
r => !accReqs.includes(r.id)
(r) => !accReqs.includes(r.id)
)
await Promise.all(
toRemove.map(async req => {
toRemove.map(async (req) => {
await this.removeRequirement(req.id)
})
)
@@ -552,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_
@@ -579,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`
@@ -605,7 +610,7 @@ class ShippingOptionService extends BaseService {
* @return {Promise} the result of update
*/
async removeRequirement(requirementId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
try {
const reqRepo = manager.getCustomRepository(this.requirementRepository_)
const requirement = await reqRepo.findOne({
@@ -624,7 +629,7 @@ class ShippingOptionService extends BaseService {
/**
* 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.
@@ -644,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) {
@@ -677,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) {
+46 -24
View File
@@ -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"],
})
@@ -3,7 +3,7 @@ import { BaseService } from "medusa-interfaces"
class SystemProviderService extends BaseService {
static identifier = "system"
constructor({}) {
constructor(_) {
super()
}
+14
View File
@@ -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 -2
View File
@@ -1,9 +1,8 @@
import { BaseService } from "medusa-interfaces"
import mongoose from "mongoose"
import _ from "lodash"
class TransactionService extends BaseService {
constructor({}) {
constructor() {
super()
}
+19 -14
View File
@@ -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,9 +48,7 @@ class UserService 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(
@@ -75,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 = {}) {
@@ -99,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 = []) {
@@ -123,6 +123,7 @@ 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, config = {}) {
@@ -144,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 })
@@ -155,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)
@@ -177,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)
@@ -222,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)
@@ -242,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)
@@ -270,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)
+7 -1
View File
@@ -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(
+110
View File
@@ -0,0 +1,110 @@
import ProductVariantService from "../services/product-variant"
import ProductService from "../services/product"
import { indexTypes } from "medusa-core-utils"
const searchFields = [
"id",
"title",
"subtitle",
"description",
"handle",
"is_giftcard",
"discountable",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"origin_country",
"created_at",
"updated_at",
]
const searchRelations = [
"variants",
"tags",
"type",
"collection",
"variants.prices",
"variants.options",
"options",
]
class ProductSearchSubscriber {
constructor({ eventBusService, searchService, productService }) {
this.eventBus_ = eventBusService
this.searchService_ = searchService
this.productService_ = productService
this.eventBus_.subscribe(
ProductService.Events.CREATED,
this.handleProductCreation
)
this.eventBus_.subscribe(
ProductService.Events.UPDATED,
this.handleProductUpdate
)
this.eventBus_.subscribe(
ProductService.Events.DELETED,
this.handleProductDeletion
)
this.eventBus_.subscribe(
ProductVariantService.Events.CREATED,
this.handleProductVariantChange
)
this.eventBus_.subscribe(
ProductVariantService.Events.UPDATED,
this.handleProductVariantChange
)
this.eventBus_.subscribe(
ProductVariantService.Events.DELETED,
this.handleProductVariantChange
)
}
handleProductCreation = async (data) => {
const product = await this.retrieveProduct_(data.id)
await this.searchService_.addDocuments(
ProductService.IndexName,
[product],
indexTypes.products
)
}
retrieveProduct_ = async (product_id) => {
return await this.productService_.retrieve(product_id, {
select: searchFields,
relations: searchRelations,
})
}
handleProductUpdate = async (data) => {
const product = await this.retrieveProduct_(data.id)
await this.searchService_.addDocuments(
ProductService.IndexName,
[product],
indexTypes.products
)
}
handleProductDeletion = async (data) => {
await this.searchService_.deleteDocument(ProductService.IndexName, data.id)
}
handleProductVariantChange = async (data) => {
const product = await this.retrieveProduct_(data.product_id)
await this.searchService_.addDocuments(
ProductService.IndexName,
[product],
indexTypes.products
)
}
}
export default ProductSearchSubscriber
+700 -968
View File
File diff suppressed because it is too large. Load diff