Merge branch 'develop' into develop
This commit is contained in:
158 files changed
+11728
-8078
No files matched your search
+337
-804
File diff suppressed because it is too large.
Load diff
@@ -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"
|
||||
}
|
||||
@@ -27,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",
|
||||
@@ -56,6 +57,7 @@ const defaultFields = [
|
||||
"metadata",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -241,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",
|
||||
@@ -271,6 +272,7 @@ export const defaultFields = [
|
||||
"metadata",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -113,12 +113,14 @@ import { PaymentSession } from "./payment-session"
|
||||
import { Payment } from "./payment"
|
||||
import { GiftCard } from "./gift-card"
|
||||
import { ShippingMethod } from "./shipping-method"
|
||||
import { CustomShippingOption } from "./custom-shipping-option"
|
||||
|
||||
export enum CartType {
|
||||
DEFAULT = "default",
|
||||
SWAP = "swap",
|
||||
DRAFT_ORDER = "draft_order",
|
||||
PAYMENT_LINK = "payment_link",
|
||||
CLAIM = "claim",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
BeforeInsert, Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
Unique,
|
||||
UpdateDateColumn
|
||||
} from "typeorm";
|
||||
import { ulid } from "ulid";
|
||||
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column";
|
||||
import { Cart } from './cart';
|
||||
import { ShippingOption } from "./shipping-option";
|
||||
|
||||
|
||||
@Entity()
|
||||
@Unique(['shipping_option_id', 'cart_id'])
|
||||
export class CustomShippingOption {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Column({ type: "int" })
|
||||
price: number
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
shipping_option_id: string;
|
||||
|
||||
@ManyToOne(() => ShippingOption)
|
||||
@JoinColumn({ name: "shipping_option_id" })
|
||||
shipping_option: ShippingOption
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
cart_id: string
|
||||
|
||||
@ManyToOne(() => Cart)
|
||||
@JoinColumn({ name: "cart_id" })
|
||||
cart: Cart
|
||||
|
||||
@CreateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
created_at: Date
|
||||
|
||||
@UpdateDateColumn({ type: resolveDbType("timestamptz") })
|
||||
updated_at: Date
|
||||
|
||||
@DeleteDateColumn({ type: resolveDbType("timestamptz") })
|
||||
deleted_at: Date
|
||||
|
||||
@DbAwareColumn({ type: "jsonb", nullable: true })
|
||||
metadata: any
|
||||
|
||||
@BeforeInsert()
|
||||
private beforeInsert() {
|
||||
if (this.id) return
|
||||
const id = ulid()
|
||||
this.id = `cso_${id}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @schema Custom shipping_option
|
||||
* title: "Custom Shipping Option"
|
||||
* description: "Custom Shipping Options are 'overriden' Shipping Options. Store managers can attach a Custom Shipping Option to a cart in order to set a custom price for a particular Shipping Option"
|
||||
* x-resourceId: custom_shipping_option
|
||||
* properties:
|
||||
* id:
|
||||
* description: "The id of the Custom Shipping Option. This value will be prefixed with `cso_`."
|
||||
* type: string
|
||||
* price:
|
||||
* description: "The custom price set that will override the shipping option's original price"
|
||||
* type: integer
|
||||
* shipping_option_id:
|
||||
* description: "The id of the Shipping Option that the custom shipping option overrides"
|
||||
* anyOf:
|
||||
* - $ref: "#/components/schemas/shipping_option"
|
||||
* cart_id:
|
||||
* description: "The id of the Cart that the custom shipping option is attached to"
|
||||
* anyOf:
|
||||
* - $ref: "#/components/schemas/cart"
|
||||
* created_at:
|
||||
* description: "The date with timezone at which the resource was created."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updated_at:
|
||||
* description: "The date with timezone at which the resource was last updated."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* deleted_at:
|
||||
* description: "The date with timezone at which the resource was deleted."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* metadata:
|
||||
* description: "An optional key-value map with additional information."
|
||||
* type: object
|
||||
*/
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { CustomShippingOption } from './../models/custom-shipping-option';
|
||||
|
||||
@EntityRepository(CustomShippingOption)
|
||||
export class CustomShippingOptionRepository extends Repository<CustomShippingOption> {}
|
||||
@@ -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(
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
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 {
|
||||
// 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
|
||||
// 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
|
||||
@@ -25,17 +38,15 @@ export class ProductRepository extends Repository<Product> {
|
||||
.where(idsOrOptionsWithoutRelations.where)
|
||||
.skip(idsOrOptionsWithoutRelations.skip)
|
||||
.take(idsOrOptionsWithoutRelations.take)
|
||||
|
||||
if (tags) {
|
||||
.orderBy(idsOrOptionsWithoutRelations.order)
|
||||
|
||||
if (tags) {
|
||||
qb = qb
|
||||
.leftJoinAndSelect("product.tags", "tags")
|
||||
.andWhere(
|
||||
`tags.id IN (:...ids)`, { ids: tags._value}
|
||||
)
|
||||
.andWhere(`tags.id IN (:...ids)`, { ids: tags._value })
|
||||
}
|
||||
|
||||
entities = await qb
|
||||
.getMany()
|
||||
|
||||
entities = await qb.getMany()
|
||||
}
|
||||
const entitiesIds = entities.map(({ id }) => id)
|
||||
|
||||
|
||||
@@ -6,29 +6,29 @@ import { MedusaError } from "medusa-core-utils"
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
describe("CartService", () => {
|
||||
const totalsService = {
|
||||
getTotal: o => {
|
||||
getTotal: (o) => {
|
||||
return o.total || 0
|
||||
},
|
||||
getSubtotal: o => {
|
||||
getSubtotal: (o) => {
|
||||
return o.subtotal || 0
|
||||
},
|
||||
getTaxTotal: o => {
|
||||
getTaxTotal: (o) => {
|
||||
return o.tax_total || 0
|
||||
},
|
||||
getDiscountTotal: o => {
|
||||
getDiscountTotal: (o) => {
|
||||
return o.discount_total || 0
|
||||
},
|
||||
getShippingTotal: o => {
|
||||
getShippingTotal: (o) => {
|
||||
return o.shipping_total || 0
|
||||
},
|
||||
getGiftCardTotal: o => {
|
||||
getGiftCardTotal: (o) => {
|
||||
return o.gift_card_total || 0
|
||||
},
|
||||
}
|
||||
@@ -117,7 +117,7 @@ describe("CartService", () => {
|
||||
|
||||
describe("deleteMetadata", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOne: id => {
|
||||
findOne: (id) => {
|
||||
if (id === "empty") {
|
||||
return Promise.resolve({
|
||||
metadata: {},
|
||||
@@ -200,7 +200,7 @@ describe("CartService", () => {
|
||||
},
|
||||
}
|
||||
|
||||
const addressRepository = MockRepository({ create: c => c })
|
||||
const addressRepository = MockRepository({ create: (c) => c })
|
||||
const cartRepository = MockRepository()
|
||||
const customerService = {
|
||||
retrieveByEmail: jest.fn().mockReturnValue(
|
||||
@@ -209,7 +209,7 @@ describe("CartService", () => {
|
||||
email: "email@test.com",
|
||||
})
|
||||
),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -317,14 +317,14 @@ describe("CartService", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
create: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
const shippingOptionService = {
|
||||
deleteShippingMethod: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -505,7 +505,7 @@ describe("CartService", () => {
|
||||
const lineItemService = {
|
||||
delete: jest.fn(),
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -547,7 +547,7 @@ describe("CartService", () => {
|
||||
|
||||
const shippingOptionService = {
|
||||
deleteShippingMethod: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -666,7 +666,7 @@ describe("CartService", () => {
|
||||
describe("updateLineItem", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -674,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({
|
||||
@@ -747,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()
|
||||
}
|
||||
@@ -756,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
|
||||
},
|
||||
}
|
||||
@@ -847,7 +847,7 @@ describe("CartService", () => {
|
||||
}),
|
||||
})
|
||||
|
||||
const addressRepository = MockRepository({ create: c => c })
|
||||
const addressRepository = MockRepository({ create: (c) => c })
|
||||
|
||||
const cartService = new CartService({
|
||||
manager: MockManager,
|
||||
@@ -908,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,
|
||||
@@ -982,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()
|
||||
}
|
||||
@@ -1034,7 +1034,7 @@ describe("CartService", () => {
|
||||
deleteSession: jest.fn(),
|
||||
updateSession: jest.fn(),
|
||||
createSession: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1238,7 +1238,7 @@ describe("CartService", () => {
|
||||
deleteSession: jest.fn(),
|
||||
updateSession: jest.fn(),
|
||||
createSession: jest.fn(),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1309,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: {
|
||||
@@ -1321,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),
|
||||
@@ -1338,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) => {
|
||||
@@ -1346,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)
|
||||
}
|
||||
@@ -1354,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,
|
||||
@@ -1367,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,
|
||||
@@ -1379,6 +1435,7 @@ describe("CartService", () => {
|
||||
shippingOptionService,
|
||||
lineItemService,
|
||||
eventBusService,
|
||||
customShippingOptionService,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -1396,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 () => {
|
||||
@@ -1410,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: {
|
||||
@@ -1438,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 () => {
|
||||
@@ -1460,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
|
||||
@@ -1509,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: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
@@ -199,6 +210,8 @@ describe("SwapService", () => {
|
||||
"order.swaps.additional_items",
|
||||
"order.discounts",
|
||||
"order.discounts.rule",
|
||||
"order.claims",
|
||||
"order.claims.additional_items",
|
||||
"additional_items",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
@@ -304,7 +317,7 @@ describe("SwapService", () => {
|
||||
const swapRepo = MockRepository()
|
||||
const returnService = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve({ id: "ret" })),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -401,7 +414,7 @@ describe("SwapService", () => {
|
||||
receiveReturn: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ test: "received" })),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -447,7 +460,7 @@ describe("SwapService", () => {
|
||||
receiveReturn: jest
|
||||
.fn()
|
||||
.mockReturnValue(Promise.resolve({ status: "requires_action" })),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -525,7 +538,7 @@ describe("SwapService", () => {
|
||||
{ items: [{ item_id: "1234", quantity: 2 }], data: "new" },
|
||||
])
|
||||
),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -546,7 +559,7 @@ describe("SwapService", () => {
|
||||
const lineItemService = {
|
||||
update: jest.fn(),
|
||||
retrieve: () => Promise.resolve({}),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -621,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({})
|
||||
@@ -635,7 +648,7 @@ describe("SwapService", () => {
|
||||
})
|
||||
}
|
||||
}),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -689,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
|
||||
},
|
||||
}
|
||||
@@ -737,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
|
||||
},
|
||||
}
|
||||
@@ -820,7 +833,7 @@ describe("SwapService", () => {
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -835,7 +848,7 @@ describe("SwapService", () => {
|
||||
updateShippingMethod: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -844,7 +857,7 @@ describe("SwapService", () => {
|
||||
update: () => {
|
||||
return Promise.resolve()
|
||||
},
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -859,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
|
||||
},
|
||||
}
|
||||
@@ -987,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
|
||||
},
|
||||
}
|
||||
@@ -1127,7 +1140,7 @@ describe("SwapService", () => {
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1190,7 +1203,7 @@ describe("SwapService", () => {
|
||||
|
||||
const paymentProviderService = {
|
||||
cancelPayment: jest.fn(() => Promise.resolve({})),
|
||||
withTransaction: function() {
|
||||
withTransaction: function () {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -1222,7 +1235,7 @@ describe("SwapService", () => {
|
||||
return Promise.resolve(swap)
|
||||
}
|
||||
},
|
||||
save: f => f,
|
||||
save: (f) => f,
|
||||
})
|
||||
|
||||
const swapService = new SwapService({
|
||||
@@ -1271,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"
|
||||
)
|
||||
|
||||
@@ -24,7 +24,6 @@ class CartService extends BaseService {
|
||||
regionService,
|
||||
lineItemService,
|
||||
shippingOptionService,
|
||||
shippingProfileService,
|
||||
customerService,
|
||||
discountService,
|
||||
giftCardService,
|
||||
@@ -32,6 +31,7 @@ class CartService extends BaseService {
|
||||
addressRepository,
|
||||
paymentSessionRepository,
|
||||
inventoryService,
|
||||
customShippingOptionService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -62,9 +62,6 @@ class CartService extends BaseService {
|
||||
/** @private @const {PaymentProviderService} */
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
|
||||
/** @private @const {ShippingProfileService} */
|
||||
this.shippingProfileService_ = shippingProfileService
|
||||
|
||||
/** @private @const {CustomerService} */
|
||||
this.customerService_ = customerService
|
||||
|
||||
@@ -88,6 +85,9 @@ class CartService extends BaseService {
|
||||
|
||||
/** @private @const {InventoryService} */
|
||||
this.inventoryService_ = inventoryService
|
||||
|
||||
/** @private @const {CustomShippingOptionService} */
|
||||
this.customShippingOptionService_ = customShippingOptionService
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -107,13 +107,13 @@ class CartService extends BaseService {
|
||||
regionService: this.regionService_,
|
||||
lineItemService: this.lineItemService_,
|
||||
shippingOptionService: this.shippingOptionService_,
|
||||
shippingProfileService: this.shippingProfileService_,
|
||||
customerService: this.customerService_,
|
||||
discountService: this.discountService_,
|
||||
totalsService: this.totalsService_,
|
||||
addressRepository: this.addressRepository_,
|
||||
giftCardService: this.giftCardService_,
|
||||
inventoryService: this.inventoryService_,
|
||||
customShippingOptionService: this.customShippingOptionService_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -160,7 +160,7 @@ class CartService extends BaseService {
|
||||
"total",
|
||||
]
|
||||
|
||||
const totalsToSelect = select.filter(v => totalFields.includes(v))
|
||||
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
||||
if (totalsToSelect.length > 0) {
|
||||
const relationSet = new Set(relations)
|
||||
relationSet.add("items")
|
||||
@@ -168,14 +168,14 @@ class CartService extends BaseService {
|
||||
relationSet.add("discounts")
|
||||
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("discounts.parent_discount")
|
||||
// relationSet.add("discounts.parent_discount.rule")
|
||||
// relationSet.add("discounts.parent_discount.regions")
|
||||
relationSet.add("shipping_methods")
|
||||
relationSet.add("region")
|
||||
relations = [...relationSet]
|
||||
|
||||
select = select.filter(v => !totalFields.includes(v))
|
||||
select = select.filter((v) => !totalFields.includes(v))
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -238,9 +238,8 @@ class CartService extends BaseService {
|
||||
const cartRepo = this.manager_.getCustomRepository(this.cartRepository_)
|
||||
const validatedId = this.validateId_(cartId)
|
||||
|
||||
const { select, relations, totalsToSelect } = this.transformQueryForTotals_(
|
||||
options
|
||||
)
|
||||
const { select, relations, totalsToSelect } =
|
||||
this.transformQueryForTotals_(options)
|
||||
|
||||
const query = {
|
||||
where: { id: validatedId },
|
||||
@@ -275,7 +274,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the create operation
|
||||
*/
|
||||
async create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const addressRepo = manager.getCustomRepository(this.addressRepository_)
|
||||
const { region_id } = data
|
||||
@@ -344,7 +343,7 @@ class CartService extends BaseService {
|
||||
* @retur {Promise} the result of the update operation
|
||||
*/
|
||||
async removeLineItem(cartId, lineItemId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"items",
|
||||
@@ -354,7 +353,7 @@ class CartService extends BaseService {
|
||||
],
|
||||
})
|
||||
|
||||
const lineItem = cart.items.find(li => li.id === lineItemId)
|
||||
const lineItem = cart.items.find((li) => li.id === lineItemId)
|
||||
if (!lineItem) {
|
||||
return cart
|
||||
}
|
||||
@@ -423,7 +422,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addLineItem(cartId, lineItem) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"shipping_methods",
|
||||
@@ -436,7 +435,7 @@ class CartService extends BaseService {
|
||||
|
||||
let currentItem
|
||||
if (lineItem.should_merge) {
|
||||
currentItem = cart.items.find(line => {
|
||||
currentItem = cart.items.find((line) => {
|
||||
if (line.should_merge && line.variant_id === lineItem.variant_id) {
|
||||
return _.isEqual(line.metadata, lineItem.metadata)
|
||||
}
|
||||
@@ -502,13 +501,13 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateLineItem(cartId, lineItemId, lineItemUpdate) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["items", "payment_sessions"],
|
||||
})
|
||||
|
||||
// Ensure that the line item exists in the cart
|
||||
const lineItemExists = cart.items.find(i => i.id === lineItemId)
|
||||
const lineItemExists = cart.items.find((i) => i.id === lineItemId)
|
||||
if (!lineItemExists) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -555,7 +554,7 @@ class CartService extends BaseService {
|
||||
// if any free shipping discounts, we ensure to update shipping method amount
|
||||
if (shouldAdd) {
|
||||
await Promise.all(
|
||||
cart.shipping_methods.map(async sm => {
|
||||
cart.shipping_methods.map(async (sm) => {
|
||||
const smRepo = this.manager_.getCustomRepository(
|
||||
this.shippingMethodRepository_
|
||||
)
|
||||
@@ -570,7 +569,7 @@ class CartService extends BaseService {
|
||||
)
|
||||
} else {
|
||||
await Promise.all(
|
||||
cart.shipping_methods.map(async sm => {
|
||||
cart.shipping_methods.map(async (sm) => {
|
||||
const smRepo = this.manager_.getCustomRepository(
|
||||
this.shippingMethodRepository_
|
||||
)
|
||||
@@ -586,7 +585,7 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
async update(cartId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: [
|
||||
@@ -733,9 +732,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the resultign customer object
|
||||
*/
|
||||
async createOrFetchUserFromEmail_(email) {
|
||||
const schema = Validator.string()
|
||||
.email()
|
||||
.required()
|
||||
const schema = Validator.string().email().required()
|
||||
const { value, error } = schema.validate(email.toLowerCase())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
@@ -886,11 +883,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()
|
||||
@@ -942,7 +940,7 @@ class CartService extends BaseService {
|
||||
const toParse = [...cart.discounts, discount]
|
||||
|
||||
let sawNotShipping = false
|
||||
const newDiscounts = toParse.map(d => {
|
||||
const newDiscounts = toParse.map((d) => {
|
||||
const drule = d.rule
|
||||
switch (drule.type) {
|
||||
case "free_shipping":
|
||||
@@ -972,7 +970,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise<Cart>} the resulting cart
|
||||
*/
|
||||
async removeDiscount(cartId, discountCode) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"discounts",
|
||||
@@ -987,7 +985,7 @@ class CartService extends BaseService {
|
||||
await this.adjustFreeShipping_(cart, false)
|
||||
}
|
||||
|
||||
cart.discounts = cart.discounts.filter(d => d.code !== discountCode)
|
||||
cart.discounts = cart.discounts.filter((d) => d.code !== discountCode)
|
||||
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
@@ -1018,7 +1016,7 @@ class CartService extends BaseService {
|
||||
* Updates the currently selected payment session.
|
||||
*/
|
||||
async updatePaymentSession(cartId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1052,7 +1050,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise<Cart>} the resulting cart
|
||||
*/
|
||||
async authorizePayment(cartId, context = {}) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepository = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
const cart = await this.retrieve(cartId, {
|
||||
@@ -1096,10 +1094,10 @@ class CartService extends BaseService {
|
||||
* Sets a payment method for a cart.
|
||||
* @param {string} cartId - the id of the cart to add payment method to
|
||||
* @param {PaymentMethod} paymentMethod - the method to be set to the cart
|
||||
* @returns {Promise} result of update operation
|
||||
* @return {Promise} result of update operation
|
||||
*/
|
||||
async setPaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const psRepo = manager.getCustomRepository(this.paymentSessionRepository_)
|
||||
|
||||
const cart = await this.retrieve(cartId, {
|
||||
@@ -1128,13 +1126,13 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
cart.payment_sessions.map(ps => {
|
||||
cart.payment_sessions.map((ps) => {
|
||||
return psRepo.save({ ...ps, is_selected: null })
|
||||
})
|
||||
)
|
||||
|
||||
const sess = cart.payment_sessions.find(
|
||||
ps => ps.provider_id === providerId
|
||||
(ps) => ps.provider_id === providerId
|
||||
)
|
||||
|
||||
sess.is_selected = true
|
||||
@@ -1157,13 +1155,13 @@ class CartService extends BaseService {
|
||||
* amounts, currencies, etc. as well as make sure to filter payment sessions
|
||||
* that are not available for the cart's region.
|
||||
* @param {string} cartId - the id of the cart to set payment session for
|
||||
* @returns {Promise} the result of the update operation.
|
||||
* @return {Promise} the result of the update operation.
|
||||
*/
|
||||
async setPaymentSessions(cartOrCartId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const psRepo = manager.getCustomRepository(this.paymentSessionRepository_)
|
||||
|
||||
let cartId =
|
||||
const cartId =
|
||||
typeof cartOrCartId === `string` ? cartOrCartId : cartOrCartId.id
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: [
|
||||
@@ -1192,7 +1190,7 @@ class CartService extends BaseService {
|
||||
const region = cart.region
|
||||
|
||||
// If there are existing payment sessions ensure that these are up to date
|
||||
let seen = []
|
||||
const seen = []
|
||||
if (cart.payment_sessions && cart.payment_sessions.length) {
|
||||
for (const session of cart.payment_sessions) {
|
||||
if (
|
||||
@@ -1242,10 +1240,10 @@ class CartService extends BaseService {
|
||||
* @param {string} cartId - the id of the cart to remove from
|
||||
* @param {string} providerId - the id of the provider whoose payment session
|
||||
* should be removed.
|
||||
* @returns {Promise<Cart>} the resulting cart.
|
||||
* @return {Promise<Cart>} the resulting cart.
|
||||
*/
|
||||
async deletePaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1283,10 +1281,10 @@ class CartService extends BaseService {
|
||||
* @param {string} cartId - the id of the cart to remove from
|
||||
* @param {string} providerId - the id of the provider whoose payment session
|
||||
* should be removed.
|
||||
* @returns {Promise<Cart>} the resulting cart.
|
||||
* @return {Promise<Cart>} the resulting cart.
|
||||
*/
|
||||
async refreshPaymentSession(cartId, providerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -1325,7 +1323,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addShippingMethod(cartId, optionId, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
@@ -1340,11 +1338,32 @@ class CartService extends BaseService {
|
||||
"items.variant.product",
|
||||
],
|
||||
})
|
||||
|
||||
const cartCustomShippingOptions =
|
||||
await this.customShippingOptionService_.list({ cart_id: cart.id })
|
||||
|
||||
const customShippingOption = this.findCustomShippingOption(
|
||||
cartCustomShippingOptions,
|
||||
optionId
|
||||
)
|
||||
|
||||
const { shipping_methods } = cart
|
||||
|
||||
/**
|
||||
* If we have a custom shipping option configured we want the price
|
||||
* override to take effect and do not want `validateCartOption` to check
|
||||
* if requirements are met, hence we are not passing the entire cart, but
|
||||
* just the id.
|
||||
*/
|
||||
const shippingMethodConfig = customShippingOption
|
||||
? { cart_id: cart.id, price: customShippingOption.price }
|
||||
: {
|
||||
cart,
|
||||
}
|
||||
|
||||
const newMethod = await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.createShippingMethod(optionId, data, { cart })
|
||||
.createShippingMethod(optionId, data, shippingMethodConfig)
|
||||
|
||||
const methods = [newMethod]
|
||||
if (shipping_methods.length) {
|
||||
@@ -1389,6 +1408,29 @@ class CartService extends BaseService {
|
||||
}, "SERIALIZABLE")
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the cart's custom shipping options based on the passed option id.
|
||||
* throws if custom options is not empty and no shipping option corresponds to optionId
|
||||
* @param {Object} cartCustomShippingOptions - the cart's custom shipping options
|
||||
* @param {string} option - id of the normal or custom shipping option to find in the cartCustomShippingOptions
|
||||
* @return {CustomShippingOption | undefined}
|
||||
*/
|
||||
findCustomShippingOption(cartCustomShippingOptions, optionId) {
|
||||
const customOption = cartCustomShippingOptions?.find(
|
||||
(cso) => cso.shipping_option_id === optionId
|
||||
)
|
||||
const hasCustomOptions = cartCustomShippingOptions?.length
|
||||
|
||||
if (hasCustomOptions && !customOption) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Wrong shipping option"
|
||||
)
|
||||
}
|
||||
|
||||
return customOption
|
||||
}
|
||||
|
||||
/**
|
||||
* Set's the region of a cart.
|
||||
* @param {string} cartId - the id of the cart to set region on
|
||||
@@ -1416,7 +1458,7 @@ class CartService extends BaseService {
|
||||
if (cart.items.length) {
|
||||
cart.items = await Promise.all(
|
||||
cart.items
|
||||
.map(async item => {
|
||||
.map(async (item) => {
|
||||
const availablePrice = await this.productVariantService_
|
||||
.getRegionPrice(item.variant_id, regionId)
|
||||
.catch(() => undefined)
|
||||
@@ -1495,20 +1537,20 @@ class CartService extends BaseService {
|
||||
}
|
||||
|
||||
if (cart.discounts && cart.discounts.length) {
|
||||
const newDiscounts = cart.discounts.map(d => {
|
||||
const newDiscounts = cart.discounts.map((d) => {
|
||||
if (d.regions.find(({ id }) => id === regionId)) {
|
||||
return d
|
||||
}
|
||||
})
|
||||
|
||||
cart.discounts = newDiscounts.filter(d => !!d)
|
||||
cart.discounts = newDiscounts.filter((d) => !!d)
|
||||
}
|
||||
|
||||
cart.gift_cards = []
|
||||
|
||||
if (cart.payment_sessions && cart.payment_sessions.length) {
|
||||
await Promise.all(
|
||||
cart.payment_sessions.map(ps =>
|
||||
cart.payment_sessions.map((ps) =>
|
||||
this.paymentProviderService_
|
||||
.withTransaction(this.manager_)
|
||||
.deleteSession(ps)
|
||||
@@ -1522,11 +1564,11 @@ class CartService extends BaseService {
|
||||
/**
|
||||
* Deletes a cart from the database. Completed carts cannot be deleted.
|
||||
* @param {string} cartId - the id of the cart to delete
|
||||
* @returns {Promise<Cart?>} the deleted cart or undefined if the cart was
|
||||
* @return {Promise<Cart?>} the deleted cart or undefined if the cart was
|
||||
* not found.
|
||||
*/
|
||||
async delete(cartId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cart = await this.retrieve(cartId, {
|
||||
relations: [
|
||||
"items",
|
||||
@@ -1566,7 +1608,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async setMetadata(cartId, key, value) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
|
||||
const validatedId = this.validateId_(cartId)
|
||||
@@ -1600,7 +1642,7 @@ class CartService extends BaseService {
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
async deleteMetadata(cartId, key) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const cartRepo = manager.getCustomRepository(this.cartRepository_)
|
||||
const validatedId = this.validateId_(cartId)
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
|
||||
class CustomShippingOptionService extends BaseService {
|
||||
constructor({ manager, customShippingOptionRepository }) {
|
||||
super()
|
||||
|
||||
/** @private @const {EntityManager} */
|
||||
this.manager_ = manager
|
||||
|
||||
/** @private @const {CustomShippingOptionRepository} */
|
||||
this.customShippingOptionRepository_ = customShippingOptionRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the service's manager to a given transaction manager
|
||||
* @param {EntityManager} manager - the manager to use
|
||||
* @return {CustomShippingOptionService} a cloned CustomShippingOption service
|
||||
*/
|
||||
withTransaction(manager) {
|
||||
if (!manager) {
|
||||
return this
|
||||
}
|
||||
|
||||
const cloned = new CustomShippingOptionService({
|
||||
manager,
|
||||
customShippingOptionRepository: this.customShippingOptionRepository_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = manager
|
||||
return cloned
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific shipping option.
|
||||
* @param {string} id - the id of the custom shipping option to retrieve.
|
||||
* @param {*} config - any options needed to query for the result.
|
||||
* @return {Promise<CustomShippingOption>} which resolves to the requested custom shipping option.
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
const customShippingOptionRepo = this.manager_.getCustomRepository(
|
||||
this.customShippingOptionRepository_
|
||||
)
|
||||
|
||||
const validatedId = this.validateId_(id)
|
||||
const query = this.buildQuery_({ id: validatedId }, config)
|
||||
|
||||
const customShippingOption = await customShippingOptionRepo.findOne(query)
|
||||
|
||||
if (!customShippingOption) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Custom shipping option with id: ${id} was not found.`
|
||||
)
|
||||
}
|
||||
|
||||
return customShippingOption
|
||||
}
|
||||
|
||||
/** Fetches all custom shipping options related to the given selector
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the configuration used to find the objects. contains relations, skip, and take.
|
||||
* @return {Promise<CustomShippingOption[]>} custom shipping options matching the query
|
||||
*/
|
||||
async list(
|
||||
selector,
|
||||
config = {
|
||||
skip: 0,
|
||||
take: 50,
|
||||
relations: [],
|
||||
}
|
||||
) {
|
||||
const customShippingOptionRepo = this.manager_.getCustomRepository(
|
||||
this.customShippingOptionRepository_
|
||||
)
|
||||
|
||||
const query = this.buildQuery_(selector, config)
|
||||
|
||||
return customShippingOptionRepo.find(query)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom shipping option associated with a given author
|
||||
* @param {object} data - the custom shipping option to create
|
||||
* @param {*} config - any configurations if needed, including meta data
|
||||
* @return {Promise<CustomShippingOption>} resolves to the creation result
|
||||
*/
|
||||
async create(data, config = { metadata: {} }) {
|
||||
const { metadata } = config
|
||||
|
||||
const { cart_id, shipping_option_id, price } = data
|
||||
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customShippingOptionRepo = manager.getCustomRepository(
|
||||
this.customShippingOptionRepository_
|
||||
)
|
||||
|
||||
const customShippingOption = await customShippingOptionRepo.create({
|
||||
cart_id,
|
||||
shipping_option_id,
|
||||
price,
|
||||
metadata,
|
||||
})
|
||||
const result = await customShippingOptionRepo.save(customShippingOption)
|
||||
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default CustomShippingOptionService
|
||||
@@ -7,7 +7,7 @@ import { Brackets, ILike } from "typeorm"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate customers.
|
||||
* @implements BaseService
|
||||
* @implements {BaseService}
|
||||
*/
|
||||
class CustomerService extends BaseService {
|
||||
static Events = {
|
||||
@@ -60,9 +60,7 @@ class CustomerService extends BaseService {
|
||||
* @return {string} the validated email
|
||||
*/
|
||||
validateEmail_(email) {
|
||||
const schema = Validator.string()
|
||||
.email()
|
||||
.required()
|
||||
const schema = Validator.string().email().required()
|
||||
const { value, error } = schema.validate(email)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
@@ -93,7 +91,7 @@ class CustomerService extends BaseService {
|
||||
* secret a long side a payload with userId and the expiry time for the token,
|
||||
* which is always 15 minutes.
|
||||
* @param {string} customerId - the customer to reset the password for
|
||||
* @returns {string} the generated JSON web token
|
||||
* @return {string} the generated JSON web token
|
||||
*/
|
||||
async generateResetPasswordToken(customerId) {
|
||||
const customer = await this.retrieve(customerId)
|
||||
@@ -122,6 +120,7 @@ class CustomerService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(selector = {}, config = { relations: [], skip: 0, take: 50 }) {
|
||||
@@ -144,11 +143,11 @@ class CustomerService extends BaseService {
|
||||
delete where.first_name
|
||||
delete where.last_name
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
qb.where({ email: ILike(`%${q}%`) })
|
||||
.orWhere({ first_name: ILike(`%${q}%`) })
|
||||
.orWhere({ last_name: ILike(`%${q}%`) })
|
||||
@@ -183,11 +182,11 @@ class CustomerService extends BaseService {
|
||||
delete where.first_name
|
||||
delete where.last_name
|
||||
|
||||
query.where = qb => {
|
||||
query.where = (qb) => {
|
||||
qb.where(where)
|
||||
|
||||
qb.andWhere(
|
||||
new Brackets(qb => {
|
||||
new Brackets((qb) => {
|
||||
qb.where({ email: ILike(`%${q}%`) })
|
||||
.orWhere({ first_name: ILike(`%${q}%`) })
|
||||
.orWhere({ last_name: ILike(`%${q}%`) })
|
||||
@@ -214,6 +213,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Gets a customer by id.
|
||||
* @param {string} customerId - the id of the customer to get.
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Customer>} the customer document.
|
||||
*/
|
||||
async retrieve(customerId, config = {}) {
|
||||
@@ -224,7 +224,6 @@ class CustomerService extends BaseService {
|
||||
const query = this.buildQuery_({ id: validatedId }, config)
|
||||
|
||||
const customer = await customerRepo.findOne(query)
|
||||
|
||||
if (!customer) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
@@ -238,6 +237,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Gets a customer by email.
|
||||
* @param {string} email - the email of the customer to get.
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Customer>} the customer document.
|
||||
*/
|
||||
async retrieveByEmail(email, config = {}) {
|
||||
@@ -261,6 +261,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Gets a customer by phone.
|
||||
* @param {string} phone - the phone of the customer to get.
|
||||
* @param {Object} config - the config object containing query settings
|
||||
* @return {Promise<Customer>} the customer document.
|
||||
*/
|
||||
async retrieveByPhone(phone, config = {}) {
|
||||
@@ -284,7 +285,7 @@ class CustomerService extends BaseService {
|
||||
/**
|
||||
* Hashes a password
|
||||
* @param {string} password - the value to hash
|
||||
* @return hashed password
|
||||
* @return {Promise<string>} hashed password
|
||||
*/
|
||||
async hashPassword_(password) {
|
||||
const buf = await Scrypt.kdf(password, { logN: 1, r: 1, p: 1 })
|
||||
@@ -300,7 +301,7 @@ class CustomerService extends BaseService {
|
||||
* @return {Promise} the result of create
|
||||
*/
|
||||
async create(customer) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customerRepository = manager.getCustomRepository(
|
||||
this.customerRepository_
|
||||
)
|
||||
@@ -312,7 +313,9 @@ class CustomerService extends BaseService {
|
||||
customer.billing_address = this.validateBillingAddress_(billing_address)
|
||||
}
|
||||
|
||||
const existing = await this.retrieveByEmail(email).catch(err => undefined)
|
||||
const existing = await this.retrieveByEmail(email).catch(
|
||||
(err) => undefined
|
||||
)
|
||||
|
||||
if (existing && existing.has_account) {
|
||||
throw new MedusaError(
|
||||
@@ -353,13 +356,13 @@ class CustomerService extends BaseService {
|
||||
|
||||
/**
|
||||
* Updates a customer.
|
||||
* @param {string} variantId - the id of the variant. Must be a string that
|
||||
* @param {string} customerId - the id of the variant. Must be a string that
|
||||
* can be casted to an ObjectId
|
||||
* @param {object} update - an object with the update values.
|
||||
* @return {Promise} resolves to the update result.
|
||||
*/
|
||||
async update(customerId, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customerRepository = manager.getCustomRepository(
|
||||
this.customerRepository_
|
||||
)
|
||||
@@ -370,10 +373,9 @@ class CustomerService extends BaseService {
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
password_hash,
|
||||
metadata,
|
||||
billing_address,
|
||||
billing_address_id,
|
||||
metadata,
|
||||
...rest
|
||||
} = update
|
||||
|
||||
@@ -386,7 +388,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
if ("billing_address_id" in update || "billing_address" in update) {
|
||||
const address = update.billing_address_id || update.billing_address
|
||||
const address = billing_address_id || billing_address
|
||||
await this.updateBillingAddress_(customer, address, addrRepo)
|
||||
}
|
||||
|
||||
@@ -408,12 +410,18 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the customers's billing address.
|
||||
* Updates the customers' billing address.
|
||||
* @param {Customer} customer - the Customer to update
|
||||
* @param {object} address - the value to set the billing address to
|
||||
* @param {Object|string} addressOrId - the value to set the billing address to
|
||||
* @param {Object} addrRepo - address repository
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateBillingAddress_(customer, addressOrId, addrRepo) {
|
||||
if (addressOrId === null) {
|
||||
customer.billing_address_id = null
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof addressOrId === `string`) {
|
||||
addressOrId = await addrRepo.findOne({
|
||||
where: { id: addressOrId },
|
||||
@@ -424,7 +432,6 @@ class CustomerService extends BaseService {
|
||||
|
||||
if (addressOrId.id) {
|
||||
customer.billing_address_id = addressOrId.id
|
||||
customer.billing_address = addressOrId
|
||||
} else {
|
||||
if (customer.billing_address_id) {
|
||||
const addr = await addrRepo.findOne({
|
||||
@@ -443,7 +450,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
async updateAddress(customerId, addressId, address) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const addressRepo = manager.getCustomRepository(this.addressRepository_)
|
||||
|
||||
address.country_code = address.country_code.toLowerCase()
|
||||
@@ -464,7 +471,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
async removeAddress(customerId, addressId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const addressRepo = manager.getCustomRepository(this.addressRepository_)
|
||||
|
||||
// Should not fail, if user does not exist, since delete is idempotent
|
||||
@@ -472,7 +479,9 @@ class CustomerService extends BaseService {
|
||||
where: { id: addressId, customer_id: customerId },
|
||||
})
|
||||
|
||||
if (!address) return Promise.resolve()
|
||||
if (!address) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await addressRepo.softRemove(address)
|
||||
|
||||
@@ -481,7 +490,7 @@ class CustomerService extends BaseService {
|
||||
}
|
||||
|
||||
async addAddress(customerId, address) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const addressRepository = manager.getCustomRepository(
|
||||
this.addressRepository_
|
||||
)
|
||||
@@ -493,8 +502,8 @@ class CustomerService extends BaseService {
|
||||
})
|
||||
this.validateBillingAddress_(address)
|
||||
|
||||
let shouldAdd = !customer.shipping_addresses.find(
|
||||
a =>
|
||||
const shouldAdd = !customer.shipping_addresses.find(
|
||||
(a) =>
|
||||
a.country_code.toLowerCase() === address.country_code.toLowerCase() &&
|
||||
a.address_1 === address.address_1 &&
|
||||
a.address_2 === address.address_2 &&
|
||||
@@ -526,13 +535,15 @@ class CustomerService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation.
|
||||
*/
|
||||
async delete(customerId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const customerRepo = manager.getCustomRepository(this.customerRepository_)
|
||||
|
||||
// Should not fail, if user does not exist, since delete is idempotent
|
||||
const customer = await customerRepo.findOne({ where: { id: customerId } })
|
||||
|
||||
if (!customer) return Promise.resolve()
|
||||
if (!customer) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await customerRepo.softRemove(customer)
|
||||
|
||||
|
||||
@@ -1,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_
|
||||
)
|
||||
@@ -332,7 +333,7 @@ class DraftOrderService extends BaseService {
|
||||
* @return {Promise} the created order
|
||||
*/
|
||||
async registerCartCompletion(doId, orderId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const draftOrderRepo = manager.getCustomRepository(
|
||||
this.draftOrderRepository_
|
||||
)
|
||||
@@ -350,10 +351,10 @@ class DraftOrderService extends BaseService {
|
||||
* Updates a draft order with the given data
|
||||
* @param {String} doId - id of the draft order
|
||||
* @param {DraftOrder} data - values to update the order with
|
||||
* @returns {Promise<DraftOrder>} the updated draft order
|
||||
* @return {Promise<DraftOrder>} the updated draft order
|
||||
*/
|
||||
async update(doId, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const doRepo = manager.getCustomRepository(this.draftOrderRepository_)
|
||||
const draftOrder = await this.retrieve(doId)
|
||||
let touched = false
|
||||
|
||||
@@ -1,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(
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import _ from "lodash"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate line items.
|
||||
* @implements BaseService
|
||||
* @extends BaseService
|
||||
*/
|
||||
class LineItemService extends BaseService {
|
||||
constructor({
|
||||
@@ -67,6 +66,7 @@ class LineItemService extends BaseService {
|
||||
/**
|
||||
* Retrieves a line item by its id.
|
||||
* @param {string} id - the id of the line item to retrieve
|
||||
* @param {object} config - the config to be used at query building
|
||||
* @return {LineItem} the line item
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
@@ -90,7 +90,7 @@ class LineItemService extends BaseService {
|
||||
}
|
||||
|
||||
async generate(variantId, regionId, quantity, config = {}) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const variant = await this.productVariantService_
|
||||
.withTransaction(manager)
|
||||
.retrieve(variantId, {
|
||||
@@ -142,7 +142,7 @@ class LineItemService extends BaseService {
|
||||
* @return {LineItem} the created line item
|
||||
*/
|
||||
async create(lineItem) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const lineItemRepository = manager.getCustomRepository(
|
||||
this.lineItemRepository_
|
||||
)
|
||||
@@ -160,7 +160,7 @@ class LineItemService extends BaseService {
|
||||
* @return {LineItem} the update line item
|
||||
*/
|
||||
async update(id, update) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const lineItemRepository = manager.getCustomRepository(
|
||||
this.lineItemRepository_
|
||||
)
|
||||
@@ -188,14 +188,16 @@ class LineItemService extends BaseService {
|
||||
* @return {Promise} the result of the delete operation
|
||||
*/
|
||||
async delete(id) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const lineItemRepository = manager.getCustomRepository(
|
||||
this.lineItemRepository_
|
||||
)
|
||||
|
||||
const lineItem = await lineItemRepository.findOne({ where: { id } })
|
||||
|
||||
if (!lineItem) return Promise.resolve()
|
||||
if (!lineItem) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await lineItemRepository.remove(lineItem)
|
||||
|
||||
|
||||
@@ -1,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)
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -286,6 +286,7 @@ class OrderService extends BaseService {
|
||||
"refundable_amount",
|
||||
"items.refundable",
|
||||
"swaps.additional_items.refundable",
|
||||
"claims.additional_items.refundable",
|
||||
]
|
||||
|
||||
const totalsToSelect = select.filter((v) => totalFields.includes(v))
|
||||
@@ -294,6 +295,8 @@ class OrderService extends BaseService {
|
||||
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")
|
||||
@@ -1407,6 +1410,22 @@ class OrderService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
quantity: i.quantity - (i.returned_quantity || 0),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -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,15 +376,8 @@ 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?.length) {
|
||||
product.thumbnail = images[0]
|
||||
@@ -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"),
|
||||
}))
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ class RegionService extends BaseService {
|
||||
static Events = {
|
||||
UPDATED: "region.updated",
|
||||
CREATED: "region.created",
|
||||
DELETED: "region.deleted",
|
||||
}
|
||||
|
||||
constructor({
|
||||
@@ -389,6 +390,12 @@ class RegionService extends BaseService {
|
||||
|
||||
await regionRepo.softRemove(region)
|
||||
|
||||
await this.eventBus_
|
||||
.withTransaction(manager)
|
||||
.emit(RegionService.Events.DELETED, {
|
||||
id: regionId,
|
||||
})
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { In } from "typeorm"
|
||||
|
||||
class ReturnReasonService extends BaseService {
|
||||
constructor({ manager, returnReasonRepository }) {
|
||||
@@ -29,7 +28,7 @@ class ReturnReasonService extends BaseService {
|
||||
}
|
||||
|
||||
create(data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
|
||||
if (data.parent_return_reason_id && data.parent_return_reason_id !== "") {
|
||||
@@ -51,7 +50,7 @@ class ReturnReasonService extends BaseService {
|
||||
}
|
||||
|
||||
update(id, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
const reason = await this.retrieve(id)
|
||||
|
||||
@@ -77,6 +76,7 @@ class ReturnReasonService extends BaseService {
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @param {Object} config - config object
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
async list(
|
||||
@@ -90,7 +90,8 @@ class ReturnReasonService extends BaseService {
|
||||
|
||||
/**
|
||||
* Gets an order by id.
|
||||
* @param {string} orderId - id of order to retrieve
|
||||
* @param {string} id - id of order to retrieve
|
||||
* @param {Object} config - config object
|
||||
* @return {Promise<Order>} the order document
|
||||
*/
|
||||
async retrieve(id, config = {}) {
|
||||
@@ -111,7 +112,7 @@ class ReturnReasonService extends BaseService {
|
||||
}
|
||||
|
||||
async delete(returnReasonId) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
return this.atomicPhase_(async (manager) => {
|
||||
const rrRepo = manager.getCustomRepository(this.retReasonRepo_)
|
||||
|
||||
// We include the relation 'return_reason_children' to enable cascading deletes of return reasons if a parent is removed
|
||||
|
||||
@@ -93,6 +93,12 @@ class ReturnService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
if (order.claims && order.claims.length) {
|
||||
for (const c of order.claims) {
|
||||
merged = [...merged, ...c.additional_items]
|
||||
}
|
||||
}
|
||||
|
||||
const toReturn = await Promise.all(
|
||||
items.map(async data => {
|
||||
const item = merged.find(i => i.id === data.item_id)
|
||||
@@ -325,7 +331,13 @@ class ReturnService extends BaseService {
|
||||
.withTransaction(manager)
|
||||
.retrieve(orderId, {
|
||||
select: ["refunded_total", "total", "refundable_amount"],
|
||||
relations: ["swaps", "swaps.additional_items", "items"],
|
||||
relations: [
|
||||
"swaps",
|
||||
"swaps.additional_items",
|
||||
"claims",
|
||||
"claims.additional_items",
|
||||
"items",
|
||||
],
|
||||
})
|
||||
|
||||
const returnLines = await this.getFulfillmentItems_(
|
||||
@@ -487,13 +499,18 @@ class ReturnService extends BaseService {
|
||||
* @param {string[]} lineItems - the line items to return
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async receive(returnId, receivedItems, refundAmount, allowMismatch = false) {
|
||||
async receive(
|
||||
return_id,
|
||||
received_items,
|
||||
refund_amount,
|
||||
allow_mismatch = false
|
||||
) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const returnRepository = manager.getCustomRepository(
|
||||
this.returnRepository_
|
||||
)
|
||||
|
||||
const returnObj = await this.retrieve(returnId, {
|
||||
const returnObj = await this.retrieve(return_id, {
|
||||
relations: ["items", "swap", "swap.additional_items"],
|
||||
})
|
||||
|
||||
@@ -537,7 +554,7 @@ class ReturnService extends BaseService {
|
||||
|
||||
const returnLines = await this.getFulfillmentItems_(
|
||||
order,
|
||||
receivedItems,
|
||||
received_items,
|
||||
this.validateReturnLineItem_
|
||||
)
|
||||
|
||||
@@ -566,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 = {
|
||||
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
@@ -534,6 +537,8 @@ class SwapService extends BaseService {
|
||||
"order.swaps.additional_items",
|
||||
"order.discounts",
|
||||
"order.discounts.rule",
|
||||
"order.claims",
|
||||
"order.claims.additional_items",
|
||||
"additional_items",
|
||||
"return_order",
|
||||
"return_order.items",
|
||||
@@ -576,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,
|
||||
@@ -607,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,
|
||||
@@ -638,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",
|
||||
@@ -770,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) {
|
||||
@@ -811,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"],
|
||||
})
|
||||
@@ -877,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",
|
||||
@@ -937,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,
|
||||
})),
|
||||
@@ -954,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) {
|
||||
@@ -999,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)
|
||||
@@ -1042,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"],
|
||||
})
|
||||
@@ -1068,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, {
|
||||
@@ -1117,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)
|
||||
})
|
||||
}
|
||||
@@ -1130,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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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..
|
||||
@@ -159,7 +160,14 @@ class TotalsService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
const refunds = lineItems.map((i) => {
|
||||
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(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -254,6 +262,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
|
||||
|
||||
@@ -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
|
||||
+695
-968
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user