Creates prices modifiers in productVariant
This commit is contained in:
@@ -55,6 +55,56 @@ export const ProductVariantModelMock = {
|
||||
manage_inventory: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (query._id === IdMap.getId("no-prices")) {
|
||||
return Promise.resolve({
|
||||
_id: IdMap.getId("no-prices"),
|
||||
title: "No Prices",
|
||||
prices: [],
|
||||
})
|
||||
}
|
||||
|
||||
if (query._id === IdMap.getId("eur-prices")) {
|
||||
return Promise.resolve({
|
||||
_id: IdMap.getId("eur-prices"),
|
||||
title: "eur Prices",
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
region_id: IdMap.getId("region-france"),
|
||||
currency_code: "eur",
|
||||
amount: 950,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
if (query._id === IdMap.getId("france-prices")) {
|
||||
return Promise.resolve({
|
||||
_id: IdMap.getId("france-prices"),
|
||||
title: "France Prices",
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
region_id: IdMap.getId("region-france"),
|
||||
currency_code: "eur",
|
||||
amount: 950,
|
||||
},
|
||||
{
|
||||
region_id: IdMap.getId("region-us"),
|
||||
currency_code: "usd",
|
||||
amount: 1200,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import mongoose from "mongoose"
|
||||
|
||||
export default new mongoose.Schema({
|
||||
region_id: { type: String },
|
||||
currency_code: { type: String, required: true },
|
||||
amount: { type: Number, required: true, min: 0 },
|
||||
})
|
||||
|
||||
@@ -10,6 +10,18 @@ export const regions = {
|
||||
shipping_providers: ["test_shipper"],
|
||||
currency_code: "usd",
|
||||
},
|
||||
regionFrance: {
|
||||
_id: IdMap.getId("region-france"),
|
||||
name: "France",
|
||||
countries: ["FR"],
|
||||
currency_code: "eur",
|
||||
},
|
||||
regionUs: {
|
||||
_id: IdMap.getId("region-us"),
|
||||
name: "USA",
|
||||
countries: ["US"],
|
||||
currency_code: "usd",
|
||||
},
|
||||
}
|
||||
|
||||
export const RegionServiceMock = {
|
||||
@@ -17,10 +29,20 @@ export const RegionServiceMock = {
|
||||
if (regionId === IdMap.getId("testRegion")) {
|
||||
return Promise.resolve(regions.testRegion)
|
||||
}
|
||||
if (regionId === IdMap.getId("region-france")) {
|
||||
return Promise.resolve(regions.regionFrance)
|
||||
}
|
||||
if (regionId === IdMap.getId("region-us")) {
|
||||
return Promise.resolve(regions.regionUs)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
list: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve([regions.testRegion])
|
||||
return Promise.resolve([
|
||||
regions.testRegion,
|
||||
regions.regionFrance,
|
||||
regions.regionUs,
|
||||
])
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,53 @@ describe("CartService", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully defaults quantity of content to 1", async () => {
|
||||
const lineItem = {
|
||||
title: "New Line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: {
|
||||
unit_price: 123,
|
||||
variant: {
|
||||
_id: IdMap.getId("can-cover"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
},
|
||||
},
|
||||
quantity: 10,
|
||||
}
|
||||
|
||||
await cartService.addLineItem(IdMap.getId("emptyCart"), lineItem)
|
||||
|
||||
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("emptyCart"),
|
||||
},
|
||||
{
|
||||
$push: {
|
||||
items: {
|
||||
title: "New Line",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
content: {
|
||||
unit_price: 123,
|
||||
variant: {
|
||||
_id: IdMap.getId("can-cover"),
|
||||
},
|
||||
product: {
|
||||
_id: IdMap.getId("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
quantity: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully merges existing line item", async () => {
|
||||
const lineItem = {
|
||||
title: "merge line",
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IdMap } from "medusa-test-utils"
|
||||
import ProductVariantService from "../product-variant"
|
||||
import { ProductVariantModelMock } from "../../models/__mocks__/product-variant"
|
||||
import { ProductServiceMock } from "../__mocks__/product"
|
||||
import { RegionServiceMock } from "../__mocks__/region"
|
||||
|
||||
describe("ProductVariantService", () => {
|
||||
describe("retrieve", () => {
|
||||
@@ -459,4 +460,203 @@ describe("ProductVariantService", () => {
|
||||
expect(res).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("setCurrencyPrice", () => {
|
||||
const productVariantService = new ProductVariantService({
|
||||
productVariantModel: ProductVariantModelMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("creates a prices array if none exist", async () => {
|
||||
await productVariantService.setCurrencyPrice(
|
||||
IdMap.getId("no-prices"),
|
||||
"usd",
|
||||
100
|
||||
)
|
||||
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("no-prices"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("updates all eur prices", async () => {
|
||||
await productVariantService.setCurrencyPrice(
|
||||
IdMap.getId("eur-prices"),
|
||||
"eur",
|
||||
100
|
||||
)
|
||||
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("eur-prices"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
region_id: IdMap.getId("region-france"),
|
||||
currency_code: "eur",
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("creates usd prices", async () => {
|
||||
await productVariantService.setCurrencyPrice(
|
||||
IdMap.getId("eur-prices"),
|
||||
"usd",
|
||||
300
|
||||
)
|
||||
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("eur-prices"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
region_id: IdMap.getId("region-france"),
|
||||
currency_code: "eur",
|
||||
amount: 950,
|
||||
},
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 300,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("setRegionPrice", () => {
|
||||
const productVariantService = new ProductVariantService({
|
||||
productVariantModel: ProductVariantModelMock,
|
||||
regionService: RegionServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("creates a prices array if none exist", async () => {
|
||||
await productVariantService.setCurrencyPrice(
|
||||
IdMap.getId("no-prices"),
|
||||
"usd",
|
||||
100
|
||||
)
|
||||
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("no-prices"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("updates all eur prices", async () => {
|
||||
await productVariantService.setCurrencyPrice(
|
||||
IdMap.getId("eur-prices"),
|
||||
"eur",
|
||||
100
|
||||
)
|
||||
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("eur-prices"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
region_id: IdMap.getId("region-france"),
|
||||
currency_code: "eur",
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("creates usd prices", async () => {
|
||||
await productVariantService.setCurrencyPrice(
|
||||
IdMap.getId("eur-prices"),
|
||||
"usd",
|
||||
300
|
||||
)
|
||||
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("eur-prices"),
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
region_id: IdMap.getId("region-france"),
|
||||
currency_code: "eur",
|
||||
amount: 950,
|
||||
},
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 300,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import mongoose from "mongoose"
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
@@ -63,7 +62,8 @@ class CartService extends BaseService {
|
||||
product: Validator.object().required(),
|
||||
quantity: Validator.number()
|
||||
.integer()
|
||||
.min(1),
|
||||
.min(1)
|
||||
.default(1),
|
||||
})
|
||||
|
||||
const lineItemSchema = Validator.object({
|
||||
@@ -246,7 +246,15 @@ class CartService extends BaseService {
|
||||
* @param {string} email - the email to add to cart
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
updateEmail(cartId, email) {
|
||||
async updateEmail(cartId, email) {
|
||||
const cart = await this.retrieve(cartId)
|
||||
if (!cart) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"The cart was not found"
|
||||
)
|
||||
}
|
||||
|
||||
const schema = Validator.string()
|
||||
.email()
|
||||
.required()
|
||||
@@ -268,7 +276,21 @@ class CartService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
updateBillingAddress(cartId, address) {
|
||||
/**
|
||||
* Updates the cart's billing address.
|
||||
* @param {string} cartId - the id of the cart to update
|
||||
* @param {object} address - the value to set the billing address to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateBillingAddress(cartId, address) {
|
||||
const cart = await this.retrieve(cartId)
|
||||
if (!cart) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"The cart was not found"
|
||||
)
|
||||
}
|
||||
|
||||
const { value, error } = Validator.address().validate(address)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
@@ -287,7 +309,21 @@ class CartService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
updateShippingAddress(cartId, address) {
|
||||
/**
|
||||
* Updates the cart's shipping address.
|
||||
* @param {string} cartId - the id of the cart to update
|
||||
* @param {object} address - the value to set the shipping address to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async updateShippingAddress(cartId, address) {
|
||||
const cart = await this.retrieve(cartId)
|
||||
if (!cart) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
"The cart was not found"
|
||||
)
|
||||
}
|
||||
|
||||
const { value, error } = Validator.address().validate(address)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
@@ -306,6 +342,26 @@ class CartService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set's the region of a cart.
|
||||
* @param {string} cartId - the id of the cart to set region on
|
||||
* @param {string} regionId - the id of the region to set the cart to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
setRegion(cartId, regionId) {
|
||||
// Check if cart exists
|
||||
// Check if region exists
|
||||
//
|
||||
// If the cart already has items, go through the items and update the prices
|
||||
// based on new tax rate, new currency, region specific pricing.
|
||||
//
|
||||
// If addresses are set, clear the country code.
|
||||
//
|
||||
// If the cart has a shipping method, clear this.
|
||||
//
|
||||
// Update the region
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated method to set metadata for a cart.
|
||||
* To ensure that plugins does not overwrite each
|
||||
|
||||
@@ -8,7 +8,12 @@ import { Validator, MedusaError } from "medusa-core-utils"
|
||||
*/
|
||||
class ProductVariantService extends BaseService {
|
||||
/** @param { productVariantModel: (ProductVariantModel) } */
|
||||
constructor({ productVariantModel, eventBusService, productService }) {
|
||||
constructor({
|
||||
productVariantModel,
|
||||
eventBusService,
|
||||
productService,
|
||||
regionService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
/** @private @const {ProductVariantModel} */
|
||||
@@ -19,6 +24,9 @@ class ProductVariantService extends BaseService {
|
||||
|
||||
/** @private @const {ProductService} */
|
||||
this.productService_ = productService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,6 +102,13 @@ class ProductVariantService extends BaseService {
|
||||
update(variantId, update) {
|
||||
const validatedId = this.validateId_(variantId)
|
||||
|
||||
if (update.prices) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Use setCurrencyPrices, setRegionPrices method to update prices field"
|
||||
)
|
||||
}
|
||||
|
||||
if (update.metadata) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -112,6 +127,155 @@ class ProductVariantService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default price for the given currency.
|
||||
* @param {string} variantId - the id of the variant to set prices for
|
||||
* @param {string} currencyCode - the currency to set prices for
|
||||
* @param {number} amount - the amount to set the price to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async setCurrencyPrice(variantId, currencyCode, amount) {
|
||||
const variant = await this.retrieve(variantId)
|
||||
if (!variant) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Variant: ${variantId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
// If prices already exist we need to update all prices with the same currency
|
||||
if (variant.prices.length) {
|
||||
let foundDefault = false
|
||||
const newPrices = variant.prices.map(moneyAmount => {
|
||||
if (moneyAmount.currency_code === currencyCode) {
|
||||
moneyAmount.amount = amount
|
||||
|
||||
if (!moneyAmount.region_id) {
|
||||
foundDefault = true
|
||||
}
|
||||
}
|
||||
|
||||
return moneyAmount
|
||||
})
|
||||
|
||||
// If there is no price entries for the currency we are updating we need
|
||||
// to push it
|
||||
if (!foundDefault) {
|
||||
newPrices.push({
|
||||
currency_code: currencyCode,
|
||||
amount,
|
||||
})
|
||||
}
|
||||
|
||||
return this.productVariantModel_.updateOne(
|
||||
{
|
||||
_id: variant._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: newPrices,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return this.productVariantModel_.updateOne(
|
||||
{
|
||||
_id: variant._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
currency_code: currencyCode,
|
||||
amount,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the price of a specific region
|
||||
* @param {string} variantId - the id of the variant to update
|
||||
* @param {string} regionId - the id of the region to set price for
|
||||
* @param {number} amount - the amount to set the price to
|
||||
* @return {Promise} the result of the update operation
|
||||
*/
|
||||
async setRegionPrice(variantId, regionId, amount) {
|
||||
const variant = await this.retrieve(variantId)
|
||||
if (!variant) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Variant: ${variantId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
const region = await this.regionService_.retrieve(regionId)
|
||||
if (!region) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Region: ${region} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
// If prices already exist we need to update all prices with the same currency
|
||||
if (variant.prices.length) {
|
||||
let foundRegion = false
|
||||
const newPrices = variant.prices.map(moneyAmount => {
|
||||
if (moneyAmount.region_id === region._id) {
|
||||
moneyAmount.amount = amount
|
||||
foundRegion = true
|
||||
}
|
||||
|
||||
return moneyAmount
|
||||
})
|
||||
|
||||
// If the region doesn't exist in the prices we need to push it
|
||||
if (!foundRegion) {
|
||||
newPrices.push({
|
||||
region_id: region._id,
|
||||
currency_code: region.currency_code,
|
||||
amount,
|
||||
})
|
||||
}
|
||||
|
||||
return this.productVariantModel_.updateOne(
|
||||
{
|
||||
_id: variant._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: newPrices,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Set the price both for default currency price and for the region
|
||||
return this.productVariantModel_.updateOne(
|
||||
{
|
||||
_id: variant._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
prices: [
|
||||
{
|
||||
region_id: region._id,
|
||||
currency_code: region.currency_code,
|
||||
amount,
|
||||
},
|
||||
{
|
||||
currency_code: region.currency_code,
|
||||
amount,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds option value to a varaint.
|
||||
* Fails when product with variant does not exists or
|
||||
|
||||
Reference in New Issue
Block a user