feat: rma shipping option + unit tests
This commit is contained in:
@@ -78,6 +78,12 @@ export default async (req, res) => {
|
||||
.optional(),
|
||||
})
|
||||
.optional(),
|
||||
rma_shipping_options: Validator.array().items({
|
||||
option_id: Validator.string().optional(),
|
||||
price: Validator.number()
|
||||
.integer()
|
||||
.optional(),
|
||||
}),
|
||||
additional_items: Validator.array().items({
|
||||
variant_id: Validator.string().required(),
|
||||
quantity: Validator.number().required(),
|
||||
@@ -138,6 +144,7 @@ export default async (req, res) => {
|
||||
value.return_items,
|
||||
value.additional_items,
|
||||
value.return_shipping,
|
||||
value.rma_shipping_options,
|
||||
{
|
||||
idempotency_key: idempotencyKey.idempotency_key,
|
||||
no_notification: value.no_notification,
|
||||
|
||||
@@ -23,9 +23,9 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShipping", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
it("calls CartService addRMAMethod", () => {
|
||||
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("fr-cart"),
|
||||
IdMap.getId("freeShipping"),
|
||||
{}
|
||||
@@ -45,6 +45,50 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("successfully adds a RMA 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 addRMAMethod", () => {
|
||||
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addRMAMethod).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,9 +112,9 @@ describe("POST /store/carts/:id/shipping-methods", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService addShipping", () => {
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
|
||||
it("calls CartService addRMAMethod", () => {
|
||||
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("fr-cart"),
|
||||
IdMap.getId("freeShipping"),
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { defaultFields, defaultRelations } from "./"
|
||||
import { CartType } from "../../../../models/cart"
|
||||
|
||||
/**
|
||||
* @oas [post] /carts/{id}/shipping-methods
|
||||
@@ -44,7 +45,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)
|
||||
|
||||
await txCartService.addRMAMethod(id, value.option_id, value.data)
|
||||
|
||||
const updated = await txCartService.retrieve(id, {
|
||||
relations: ["payment_sessions"],
|
||||
})
|
||||
@@ -54,12 +57,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
|
||||
}
|
||||
|
||||
+51
-1
@@ -4,7 +4,7 @@ import { carts, CartServiceMock } from "../../../../../services/__mocks__/cart"
|
||||
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
|
||||
|
||||
describe("GET /store/shipping-options", () => {
|
||||
describe("retrieves shipping options", () => {
|
||||
describe("retrieves shipping options when cart type is not swap and not claim", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -53,4 +53,54 @@ describe("GET /store/shipping-options", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieves shipping options when cart type is swap", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"GET",
|
||||
`/store/shipping-options/${IdMap.getId("swap-cart")}`
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls CartService retrieve", () => {
|
||||
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("swap-cart"),
|
||||
{
|
||||
select: ["subtotal"],
|
||||
relations: [
|
||||
"region",
|
||||
"items",
|
||||
"items.variant",
|
||||
"items.variant.product",
|
||||
],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("calls ShippingProfileService fetchRMAOptions", () => {
|
||||
expect(ShippingProfileServiceMock.fetchRMAOptions).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(ShippingProfileServiceMock.fetchRMAOptions).toHaveBeenCalledWith(
|
||||
carts.testSwapCart
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns the RMAshippingOptions", () => {
|
||||
expect(subject.body.shipping_options[0].id).toEqual(
|
||||
IdMap.getId("cartRMAShippingOption")
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { CartType } from "../../../../models/cart"
|
||||
|
||||
/**
|
||||
* @oas [get] /shipping-options/{cart_id}
|
||||
@@ -40,7 +41,12 @@ export default async (req, res) => {
|
||||
relations: ["region", "items", "items.variant", "items.variant.product"],
|
||||
})
|
||||
|
||||
const options = await shippingProfileService.fetchCartOptions(cart)
|
||||
let options
|
||||
if (cart.type === CartType.SWAP || cart.type === CartType.CLAIM) {
|
||||
options = await shippingProfileService.fetchRMAOptions(cart)
|
||||
} else {
|
||||
options = await shippingProfileService.fetchCartOptions(cart)
|
||||
}
|
||||
|
||||
res.status(200).json({ shipping_options: options })
|
||||
} catch (err) {
|
||||
|
||||
@@ -45,3 +45,4 @@ export { Swap } from "./models/swap"
|
||||
export { User } from "./models/user"
|
||||
export { DraftOrder } from "./models/draft-order"
|
||||
export { ReturnReason } from "./models/return-reason"
|
||||
export { RMAShippingOption } from "./models/rma-shipping-option"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class addRmaShippingOptions1632851018347 implements MigrationInterface {
|
||||
name = 'addRmaShippingOptions1632851018347'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "rma_shipping_option" ("id" character varying NOT NULL, "price" integer NOT NULL, "shipping_option_id" character varying NOT NULL, "swap_id" character varying, "claim_order_id" character varying, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP WITH TIME ZONE, "metadata" jsonb, CONSTRAINT "PK_12d2eebc6cef997c9057b338647" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_f3618cd0930a2e7357eddbebf4" ON "rma_shipping_option" ("shipping_option_id") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_4e4c2c0a3223c79a84a6f54e58" ON "rma_shipping_option" ("swap_id") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_2e262bb0e324b501d80a69f9df" ON "rma_shipping_option" ("claim_order_id") `);
|
||||
await queryRunner.query(`ALTER TYPE "public"."cart_type_enum" RENAME TO "cart_type_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."cart_type_enum" AS ENUM('default', 'swap', 'draft_order', 'payment_link', 'claim')`);
|
||||
await queryRunner.query(`ALTER TABLE "public"."cart" ALTER COLUMN "type" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "public"."cart" ALTER COLUMN "type" TYPE "public"."cart_type_enum" USING "type"::"text"::"public"."cart_type_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "public"."cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."cart_type_enum_old"`);
|
||||
await queryRunner.query(`ALTER TABLE "rma_shipping_option" ADD CONSTRAINT "FK_f3618cd0930a2e7357eddbebf4c" FOREIGN KEY ("shipping_option_id") REFERENCES "shipping_option"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "rma_shipping_option" ADD CONSTRAINT "FK_4e4c2c0a3223c79a84a6f54e583" FOREIGN KEY ("swap_id") REFERENCES "swap"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "rma_shipping_option" ADD CONSTRAINT "FK_2e262bb0e324b501d80a69f9df4" FOREIGN KEY ("claim_order_id") REFERENCES "claim_order"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "rma_shipping_option" DROP CONSTRAINT "FK_2e262bb0e324b501d80a69f9df4"`);
|
||||
await queryRunner.query(`ALTER TABLE "rma_shipping_option" DROP CONSTRAINT "FK_4e4c2c0a3223c79a84a6f54e583"`);
|
||||
await queryRunner.query(`ALTER TABLE "rma_shipping_option" DROP CONSTRAINT "FK_f3618cd0930a2e7357eddbebf4c"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."cart_type_enum_old" AS ENUM('default', 'swap', 'draft_order', 'payment_link')`);
|
||||
await queryRunner.query(`ALTER TABLE "public"."cart" ALTER COLUMN "type" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "public"."cart" ALTER COLUMN "type" TYPE "public"."cart_type_enum_old" USING "type"::"text"::"public"."cart_type_enum_old"`);
|
||||
await queryRunner.query(`ALTER TABLE "public"."cart" ALTER COLUMN "type" SET DEFAULT 'default'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."cart_type_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."cart_type_enum_old" RENAME TO "cart_type_enum"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_2e262bb0e324b501d80a69f9df"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_4e4c2c0a3223c79a84a6f54e58"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_f3618cd0930a2e7357eddbebf4"`);
|
||||
await queryRunner.query(`DROP TABLE "rma_shipping_option"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,6 +119,7 @@ export enum CartType {
|
||||
SWAP = "swap",
|
||||
DRAFT_ORDER = "draft_order",
|
||||
PAYMENT_LINK = "payment_link",
|
||||
CLAIM = "claim",
|
||||
}
|
||||
|
||||
@Entity()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RMAShippingOption } from './rma-shipping-option';
|
||||
import {
|
||||
Entity,
|
||||
BeforeInsert,
|
||||
@@ -113,6 +114,13 @@ export class ClaimOrder {
|
||||
)
|
||||
shipping_methods: ShippingMethod[]
|
||||
|
||||
@OneToMany(
|
||||
() => RMAShippingOption,
|
||||
method => method.swap,
|
||||
{ cascade: ["insert"] }
|
||||
)
|
||||
rma_shipping_options: RMAShippingOption[]
|
||||
|
||||
@OneToMany(
|
||||
() => Fulfillment,
|
||||
fulfillment => fulfillment.claim_order,
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import {
|
||||
BeforeInsert,
|
||||
Check,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryColumn,
|
||||
UpdateDateColumn
|
||||
} from "typeorm";
|
||||
import { ulid } from "ulid";
|
||||
import { DbAwareColumn, resolveDbType } from "../utils/db-aware-column";
|
||||
import { ClaimOrder } from './claim-order';
|
||||
import { ShippingOption } from "./shipping-option";
|
||||
import { Swap } from './swap';
|
||||
|
||||
|
||||
@Entity()
|
||||
export class RMAShippingOption {
|
||||
@PrimaryColumn()
|
||||
id: string
|
||||
|
||||
@Column({ type: "int" })
|
||||
price: number
|
||||
|
||||
@Index()
|
||||
@Column()
|
||||
shipping_option_id: string;
|
||||
|
||||
@ManyToOne(() => ShippingOption, { eager: true })
|
||||
@JoinColumn({ name: "shipping_option_id" })
|
||||
shipping_option: ShippingOption
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
swap_id: string
|
||||
|
||||
@ManyToOne(() => Swap)
|
||||
@JoinColumn({ name: "swap_id" })
|
||||
swap: Swap
|
||||
|
||||
@Index()
|
||||
@Column({ nullable: true })
|
||||
claim_order_id: string
|
||||
|
||||
@ManyToOne(() => ClaimOrder)
|
||||
@JoinColumn({ name: "claim_order_id" })
|
||||
claim_order: ClaimOrder
|
||||
|
||||
@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 = `rmaso_${id}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @schema RMA shipping_option
|
||||
* title: "RMA Shipping Option"
|
||||
* description: "Shipping Options represent a way in which an Order or Return can be shipped. Shipping Options have an associated Fulfillment Provider that will be used when the fulfillment of an Order is initiated. Shipping Options themselves cannot be added to Carts, but serve as a template for Shipping Methods. This distinction makes it possible to customize individual Shipping Methods with additional information."
|
||||
* x-resourceId: shipping_option
|
||||
* properties:
|
||||
* id:
|
||||
* description: "The id of the Shipping Option. This value will be prefixed with `so_`."
|
||||
* type: string
|
||||
* name:
|
||||
* description: "The name given to the Shipping Option - this may be displayed to the Customer."
|
||||
* type: string
|
||||
* region_id:
|
||||
* description: "The id of the Region that the Shipping Option belongs to."
|
||||
* type: string
|
||||
* region:
|
||||
* description: "The id of the Region that the Shipping Option belongs to."
|
||||
* anyOf:
|
||||
* - $ref: "#/components/schemas/region"
|
||||
* profile_id:
|
||||
* description: "The id of the Shipping Profile that the Shipping Option belongs to. Shipping Profiles have a set of defined Shipping Options that can be used to Fulfill a given set of Products."
|
||||
* type: string
|
||||
* provider_id:
|
||||
* description: "The id of the Fulfillment Provider, that will be used to process Fulfillments from the Shipping Option."
|
||||
* type: string
|
||||
* price_type:
|
||||
* description: "The type of pricing calculation that is used when creatin Shipping Methods from the Shipping Option. Can be `flat_rate` for fixed prices or `calculated` if the Fulfillment Provider can provide price calulations."
|
||||
* type: string
|
||||
* enum:
|
||||
* - flat_rate
|
||||
* - calculated
|
||||
* amount:
|
||||
* description: "The amount to charge for shipping when the Shipping Option price type is `flat_rate`."
|
||||
* type: integer
|
||||
* is_return:
|
||||
* description: "Flag to indicate if the Shipping Option can be used for Return shipments."
|
||||
* type: boolean
|
||||
* requirements:
|
||||
* description: "The requirements that must be satisfied for the Shipping Option to be available for a Cart."
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/shipping_option_requirement"
|
||||
* data:
|
||||
* description: "The data needed for the Fulfillment Provider to identify the Shipping Option."
|
||||
* type: object
|
||||
* created_at:
|
||||
* description: "The date with timezone at which the resource was created."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* updated_at:
|
||||
* description: "The date with timezone at which the resource was last updated."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* deleted_at:
|
||||
* description: "The date with timezone at which the resource was deleted."
|
||||
* type: string
|
||||
* format: date-time
|
||||
* metadata:
|
||||
* description: "An optional key-value map with additional information."
|
||||
* type: object
|
||||
*/
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RMAShippingOption } from './rma-shipping-option';
|
||||
import {
|
||||
Entity,
|
||||
Index,
|
||||
@@ -112,6 +113,13 @@ export class Swap {
|
||||
)
|
||||
shipping_methods: ShippingMethod[]
|
||||
|
||||
@OneToMany(
|
||||
() => RMAShippingOption,
|
||||
method => method.swap,
|
||||
{ cascade: ["insert"] }
|
||||
)
|
||||
rma_shipping_options: RMAShippingOption[]
|
||||
|
||||
@Column({ nullable: true })
|
||||
cart_id: string
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import { RMAShippingOption } from './../models/rma-shipping-option';
|
||||
|
||||
@EntityRepository(RMAShippingOption)
|
||||
export class RMAShippingOptionRepository extends Repository<RMAShippingOption> {}
|
||||
@@ -342,6 +342,9 @@ export const CartServiceMock = {
|
||||
addShippingMethod: jest.fn().mockImplementation(cartId => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
addRMAMethod: jest.fn().mockImplementation(cartId => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
retrieveShippingOption: jest.fn().mockImplementation((cartId, optionId) => {
|
||||
if (optionId === IdMap.getId("freeShipping")) {
|
||||
return {
|
||||
|
||||
@@ -134,6 +134,9 @@ export const ShippingProfileServiceMock = {
|
||||
fetchCartOptions: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve([{ id: IdMap.getId("cartShippingOption") }])
|
||||
}),
|
||||
fetchRMAOptions: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve([{ id: IdMap.getId("cartRMAShippingOption") }])
|
||||
}),
|
||||
fetchOptionsByProductIds: jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve([{ id: IdMap.getId("cartShippingOption") }])
|
||||
}),
|
||||
|
||||
@@ -1467,6 +1467,76 @@ describe("CartService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("addRMAMethod", () => {
|
||||
const rmaSO = {
|
||||
id: IdMap.getId("rmaso-option"),
|
||||
shipping_option_id: IdMap.getId("regular-so-option"),
|
||||
price: 0,
|
||||
}
|
||||
|
||||
const RMAShippingOptionRepository = MockRepository({
|
||||
findOne: q => {
|
||||
if (q.where.id === IdMap.getId("rmaso-option")) {
|
||||
return Promise.resolve(rmaSO)
|
||||
}
|
||||
return Promise.resolve(null)
|
||||
},
|
||||
})
|
||||
|
||||
const cartService = new CartService({
|
||||
manager: MockManager,
|
||||
totalsService,
|
||||
RMAShippingOptionRepository,
|
||||
eventBusService,
|
||||
})
|
||||
|
||||
cartService.addShippingMethod = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("when a normal shipping option id is provided, then it should call addShippingMethod", async () => {
|
||||
const data = {
|
||||
id: "test",
|
||||
extra: "yes",
|
||||
}
|
||||
|
||||
await cartService.addRMAMethod(
|
||||
IdMap.getId("cart"),
|
||||
IdMap.getId("regular-so-option"),
|
||||
data
|
||||
)
|
||||
expect(cartService.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("cart"),
|
||||
IdMap.getId("regular-so-option"),
|
||||
data
|
||||
)
|
||||
})
|
||||
|
||||
it("when a rma shipping option is provided, then it should call addShippingOption with a custom price", async () => {
|
||||
const data = {
|
||||
id: "testshipperid",
|
||||
}
|
||||
await cartService.addRMAMethod(
|
||||
IdMap.getId("cart"),
|
||||
IdMap.getId("rmaso-option"),
|
||||
data
|
||||
)
|
||||
|
||||
expect(cartService.addShippingMethod).toHaveBeenCalledWith(
|
||||
IdMap.getId("cart"),
|
||||
IdMap.getId("regular-so-option"),
|
||||
expect.objectContaining({
|
||||
...data,
|
||||
price: 0,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("applyDiscount", () => {
|
||||
const cartRepository = MockRepository({
|
||||
findOneWithRelations: (rels, q) => {
|
||||
|
||||
@@ -241,6 +241,104 @@ describe("ShippingProfileService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetchRMAOptions", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("given a swap cart with rma shipping options, should return correct rma shipping options ", async () => {
|
||||
const swapRepository = MockRepository({
|
||||
findOne() {
|
||||
return Promise.resolve({
|
||||
id: "swap-cart",
|
||||
type: "swap",
|
||||
rma_shipping_options: [
|
||||
{ option_id: "test-option1", id: "rmsao-option1", price: 10 },
|
||||
{ option_id: "test-option2", id: "rmsao-option2", price: 0 },
|
||||
],
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const profileService = new ShippingProfileService({
|
||||
manager: MockManager,
|
||||
swapRepository,
|
||||
})
|
||||
|
||||
const cart = {
|
||||
id: "swap-cart",
|
||||
type: "swap",
|
||||
}
|
||||
|
||||
await expect(profileService.fetchRMAOptions(cart)).resolves.toEqual([
|
||||
expect.objectContaining({ id: "rmsao-option1" }),
|
||||
expect.objectContaining({ id: "rmsao-option2" }),
|
||||
])
|
||||
})
|
||||
|
||||
it("given a swap cart with no rma shipping options, should call fetchCartOptions and return normal shipping options ", async () => {
|
||||
const swapRepository = MockRepository({
|
||||
findOne() {
|
||||
return Promise.resolve({
|
||||
id: "swap-cart",
|
||||
type: "swap",
|
||||
rma_shipping_options: [],
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const profileService = new ShippingProfileService({
|
||||
manager: MockManager,
|
||||
swapRepository,
|
||||
})
|
||||
|
||||
profileService.fetchCartOptions = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: "normal-option1",
|
||||
},
|
||||
{
|
||||
id: "normal-option2",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
const cart = {
|
||||
id: "swap-cart",
|
||||
type: "swap",
|
||||
}
|
||||
|
||||
await expect(profileService.fetchRMAOptions(cart)).resolves.toEqual([
|
||||
expect.objectContaining({
|
||||
id: "normal-option1",
|
||||
}),
|
||||
expect.objectContaining({ id: "normal-option2" }),
|
||||
])
|
||||
|
||||
expect(profileService.fetchCartOptions).toHaveBeenCalledTimes(1)
|
||||
expect(profileService.fetchCartOptions).toHaveBeenCalledWith({
|
||||
id: "swap-cart",
|
||||
type: "swap",
|
||||
})
|
||||
})
|
||||
|
||||
it("when cart is default, then should throw", async () => {
|
||||
const profileService = new ShippingProfileService({
|
||||
manager: MockManager,
|
||||
})
|
||||
|
||||
const cart = {
|
||||
id: "normal-cart",
|
||||
type: "default",
|
||||
}
|
||||
|
||||
await expect(profileService.fetchRMAOptions(cart)).rejects.toThrow({
|
||||
type: "invalid_data",
|
||||
message: "error",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("addShippingOption", () => {
|
||||
const profRepo = MockRepository({ findOne: () => Promise.resolve({}) })
|
||||
|
||||
|
||||
@@ -324,7 +324,8 @@ describe("SwapService", () => {
|
||||
{
|
||||
id: IdMap.getId("return-shipping"),
|
||||
price: 20,
|
||||
}
|
||||
},
|
||||
[{ option_id: IdMap.getId("rmaso-option1"), price: 0 }]
|
||||
)
|
||||
|
||||
expect(lineItemService.generate).toHaveBeenCalledTimes(1)
|
||||
@@ -343,7 +344,8 @@ describe("SwapService", () => {
|
||||
{
|
||||
id: IdMap.getId("return-shipping"),
|
||||
price: 20,
|
||||
}
|
||||
},
|
||||
[{ option_id: IdMap.getId("rmaso-option1"), price: 0 }]
|
||||
)
|
||||
|
||||
expect(swapRepo.create).toHaveBeenCalledWith({
|
||||
@@ -358,6 +360,9 @@ describe("SwapService", () => {
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
rma_shipping_options: [
|
||||
{ shipping_option_id: IdMap.getId("rmaso-option1"), price: 0 },
|
||||
],
|
||||
})
|
||||
|
||||
expect(returnService.create).toHaveBeenCalledTimes(1)
|
||||
@@ -378,6 +383,7 @@ describe("SwapService", () => {
|
||||
id: IdMap.getId("return-shipping"),
|
||||
price: 20,
|
||||
},
|
||||
[],
|
||||
{ no_notification: input }
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
RMAShippingOptionRepository,
|
||||
}) {
|
||||
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,8 @@ class CartService extends BaseService {
|
||||
|
||||
/** @private @const {InventoryService} */
|
||||
this.inventoryService_ = inventoryService
|
||||
|
||||
this.rmaShippingOptionRepository_ = RMAShippingOptionRepository
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -107,13 +106,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_,
|
||||
RMAShippingOptionRepository: this.rmaShippingOptionRepository_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -1303,9 +1302,13 @@ class CartService extends BaseService {
|
||||
})
|
||||
const { shipping_methods } = cart
|
||||
|
||||
const customPrice = data.price ? { price: data.price } : {}
|
||||
const newMethod = await this.shippingOptionService_
|
||||
.withTransaction(manager)
|
||||
.createShippingMethod(optionId, data, { cart })
|
||||
.createShippingMethod(optionId, data, {
|
||||
cart,
|
||||
...customPrice,
|
||||
})
|
||||
|
||||
const methods = [newMethod]
|
||||
if (shipping_methods.length) {
|
||||
@@ -1345,6 +1348,35 @@ class CartService extends BaseService {
|
||||
}, "SERIALIZABLE")
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the corresponding shipping method either from a normal or rma shipping option to the list of shipping methods associated with
|
||||
* the cart.
|
||||
* @param {string} cartId - the id of the cart to add shipping method to
|
||||
* @param {string} optionIdOrRmaOptionId - id of the normal or rma shipping option to add as valid method
|
||||
* @param {Object} data - the fulmillment data for the method
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async addRMAMethod(cartId, optionIdOrRmaOptionId, data) {
|
||||
return this.atomicPhase_(async manager => {
|
||||
const rmaShippingOptionRepo = manager.getCustomRepository(
|
||||
this.rmaShippingOptionRepository_
|
||||
)
|
||||
|
||||
const rmaOption = await rmaShippingOptionRepo.findOne({
|
||||
where: { id: optionIdOrRmaOptionId },
|
||||
})
|
||||
|
||||
if (rmaOption) {
|
||||
await this.addShippingMethod(cartId, rmaOption.shipping_option_id, {
|
||||
...data,
|
||||
price: rmaOption.price,
|
||||
})
|
||||
} else {
|
||||
await this.addShippingMethod(cartId, optionIdOrRmaOptionId, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set's the region of a cart.
|
||||
* @param {string} cartId - the id of the cart to set region on
|
||||
|
||||
@@ -2,6 +2,7 @@ import _ from "lodash"
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import { Any, In } from "typeorm"
|
||||
import { CartType } from "../models/cart"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate profiles.
|
||||
@@ -14,6 +15,7 @@ class ShippingProfileService extends BaseService {
|
||||
productService,
|
||||
productRepository,
|
||||
shippingOptionService,
|
||||
swapRepository,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -31,6 +33,9 @@ class ShippingProfileService extends BaseService {
|
||||
|
||||
/** @private @const {ShippingOptionService} */
|
||||
this.shippingOptionService_ = shippingOptionService
|
||||
|
||||
/** @private @const {SwapRepository} */
|
||||
this.swapRepository_ = swapRepository
|
||||
}
|
||||
|
||||
withTransaction(transactionManager) {
|
||||
@@ -43,6 +48,7 @@ class ShippingProfileService extends BaseService {
|
||||
shippingProfileRepository: this.shippingProfileRepository_,
|
||||
productService: this.productService_,
|
||||
shippingOptionService: this.shippingOptionService_,
|
||||
swapRepository: this.swapRepository_,
|
||||
})
|
||||
|
||||
cloned.transactionManager_ = transactionManager
|
||||
@@ -436,6 +442,34 @@ class ShippingProfileService extends BaseService {
|
||||
|
||||
return options
|
||||
}
|
||||
/**
|
||||
* Finds all the rma shipping options 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 rma shipping options for
|
||||
* @return {[RMAShippingOptions | ShippingOptions]} a list of the available rma or normal shipping options
|
||||
*/
|
||||
async fetchRMAOptions(cart) {
|
||||
if (cart.type === CartType.DEFAULT) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, "error")
|
||||
}
|
||||
|
||||
const swapRepo = await this.manager_.getCustomRepository(
|
||||
this.swapRepository_
|
||||
)
|
||||
|
||||
if (cart.type === CartType.SWAP) {
|
||||
const swap = await swapRepo.findOne({
|
||||
where: { cart_id: cart.id },
|
||||
relations: ["rma_shipping_options"],
|
||||
})
|
||||
|
||||
if (swap.rma_shipping_options.length === 0) {
|
||||
return this.fetchCartOptions(cart)
|
||||
}
|
||||
|
||||
return swap.rma_shipping_options
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ShippingProfileService
|
||||
|
||||
@@ -288,6 +288,7 @@ class SwapService extends BaseService {
|
||||
* the customer.
|
||||
* @param {ReturnShipping?} returnShipping - an optional shipping method for
|
||||
* returning the returnItems.
|
||||
* @param {rmaShippingOptions?} rmaShippingOptions - an optional list of rma shipping options for the swap
|
||||
* @param {Object} custom - contains relevant custom information. This object may
|
||||
* include no_notification which will disable sending notification when creating
|
||||
* swap. If set, it overrules the attribute inherited from the order.
|
||||
@@ -298,6 +299,7 @@ class SwapService extends BaseService {
|
||||
returnItems,
|
||||
additionalItems,
|
||||
returnShipping,
|
||||
rmaShippingOptions = [],
|
||||
custom = {
|
||||
no_notification: undefined,
|
||||
}
|
||||
@@ -318,7 +320,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 ||
|
||||
@@ -342,6 +343,11 @@ class SwapService extends BaseService {
|
||||
})
|
||||
)
|
||||
|
||||
const rma_shipping_options = rmaShippingOptions.map(so => ({
|
||||
shipping_option_id: so.option_id,
|
||||
price: so.price,
|
||||
}))
|
||||
|
||||
const evaluatedNoNotification =
|
||||
no_notification !== undefined ? no_notification : order.no_notification
|
||||
|
||||
@@ -353,6 +359,7 @@ class SwapService extends BaseService {
|
||||
order_id: order.id,
|
||||
additional_items: newItems,
|
||||
no_notification: evaluatedNoNotification,
|
||||
rma_shipping_options,
|
||||
})
|
||||
|
||||
const result = await swapRepo.save(created)
|
||||
|
||||
Reference in New Issue
Block a user