Merge branch 'develop' into develop

This commit is contained in:
Oliver Windall Juhl authored and GitHub committed 2021-10-19 16:49:03 +02:00
commit 94402a7204
158 files changed
+11728 -8078

No files matched your search

File diff suppressed because it is too large. Load diff
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@medusajs/medusa",
"version": "1.1.41",
"version": "1.1.45",
"description": "E-commerce for JAMstack",
"main": "dist/index.js",
"bin": {
@@ -20,11 +20,11 @@
"@babel/cli": "^7.14.3",
"@babel/core": "^7.14.3",
"@babel/preset-typescript": "^7.13.0",
"babel-preset-medusa-package": "^1.1.15",
"babel-preset-medusa-package": "^1.1.17",
"cross-env": "^5.2.1",
"eslint": "^6.8.0",
"jest": "^25.5.2",
"medusa-interfaces": "^1.1.23",
"medusa-interfaces": "^1.1.27",
"nodemon": "^2.0.1",
"prettier": "^1.19.1",
"sqlite3": "^5.0.2",
@@ -45,7 +45,7 @@
},
"dependencies": {
"@hapi/joi": "^16.1.8",
"@medusajs/medusa-cli": "^1.1.18",
"@medusajs/medusa-cli": "^1.1.22",
"@types/lodash": "^4.14.168",
"awilix": "^4.2.3",
"body-parser": "^1.19.0",
@@ -67,8 +67,8 @@
"joi": "^17.3.0",
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",
"medusa-core-utils": "^1.1.22",
"medusa-test-utils": "^1.1.25",
"medusa-core-utils": "^1.1.26",
"medusa-test-utils": "^1.1.29",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"passport": "^0.4.0",
@@ -87,5 +87,5 @@
"uuid": "^8.3.1",
"winston": "^3.2.1"
},
"gitHead": "41a5425405aea5045a26def95c0dc00cf4a5a44d"
"gitHead": "5afd0dfbabaebfb6b09f0e2f055386ac76059372"
}
@@ -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,
})
+1
View File
@@ -46,3 +46,4 @@ export { User } from "./models/user"
export { DraftOrder } from "./models/draft-order"
export { ReturnReason } from "./models/return-reason"
export { Note } from "./models/note"
export { CustomShippingOption } from "./models/custom-shipping-option"
+13 -6
View File
@@ -16,6 +16,7 @@ import passportLoader from "./passport"
import pluginsLoader, { registerPluginModels } from "./plugins"
import defaultsLoader from "./defaults"
import Logger from "./logger"
import searchIndexLoader from "./search-index"
export default async ({ directory: rootDirectory, expressApp }) => {
const { configModule } = getConfigFile(rootDirectory, `medusa-config`)
@@ -101,12 +102,6 @@ export default async ({ directory: rootDirectory, expressApp }) => {
const servAct = Logger.success(servicesActivity, "Services initialized") || {}
track("SERVICES_INIT_COMPLETED", { duration: servAct.duration })
const subActivity = Logger.activity("Initializing subscribers")
track("SUBSCRIBERS_INIT_STARTED")
subscribersLoader({ container, activityId: subActivity })
const subAct = Logger.success(subActivity, "Subscribers initialized") || {}
track("SUBSCRIBERS_INIT_COMPLETED", { duration: subAct.duration })
const expActivity = Logger.activity("Initializing express")
track("EXPRESS_INIT_STARTED")
await expressLoader({
@@ -138,6 +133,12 @@ export default async ({ directory: rootDirectory, expressApp }) => {
const pAct = Logger.success(pluginsActivity, "Plugins intialized") || {}
track("PLUGINS_INIT_COMPLETED", { duration: pAct.duration })
const subActivity = Logger.activity("Initializing subscribers")
track("SUBSCRIBERS_INIT_STARTED")
subscribersLoader({ container, activityId: subActivity })
const subAct = Logger.success(subActivity, "Subscribers initialized") || {}
track("SUBSCRIBERS_INIT_COMPLETED", { duration: subAct.duration })
const apiActivity = Logger.activity("Initializing API")
track("API_INIT_STARTED")
await apiLoader({
@@ -155,6 +156,12 @@ export default async ({ directory: rootDirectory, expressApp }) => {
const dAct = Logger.success(defaultsActivity, "Defaults initialized") || {}
track("DEFAULTS_INIT_COMPLETED", { duration: dAct.duration })
const searchActivity = Logger.activity("Initializing search engine indexing")
track("SEARCH_ENGINE_INDEXING_STARTED")
searchIndexLoader({ container, activityId: searchActivity })
const searchAct = Logger.success(searchActivity, "Indexing completed") || {}
track("SEARCH_ENGINE_INDEXING_COMPLETED", { duration: searchAct.duration })
return { container, dbConnection, app: expressApp }
}
+13 -3
View File
@@ -8,6 +8,7 @@ import {
NotificationService,
FileService,
OauthService,
SearchService,
} from "medusa-interfaces"
import { getConfigFile, createRequireFromPath } from "medusa-core-utils"
import _ from "lodash"
@@ -249,7 +250,7 @@ async function registerServices(pluginDetails, container) {
)
// Add the service directly to the container in order to make simple
// resolution if we already know which payment provider we need to use
// resolution if we already know which fulfillment provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
@@ -263,7 +264,7 @@ async function registerServices(pluginDetails, container) {
)
// Add the service directly to the container in order to make simple
// resolution if we already know which payment provider we need to use
// resolution if we already know which notification provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
@@ -272,13 +273,22 @@ async function registerServices(pluginDetails, container) {
})
} else if (loaded.prototype instanceof FileService) {
// Add the service directly to the container in order to make simple
// resolution if we already know which payment provider we need to use
// resolution if we already know which file storage provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
),
[`fileService`]: aliasTo(name),
})
} else if (loaded.prototype instanceof SearchService) {
// Add the service directly to the container in order to make simple
// resolution if we already know which search provider we need to use
container.register({
[name]: asFunction(
cradle => new loaded(cradle, pluginDetails.options)
),
[`searchService`]: aliasTo(name),
})
} else {
container.register({
[name]: asFunction(
@@ -0,0 +1,71 @@
import ProductService from "../services/product"
import { indexTypes } from "medusa-core-utils"
async function loadProductsIntoSearchEngine(container) {
const searchService = container.resolve("searchService")
const productService = container.resolve("productService")
const TAKE = 20
let hasMore = true
let lastSeenId = ""
while (hasMore) {
const products = await productService.list(
{ id: { gt: lastSeenId } },
{
select: [
"id",
"title",
"subtitle",
"description",
"handle",
"is_giftcard",
"discountable",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"origin_country",
"created_at",
"updated_at",
],
relations: [
"variants",
"tags",
"type",
"collection",
"variants.prices",
"variants.options",
"options",
],
take: TAKE,
order: { id: "ASC" },
}
)
if (products.length > 0) {
await searchService.addDocuments(
ProductService.IndexName,
products,
indexTypes.products
)
lastSeenId = products[products.length - 1].id
} else {
hasMore = false
}
}
}
export default async ({ container }) => {
const searchService = container.resolve("searchService")
const logger = container.resolve("logger")
if (searchService.isDefault) {
logger.warn(
"No search engine provider was found: make sure to include a search plugin to enable searching"
)
return
}
await loadProductsIntoSearchEngine(container)
}
@@ -0,0 +1,34 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class addCustomShippingOptions1633614437919 implements MigrationInterface {
name = 'addCustomShippingOptions1633614437919'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "custom_shipping_option" ("id" character varying NOT NULL, "price" integer NOT NULL, "shipping_option_id" character varying NOT NULL, "cart_id" character varying, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "UQ_0f838b122a9a01d921aa1cdb669" UNIQUE ("shipping_option_id", "cart_id"), CONSTRAINT "PK_8dfcb5c1172c29eec4a728420cc" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_44090cb11b06174cbcc667e91c" ON "custom_shipping_option" ("shipping_option_id") `);
await queryRunner.query(`CREATE INDEX "IDX_93caeb1bb70d37c1d36d6701a7" ON "custom_shipping_option" ("cart_id") `);
await queryRunner.query(`ALTER TYPE "cart_type_enum" RENAME TO "cart_type_enum_old"`);
await queryRunner.query(`CREATE TYPE "cart_type_enum" AS ENUM('default', 'swap', 'draft_order', 'payment_link', 'claim')`);
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" TYPE "cart_type_enum" USING "type"::"text"::"cart_type_enum"`);
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
await queryRunner.query(`DROP TYPE "cart_type_enum_old"`);
await queryRunner.query(`ALTER TABLE "custom_shipping_option" ADD CONSTRAINT "FK_44090cb11b06174cbcc667e91ca" FOREIGN KEY ("shipping_option_id") REFERENCES "shipping_option"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "custom_shipping_option" ADD CONSTRAINT "FK_93caeb1bb70d37c1d36d6701a7a" FOREIGN KEY ("cart_id") REFERENCES "cart"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "custom_shipping_option" DROP CONSTRAINT "FK_93caeb1bb70d37c1d36d6701a7a"`);
await queryRunner.query(`ALTER TABLE "custom_shipping_option" DROP CONSTRAINT "FK_44090cb11b06174cbcc667e91ca"`);
await queryRunner.query(`CREATE TYPE "cart_type_enum_old" AS ENUM('default', 'swap', 'draft_order', 'payment_link')`);
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" DROP DEFAULT`);
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" TYPE "cart_type_enum_old" USING "type"::"text"::"cart_type_enum_old"`);
await queryRunner.query(`ALTER TABLE "cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
await queryRunner.query(`DROP TYPE "cart_type_enum"`);
await queryRunner.query(`ALTER TYPE "cart_type_enum_old" RENAME TO "cart_type_enum"`);
await queryRunner.query(`DROP INDEX "IDX_93caeb1bb70d37c1d36d6701a7"`);
await queryRunner.query(`DROP INDEX "IDX_44090cb11b06174cbcc667e91c"`);
await queryRunner.query(`DROP TABLE "custom_shipping_option"`);
}
}
+2
View File
@@ -113,12 +113,14 @@ import { PaymentSession } from "./payment-session"
import { Payment } from "./payment"
import { GiftCard } from "./gift-card"
import { ShippingMethod } from "./shipping-method"
import { CustomShippingOption } from "./custom-shipping-option"
export enum CartType {
DEFAULT = "default",
SWAP = "swap",
DRAFT_ORDER = "draft_order",
PAYMENT_LINK = "payment_link",
CLAIM = "claim",
}
@Entity()
@@ -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> {}
+1 -1
View File
@@ -34,7 +34,7 @@ export class OrderRepository extends Repository<Order> {
const entitiesAndRelationsById = groupBy(entitiesAndRelations, "id")
return map(entities, e => merge({}, ...entitiesAndRelationsById[e.id]))
return map(entities, (e) => merge({}, ...entitiesAndRelationsById[e.id]))
}
public async findOneWithRelations(
+27 -16
View File
@@ -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)
+140 -46
View File
@@ -6,29 +6,29 @@ import { MedusaError } from "medusa-core-utils"
const eventBusService = {
emit: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
}
describe("CartService", () => {
const totalsService = {
getTotal: o => {
getTotal: (o) => {
return o.total || 0
},
getSubtotal: o => {
getSubtotal: (o) => {
return o.subtotal || 0
},
getTaxTotal: o => {
getTaxTotal: (o) => {
return o.tax_total || 0
},
getDiscountTotal: o => {
getDiscountTotal: (o) => {
return o.discount_total || 0
},
getShippingTotal: o => {
getShippingTotal: (o) => {
return o.shipping_total || 0
},
getGiftCardTotal: o => {
getGiftCardTotal: (o) => {
return o.gift_card_total || 0
},
}
@@ -117,7 +117,7 @@ describe("CartService", () => {
describe("deleteMetadata", () => {
const cartRepository = MockRepository({
findOne: id => {
findOne: (id) => {
if (id === "empty") {
return Promise.resolve({
metadata: {},
@@ -200,7 +200,7 @@ describe("CartService", () => {
},
}
const addressRepository = MockRepository({ create: c => c })
const addressRepository = MockRepository({ create: (c) => c })
const cartRepository = MockRepository()
const customerService = {
retrieveByEmail: jest.fn().mockReturnValue(
@@ -209,7 +209,7 @@ describe("CartService", () => {
email: "email@test.com",
})
),
withTransaction: function() {
withTransaction: function () {
return this
},
}
@@ -317,14 +317,14 @@ describe("CartService", () => {
const lineItemService = {
update: jest.fn(),
create: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
}
const shippingOptionService = {
deleteShippingMethod: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
}
@@ -505,7 +505,7 @@ describe("CartService", () => {
const lineItemService = {
delete: jest.fn(),
update: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
}
@@ -547,7 +547,7 @@ describe("CartService", () => {
const shippingOptionService = {
deleteShippingMethod: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
}
@@ -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: [
{
+45 -32
View File
@@ -5,7 +5,7 @@ import { InventoryServiceMock } from "../__mocks__/inventory"
const eventBusService = {
emit: jest.fn(),
withTransaction: function() {
withTransaction: function () {
return this
},
}
@@ -161,7 +161,7 @@ describe("SwapService", () => {
const cartService = {
create: jest.fn().mockReturnValue(Promise.resolve({ id: "cart" })),
update: jest.fn().mockReturnValue(Promise.resolve()),
withTransaction: function() {
withTransaction: function () {
return this
},
}
@@ -170,25 +170,36 @@ describe("SwapService", () => {
findOneWithRelations: () => Promise.resolve(existing),
})
const lineItemService = {
create: jest.fn().mockImplementation(d => Promise.resolve(d)),
update: jest.fn().mockImplementation(d => Promise.resolve(d)),
retrieve: () => Promise.resolve({}),
const customShippingOptionService = {
create: jest.fn().mockReturnValue(Promise.resolve({ id: "cso-test" })),
update: jest.fn().mockReturnValue(Promise.resolve()),
withTransaction: function() {
return this
},
}
const lineItemService = {
create: jest.fn().mockImplementation((d) => Promise.resolve(d)),
update: jest.fn().mockImplementation((d) => Promise.resolve(d)),
retrieve: () => Promise.resolve({}),
withTransaction: function () {
return this
},
}
const swapService = new SwapService({
manager: MockManager,
eventBusService,
swapRepository: swapRepo,
cartService,
lineItemService,
customShippingOptionService,
})
it("finds swap and calls return create cart", async () => {
await swapService.createCart(IdMap.getId("swap-1"))
await swapService.createCart(IdMap.getId("swap-1"), [
{ option_id: "test-option", price: 10 },
])
expect(swapRepo.findOneWithRelations).toHaveBeenCalledTimes(1)
expect(swapRepo.findOneWithRelations).toHaveBeenCalledWith(
@@ -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"
)
+96 -54
View File
@@ -24,7 +24,6 @@ class CartService extends BaseService {
regionService,
lineItemService,
shippingOptionService,
shippingProfileService,
customerService,
discountService,
giftCardService,
@@ -32,6 +31,7 @@ class CartService extends BaseService {
addressRepository,
paymentSessionRepository,
inventoryService,
customShippingOptionService,
}) {
super()
@@ -62,9 +62,6 @@ class CartService extends BaseService {
/** @private @const {PaymentProviderService} */
this.paymentProviderService_ = paymentProviderService
/** @private @const {ShippingProfileService} */
this.shippingProfileService_ = shippingProfileService
/** @private @const {CustomerService} */
this.customerService_ = customerService
@@ -88,6 +85,9 @@ class CartService extends BaseService {
/** @private @const {InventoryService} */
this.inventoryService_ = inventoryService
/** @private @const {CustomShippingOptionService} */
this.customShippingOptionService_ = customShippingOptionService
}
withTransaction(transactionManager) {
@@ -107,13 +107,13 @@ class CartService extends BaseService {
regionService: this.regionService_,
lineItemService: this.lineItemService_,
shippingOptionService: this.shippingOptionService_,
shippingProfileService: this.shippingProfileService_,
customerService: this.customerService_,
discountService: this.discountService_,
totalsService: this.totalsService_,
addressRepository: this.addressRepository_,
giftCardService: this.giftCardService_,
inventoryService: this.inventoryService_,
customShippingOptionService: this.customShippingOptionService_,
})
cloned.transactionManager_ = transactionManager
@@ -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
+40 -29
View File
@@ -7,7 +7,7 @@ import { Brackets, ILike } from "typeorm"
/**
* Provides layer to manipulate customers.
* @implements BaseService
* @implements {BaseService}
*/
class CustomerService extends BaseService {
static Events = {
@@ -60,9 +60,7 @@ class CustomerService extends BaseService {
* @return {string} the validated email
*/
validateEmail_(email) {
const schema = Validator.string()
.email()
.required()
const schema = Validator.string().email().required()
const { value, error } = schema.validate(email)
if (error) {
throw new MedusaError(
@@ -93,7 +91,7 @@ class CustomerService extends BaseService {
* secret a long side a payload with userId and the expiry time for the token,
* which is always 15 minutes.
* @param {string} customerId - the customer to reset the password for
* @returns {string} the generated JSON web token
* @return {string} the generated JSON web token
*/
async generateResetPasswordToken(customerId) {
const customer = await this.retrieve(customerId)
@@ -122,6 +120,7 @@ class CustomerService extends BaseService {
/**
* @param {Object} selector - the query object for find
* @param {Object} config - the config object containing query settings
* @return {Promise} the result of the find operation
*/
async list(selector = {}, config = { relations: [], skip: 0, take: 50 }) {
@@ -144,11 +143,11 @@ class CustomerService extends BaseService {
delete where.first_name
delete where.last_name
query.where = qb => {
query.where = (qb) => {
qb.where(where)
qb.andWhere(
new Brackets(qb => {
new Brackets((qb) => {
qb.where({ email: ILike(`%${q}%`) })
.orWhere({ first_name: ILike(`%${q}%`) })
.orWhere({ last_name: ILike(`%${q}%`) })
@@ -183,11 +182,11 @@ class CustomerService extends BaseService {
delete where.first_name
delete where.last_name
query.where = qb => {
query.where = (qb) => {
qb.where(where)
qb.andWhere(
new Brackets(qb => {
new Brackets((qb) => {
qb.where({ email: ILike(`%${q}%`) })
.orWhere({ first_name: ILike(`%${q}%`) })
.orWhere({ last_name: ILike(`%${q}%`) })
@@ -214,6 +213,7 @@ class CustomerService extends BaseService {
/**
* Gets a customer by id.
* @param {string} customerId - the id of the customer to get.
* @param {Object} config - the config object containing query settings
* @return {Promise<Customer>} the customer document.
*/
async retrieve(customerId, config = {}) {
@@ -224,7 +224,6 @@ class CustomerService extends BaseService {
const query = this.buildQuery_({ id: validatedId }, config)
const customer = await customerRepo.findOne(query)
if (!customer) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
@@ -238,6 +237,7 @@ class CustomerService extends BaseService {
/**
* Gets a customer by email.
* @param {string} email - the email of the customer to get.
* @param {Object} config - the config object containing query settings
* @return {Promise<Customer>} the customer document.
*/
async retrieveByEmail(email, config = {}) {
@@ -261,6 +261,7 @@ class CustomerService extends BaseService {
/**
* Gets a customer by phone.
* @param {string} phone - the phone of the customer to get.
* @param {Object} config - the config object containing query settings
* @return {Promise<Customer>} the customer document.
*/
async retrieveByPhone(phone, config = {}) {
@@ -284,7 +285,7 @@ class CustomerService extends BaseService {
/**
* Hashes a password
* @param {string} password - the value to hash
* @return hashed password
* @return {Promise<string>} hashed password
*/
async hashPassword_(password) {
const buf = await Scrypt.kdf(password, { logN: 1, r: 1, p: 1 })
@@ -300,7 +301,7 @@ class CustomerService extends BaseService {
* @return {Promise} the result of create
*/
async create(customer) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const customerRepository = manager.getCustomRepository(
this.customerRepository_
)
@@ -312,7 +313,9 @@ class CustomerService extends BaseService {
customer.billing_address = this.validateBillingAddress_(billing_address)
}
const existing = await this.retrieveByEmail(email).catch(err => undefined)
const existing = await this.retrieveByEmail(email).catch(
(err) => undefined
)
if (existing && existing.has_account) {
throw new MedusaError(
@@ -353,13 +356,13 @@ class CustomerService extends BaseService {
/**
* Updates a customer.
* @param {string} variantId - the id of the variant. Must be a string that
* @param {string} customerId - the id of the variant. Must be a string that
* can be casted to an ObjectId
* @param {object} update - an object with the update values.
* @return {Promise} resolves to the update result.
*/
async update(customerId, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const customerRepository = manager.getCustomRepository(
this.customerRepository_
)
@@ -370,10 +373,9 @@ class CustomerService extends BaseService {
const {
email,
password,
password_hash,
metadata,
billing_address,
billing_address_id,
metadata,
...rest
} = update
@@ -386,7 +388,7 @@ class CustomerService extends BaseService {
}
if ("billing_address_id" in update || "billing_address" in update) {
const address = update.billing_address_id || update.billing_address
const address = billing_address_id || billing_address
await this.updateBillingAddress_(customer, address, addrRepo)
}
@@ -408,12 +410,18 @@ class CustomerService extends BaseService {
}
/**
* Updates the customers's billing address.
* Updates the customers' billing address.
* @param {Customer} customer - the Customer to update
* @param {object} address - the value to set the billing address to
* @param {Object|string} addressOrId - the value to set the billing address to
* @param {Object} addrRepo - address repository
* @return {Promise} the result of the update operation
*/
async updateBillingAddress_(customer, addressOrId, addrRepo) {
if (addressOrId === null) {
customer.billing_address_id = null
return
}
if (typeof addressOrId === `string`) {
addressOrId = await addrRepo.findOne({
where: { id: addressOrId },
@@ -424,7 +432,6 @@ class CustomerService extends BaseService {
if (addressOrId.id) {
customer.billing_address_id = addressOrId.id
customer.billing_address = addressOrId
} else {
if (customer.billing_address_id) {
const addr = await addrRepo.findOne({
@@ -443,7 +450,7 @@ class CustomerService extends BaseService {
}
async updateAddress(customerId, addressId, address) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const addressRepo = manager.getCustomRepository(this.addressRepository_)
address.country_code = address.country_code.toLowerCase()
@@ -464,7 +471,7 @@ class CustomerService extends BaseService {
}
async removeAddress(customerId, addressId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const addressRepo = manager.getCustomRepository(this.addressRepository_)
// Should not fail, if user does not exist, since delete is idempotent
@@ -472,7 +479,9 @@ class CustomerService extends BaseService {
where: { id: addressId, customer_id: customerId },
})
if (!address) return Promise.resolve()
if (!address) {
return Promise.resolve()
}
await addressRepo.softRemove(address)
@@ -481,7 +490,7 @@ class CustomerService extends BaseService {
}
async addAddress(customerId, address) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const addressRepository = manager.getCustomRepository(
this.addressRepository_
)
@@ -493,8 +502,8 @@ class CustomerService extends BaseService {
})
this.validateBillingAddress_(address)
let shouldAdd = !customer.shipping_addresses.find(
a =>
const shouldAdd = !customer.shipping_addresses.find(
(a) =>
a.country_code.toLowerCase() === address.country_code.toLowerCase() &&
a.address_1 === address.address_1 &&
a.address_2 === address.address_2 &&
@@ -526,13 +535,15 @@ class CustomerService extends BaseService {
* @return {Promise} the result of the delete operation.
*/
async delete(customerId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const customerRepo = manager.getCustomRepository(this.customerRepository_)
// Should not fail, if user does not exist, since delete is idempotent
const customer = await customerRepo.findOne({ where: { id: customerId } })
if (!customer) return Promise.resolve()
if (!customer) {
return Promise.resolve()
}
await customerRepo.softRemove(customer)
+13 -12
View File
@@ -1,11 +1,10 @@
import _ from "lodash"
import { BaseService } from "medusa-interfaces"
import { MedusaError } from "medusa-core-utils"
import { Brackets, ILike } from "typeorm"
import { Brackets } from "typeorm"
/**
* Handles draft orders
* @implements BaseService
* @implements {BaseService}
*/
class DraftOrderService extends BaseService {
static Events = {
@@ -106,7 +105,7 @@ class DraftOrderService extends BaseService {
/**
* Retrieves a draft order based on its associated cart id
* @param {string} cartId - cart id that the draft orders's cart has
* * @param {object} config - query object for findOne
* @param {object} config - query object for findOne
* @return {Promise<DraftOrder>} the draft order
*/
async retrieveByCartId(cartId, config = {}) {
@@ -134,7 +133,7 @@ class DraftOrderService extends BaseService {
* @return {Promise} empty promise
*/
async delete(draftOrderId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const draftOrderRepo = manager.getCustomRepository(
this.draftOrderRepository_
)
@@ -143,7 +142,9 @@ class DraftOrderService extends BaseService {
where: { id: draftOrderId },
})
if (!draftOrder) return Promise.resolve()
if (!draftOrder) {
return Promise.resolve()
}
await draftOrderRepo.remove(draftOrder)
@@ -185,11 +186,11 @@ class DraftOrderService extends BaseService {
},
}
query.where = qb => {
query.where = (qb) => {
qb.where(where)
qb.andWhere(
new Brackets(qb => {
new Brackets((qb) => {
qb.where(`cart.email ILIKE :q`, {
q: `%${q}%`,
}).orWhere(`draft_order.display_id::varchar(255) ILIKE :dId`, {
@@ -230,7 +231,7 @@ class DraftOrderService extends BaseService {
* @return {Promise<DraftOrder>} the created draft order
*/
async create(data) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const draftOrderRepo = manager.getCustomRepository(
this.draftOrderRepository_
)
@@ -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(
+10 -8
View File
@@ -1,10 +1,9 @@
import { Validator, MedusaError } from "medusa-core-utils"
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import _ from "lodash"
/**
* Provides layer to manipulate line items.
* @implements BaseService
* @extends BaseService
*/
class LineItemService extends BaseService {
constructor({
@@ -67,6 +66,7 @@ class LineItemService extends BaseService {
/**
* Retrieves a line item by its id.
* @param {string} id - the id of the line item to retrieve
* @param {object} config - the config to be used at query building
* @return {LineItem} the line item
*/
async retrieve(id, config = {}) {
@@ -90,7 +90,7 @@ class LineItemService extends BaseService {
}
async generate(variantId, regionId, quantity, config = {}) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const variant = await this.productVariantService_
.withTransaction(manager)
.retrieve(variantId, {
@@ -142,7 +142,7 @@ class LineItemService extends BaseService {
* @return {LineItem} the created line item
*/
async create(lineItem) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const lineItemRepository = manager.getCustomRepository(
this.lineItemRepository_
)
@@ -160,7 +160,7 @@ class LineItemService extends BaseService {
* @return {LineItem} the update line item
*/
async update(id, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const lineItemRepository = manager.getCustomRepository(
this.lineItemRepository_
)
@@ -188,14 +188,16 @@ class LineItemService extends BaseService {
* @return {Promise} the result of the delete operation
*/
async delete(id) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const lineItemRepository = manager.getCustomRepository(
this.lineItemRepository_
)
const lineItem = await lineItemRepository.findOne({ where: { id } })
if (!lineItem) return Promise.resolve()
if (!lineItem) {
return Promise.resolve()
}
await lineItemRepository.remove(lineItem)
+7 -8
View File
@@ -1,6 +1,5 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import _ from "lodash"
import { TransactionManager } from "typeorm"
class NoteService extends BaseService {
@@ -47,7 +46,7 @@ class NoteService extends BaseService {
* Retrieves a specific note.
* @param {*} id - the id of the note to retrieve.
* @param {*} config - any options needed to query for the result.
* @returns {Promise} which resolves to the requested note.
* @return {Promise} which resolves to the requested note.
*/
async retrieve(id, config = {}) {
const noteRepo = this.manager_.getCustomRepository(this.noteRepository_)
@@ -91,14 +90,14 @@ class NoteService extends BaseService {
* Creates a note associated with a given author
* @param {object} data - the note to create
* @param {*} config - any configurations if needed, including meta data
* @returns {Promise} resolves to the creation result
* @return {Promise} resolves to the creation result
*/
async create(data, config = { metadata: {} }) {
const { metadata } = config
const { resource_id, resource_type, value, author_id } = data
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const toCreate = {
@@ -124,10 +123,10 @@ class NoteService extends BaseService {
* Updates a given note with a new value
* @param {*} noteId - the id of the note to update
* @param {*} value - the new value
* @returns {Promise} resolves to the updated element
* @return {Promise} resolves to the updated element
*/
async update(noteId, value) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const note = await this.retrieve(noteId, { relations: ["author"] })
@@ -147,10 +146,10 @@ class NoteService extends BaseService {
/**
* Deletes a given note
* @param {*} noteId - id of the note to delete
* @returns {Promise}
* @return {Promise}
*/
async delete(noteId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const noteRepo = manager.getCustomRepository(this.noteRepository_)
const note = await this.retrieve(noteId)
+10 -6
View File
@@ -1,10 +1,9 @@
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import _ from "lodash"
/**
* Provides layer to manipulate orchestrate notifications.
* @implements BaseService
* @extends BaseService
*/
class NotificationService extends BaseService {
constructor(container) {
@@ -34,6 +33,7 @@ class NotificationService extends BaseService {
/**
* Registers an attachment generator to the service. The generator can be
* used to generate on demand invoices or other documents.
* @param {object} service
*/
registerAttachmentGenerator(service) {
this.attachmentGenerator_ = service
@@ -42,16 +42,18 @@ class NotificationService extends BaseService {
/**
* Sets the service's manager to a given transaction manager.
* @param {EntityManager} transactionManager - the manager to use
* return {NotificationService} a cloned notification service
* @return {NotificationService} a cloned notification service
*/
withTransaction(transactionManager) {
if (!transactionManager) {
return this
}
const cloned = new LineItemService({
const cloned = new NotificationService({
manager: transactionManager,
notificationProviderRepository: this.notificationProviderRepository_,
notificationRepository: this.notificationRepository_,
logger: this.logger_,
})
cloned.transactionManager_ = transactionManager
@@ -93,6 +95,7 @@ class NotificationService extends BaseService {
/**
* Retrieves a notification with a given id
* @param {string} id - the id of the notification
* @param {object} config - the configuration to apply to the query
* @return {Notification} the notification
*/
async retrieve(id, config = {}) {
@@ -158,6 +161,7 @@ class NotificationService extends BaseService {
* order to allow for resends. Will log any errors that are encountered.
* @param {string} eventName - the event to handle
* @param {object} data - the data the event was sent with
* @return {Promise} - the result of notification subscribed
*/
handleEvent(eventName, data) {
const subs = this.subscribers_[eventName]
@@ -169,8 +173,8 @@ class NotificationService extends BaseService {
}
return Promise.all(
subs.map(async providerId => {
return this.send(eventName, data, providerId).catch(err => {
subs.map(async (providerId) => {
return this.send(eventName, data, providerId).catch((err) => {
console.log(err)
this.logger_.warn(
`An error occured while ${providerId} was processing a notification for ${eventName}: ${err.message}`
+19
View File
@@ -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
}
+27 -16
View File
@@ -11,6 +11,7 @@ class ProductVariantService extends BaseService {
static Events = {
UPDATED: "product-variant.updated",
CREATED: "product-variant.created",
DELETED: "product-variant.deleted",
}
/** @param { productVariantModel: (ProductVariantModel) } */
@@ -118,7 +119,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} resolves to the creation result.
*/
async create(productOrProductId, variant) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productRepo = manager.getCustomRepository(this.productRepository_)
const variantRepo = manager.getCustomRepository(
this.productVariantRepository_
@@ -147,8 +148,8 @@ class ProductVariantService extends BaseService {
)
}
product.options.forEach(option => {
if (!variant.options.find(vo => option.id === vo.option_id)) {
product.options.forEach((option) => {
if (!variant.options.find((vo) => option.id === vo.option_id)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Variant options do not contain value for ${option.title}`
@@ -157,10 +158,10 @@ class ProductVariantService extends BaseService {
})
let variantExists = undefined
variantExists = product.variants.find(v => {
return v.options.every(option => {
variantExists = product.variants.find((v) => {
return v.options.every((option) => {
const variantOption = variant.options.find(
o => option.option_id === o.option_id
(o) => option.option_id === o.option_id
)
return option.value === variantOption.value
@@ -206,6 +207,7 @@ class ProductVariantService extends BaseService {
.withTransaction(manager)
.emit(ProductVariantService.Events.CREATED, {
id: result.id,
product_id: result.product_id,
})
return result
@@ -218,7 +220,7 @@ class ProductVariantService extends BaseService {
* @return {Promise}
*/
async publish(variantId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const variantRepo = manager.getCustomRepository(
this.productVariantRepository_
)
@@ -233,6 +235,7 @@ class ProductVariantService extends BaseService {
.withTransaction(manager)
.emit(ProductVariantService.Events.UPDATED, {
id: result.id,
product_id: result.product_id,
})
return result
@@ -249,7 +252,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} resolves to the update result.
*/
async update(variantOrVariantId, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const variantRepo = manager.getCustomRepository(
this.productVariantRepository_
)
@@ -309,6 +312,7 @@ class ProductVariantService extends BaseService {
.withTransaction(manager)
.emit(ProductVariantService.Events.UPDATED, {
id: result.id,
product_id: result.product_id,
fields: Object.keys(update),
})
return result
@@ -324,7 +328,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} the result of the update operation
*/
async setCurrencyPrice(variantId, price) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const moneyAmountRepo = manager.getCustomRepository(
this.moneyAmountRepository_
)
@@ -363,7 +367,7 @@ class ProductVariantService extends BaseService {
* @return {number} the price specific to the region
*/
async getRegionPrice(variantId, regionId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const moneyAmountRepo = manager.getCustomRepository(
this.moneyAmountRepository_
)
@@ -411,7 +415,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} the result of the update operation
*/
async setRegionPrice(variantId, price) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const moneyAmountRepo = manager.getCustomRepository(
this.moneyAmountRepository_
)
@@ -448,7 +452,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} the result of the update operation.
*/
async updateOptionValue(variantId, optionId, optionValue) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productOptionValueRepo = manager.getCustomRepository(
this.productOptionValueRepository_
)
@@ -483,7 +487,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} the result of the update operation.
*/
async addOptionValue(variantId, optionId, optionValue) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productOptionValueRepo = manager.getCustomRepository(
this.productOptionValueRepository_
)
@@ -507,7 +511,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} empty promise
*/
async deleteOptionValue(variantId, optionId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productOptionValueRepo = manager.getCustomRepository(
this.productOptionValueRepository_
)
@@ -557,7 +561,7 @@ class ProductVariantService extends BaseService {
},
}
query.where = qb => {
query.where = (qb) => {
qb.where(where).andWhere([
{ sku: ILike(`%${q}%`) },
{ title: ILike(`%${q}%`) },
@@ -577,7 +581,7 @@ class ProductVariantService extends BaseService {
* @return {Promise} empty promise
*/
async delete(variantId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const variantRepo = manager.getCustomRepository(
this.productVariantRepository_
)
@@ -588,6 +592,13 @@ class ProductVariantService extends BaseService {
await variantRepo.softRemove(variant)
await this.eventBus_
.withTransaction(manager)
.emit(ProductVariantService.Events.DELETED, {
id: variant.id,
product_id: variant.product_id,
})
return Promise.resolve()
})
}
+38 -32
View File
@@ -8,9 +8,11 @@ import { Brackets } from "typeorm"
* @implements BaseService
*/
class ProductService extends BaseService {
static IndexName = `products`
static Events = {
UPDATED: "product.updated",
CREATED: "product.created",
DELETED: "product.deleted",
}
constructor({
@@ -24,6 +26,7 @@ class ProductService extends BaseService {
productTypeRepository,
productTagRepository,
imageRepository,
searchService,
}) {
super()
@@ -56,6 +59,9 @@ class ProductService extends BaseService {
/** @private @const {ImageRepository} */
this.imageRepository_ = imageRepository
/** @private @const {SearchService} */
this.searchService_ = searchService
}
withTransaction(transactionManager) {
@@ -122,7 +128,7 @@ class ProductService extends BaseService {
.select(["product.id"])
.where(where)
.andWhere(
new Brackets(qb => {
new Brackets((qb) => {
qb.where(`product.description ILIKE :q`, { q: `%${q}%` })
.orWhere(`product.title ILIKE :q`, { q: `%${q}%` })
.orWhere(`variant.title ILIKE :q`, { q: `%${q}%` })
@@ -134,7 +140,7 @@ class ProductService extends BaseService {
return productRepo.findWithRelations(
rels,
raw.map(i => i.id)
raw.map((i) => i.id)
)
}
@@ -276,7 +282,7 @@ class ProductService extends BaseService {
* @return {Promise} resolves to the creation result.
*/
async create(productObject) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const productRepo = manager.getCustomRepository(this.productRepository_)
const optionRepo = manager.getCustomRepository(
this.productOptionRepository_
@@ -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"),
}))
+7
View File
@@ -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
+23 -6
View File
@@ -93,6 +93,12 @@ class ReturnService extends BaseService {
}
}
if (order.claims && order.claims.length) {
for (const c of order.claims) {
merged = [...merged, ...c.additional_items]
}
}
const toReturn = await Promise.all(
items.map(async data => {
const item = merged.find(i => i.id === data.item_id)
@@ -325,7 +331,13 @@ class ReturnService extends BaseService {
.withTransaction(manager)
.retrieve(orderId, {
select: ["refunded_total", "total", "refundable_amount"],
relations: ["swaps", "swaps.additional_items", "items"],
relations: [
"swaps",
"swaps.additional_items",
"claims",
"claims.additional_items",
"items",
],
})
const returnLines = await this.getFulfillmentItems_(
@@ -487,13 +499,18 @@ class ReturnService extends BaseService {
* @param {string[]} lineItems - the line items to return
* @return {Promise} the result of the update operation
*/
async receive(returnId, receivedItems, refundAmount, allowMismatch = false) {
async receive(
return_id,
received_items,
refund_amount,
allow_mismatch = false
) {
return this.atomicPhase_(async manager => {
const returnRepository = manager.getCustomRepository(
this.returnRepository_
)
const returnObj = await this.retrieve(returnId, {
const returnObj = await this.retrieve(return_id, {
relations: ["items", "swap", "swap.additional_items"],
})
@@ -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 = {
+66
View File
@@ -0,0 +1,66 @@
import { SearchService } from "medusa-interfaces"
/**
* Default class that implements SearchService but provides stuv implementation for all methods
* @extends SearchService
*/
class DefaultSearchService extends SearchService {
constructor(container) {
super()
this.isDefault = true
this.logger_ = container.logger
}
createIndex(indexName, options) {
this.logger_.warn(
"This is an empty method: createIndex must be overridden by a child class"
)
}
getIndex(indexName) {
this.logger_.warn(
"This is an empty method: getIndex must be overridden by a child class"
)
}
addDocuments(indexName, documents, type) {
this.logger_.warn(
"This is an empty method: addDocuments must be overridden by a child class"
)
}
replaceDocuments(indexName, documents, type) {
this.logger_.warn(
"This is an empty method: replaceDocuments must be overridden by a child class"
)
}
deleteDocument(indexName, document_id) {
this.logger_.warn(
"This is an empty method: deleteDocument must be overridden by a child class"
)
}
deleteAllDocuments(indexName) {
this.logger_.warn(
"This is an empty method: deleteAllDocuments must be overridden by a child class"
)
}
search(indexName, query, options) {
this.logger_.warn(
"This is an empty method: search must be overridden a the child class"
)
return { hits: [] }
}
updateSettings(indexName, settings) {
this.logger_.warn(
"This is an empty method: updateSettings must be overridden by a child class"
)
}
}
export default DefaultSearchService
+37 -32
View File
@@ -1,11 +1,9 @@
import _ from "lodash"
import { MedusaError } from "medusa-core-utils"
import { BaseService } from "medusa-interfaces"
import { In } from "typeorm"
/**
* Provides layer to manipulate profiles.
* @implements BaseService
* @extends BaseService
*/
class ShippingOptionService extends BaseService {
constructor({
@@ -64,6 +62,7 @@ class ShippingOptionService extends BaseService {
/**
* Validates a requirement
* @param {ShippingRequirement} requirement - the requirement to validate
* @param {string} optionId - the id to validate the requirement
* @return {ShippingRequirement} a validated shipping requirement
*/
async validateRequirement_(requirement, optionId) {
@@ -123,6 +122,7 @@ class ShippingOptionService extends BaseService {
/**
* @param {Object} selector - the query object for find
* @param {object} config - config object
* @return {Promise} the result of the find operation
*/
async list(selector, config = { skip: 0, take: 50 }) {
@@ -136,6 +136,7 @@ class ShippingOptionService extends BaseService {
* Gets a profile by id.
* Throws in case of DB Error and if profile was not found.
* @param {string} optionId - the id of the profile to get.
* @param {object} options - the options to get a profile
* @return {Promise<Product>} the profile document.
*/
async retrieve(optionId, options = {}) {
@@ -171,10 +172,10 @@ class ShippingOptionService extends BaseService {
* and its methods should be copied to an order/swap entity.
* @param {string} id - the id of the shipping method to update
* @param {object} update - the values to update the method with
* @returns {Promise<ShippingMethod>} the resulting shipping method
* @return {Promise<ShippingMethod>} the resulting shipping method
*/
async updateShippingMethod(id, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const methodRepo = manager.getCustomRepository(this.methodRepository_)
const method = await methodRepo.findOne({ where: { id } })
@@ -200,10 +201,10 @@ class ShippingOptionService extends BaseService {
/**
* Removes a given shipping method
* @param {string} id - the id of the option to use for the method.
* @param {string} sm - the shipping method to remove
*/
async deleteShippingMethod(sm) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const methodRepo = manager.getCustomRepository(this.methodRepository_)
return methodRepo.remove(sm)
})
@@ -214,10 +215,10 @@ class ShippingOptionService extends BaseService {
* @param {string} optionId - the id of the option to use for the method.
* @param {object} data - the optional provider data to use.
* @param {object} config - the cart to create the shipping method for.
* @returns {ShippingMethod} the resulting shipping method.
* @return {ShippingMethod} the resulting shipping method.
*/
async createShippingMethod(optionId, data, config) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const option = await this.retrieve(optionId, {
relations: ["requirements"],
})
@@ -255,6 +256,10 @@ class ShippingOptionService extends BaseService {
toCreate.cart_id = config.cart.id
}
if (config.cart_id) {
toCreate.cart_id = config.cart_id
}
if (config.return_id) {
toCreate.return_id = config.return_id
}
@@ -286,7 +291,7 @@ class ShippingOptionService extends BaseService {
* Checks if a given option id is a valid option for a cart. If it is the
* option is returned with the correct price. Throws when region_ids do not
* match, or when the shipping option requirements are not satisfied.
* @param {string} optionId - the id of the option to check
* @param {object} option - the option object to check
* @param {Cart} cart - the cart object to check against
* @return {ShippingOption} the validated shipping option
*/
@@ -303,7 +308,7 @@ class ShippingOptionService extends BaseService {
}
const subtotal = cart.subtotal
const requirementResults = option.requirements.map(requirement => {
const requirementResults = option.requirements.map((requirement) => {
switch (requirement.type) {
case "max_subtotal":
return requirement.amount > subtotal
@@ -328,11 +333,11 @@ class ShippingOptionService extends BaseService {
* Creates a new shipping option. Used both for outbound and inbound shipping
* options. The difference is registered by the `is_return` field which
* defaults to false.
* @param {ShippingOption} option - the shipping option to create
* @param {ShippingOption} data - the data to create shipping options
* @return {Promise<ShippingOption>} the result of the create operation
*/
async create(data) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const optionRepo = manager.getCustomRepository(this.optionRepository_)
const option = await optionRepo.create(data)
@@ -370,7 +375,7 @@ class ShippingOptionService extends BaseService {
for (const r of data.requirements) {
const validated = await this.validateRequirement_(r)
if (acc.find(raw => raw.type === validated.type)) {
if (acc.find((raw) => raw.type === validated.type)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Only one requirement of each type is allowed"
@@ -379,7 +384,7 @@ class ShippingOptionService extends BaseService {
if (
acc.find(
raw =>
(raw) =>
(raw.type === "max_subtotal" &&
validated.amount > raw.amount) ||
(raw.type === "min_subtotal" && validated.amount < raw.amount)
@@ -408,7 +413,7 @@ class ShippingOptionService extends BaseService {
/**
* Validates a shipping option price
* @param {ShippingOptionPrice} price - the price to validate
* @param {ShippingOptionPrice} priceType - the price to validate
* @param {ShippingOption} option - the option to validate against
* @return {Promise<ShippingOptionPrice>} the validated price
*/
@@ -449,7 +454,7 @@ class ShippingOptionService extends BaseService {
* @return {Promise} resolves to the update result.
*/
async update(optionId, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const option = await this.retrieve(optionId, {
relations: ["requirements"],
})
@@ -477,7 +482,7 @@ class ShippingOptionService extends BaseService {
for (const r of update.requirements) {
const validated = await this.validateRequirement_(r, optionId)
if (acc.find(raw => raw.type === validated.type)) {
if (acc.find((raw) => raw.type === validated.type)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Only one requirement of each type is allowed"
@@ -486,7 +491,7 @@ class ShippingOptionService extends BaseService {
if (
acc.find(
raw =>
(raw) =>
(raw.type === "max_subtotal" &&
validated.amount > raw.amount) ||
(raw.type === "min_subtotal" && validated.amount < raw.amount)
@@ -502,12 +507,12 @@ class ShippingOptionService extends BaseService {
}
if (option.requirements) {
const accReqs = acc.map(a => a.id)
const accReqs = acc.map((a) => a.id)
const toRemove = option.requirements.filter(
r => !accReqs.includes(r.id)
(r) => !accReqs.includes(r.id)
)
await Promise.all(
toRemove.map(async req => {
toRemove.map(async (req) => {
await this.removeRequirement(req.id)
})
)
@@ -552,7 +557,7 @@ class ShippingOptionService extends BaseService {
*/
async delete(optionId) {
try {
let option = await this.retrieve(optionId)
const option = await this.retrieve(optionId)
const optionRepo = this.manager_.getCustomRepository(
this.optionRepository_
@@ -579,13 +584,13 @@ class ShippingOptionService extends BaseService {
* @return {Promise} the result of update
*/
async addRequirement(optionId, requirement) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const option = await this.retrieve(optionId, {
relations: ["requirements"],
})
const validatedReq = await this.validateRequirement_(requirement)
if (option.requirements.find(r => r.type === validatedReq.type)) {
if (option.requirements.find((r) => r.type === validatedReq.type)) {
throw new MedusaError(
MedusaError.Types.DUPLICATE_ERROR,
`A requirement with type: ${validatedReq.type} already exists`
@@ -605,7 +610,7 @@ class ShippingOptionService extends BaseService {
* @return {Promise} the result of update
*/
async removeRequirement(requirementId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
try {
const reqRepo = manager.getCustomRepository(this.requirementRepository_)
const requirement = await reqRepo.findOne({
@@ -624,7 +629,7 @@ class ShippingOptionService extends BaseService {
/**
* Decorates a shipping option.
* @param {ShippingOption} shippingOption - the shipping option to decorate.
* @param {ShippingOption} optionId - the shipping option to decorate using optionId.
* @param {string[]} fields - the fields to include.
* @param {string[]} expandFields - fields to expand.
* @return {ShippingOption} the decorated ShippingOption.
@@ -644,9 +649,8 @@ class ShippingOptionService extends BaseService {
/**
* Dedicated method to set metadata for a shipping option.
* @param {string} optionId - the option to set metadata for.
* @param {string} key - key for metadata field
* @param {string} value - value for metadata field.
* @param {object} option - the option to set metadata for.
* @param {object} metadata - object for metadata field
* @return {Promise} resolves to the updated result.
*/
async setMetadata_(option, metadata) {
@@ -677,9 +681,10 @@ class ShippingOptionService extends BaseService {
* price type "calculated".
* @param {ShippingOption} option - the shipping option to retrieve the price
* for.
* @param {Cart || Order} cart - the context in which the price should be
* @param {ShippingData} data - the shipping data to retrieve the price.
* @param {Cart | Order} cart - the context in which the price should be
* retrieved.
* @returns {Promise<Number>} the price of the shipping option.
* @return {Promise<Number>} the price of the shipping option.
*/
async getPrice_(option, data, cart) {
if (option.price_type === "calculated") {
@@ -14,6 +14,7 @@ class ShippingProfileService extends BaseService {
productService,
productRepository,
shippingOptionService,
customShippingOptionService,
}) {
super()
@@ -31,6 +32,9 @@ class ShippingProfileService extends BaseService {
/** @private @const {ShippingOptionService} */
this.shippingOptionService_ = shippingOptionService
/** @private @const {CustomShippingOptionService} */
this.customShippingOptionService_ = customShippingOptionService
}
withTransaction(transactionManager) {
@@ -43,6 +47,7 @@ class ShippingProfileService extends BaseService {
shippingProfileRepository: this.shippingProfileRepository_,
productService: this.productService_,
shippingOptionService: this.shippingOptionService_,
customShippingOptionService: this.customShippingOptionService_,
})
cloned.transactionManager_ = transactionManager
@@ -410,19 +415,47 @@ class ShippingProfileService extends BaseService {
* Finds all the shipping profiles that cover the products in a cart, and
* validates all options that are available for the cart.
* @param {Cart} cart - the cart object to find shipping options for
* @return {[ShippingOptions]} a list of the available shipping options
* @return {[ShippingOption]} a list of the available shipping options
*/
async fetchCartOptions(cart) {
const profileIds = this.getProfilesInCart_(cart)
const rawOpts = await this.shippingOptionService_.list(
const selector = {
profile_id: profileIds,
admin_only: false,
}
const customShippingOptions = await this.customShippingOptionService_.list(
{
profile_id: profileIds,
admin_only: false,
cart_id: cart.id,
},
{ relations: ["requirements", "profile"] }
{ select: ["id", "shipping_option_id", "price"] }
)
const hasCustomShippingOptions = customShippingOptions?.length
// if there are custom shipping options associated with the cart, use those
if (hasCustomShippingOptions) {
selector.id = customShippingOptions.map(cso => cso.shipping_option_id)
}
const rawOpts = await this.shippingOptionService_.list(selector, {
relations: ["requirements", "profile"],
})
// if there are custom shipping options associated with the cart, return cart shipping options with custom price
if (hasCustomShippingOptions) {
return rawOpts.map(so => {
const customOption = customShippingOptions.find(
cso => cso.shipping_option_id === so.id
)
return {
...so,
amount: customOption?.price,
}
})
}
const options = []
for (const o of rawOpts) {
+45 -24
View File
@@ -32,6 +32,7 @@ class SwapService extends BaseService {
fulfillmentService,
orderService,
inventoryService,
customShippingOptionService,
}) {
super()
@@ -70,6 +71,9 @@ class SwapService extends BaseService {
/** @private @const {EventBusService} */
this.eventBus_ = eventBusService
/** @private @const {CustomShippingOptionService} */
this.customShippingOptionService_ = customShippingOptionService
}
withTransaction(transactionManager) {
@@ -90,6 +94,7 @@ class SwapService extends BaseService {
orderService: this.orderService_,
inventoryService: this.inventoryService_,
fulfillmentService: this.fulfillmentService_,
customShippingOptionService: this.customShippingOptionService_,
})
cloned.transactionManager_ = transactionManager
@@ -116,7 +121,7 @@ class SwapService extends BaseService {
"cart.total",
]
const totalsToSelect = select.filter(v => totalFields.includes(v))
const totalsToSelect = select.filter((v) => totalFields.includes(v))
if (totalsToSelect.length > 0) {
const relationSet = new Set(relations)
relationSet.add("cart")
@@ -127,7 +132,7 @@ class SwapService extends BaseService {
relationSet.add("cart.region")
relations = [...relationSet]
select = select.filter(v => !totalFields.includes(v))
select = select.filter((v) => !totalFields.includes(v))
}
return {
@@ -170,9 +175,8 @@ class SwapService extends BaseService {
const validatedId = this.validateId_(id)
const { totalsToSelect, ...newConfig } = this.transformQueryForTotals_(
config
)
const { totalsToSelect, ...newConfig } =
this.transformQueryForTotals_(config)
const query = this.buildQuery_({ id: validatedId }, newConfig)
@@ -251,7 +255,7 @@ class SwapService extends BaseService {
*/
validateReturnItems_(order, returnItems) {
return returnItems.map(({ item_id, quantity }) => {
const item = order.items.find(i => i.id === item_id)
const item = order.items.find((i) => i.id === item_id)
// The item must exist in the order
if (!item) {
@@ -303,7 +307,7 @@ class SwapService extends BaseService {
}
) {
const { no_notification, ...rest } = custom
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
if (
order.fulfillment_status === "not_fulfilled" ||
order.payment_status !== "captured"
@@ -318,7 +322,6 @@ class SwapService extends BaseService {
const line = await this.lineItemService_.retrieve(item.item_id, {
relations: ["order", "swap", "claim_order"],
})
console.log(line)
if (
line.order?.canceled_at ||
@@ -377,7 +380,7 @@ class SwapService extends BaseService {
}
async processDifference(swapId) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const swap = await this.retrieve(swapId, {
relations: ["payment", "order", "order.payments"],
})
@@ -493,7 +496,7 @@ class SwapService extends BaseService {
}
async update(swapId, update) {
return this.atomicPhase_(async manager => {
return this.atomicPhase_(async (manager) => {
const swap = await this.retrieve(swapId)
if ("metadata" in update) {
@@ -524,8 +527,8 @@ class SwapService extends BaseService {
* @returns {Promise<Swap>} the swap with its cart_id prop set to the id of
* the new cart.
*/
async createCart(swapId) {
return this.atomicPhase_(async manager => {
async createCart(swapId, customShippingOptions = []) {
return this.atomicPhase_(async (manager) => {
const swap = await this.retrieve(swapId, {
relations: [
"order",
@@ -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()
}
+15 -1
View File
@@ -1,6 +1,7 @@
import _ from "lodash"
import { BaseService } from "medusa-interfaces"
import { MedusaError } from "medusa-core-utils"
import carts from "../api/routes/store/carts"
/**
* A service that calculates total and subtotals for orders, carts etc..
@@ -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
+110
View File
@@ -0,0 +1,110 @@
import ProductVariantService from "../services/product-variant"
import ProductService from "../services/product"
import { indexTypes } from "medusa-core-utils"
const searchFields = [
"id",
"title",
"subtitle",
"description",
"handle",
"is_giftcard",
"discountable",
"thumbnail",
"profile_id",
"collection_id",
"type_id",
"origin_country",
"created_at",
"updated_at",
]
const searchRelations = [
"variants",
"tags",
"type",
"collection",
"variants.prices",
"variants.options",
"options",
]
class ProductSearchSubscriber {
constructor({ eventBusService, searchService, productService }) {
this.eventBus_ = eventBusService
this.searchService_ = searchService
this.productService_ = productService
this.eventBus_.subscribe(
ProductService.Events.CREATED,
this.handleProductCreation
)
this.eventBus_.subscribe(
ProductService.Events.UPDATED,
this.handleProductUpdate
)
this.eventBus_.subscribe(
ProductService.Events.DELETED,
this.handleProductDeletion
)
this.eventBus_.subscribe(
ProductVariantService.Events.CREATED,
this.handleProductVariantChange
)
this.eventBus_.subscribe(
ProductVariantService.Events.UPDATED,
this.handleProductVariantChange
)
this.eventBus_.subscribe(
ProductVariantService.Events.DELETED,
this.handleProductVariantChange
)
}
handleProductCreation = async (data) => {
const product = await this.retrieveProduct_(data.id)
await this.searchService_.addDocuments(
ProductService.IndexName,
[product],
indexTypes.products
)
}
retrieveProduct_ = async (product_id) => {
return await this.productService_.retrieve(product_id, {
select: searchFields,
relations: searchRelations,
})
}
handleProductUpdate = async (data) => {
const product = await this.retrieveProduct_(data.id)
await this.searchService_.addDocuments(
ProductService.IndexName,
[product],
indexTypes.products
)
}
handleProductDeletion = async (data) => {
await this.searchService_.deleteDocument(ProductService.IndexName, data.id)
}
handleProductVariantChange = async (data) => {
const product = await this.retrieveProduct_(data.product_id)
await this.searchService_.addDocuments(
ProductService.IndexName,
[product],
indexTypes.products
)
}
}
export default ProductSearchSubscriber
+695 -968
View File
File diff suppressed because it is too large. Load diff