fix: variant price update (#1093)
* fix: variant prices update + integration tests * add: unit tests * fix: rename variable * add: integration tests * fix: integration tests * fix: test name * fix: move db logic to repo layer + create upsert method * fix: linting
This commit is contained in:
@@ -1,5 +1,58 @@
|
||||
import { EntityRepository, Repository } from "typeorm"
|
||||
import {
|
||||
Brackets,
|
||||
EntityRepository,
|
||||
In,
|
||||
IsNull,
|
||||
Not,
|
||||
Repository,
|
||||
} from "typeorm"
|
||||
import { MoneyAmount } from "../models/money-amount"
|
||||
|
||||
type Price = Partial<
|
||||
Pick<
|
||||
MoneyAmount,
|
||||
"currency_code" | "region_id" | "sale_amount" | "currency_code"
|
||||
>
|
||||
> & { amount: number }
|
||||
|
||||
@EntityRepository(MoneyAmount)
|
||||
export class MoneyAmountRepository extends Repository<MoneyAmount> { }
|
||||
export class MoneyAmountRepository extends Repository<MoneyAmount> {
|
||||
public async findVariantPricesNotIn(variantId: string, prices: Price[]) {
|
||||
const pricesNotInPricesPayload = await this.createQueryBuilder()
|
||||
.where({
|
||||
variant_id: variantId,
|
||||
})
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where({
|
||||
currency_code: Not(In(prices.map((p) => p.currency_code))),
|
||||
}).orWhere({ region_id: Not(In(prices.map((p) => p.region_id))) })
|
||||
})
|
||||
)
|
||||
.getMany()
|
||||
return pricesNotInPricesPayload
|
||||
}
|
||||
|
||||
public async upsertCurrencyPrice(variantId: string, price: Price) {
|
||||
let moneyAmount = await this.findOne({
|
||||
where: {
|
||||
currency_code: price.currency_code,
|
||||
variant_id: variantId,
|
||||
region_id: IsNull(),
|
||||
},
|
||||
})
|
||||
|
||||
if (!moneyAmount) {
|
||||
moneyAmount = this.create({
|
||||
...price,
|
||||
currency_code: price.currency_code?.toLowerCase(),
|
||||
variant_id: variantId,
|
||||
})
|
||||
} else {
|
||||
moneyAmount.amount = price.amount
|
||||
moneyAmount.sale_amount = price.sale_amount
|
||||
}
|
||||
|
||||
return await this.save(moneyAmount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import ProductVariantService from "../product-variant"
|
||||
|
||||
const eventBusService = {
|
||||
emit: jest.fn(),
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
@@ -253,7 +253,7 @@ describe("ProductVariantService", () => {
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
productVariantService.setCurrencyPrice = jest
|
||||
productVariantService.updateVariantPrices = jest
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
@@ -381,14 +381,16 @@ describe("ProductVariantService", () => {
|
||||
],
|
||||
})
|
||||
|
||||
expect(productVariantService.setCurrencyPrice).toHaveBeenCalledTimes(1)
|
||||
expect(productVariantService.setCurrencyPrice).toHaveBeenCalledWith(
|
||||
expect(productVariantService.updateVariantPrices).toHaveBeenCalledTimes(1)
|
||||
expect(productVariantService.updateVariantPrices).toHaveBeenCalledWith(
|
||||
IdMap.getId("ironman"),
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 1000,
|
||||
sale_amount: 750,
|
||||
}
|
||||
[
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 1000,
|
||||
sale_amount: 750,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
expect(productVariantRepository.save).toHaveBeenCalledTimes(1)
|
||||
@@ -416,23 +418,125 @@ describe("ProductVariantService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateVariantPrices", () => {
|
||||
const moneyAmountRepository = MockRepository({
|
||||
remove: () => Promise.resolve(),
|
||||
})
|
||||
const oldPrices = [
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 1000,
|
||||
variant_id: "ironman",
|
||||
region_id: null,
|
||||
},
|
||||
]
|
||||
|
||||
moneyAmountRepository.findVariantPricesNotIn = jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(oldPrices))
|
||||
|
||||
const productVariantRepository = MockRepository({
|
||||
findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }),
|
||||
})
|
||||
|
||||
const productOptionValueRepository = MockRepository({
|
||||
findOne: () =>
|
||||
Promise.resolve({ id: IdMap.getId("some-value"), value: "blue" }),
|
||||
})
|
||||
|
||||
const productVariantService = new ProductVariantService({
|
||||
manager: MockManager,
|
||||
eventBusService,
|
||||
moneyAmountRepository,
|
||||
productVariantRepository,
|
||||
productOptionValueRepository,
|
||||
})
|
||||
|
||||
productVariantService.updateOptionValue = jest
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
productVariantService.setCurrencyPrice = jest
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
productVariantService.setRegionPrice = jest
|
||||
.fn()
|
||||
.mockReturnValue(() => Promise.resolve())
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully removes obsolete prices and calls setCurrencyPrice on new/existing prices", async () => {
|
||||
await productVariantService.updateVariantPrices("ironman", [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 4000,
|
||||
},
|
||||
])
|
||||
|
||||
expect(
|
||||
moneyAmountRepository.findVariantPricesNotIn
|
||||
).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(productVariantService.setCurrencyPrice).toHaveBeenCalledTimes(1)
|
||||
expect(productVariantService.setCurrencyPrice).toHaveBeenCalledWith(
|
||||
"ironman",
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 4000,
|
||||
}
|
||||
)
|
||||
|
||||
expect(moneyAmountRepository.remove).toHaveBeenCalledTimes(1)
|
||||
expect(moneyAmountRepository.remove).toHaveBeenCalledWith(oldPrices)
|
||||
})
|
||||
|
||||
it("successfully removes obsolete prices and calls setRegionPrice on new/existing prices", async () => {
|
||||
await productVariantService.updateVariantPrices("ironman", [
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 4000,
|
||||
sale_amount: 2000,
|
||||
},
|
||||
])
|
||||
|
||||
expect(
|
||||
moneyAmountRepository.findVariantPricesNotIn
|
||||
).toHaveBeenCalledTimes(1)
|
||||
|
||||
expect(productVariantService.setRegionPrice).toHaveBeenCalledTimes(1)
|
||||
expect(productVariantService.setRegionPrice).toHaveBeenCalledWith(
|
||||
"ironman",
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 4000,
|
||||
sale_amount: 2000,
|
||||
}
|
||||
)
|
||||
|
||||
expect(moneyAmountRepository.remove).toHaveBeenCalledTimes(1)
|
||||
expect(moneyAmountRepository.remove).toHaveBeenCalledWith(oldPrices)
|
||||
})
|
||||
})
|
||||
|
||||
describe("setCurrencyPrice", () => {
|
||||
const productVariantRepository = MockRepository({
|
||||
findOne: (query) => Promise.resolve({ id: IdMap.getId("ironman") }),
|
||||
})
|
||||
|
||||
const moneyAmountRepository = MockRepository({
|
||||
findOne: (query) => {
|
||||
if (query.where.currency_code === "usd") {
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
const moneyAmountRepository = MockRepository()
|
||||
|
||||
moneyAmountRepository.upsertCurrencyPrice = jest
|
||||
.fn()
|
||||
.mockImplementation((variantId, price) => {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("dkk"),
|
||||
variant_id: IdMap.getId("ironman"),
|
||||
currency_code: "dkk",
|
||||
id: IdMap.getId("test-amount"),
|
||||
variant_id: IdMap.getId(variantId),
|
||||
...price,
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const productVariantService = new ProductVariantService({
|
||||
manager: MockManager,
|
||||
@@ -445,50 +549,32 @@ describe("ProductVariantService", () => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully creates a price if none exist with given currency", async () => {
|
||||
it("calls upsert price with given currency", async () => {
|
||||
await productVariantService.setCurrencyPrice(IdMap.getId("ironman"), {
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
})
|
||||
|
||||
expect(moneyAmountRepository.create).toHaveBeenCalledTimes(1)
|
||||
expect(moneyAmountRepository.create).toHaveBeenCalledWith({
|
||||
variant_id: IdMap.getId("ironman"),
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
})
|
||||
|
||||
expect(moneyAmountRepository.save).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("successfully updates a non-regional price if currency exists", async () => {
|
||||
await productVariantService.setCurrencyPrice(IdMap.getId("ironman"), {
|
||||
currency_code: "dkk",
|
||||
amount: 1000,
|
||||
})
|
||||
|
||||
expect(moneyAmountRepository.create).toHaveBeenCalledTimes(0)
|
||||
|
||||
expect(moneyAmountRepository.save).toHaveBeenCalledTimes(1)
|
||||
expect(moneyAmountRepository.save).toHaveBeenCalledWith({
|
||||
variant_id: IdMap.getId("ironman"),
|
||||
id: IdMap.getId("dkk"),
|
||||
currency_code: "dkk",
|
||||
amount: 1000,
|
||||
sale_amount: undefined,
|
||||
})
|
||||
expect(moneyAmountRepository.upsertCurrencyPrice).toHaveBeenCalledTimes(1)
|
||||
expect(moneyAmountRepository.upsertCurrencyPrice).toHaveBeenCalledWith(
|
||||
IdMap.getId("ironman"),
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getRegionPrice", () => {
|
||||
const regionService = {
|
||||
retrieve: function () {
|
||||
retrieve: function() {
|
||||
return Promise.resolve({
|
||||
id: IdMap.getId("california"),
|
||||
name: "California",
|
||||
})
|
||||
},
|
||||
withTransaction: function () {
|
||||
withTransaction: function() {
|
||||
return this
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
import {
|
||||
Brackets,
|
||||
EntityManager,
|
||||
ILike,
|
||||
IsNull,
|
||||
SelectQueryBuilder,
|
||||
} from "typeorm"
|
||||
import { Brackets, EntityManager, ILike, In, SelectQueryBuilder } from "typeorm"
|
||||
import { MoneyAmount } from "../models/money-amount"
|
||||
import { Product } from "../models/product"
|
||||
import { ProductOptionValue } from "../models/product-option-value"
|
||||
@@ -285,17 +279,7 @@ class ProductVariantService extends BaseService {
|
||||
const { prices, options, metadata, inventory_quantity, ...rest } = update
|
||||
|
||||
if (prices) {
|
||||
for (const price of prices) {
|
||||
if (price.region_id) {
|
||||
await this.setRegionPrice(variant.id, {
|
||||
region_id: price.region_id,
|
||||
amount: price.amount,
|
||||
sale_amount: price.sale_amount || undefined,
|
||||
})
|
||||
} else {
|
||||
await this.setCurrencyPrice(variant.id, price)
|
||||
}
|
||||
}
|
||||
await this.updateVariantPrices(variant.id, prices)
|
||||
}
|
||||
|
||||
if (options) {
|
||||
@@ -333,6 +317,37 @@ class ProductVariantService extends BaseService {
|
||||
})
|
||||
}
|
||||
|
||||
async updateVariantPrices(
|
||||
variantId: string,
|
||||
prices: ProductVariantPrice[]
|
||||
): Promise<void> {
|
||||
return this.atomicPhase_(async (manager: EntityManager) => {
|
||||
const moneyAmountRepo = manager.getCustomRepository(
|
||||
this.moneyAmountRepository_
|
||||
)
|
||||
|
||||
// get prices to be deleted
|
||||
const obsoletePrices = await moneyAmountRepo.findVariantPricesNotIn(
|
||||
variantId,
|
||||
prices
|
||||
)
|
||||
|
||||
for (const price of prices) {
|
||||
if (price.region_id) {
|
||||
await this.setRegionPrice(variantId, {
|
||||
region_id: price.region_id,
|
||||
amount: price.amount,
|
||||
sale_amount: price.sale_amount || undefined,
|
||||
})
|
||||
} else {
|
||||
await this.setCurrencyPrice(variantId, price)
|
||||
}
|
||||
}
|
||||
|
||||
await moneyAmountRepo.remove(obsoletePrices)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default price for the given currency.
|
||||
* @param {string} variantId - the id of the variant to set prices for
|
||||
@@ -348,26 +363,7 @@ class ProductVariantService extends BaseService {
|
||||
this.moneyAmountRepository_
|
||||
)
|
||||
|
||||
let moneyAmount = await moneyAmountRepo.findOne({
|
||||
where: {
|
||||
currency_code: price.currency_code?.toLowerCase(),
|
||||
variant_id: variantId,
|
||||
region_id: IsNull(),
|
||||
},
|
||||
})
|
||||
|
||||
if (!moneyAmount) {
|
||||
moneyAmount = moneyAmountRepo.create({
|
||||
...price,
|
||||
currency_code: price.currency_code?.toLowerCase(),
|
||||
variant_id: variantId,
|
||||
})
|
||||
} else {
|
||||
moneyAmount.amount = price.amount
|
||||
moneyAmount.sale_amount = price.sale_amount
|
||||
}
|
||||
|
||||
return await moneyAmountRepo.save(moneyAmount)
|
||||
return await moneyAmountRepo.upsertCurrencyPrice(variantId, price)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user