fix: more adjustments
add: composite unique constraint for shipping_option_id and cart_id fix: fetchCartOptions to format custom shipping options like normal shipping options fix: addShippingMethod should throw when custom shipping options is not empty and no optionId corresponds to custom shipping options
This commit is contained in:
@@ -5,6 +5,7 @@ const {
|
|||||||
GiftCard,
|
GiftCard,
|
||||||
Cart,
|
Cart,
|
||||||
CustomShippingOption,
|
CustomShippingOption,
|
||||||
|
ShippingOption,
|
||||||
} = require("@medusajs/medusa")
|
} = require("@medusajs/medusa")
|
||||||
|
|
||||||
const setupServer = require("../../../helpers/setup-server")
|
const setupServer = require("../../../helpers/setup-server")
|
||||||
@@ -466,11 +467,6 @@ describe("/store/carts", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
cartWithCustomSo = await manager.save(_cart)
|
cartWithCustomSo = await manager.save(_cart)
|
||||||
|
|
||||||
await manager.insert(CustomShippingOption, {
|
|
||||||
id: "orphan-cso",
|
|
||||||
price: 0,
|
|
||||||
})
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
@@ -497,8 +493,9 @@ describe("/store/carts", () => {
|
|||||||
expect(cartWithShippingMethod.status).toEqual(200)
|
expect(cartWithShippingMethod.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("given a cart with custom options and a custom option id already belonging to said cart, then it should add a shipping method based on the given custom shipping option", async () => {
|
it("given a cart with custom options and a shipping option already belonging to said cart, then it should add a shipping method based on the given custom shipping option", async () => {
|
||||||
const customOptionId = cartWithCustomSo.custom_shipping_options[0].id
|
const shippingOptionId =
|
||||||
|
cartWithCustomSo.custom_shipping_options[0].shipping_option_id
|
||||||
|
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
@@ -506,7 +503,7 @@ describe("/store/carts", () => {
|
|||||||
.post(
|
.post(
|
||||||
"/store/carts/test-cart-with-cso/shipping-methods",
|
"/store/carts/test-cart-with-cso/shipping-methods",
|
||||||
{
|
{
|
||||||
option_id: customOptionId,
|
option_id: shippingOptionId,
|
||||||
},
|
},
|
||||||
{ withCredentials: true }
|
{ withCredentials: true }
|
||||||
)
|
)
|
||||||
@@ -515,27 +512,28 @@ describe("/store/carts", () => {
|
|||||||
expect(
|
expect(
|
||||||
cartWithCustomShippingMethod.data.cart.shipping_methods
|
cartWithCustomShippingMethod.data.cart.shipping_methods
|
||||||
).toContainEqual(
|
).toContainEqual(
|
||||||
expect.objectContaining({ shipping_option_id: "test-option", price: 5 })
|
expect.objectContaining({
|
||||||
|
shipping_option_id: shippingOptionId,
|
||||||
|
price: 5,
|
||||||
|
})
|
||||||
)
|
)
|
||||||
expect(cartWithCustomShippingMethod.status).toEqual(200)
|
expect(cartWithCustomShippingMethod.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("given a cart with custom options and a custom option id not belonging to said cart, then it should throw a shipping option not found error", async () => {
|
it("given a cart with custom options and an option id not corresponding to any custom shipping option, then it should throw an invalid error", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post(
|
await api.post(
|
||||||
"/store/carts/test-cart-with-cso/shipping-methods",
|
"/store/carts/test-cart-with-cso/shipping-methods",
|
||||||
{
|
{
|
||||||
option_id: "orphan-cso",
|
option_id: "orphan-so",
|
||||||
},
|
},
|
||||||
{ withCredentials: true }
|
{ withCredentials: true }
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
expect(err.response.status).toEqual(404)
|
expect(err.response.status).toEqual(400)
|
||||||
expect(err.response.data.message).toEqual(
|
expect(err.response.data.message).toEqual("Wrong shipping option")
|
||||||
"Shipping Option with orphan-cso was not found"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ describe("/store/shipping-options", () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("given a swap cart, when user retrieves its shipping options, then should return a list of custom shipping options", async () => {
|
it("given a cart with custom shipping options, when user retrieves its shipping options, then should return the list of custom shipping options", async () => {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
const response = await api
|
const response = await api
|
||||||
@@ -173,8 +173,9 @@ describe("/store/shipping-options", () => {
|
|||||||
expect(response.data.shipping_options).toEqual(
|
expect(response.data.shipping_options).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
shipping_option_id: "test-option",
|
id: "test-option",
|
||||||
price: 0,
|
amount: 0,
|
||||||
|
name: "test-option",
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-3
@@ -1,10 +1,10 @@
|
|||||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||||
|
|
||||||
export class addCustomShippingOptions1633522106578 implements MigrationInterface {
|
export class addCustomShippingOptions1633614437919 implements MigrationInterface {
|
||||||
name = 'addCustomShippingOptions1633522106578'
|
name = 'addCustomShippingOptions1633614437919'
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
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 "PK_8dfcb5c1172c29eec4a728420cc" PRIMARY KEY ("id"))`);
|
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_44090cb11b06174cbcc667e91c" ON "custom_shipping_option" ("shipping_option_id") `);
|
||||||
await queryRunner.query(`CREATE INDEX "IDX_93caeb1bb70d37c1d36d6701a7" ON "custom_shipping_option" ("cart_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(`ALTER TYPE "cart_type_enum" RENAME TO "cart_type_enum_old"`);
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
JoinColumn,
|
JoinColumn,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
PrimaryColumn,
|
PrimaryColumn,
|
||||||
|
Unique,
|
||||||
UpdateDateColumn
|
UpdateDateColumn
|
||||||
} from "typeorm";
|
} from "typeorm";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
@@ -16,6 +17,7 @@ import { ShippingOption } from "./shipping-option";
|
|||||||
|
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
|
@Unique(['shipping_option_id', 'cart_id'])
|
||||||
export class CustomShippingOption {
|
export class CustomShippingOption {
|
||||||
@PrimaryColumn()
|
@PrimaryColumn()
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -1308,65 +1308,50 @@ describe("CartService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("extractShippingOptionIdAndPrice", () => {
|
describe("findCustomShippingOption", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
let cartService = new CartService({})
|
let cartService = new CartService({})
|
||||||
|
|
||||||
it("given a cart with custom shipping options and a custom shipping option id, then it should return a normal shipping option id corresponding to the custom shipping option id and a customPrice", async () => {
|
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 cart = {
|
const cart = {
|
||||||
id: "cart-with-so",
|
id: "cart-with-so",
|
||||||
custom_shipping_options: [
|
custom_shipping_options: [
|
||||||
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
|
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
const result = cartService.extractShippingOptionIdAndPrice(
|
const result = cartService.findCustomShippingOption(cart, "test-so")
|
||||||
cart,
|
|
||||||
"cso-test"
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
optionId: "test-so",
|
id: "cso-test",
|
||||||
customPrice: { price: 20 },
|
shipping_option_id: "test-so",
|
||||||
|
price: 20,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("given a cart with custom shipping options and a normal shipping option id, then it should return a normal shipping option id and empty customPrice", async () => {
|
it("given a cart with empty custom shipping options and shipping option id, then it should return undefined", async () => {
|
||||||
const cart = {
|
const cart = {
|
||||||
id: "cart-with-so",
|
id: "cart-with-so",
|
||||||
custom_shipping_options: [
|
custom_shipping_options: [],
|
||||||
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
const result = cartService.extractShippingOptionIdAndPrice(
|
const result = cartService.findCustomShippingOption(cart, "test-so")
|
||||||
cart,
|
|
||||||
"test-so"
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toBeUndefined()
|
||||||
optionId: "test-so",
|
|
||||||
customPrice: {},
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("given a cart with custom shipping options and a custom shipping option id that does not belong to the cart, then it should return the custom shipping option id and empty customPrice", async () => {
|
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 cart = {
|
const cart = {
|
||||||
id: "cart-with-so",
|
id: "cart-with-so",
|
||||||
custom_shipping_options: [
|
custom_shipping_options: [
|
||||||
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
|
{ id: "cso-test", shipping_option_id: "test-so", price: 500 },
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
const result = cartService.extractShippingOptionIdAndPrice(
|
|
||||||
cart,
|
|
||||||
"cso-test-2"
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(() => {
|
||||||
optionId: "cso-test-2",
|
cartService.findCustomShippingOption(cart, "some-other-so")
|
||||||
customPrice: {},
|
}).toThrow(MedusaError)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1545,27 +1530,25 @@ describe("CartService", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("adds a shipping method from a custom shipping option and custom price", async () => {
|
it("successfully adds a shipping method from a custom shipping option and custom price", async () => {
|
||||||
const data = {
|
const data = {
|
||||||
id: "test",
|
id: "test",
|
||||||
extra: "yes",
|
extra: "yes",
|
||||||
}
|
}
|
||||||
|
|
||||||
cartService.extractShippingOptionIdAndPrice = jest
|
cartService.findCustomShippingOption = jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementation((cart, optionId) => {
|
.mockImplementation(cart => {
|
||||||
if (cart.id === IdMap.getId("cart-with-custom-so")) {
|
if (cart.id === IdMap.getId("cart-with-custom-so")) {
|
||||||
return {
|
return {
|
||||||
optionId: IdMap.getId("test-so"),
|
price: 0,
|
||||||
customPrice: { price: 0 },
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { optionId, customPrice: {} }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await cartService.addShippingMethod(
|
await cartService.addShippingMethod(
|
||||||
IdMap.getId("cart-with-custom-so"),
|
IdMap.getId("cart-with-custom-so"),
|
||||||
IdMap.getId("cso-test"),
|
IdMap.getId("test-so"),
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -207,14 +207,24 @@ describe("ShippingProfileService", () => {
|
|||||||
id: "swap-cart",
|
id: "swap-cart",
|
||||||
type: "swap",
|
type: "swap",
|
||||||
custom_shipping_options: [
|
custom_shipping_options: [
|
||||||
{ option_id: "test-option1", id: "cso-option1", price: 10 },
|
{
|
||||||
{ option_id: "test-option2", id: "cso-option2", price: 0 },
|
shipping_option_id: "test-option1",
|
||||||
|
id: "cso-option1",
|
||||||
|
shipping_option: { id: "test-option1" },
|
||||||
|
price: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
shipping_option_id: "test-option2",
|
||||||
|
id: "cso-option2",
|
||||||
|
shipping_option: { id: "test-option2" },
|
||||||
|
price: 0,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
await expect(profileService.fetchCartOptions(cart)).resolves.toEqual([
|
await expect(profileService.fetchCartOptions(cart)).resolves.toEqual([
|
||||||
expect.objectContaining({ id: "cso-option1" }),
|
expect.objectContaining({ id: "test-option1", amount: 10 }),
|
||||||
expect.objectContaining({ id: "cso-option2" }),
|
expect.objectContaining({ id: "test-option2", amount: 0 }),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1305,7 +1305,7 @@ class CartService extends BaseService {
|
|||||||
* @param {Object} data - the fulmillment data for the method
|
* @param {Object} data - the fulmillment data for the method
|
||||||
* @return {Promise} the result of the update operation
|
* @return {Promise} the result of the update operation
|
||||||
*/
|
*/
|
||||||
async addShippingMethod(cartId, optionIdOrCustomOptionId, data) {
|
async addShippingMethod(cartId, optionId, data) {
|
||||||
return this.atomicPhase_(async manager => {
|
return this.atomicPhase_(async manager => {
|
||||||
const cart = await this.retrieve(cartId, {
|
const cart = await this.retrieve(cartId, {
|
||||||
select: ["subtotal"],
|
select: ["subtotal"],
|
||||||
@@ -1321,19 +1321,19 @@ class CartService extends BaseService {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
let { optionId, customPrice } = this.extractShippingOptionIdAndPrice(
|
let customShippingOption = this.findCustomShippingOption(cart, optionId)
|
||||||
cart,
|
|
||||||
optionIdOrCustomOptionId
|
|
||||||
)
|
|
||||||
|
|
||||||
const { shipping_methods } = cart
|
const { shipping_methods } = cart
|
||||||
|
|
||||||
|
const shippingMethodConfig = customShippingOption
|
||||||
|
? { cart, price: customShippingOption.price }
|
||||||
|
: {
|
||||||
|
cart,
|
||||||
|
}
|
||||||
|
|
||||||
const newMethod = await this.shippingOptionService_
|
const newMethod = await this.shippingOptionService_
|
||||||
.withTransaction(manager)
|
.withTransaction(manager)
|
||||||
.createShippingMethod(optionId, data, {
|
.createShippingMethod(optionId, data, shippingMethodConfig)
|
||||||
cart,
|
|
||||||
...customPrice,
|
|
||||||
})
|
|
||||||
|
|
||||||
const methods = [newMethod]
|
const methods = [newMethod]
|
||||||
if (shipping_methods.length) {
|
if (shipping_methods.length) {
|
||||||
@@ -1374,32 +1374,26 @@ class CartService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the corresponding shipping method either from a normal or custom option to the list of shipping methods associated with
|
* Finds the cart's custom shipping option based on the passed option id.
|
||||||
* the cart.
|
* throws if custom options is not empty and no shipping option corresponds to optionId
|
||||||
* @param {Object} cart - the cart object
|
* @param {Object} cart - the cart object
|
||||||
* @param {string} optionIdOrCustomOptionId - id of the normal or custom shipping option to add as valid method
|
* @param {string} option - id of the normal or custom shipping option to add as valid method
|
||||||
* @returns {{ optionId: string; customPrice: { price: number; } | {};}}
|
* @returns {CustomShippingOption | undefined}
|
||||||
*/
|
*/
|
||||||
extractShippingOptionIdAndPrice(cart, optionIdOrCustomOptionId) {
|
findCustomShippingOption(cart, optionId) {
|
||||||
if (
|
let customOption = cart.custom_shipping_options?.find(
|
||||||
cart.custom_shipping_options &&
|
cso => cso.shipping_option_id === optionId
|
||||||
cart.custom_shipping_options.length > 0
|
)
|
||||||
) {
|
const hasCustomOptions = cart.custom_shipping_options?.length
|
||||||
const customOption = cart.custom_shipping_options.find(
|
|
||||||
cso => cso.id === optionIdOrCustomOptionId
|
if (hasCustomOptions && !customOption) {
|
||||||
|
throw new MedusaError(
|
||||||
|
MedusaError.Types.INVALID_DATA,
|
||||||
|
"Wrong shipping option"
|
||||||
)
|
)
|
||||||
if (customOption) {
|
|
||||||
return {
|
|
||||||
optionId: customOption.shipping_option_id,
|
|
||||||
customPrice: { price: customOption.price },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return customOption
|
||||||
optionId: optionIdOrCustomOptionId,
|
|
||||||
customPrice: {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -410,13 +410,15 @@ class ShippingProfileService extends BaseService {
|
|||||||
* Finds all the shipping profiles that cover the products in a cart, and
|
* Finds all the shipping profiles that cover the products in a cart, and
|
||||||
* validates all options that are available for the cart.
|
* validates all options that are available for the cart.
|
||||||
* @param {Cart} cart - the cart object to find shipping options for
|
* @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) {
|
async fetchCartOptions(cart) {
|
||||||
const customShippingOptions = cart.custom_shipping_options
|
if (cart.custom_shipping_options?.length) {
|
||||||
|
return cart.custom_shipping_options.map(cso => ({
|
||||||
if (customShippingOptions && customShippingOptions.length > 0)
|
...cso.shipping_option,
|
||||||
return customShippingOptions
|
amount: cso.price,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
const profileIds = this.getProfilesInCart_(cart)
|
const profileIds = this.getProfilesInCart_(cart)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user