fix: adjustments based on feedback

rename RMAShippingOption to CustomShippingOption
update models and relations
update unit and integration tests
update services
This commit is contained in:
zakariaelas
2021-10-06 14:17:36 +01:00
parent db83448d18
commit 52be911e50
29 changed files with 467 additions and 604 deletions

View File

@@ -4,7 +4,7 @@ const {
Order,
LineItem,
ProductVariant,
RMAShippingOption,
CustomShippingOption,
} = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
@@ -1277,7 +1277,7 @@ describe("/admin/orders", () => {
expect(response.status).toEqual(200)
})
it("creates a swap with rma shipping options", async () => {
it("creates a swap with custom shipping options", async () => {
const api = useApi()
const response = await api.post(
@@ -1290,7 +1290,7 @@ describe("/admin/orders", () => {
},
],
additional_items: [{ variant_id: "test-variant-2", quantity: 1 }],
rma_shipping_options: [{ option_id: "test-option", price: 0 }],
custom_shipping_options: [{ option_id: "test-option", price: 0 }],
},
{
headers: {
@@ -1302,17 +1302,19 @@ describe("/admin/orders", () => {
const swap = response.data.order.swaps[0]
const manager = dbConnection.manager
const rma = await manager.findOne(RMAShippingOption, {
const customOptions = await manager.find(CustomShippingOption, {
shipping_option_id: "test-option",
swap_id: swap.id,
})
expect(response.status).toEqual(200)
expect(rma).toEqual(
expect.objectContaining({
shipping_option_id: "test-option",
price: 0,
})
expect(customOptions).toEqual(
expect.arrayContaining([
expect.objectContaining({
shipping_option_id: "test-option",
price: 0,
cart_id: swap.cart_id,
}),
])
)
})

View File

@@ -3,8 +3,8 @@ const {
Region,
LineItem,
GiftCard,
RMAShippingOption,
Cart,
CustomShippingOption,
} = require("@medusajs/medusa")
const setupServer = require("../../../helpers/setup-server")
@@ -145,7 +145,6 @@ describe("/store/carts", () => {
discounts: [{ code: "CREATED" }],
})
} catch (error) {
console.log(error.response)
expect(error.response.status).toEqual(400)
expect(error.response.data.message).toEqual(
"Discount has been used maximum allowed times"
@@ -440,29 +439,41 @@ describe("/store/carts", () => {
})
describe("POST /store/carts/:id/shipping-methods", () => {
let cartWithCustomSo
beforeEach(async () => {
await cartSeeder(dbConnection)
const manager = dbConnection.manager
try {
await cartSeeder(dbConnection)
const manager = dbConnection.manager
await manager.insert(Cart, {
id: "test-cart-rma",
customer_id: "some-customer",
email: "some-customer@email.com",
shipping_address: {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
},
region_id: "test-region",
currency_code: "usd",
type: "swap",
})
const _cart = await manager.create(Cart, {
id: "test-cart-with-cso",
customer_id: "some-customer",
email: "some-customer@email.com",
shipping_address: {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
},
custom_shipping_options: [
{
shipping_option_id: "test-option",
price: 5,
},
],
region_id: "test-region",
currency_code: "usd",
type: "swap",
})
await manager.insert(RMAShippingOption, {
id: "test-rmaso",
shipping_option_id: "test-option",
price: 5,
})
cartWithCustomSo = await manager.save(_cart)
await manager.insert(CustomShippingOption, {
id: "orphan-cso",
price: 0,
})
} catch (err) {
console.log(err)
}
})
afterEach(async () => {
@@ -486,25 +497,46 @@ describe("/store/carts", () => {
expect(cartWithShippingMethod.status).toEqual(200)
})
it("adds a rma shipping method to cart", async () => {
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 () => {
const customOptionId = cartWithCustomSo.custom_shipping_options[0].id
const api = useApi()
const cartWithRMAShippingMethod = await api
const cartWithCustomShippingMethod = await api
.post(
"/store/carts/test-cart-rma/shipping-methods",
"/store/carts/test-cart-with-cso/shipping-methods",
{
option_id: "test-rmaso",
option_id: customOptionId,
},
{ withCredentials: true }
)
.catch((err) => err.response)
expect(
cartWithRMAShippingMethod.data.cart.shipping_methods
cartWithCustomShippingMethod.data.cart.shipping_methods
).toContainEqual(
expect.objectContaining({ shipping_option_id: "test-option", price: 5 })
)
expect(cartWithRMAShippingMethod.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 () => {
const api = useApi()
try {
await api.post(
"/store/carts/test-cart-with-cso/shipping-methods",
{
option_id: "orphan-cso",
},
{ withCredentials: true }
)
} catch (err) {
expect(err.response.status).toEqual(404)
expect(err.response.data.message).toEqual(
"Shipping Option with orphan-cso was not found"
)
}
})
it("adds a giftcard to cart, but ensures discount only applied to discountable items", async () => {

View File

@@ -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 RMA shipping options", async () => {
it("given a swap cart, when user retrieves its shipping options, then should return a list of custom shipping options", async () => {
const api = useApi()
const response = await api

View File

@@ -101,7 +101,7 @@ module.exports = async (connection, data = {}) => {
await manager.save(swap)
const rmaCart = manager.create(Cart, {
const cartWithCustomSo = manager.create(Cart, {
id: "test-cart-rma",
customer_id: "test-customer",
email: "test-customer@email.com",
@@ -109,20 +109,26 @@ module.exports = async (connection, data = {}) => {
billing_address_id: "test-billing-address",
region_id: "test-region",
type: "swap",
custom_shipping_options: [
{
shipping_option_id: "test-option",
price: 0,
},
],
metadata: {
swap_id: "test-swap",
parent_order_id: orderWithSwap.id,
},
})
await manager.save(rmaCart)
await manager.save(cartWithCustomSo)
const swapWithRMAMethod = manager.create(Swap, {
id: "test-swap-rma",
order_id: "order-with-swap",
payment_status: "captured",
fulfillment_status: "fulfilled",
cart_id: "test-cart-rma",
cart_id: cartWithCustomSo.id,
payment: {
id: "test-payment-swap",
amount: 10000,
@@ -144,12 +150,6 @@ module.exports = async (connection, data = {}) => {
cart_id: "test-cart",
},
],
rma_shipping_options: [
{
shipping_option_id: "test-option",
price: 0,
},
],
})
await manager.save(swapWithRMAMethod)

View File

@@ -8,15 +8,15 @@
"build": "babel src -d dist --extensions \".ts,.js\""
},
"dependencies": {
"@medusajs/medusa": "1.1.41-dev-1633030366783",
"medusa-interfaces": "1.1.23-dev-1633030366783",
"@medusajs/medusa": "1.1.41-dev-1633520747607",
"medusa-interfaces": "1.1.23-dev-1633520747607",
"typeorm": "^0.2.31"
},
"devDependencies": {
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.12.10",
"babel-preset-medusa-package": "1.1.15-dev-1633030366783",
"babel-preset-medusa-package": "1.1.15-dev-1633520747607",
"jest": "^26.6.3"
}
}

View File

@@ -1223,10 +1223,10 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@medusajs/medusa-cli@1.1.18-dev-1633030366783":
version "1.1.18-dev-1633030366783"
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.18-dev-1633030366783.tgz#1112165ea9c03c797cd8d01f1149da989b9bea55"
integrity sha512-EIDxOs9STvFWwXv+8VWetrzABkijGOzcbKJzJ1jeDYjl61uRnELoWg57aeWj6wzKcTjhYRRKDrivMRUiexkHsA==
"@medusajs/medusa-cli@1.1.18-dev-1633520747607":
version "1.1.18-dev-1633520747607"
resolved "http://localhost:4873/@medusajs%2fmedusa-cli/-/medusa-cli-1.1.18-dev-1633520747607.tgz#46a3f73f6aff016f50a8123f47d69c64b7a52037"
integrity sha512-4KLnR6gq8R3sCAhOcYCI5SE+G9vyt2l6RyImvvPw7I03iDiNMc3URwoq2cFwV+V4dSrW59WFIlMJR7ALCTygdA==
dependencies:
"@babel/polyfill" "^7.8.7"
"@babel/runtime" "^7.9.6"
@@ -1244,8 +1244,8 @@
is-valid-path "^0.1.1"
joi-objectid "^3.0.1"
meant "^1.0.1"
medusa-core-utils "1.1.22-dev-1633030366783"
medusa-telemetry "0.0.5-dev-1633030366783"
medusa-core-utils "1.1.22-dev-1633520747607"
medusa-telemetry "0.0.5-dev-1633520747607"
netrc-parser "^3.1.6"
open "^8.0.6"
ora "^5.4.1"
@@ -1259,13 +1259,13 @@
winston "^3.3.3"
yargs "^15.3.1"
"@medusajs/medusa@1.1.41-dev-1633030366783":
version "1.1.41-dev-1633030366783"
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.41-dev-1633030366783.tgz#0beb57e9844c3b85c59bfcde86183a2a625e3a91"
integrity sha512-b1QGscpszVYhDLOea7WhbW0DCgEKBKchHP1nNLj07mWTxE5vOBuAZeUSh1FHNw0jbPhtn9mOaFz2wwIgxPcfqw==
"@medusajs/medusa@1.1.41-dev-1633520747607":
version "1.1.41-dev-1633520747607"
resolved "http://localhost:4873/@medusajs%2fmedusa/-/medusa-1.1.41-dev-1633520747607.tgz#82efd5b0bbaaaac76ce740dc9b35fe47c780592f"
integrity sha512-D+1WVpMWDaVki+Ti3rXYAl+3rb3eaX16BZ1RU/bG7FbhFJt8P1a1LXwMMQLV2rG/mUDgs6kDyl2qhYTzZ7X7aA==
dependencies:
"@hapi/joi" "^16.1.8"
"@medusajs/medusa-cli" "1.1.18-dev-1633030366783"
"@medusajs/medusa-cli" "1.1.18-dev-1633520747607"
"@types/lodash" "^4.14.168"
awilix "^4.2.3"
body-parser "^1.19.0"
@@ -1287,8 +1287,8 @@
joi "^17.3.0"
joi-objectid "^3.0.1"
jsonwebtoken "^8.5.1"
medusa-core-utils "1.1.22-dev-1633030366783"
medusa-test-utils "1.1.25-dev-1633030366783"
medusa-core-utils "1.1.22-dev-1633520747607"
medusa-test-utils "1.1.25-dev-1633520747607"
morgan "^1.9.1"
multer "^1.4.2"
passport "^0.4.0"
@@ -1933,10 +1933,10 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
babel-preset-medusa-package@1.1.15-dev-1633030366783:
version "1.1.15-dev-1633030366783"
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.15-dev-1633030366783.tgz#00f8aa5ebcb98c9a161a2c12dad55ffac53df88c"
integrity sha512-A/qfZNpIcYFMjAHYqVvavP9uY7ODtKGaQwZyx9quFydxebLSUstHTJ5s9ES1XjqhRYpEy//ixwNBkzggig0F9w==
babel-preset-medusa-package@1.1.15-dev-1633520747607:
version "1.1.15-dev-1633520747607"
resolved "http://localhost:4873/babel-preset-medusa-package/-/babel-preset-medusa-package-1.1.15-dev-1633520747607.tgz#ac970d565ac9803333bb5fecc7d16c162302c97b"
integrity sha512-XpU4J3xmdk8y2QRrxXFbkCq+9OzqUNeZlPAffiGlFTWYx0VRD4tZS4DWazrA5ub3RfqMFl/cxZA6NBcbJhZ/iQ==
dependencies:
"@babel/plugin-proposal-class-properties" "^7.12.1"
"@babel/plugin-proposal-decorators" "^7.12.1"
@@ -5110,25 +5110,25 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
medusa-core-utils@1.1.22-dev-1633030366783:
version "1.1.22-dev-1633030366783"
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.22-dev-1633030366783.tgz#988358df8d8c7350d4fea3c3c00ae9f2e4aaa434"
integrity sha512-WYJ141mu6aYFGO9Vs9lXx+mJWXogrASxWJOIT29bZIFuaqI/LuiZysFn+Y6tzGBHLRdMKwHE+u/Zg1vh1NGfrw==
medusa-core-utils@1.1.22-dev-1633520747607:
version "1.1.22-dev-1633520747607"
resolved "http://localhost:4873/medusa-core-utils/-/medusa-core-utils-1.1.22-dev-1633520747607.tgz#d18c79e4ba9ee8b373bfc02024e23763f079ab87"
integrity sha512-pP1FdbrXbHqRxzMh2nKX6ByxtCrNSjJzOIJ45k1/Kl41VortD9EYiOpuLGOdodkD1jbuf6aV4x0zNIoWpGyHew==
dependencies:
joi "^17.3.0"
joi-objectid "^3.0.1"
medusa-interfaces@1.1.23-dev-1633030366783:
version "1.1.23-dev-1633030366783"
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.23-dev-1633030366783.tgz#d3cb82493e2bb53ff728e369d38183d964ac2cc9"
integrity sha512-7X3KnKUJHBye7ikmLouvYQCCINHb2DpBy9cOQKpUSwsaKlUMIvxJq2T+AieuwWhiTACwAwTLiWshSILUdbvKcQ==
medusa-interfaces@1.1.23-dev-1633520747607:
version "1.1.23-dev-1633520747607"
resolved "http://localhost:4873/medusa-interfaces/-/medusa-interfaces-1.1.23-dev-1633520747607.tgz#9487b2979cee9784b219a2117cf7199fbfadceb7"
integrity sha512-jyoc0wemXrZBtJMA6e7FcdnB/7do7j6oyLevCon7A6HibqQkuxLZ7TfWLHGROj4qy8aP8sAvtuXq9gpwkIm3CQ==
dependencies:
medusa-core-utils "1.1.22-dev-1633030366783"
medusa-core-utils "1.1.22-dev-1633520747607"
medusa-telemetry@0.0.5-dev-1633030366783:
version "0.0.5-dev-1633030366783"
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.5-dev-1633030366783.tgz#ee9368da672a5f46d323a98b521d49983f6f1e9d"
integrity sha512-BU1XyCWS2iX5lqfxcCRAxOCrtY55eLyH1XoLLPoO8dWKIDn3G0uh4N/WKBPn40cVLbeTRqOoVRYQP6jnuEjn6w==
medusa-telemetry@0.0.5-dev-1633520747607:
version "0.0.5-dev-1633520747607"
resolved "http://localhost:4873/medusa-telemetry/-/medusa-telemetry-0.0.5-dev-1633520747607.tgz#c8d23d5acf744eea8c88102a792b481051f84fb9"
integrity sha512-YX2hKGIANC0Ha9QKkMz5se9wDLJ940VEXwlMMGZzIRHoCoNpmxnsgXQXgeCtC1LKmlcaOaOzMqYyk19fgVCybQ==
dependencies:
axios "^0.21.1"
axios-retry "^3.1.9"
@@ -5140,13 +5140,13 @@ medusa-telemetry@0.0.5-dev-1633030366783:
remove-trailing-slash "^0.1.1"
uuid "^8.3.2"
medusa-test-utils@1.1.25-dev-1633030366783:
version "1.1.25-dev-1633030366783"
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.25-dev-1633030366783.tgz#97b8235e4fbfd5cf16ed91444df1af44d4c45a60"
integrity sha512-9cztZpuTMbn++Zg/06+vlOnAFa7jUAFf7o7i7kXQGXInlJngS0Tw9pq8H0vQ4vz7QRdiOiFPXb11gymLUT64uA==
medusa-test-utils@1.1.25-dev-1633520747607:
version "1.1.25-dev-1633520747607"
resolved "http://localhost:4873/medusa-test-utils/-/medusa-test-utils-1.1.25-dev-1633520747607.tgz#7c283312d241e012ce9814841ab0fcb0e141245e"
integrity sha512-rW4GRGXlKBoqHeX3AvPWpV9R6AWe5CXEhDcTxxPNk/Nog8E8zBlWDHsff1057RagVIMjd4wkFx4Kz3mgUMgQAg==
dependencies:
"@babel/plugin-transform-classes" "^7.9.5"
medusa-core-utils "1.1.22-dev-1633030366783"
medusa-core-utils "1.1.22-dev-1633520747607"
randomatic "^3.1.1"
merge-descriptors@1.0.1:

View File

@@ -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
@@ -81,18 +92,16 @@ export default async (req, res) => {
.optional(),
})
.optional(),
rma_shipping_options: Validator.array()
.items({
option_id: Validator.string().optional(),
price: Validator.number()
.integer()
.optional(),
})
.default([]),
additional_items: Validator.array().items({
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),
})
@@ -150,7 +159,6 @@ 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,
@@ -158,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)

View File

@@ -23,9 +23,9 @@ describe("POST /store/carts/:id/shipping-methods", () => {
jest.clearAllMocks()
})
it("calls CartService addRMAMethod", () => {
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledWith(
it("calls CartService addShippingMethod", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
IdMap.getId("freeShipping"),
{}
@@ -45,7 +45,7 @@ describe("POST /store/carts/:id/shipping-methods", () => {
})
})
describe("successfully adds a RMA shipping method", () => {
describe("successfully adds a shipping method", () => {
let subject
beforeAll(async () => {
@@ -65,9 +65,9 @@ describe("POST /store/carts/:id/shipping-methods", () => {
jest.clearAllMocks()
})
it("calls CartService addRMAMethod", () => {
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledWith(
it("calls CartService addShippingMethod", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("swap-cart"),
IdMap.getId("freeShipping"),
{}
@@ -112,9 +112,9 @@ describe("POST /store/carts/:id/shipping-methods", () => {
jest.clearAllMocks()
})
it("calls CartService addRMAMethod", () => {
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addRMAMethod).toHaveBeenCalledWith(
it("calls CartService addShippingMethod", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
IdMap.getId("freeShipping"),
{

View File

@@ -45,7 +45,7 @@ export default async (req, res) => {
await manager.transaction(async m => {
const txCartService = cartService.withTransaction(m)
await txCartService.addRMAMethod(id, value.option_id, value.data)
await txCartService.addShippingMethod(id, value.option_id, value.data)
const updated = await txCartService.retrieve(id, {
relations: ["payment_sessions"],

View File

@@ -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 when cart type is not swap and not claim", () => {
describe("retrieves shipping options", () => {
let subject
beforeAll(async () => {
@@ -29,6 +29,7 @@ describe("GET /store/shipping-options", () => {
"items",
"items.variant",
"items.variant.product",
"custom_shipping_options",
],
}
)
@@ -53,54 +54,4 @@ 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")
)
})
})
})

View File

@@ -37,15 +37,16 @@ export default async (req, res) => {
const cart = await cartService.retrieve(value.cart_id, {
select: ["subtotal"],
relations: ["region", "items", "items.variant", "items.variant.product"],
relations: [
"region",
"items",
"items.variant",
"items.variant.product",
"custom_shipping_options",
],
})
let options
if (cart.type === "swap" || cart.type === "claim") {
options = await shippingProfileService.fetchRMAOptions(cart)
} else {
options = await shippingProfileService.fetchCartOptions(cart)
}
const options = await shippingProfileService.fetchCartOptions(cart)
res.status(200).json({ shipping_options: options })
} catch (err) {

View File

@@ -130,7 +130,6 @@ export default async (req, res) => {
value.return_items,
value.additional_items,
returnShipping,
[],
{
idempotency_key: idempotencyKey.idempotency_key,
no_notification: true,

View File

@@ -46,4 +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 { RMAShippingOption } from "./models/rma-shipping-option"
export { CustomShippingOption } from "./models/custom-shipping-option"

View File

@@ -1,38 +0,0 @@
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"`);
}
}

View File

@@ -0,0 +1,34 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class addCustomShippingOptions1633522106578 implements MigrationInterface {
name = 'addCustomShippingOptions1633522106578'
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 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"`);
}
}

View File

@@ -44,6 +44,8 @@
* $ref: "#/components/schemas/payment_session"
* payment:
* $ref: "#/components/schemas/payment"
* custom_shipping_options:
* $ref: "#/components/schemas/custom_shipping_option"
* shipping_methods:
* type: array
* items:
@@ -113,6 +115,7 @@ 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",
@@ -218,6 +221,13 @@ export class Cart {
@JoinColumn({ name: "payment_id" })
payment: Payment
@OneToMany(
() => CustomShippingOption,
method => method.cart,
{ cascade: ["insert"] }
)
custom_shipping_options: CustomShippingOption[]
@OneToMany(
() => ShippingMethod,
method => method.cart,

View File

@@ -1,4 +1,3 @@
import { RMAShippingOption } from './rma-shipping-option';
import {
Entity,
BeforeInsert,
@@ -114,13 +113,6 @@ export class ClaimOrder {
)
shipping_methods: ShippingMethod[]
@OneToMany(
() => RMAShippingOption,
method => method.swap,
{ cascade: ["insert"] }
)
rma_shipping_options: RMAShippingOption[]
@OneToMany(
() => Fulfillment,
fulfillment => fulfillment.claim_order,

View File

@@ -0,0 +1,97 @@
import {
BeforeInsert, Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Index,
JoinColumn,
ManyToOne,
PrimaryColumn,
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()
export class CustomShippingOption {
@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 })
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
*/

View File

@@ -1,133 +0,0 @@
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
*/

View File

@@ -1,4 +1,3 @@
import { RMAShippingOption } from './rma-shipping-option';
import {
Entity,
Index,
@@ -114,13 +113,6 @@ export class Swap {
)
shipping_methods: ShippingMethod[]
@OneToMany(
() => RMAShippingOption,
method => method.swap,
{ cascade: ["insert"] }
)
rma_shipping_options: RMAShippingOption[]
@Column({ nullable: true })
cart_id: string

View File

@@ -1,5 +0,0 @@
import { EntityRepository, Repository } from "typeorm"
import { RMAShippingOption } from './../models/rma-shipping-option';
@EntityRepository(RMAShippingOption)
export class RMAShippingOptionRepository extends Repository<RMAShippingOption> {}

View File

@@ -342,9 +342,6 @@ 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 {

View File

@@ -134,9 +134,6 @@ 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") }])
}),

View File

@@ -1308,6 +1308,68 @@ describe("CartService", () => {
})
})
describe("extractShippingOptionIdAndPrice", () => {
beforeEach(() => {
jest.clearAllMocks()
})
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 () => {
const cart = {
id: "cart-with-so",
custom_shipping_options: [
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
],
}
const result = cartService.extractShippingOptionIdAndPrice(
cart,
"cso-test"
)
expect(result).toEqual({
optionId: "test-so",
customPrice: { 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 () => {
const cart = {
id: "cart-with-so",
custom_shipping_options: [
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
],
}
const result = cartService.extractShippingOptionIdAndPrice(
cart,
"test-so"
)
expect(result).toEqual({
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 () => {
const cart = {
id: "cart-with-so",
custom_shipping_options: [
{ id: "cso-test", shipping_option_id: "test-so", price: 20 },
],
}
const result = cartService.extractShippingOptionIdAndPrice(
cart,
"cso-test-2"
)
expect(result).toEqual({
optionId: "cso-test-2",
customPrice: {},
})
})
})
describe("addShippingMethod", () => {
const buildCart = (id, config = {}) => {
return {
@@ -1326,6 +1388,13 @@ describe("CartService", () => {
profile_id: IdMap.getId(m.profile),
},
})),
custom_shipping_options: (config.custom_shipping_options || []).map(
cso => ({
...cso,
id: IdMap.getId(cso.id),
shipping_option_id: IdMap.getId(cso.shipping_option_id),
})
),
discounts: [],
}
}
@@ -1337,6 +1406,11 @@ describe("CartService", () => {
const cart3 = buildCart("lines", {
items: [{ id: "line", profile: "profile1" }],
})
const cartWithCustomSO = buildCart("cart-with-custom-so", {
custom_shipping_options: [
{ id: "cso-test", shipping_option_id: "test-so" },
],
})
const cartRepository = MockRepository({
findOneWithRelations: (rels, q) => {
@@ -1345,6 +1419,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)
}
@@ -1468,74 +1544,37 @@ describe("CartService", () => {
has_shipping: true,
})
})
})
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 () => {
it("adds a shipping method from a custom shipping option and custom price", 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,
cartService.extractShippingOptionIdAndPrice = jest
.fn()
.mockImplementation((cart, optionId) => {
if (cart.id === IdMap.getId("cart-with-custom-so")) {
return {
optionId: IdMap.getId("test-so"),
customPrice: { price: 0 },
}
}
return { optionId, customPrice: {} }
})
await cartService.addShippingMethod(
IdMap.getId("cart-with-custom-so"),
IdMap.getId("cso-test"),
data
)
expect(shippingOptionService.createShippingMethod).toHaveBeenCalledWith(
IdMap.getId("test-so"),
data,
{
cart: cartWithCustomSO,
price: 0,
}
)
})
})

View File

@@ -202,7 +202,23 @@ describe("ShippingProfileService", () => {
jest.clearAllMocks()
})
it("fetches correct options", async () => {
it("given a swap cart with custom shipping options, should return correct custom shipping options ", async () => {
const cart = {
id: "swap-cart",
type: "swap",
custom_shipping_options: [
{ option_id: "test-option1", id: "cso-option1", price: 10 },
{ option_id: "test-option2", id: "cso-option2", price: 0 },
],
}
await expect(profileService.fetchCartOptions(cart)).resolves.toEqual([
expect.objectContaining({ id: "cso-option1" }),
expect.objectContaining({ id: "cso-option2" }),
])
})
it("given correct options when cart has no custom shipping options, should return normal shipping options", async () => {
const cart = {
items: [
{
@@ -241,104 +257,6 @@ 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({}) })

View File

@@ -188,7 +188,9 @@ describe("SwapService", () => {
})
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(
@@ -214,6 +216,9 @@ describe("SwapService", () => {
discounts: testOrder.discounts,
region_id: testOrder.region_id,
customer_id: testOrder.customer_id,
custom_shipping_options: [
{ shipping_option_id: "test-option", price: 10 },
],
type: "swap",
metadata: {
swap_id: IdMap.getId("test-swap"),
@@ -344,8 +349,7 @@ describe("SwapService", () => {
{
id: IdMap.getId("return-shipping"),
price: 20,
},
[{ option_id: IdMap.getId("rmaso-option1"), price: 0 }]
}
)
expect(swapRepo.create).toHaveBeenCalledWith({
@@ -360,9 +364,6 @@ describe("SwapService", () => {
quantity: 1,
},
],
rma_shipping_options: [
{ shipping_option_id: IdMap.getId("rmaso-option1"), price: 0 },
],
})
expect(returnService.create).toHaveBeenCalledTimes(1)
@@ -383,7 +384,6 @@ describe("SwapService", () => {
id: IdMap.getId("return-shipping"),
price: 20,
},
[],
{ no_notification: input }
)

View File

@@ -31,8 +31,6 @@ class CartService extends BaseService {
addressRepository,
paymentSessionRepository,
inventoryService,
RMAShippingOptionRepository,
swapRepository,
}) {
super()
@@ -86,9 +84,6 @@ class CartService extends BaseService {
/** @private @const {InventoryService} */
this.inventoryService_ = inventoryService
/** @private @const {RMAShippingOptionRepository} */
this.rmaShippingOptionRepository_ = RMAShippingOptionRepository
}
withTransaction(transactionManager) {
@@ -114,7 +109,6 @@ class CartService extends BaseService {
addressRepository: this.addressRepository_,
giftCardService: this.giftCardService_,
inventoryService: this.inventoryService_,
RMAShippingOptionRepository: this.rmaShippingOptionRepository_,
})
cloned.transactionManager_ = transactionManager
@@ -1311,7 +1305,7 @@ class CartService extends BaseService {
* @param {Object} data - the fulmillment data for the method
* @return {Promise} the result of the update operation
*/
async addShippingMethod(cartId, optionId, data) {
async addShippingMethod(cartId, optionIdOrCustomOptionId, data) {
return this.atomicPhase_(async manager => {
const cart = await this.retrieve(cartId, {
select: ["subtotal"],
@@ -1323,11 +1317,17 @@ class CartService extends BaseService {
"items.variant",
"payment_sessions",
"items.variant.product",
"custom_shipping_options",
],
})
let { optionId, customPrice } = this.extractShippingOptionIdAndPrice(
cart,
optionIdOrCustomOptionId
)
const { shipping_methods } = cart
const customPrice = data && data.price ? { price: data.price } : {}
const newMethod = await this.shippingOptionService_
.withTransaction(manager)
.createShippingMethod(optionId, data, {
@@ -1374,32 +1374,32 @@ class CartService extends BaseService {
}
/**
* Adds the corresponding shipping method either from a normal or rma shipping option to the list of shipping methods associated with
* Adds the corresponding shipping method either from a normal or custom 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
* @param {Object} cart - the cart object
* @param {string} optionIdOrCustomOptionId - id of the normal or custom shipping option to add as valid method
* @returns {{ optionId: string; customPrice: { price: number; } | {};}}
*/
async addRMAMethod(cartId, optionIdOrRmaOptionId, data) {
return this.atomicPhase_(async manager => {
const rmaShippingOptionRepo = manager.getCustomRepository(
this.rmaShippingOptionRepository_
extractShippingOptionIdAndPrice(cart, optionIdOrCustomOptionId) {
if (
cart.custom_shipping_options &&
cart.custom_shipping_options.length > 0
) {
const customOption = cart.custom_shipping_options.find(
cso => cso.id === optionIdOrCustomOptionId
)
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)
if (customOption) {
return {
optionId: customOption.shipping_option_id,
customPrice: { price: customOption.price },
}
}
})
}
return {
optionId: optionIdOrCustomOptionId,
customPrice: {},
}
}
/**

View File

@@ -14,7 +14,6 @@ class ShippingProfileService extends BaseService {
productService,
productRepository,
shippingOptionService,
swapRepository,
}) {
super()
@@ -32,9 +31,6 @@ class ShippingProfileService extends BaseService {
/** @private @const {ShippingOptionService} */
this.shippingOptionService_ = shippingOptionService
/** @private @const {SwapRepository} */
this.swapRepository_ = swapRepository
}
withTransaction(transactionManager) {
@@ -47,7 +43,6 @@ class ShippingProfileService extends BaseService {
shippingProfileRepository: this.shippingProfileRepository_,
productService: this.productService_,
shippingOptionService: this.shippingOptionService_,
swapRepository: this.swapRepository_,
})
cloned.transactionManager_ = transactionManager
@@ -418,6 +413,11 @@ class ShippingProfileService extends BaseService {
* @return {[ShippingOptions]} a list of the available shipping options
*/
async fetchCartOptions(cart) {
const customShippingOptions = cart.custom_shipping_options
if (customShippingOptions && customShippingOptions.length > 0)
return customShippingOptions
const profileIds = this.getProfilesInCart_(cart)
const rawOpts = await this.shippingOptionService_.list(
@@ -441,34 +441,6 @@ 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 === "default") {
throw new MedusaError(MedusaError.Types.INVALID_DATA, "error")
}
const swapRepo = await this.manager_.getCustomRepository(
this.swapRepository_
)
if (cart.type === "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

View File

@@ -288,7 +288,6 @@ 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.
@@ -299,7 +298,6 @@ class SwapService extends BaseService {
returnItems,
additionalItems,
returnShipping,
rmaShippingOptions = [],
custom = {
no_notification: undefined,
}
@@ -343,11 +341,6 @@ 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
@@ -359,7 +352,6 @@ class SwapService extends BaseService {
order_id: order.id,
additional_items: newItems,
no_notification: evaluatedNoNotification,
rma_shipping_options,
})
const result = await swapRepo.save(created)
@@ -531,7 +523,7 @@ 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) {
async createCart(swapId, customShippingOptions = []) {
return this.atomicPhase_(async manager => {
const swap = await this.retrieve(swapId, {
relations: [
@@ -576,6 +568,10 @@ class SwapService extends BaseService {
region_id: order.region_id,
customer_id: order.customer_id,
type: "swap",
custom_shipping_options: customShippingOptions.map(so => ({
price: so.price,
shipping_option_id: so.option_id,
})),
metadata: {
swap_id: swap.id,
parent_order_id: order.id,