fix: adds tax calculation to product pricing (#1354)

* fix: adds tax calculation to product pricing

* fix: dedupe

* fix: merge inconsistencies

* fix: merge inconsistencies
This commit is contained in:
Sebastian Rindom authored and GitHub committed 2022-06-14 10:11:18 +02:00
1 parent 9a3ff32b42
commit 14366f536d
31 files changed
+792 -434

No files matched your search

@@ -79,6 +79,8 @@ Array [
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -105,6 +107,8 @@ Array [
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -123,6 +127,7 @@ Array [
], ],
"product_id": StringMatching /\\^test-\\*/, "product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku", "sku": "test-sku",
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": "test-upc", "upc": "test-upc",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -133,6 +138,8 @@ Array [
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean2", "ean": "test-ean2",
@@ -159,6 +166,8 @@ Array [
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -177,6 +186,7 @@ Array [
], ],
"product_id": StringMatching /\\^test-\\*/, "product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku2", "sku": "test-sku2",
"tax_rates": null,
"title": "Test variant rank (2)", "title": "Test variant rank (2)",
"upc": "test-upc2", "upc": "test-upc2",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -187,6 +197,8 @@ Array [
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode 1", "barcode": "test-barcode 1",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean1", "ean": "test-ean1",
@@ -213,6 +225,8 @@ Array [
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -231,6 +245,7 @@ Array [
], ],
"product_id": StringMatching /\\^test-\\*/, "product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku1", "sku": "test-sku1",
"tax_rates": null,
"title": "Test variant rank (1)", "title": "Test variant rank (1)",
"upc": "test-upc1", "upc": "test-upc1",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -241,6 +256,8 @@ Array [
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode-sale", "barcode": "test-barcode-sale",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean-sale", "ean": "test-ean-sale",
@@ -267,6 +284,8 @@ Array [
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 1000, "amount": 1000,
@@ -285,6 +304,7 @@ Array [
], ],
"product_id": StringMatching /\\^test-\\*/, "product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku-sale", "sku": "test-sku-sale",
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": "test-upc-sale", "upc": "test-upc-sale",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -353,6 +373,8 @@ Array [
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean4", "ean": "test-ean4",
@@ -379,6 +401,8 @@ Array [
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -397,6 +421,7 @@ Array [
], ],
"product_id": StringMatching /\\^test-\\*/, "product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku4", "sku": "test-sku4",
"tax_rates": null,
"title": "Test variant rank (2)", "title": "Test variant rank (2)",
"upc": "test-upc4", "upc": "test-upc4",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -407,6 +432,8 @@ Array [
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean3", "ean": "test-ean3",
@@ -433,6 +460,8 @@ Array [
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -451,6 +480,7 @@ Array [
], ],
"product_id": StringMatching /\\^test-\\*/, "product_id": StringMatching /\\^test-\\*/,
"sku": "test-sku3", "sku": "test-sku3",
"tax_rates": null,
"title": "Test variant rank (2)", "title": "Test variant rank (2)",
"upc": "test-upc3", "upc": "test-upc3",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -609,6 +639,8 @@ Array [
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -635,6 +667,8 @@ Array [
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -653,6 +687,7 @@ Array [
], ],
"product_id": StringMatching /\\^prod_\\*/, "product_id": StringMatching /\\^prod_\\*/,
"sku": null, "sku": null,
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": null, "upc": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -769,6 +804,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -805,6 +842,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -851,6 +890,7 @@ Object {
], ],
"product_id": StringMatching /\\^prod_\\*/, "product_id": StringMatching /\\^prod_\\*/,
"sku": null, "sku": null,
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": null, "upc": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -933,6 +973,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -959,6 +1001,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 75, "amount": 75,
@@ -977,6 +1021,7 @@ Object {
], ],
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku", "sku": "test-sku",
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": "test-upc", "upc": "test-upc",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -22,7 +22,7 @@ describe("/admin/products", () => {
beforeAll(async () => { beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..")) const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd }) dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd }) medusaProcess = await setupServer({ cwd, verbose: false })
}) })
afterAll(async () => { afterAll(async () => {
@@ -6,6 +6,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -32,6 +34,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -51,6 +55,7 @@ Object {
"product": Any<Object>, "product": Any<Object>,
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku", "sku": "test-sku",
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": "test-upc", "upc": "test-upc",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -66,7 +71,9 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": 80, "calculated_price": 80,
"calculated_price_incl_tax": null,
"calculated_price_type": "sale", "calculated_price_type": "sale",
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -93,6 +100,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": 100, "original_price": 100,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -137,6 +146,7 @@ Object {
"product": Any<Object>, "product": Any<Object>,
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku", "sku": "test-sku",
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": "test-upc", "upc": "test-upc",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -153,6 +163,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -179,6 +191,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -198,6 +212,7 @@ Object {
"product": Any<Object>, "product": Any<Object>,
"product_id": Any<String>, "product_id": Any<String>,
"sku": null, "sku": null,
"tax_rates": null,
"title": "test2", "title": "test2",
"upc": null, "upc": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -214,7 +229,9 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": 80, "calculated_price": 80,
"calculated_price_incl_tax": 80,
"calculated_price_type": "sale", "calculated_price_type": "sale",
"calculated_tax": 0,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -241,6 +258,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": 100, "original_price": 100,
"original_price_incl_tax": 100,
"original_tax": 0,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -285,6 +304,13 @@ Object {
"product": Any<Object>, "product": Any<Object>,
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku", "sku": "test-sku",
"tax_rates": Array [
Object {
"code": "default",
"name": "default",
"rate": 0,
},
],
"title": "Test variant", "title": "Test variant",
"upc": "test-upc", "upc": "test-upc",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -301,6 +327,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -327,6 +355,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -346,6 +376,7 @@ Object {
"product": Any<Object>, "product": Any<Object>,
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku", "sku": "test-sku",
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": "test-upc", "upc": "test-upc",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -131,6 +131,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode", "barcode": "test-barcode",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean", "ean": "test-ean",
@@ -157,6 +159,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -200,6 +204,7 @@ Object {
], ],
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku", "sku": "test-sku",
"tax_rates": null,
"title": "Test variant", "title": "Test variant",
"upc": "test-upc", "upc": "test-upc",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -210,6 +215,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean2", "ean": "test-ean2",
@@ -236,6 +243,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -279,6 +288,7 @@ Object {
], ],
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku2", "sku": "test-sku2",
"tax_rates": null,
"title": "Test variant rank (2)", "title": "Test variant rank (2)",
"upc": "test-upc2", "upc": "test-upc2",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -289,6 +299,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": "test-barcode 1", "barcode": "test-barcode 1",
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": "test-ean1", "ean": "test-ean1",
@@ -315,6 +327,8 @@ Object {
], ],
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -358,6 +372,7 @@ Object {
], ],
"product_id": "test-product", "product_id": "test-product",
"sku": "test-sku1", "sku": "test-sku1",
"tax_rates": null,
"title": "Test variant rank (1)", "title": "Test variant rank (1)",
"upc": "test-upc1", "upc": "test-upc1",
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -385,6 +400,8 @@ Object {
"allow_backorder": false, "allow_backorder": false,
"barcode": null, "barcode": null,
"calculated_price": null, "calculated_price": null,
"calculated_price_incl_tax": null,
"calculated_tax": null,
"created_at": Any<String>, "created_at": Any<String>,
"deleted_at": null, "deleted_at": null,
"ean": null, "ean": null,
@@ -399,6 +416,8 @@ Object {
"mid_code": null, "mid_code": null,
"origin_country": null, "origin_country": null,
"original_price": null, "original_price": null,
"original_price_incl_tax": null,
"original_tax": null,
"prices": Array [ "prices": Array [
Object { Object {
"amount": 100, "amount": 100,
@@ -417,6 +436,7 @@ Object {
], ],
"product_id": Any<String>, "product_id": Any<String>,
"sku": null, "sku": null,
"tax_rates": null,
"title": "test-variant", "title": "test-variant",
"upc": null, "upc": null,
"updated_at": Any<String>, "updated_at": Any<String>,
@@ -15,7 +15,7 @@ describe("/store/variants", () => {
beforeAll(async () => { beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", "..")) const cwd = path.resolve(path.join(__dirname, "..", ".."))
dbConnection = await initDb({ cwd }) dbConnection = await initDb({ cwd })
medusaProcess = await setupServer({ cwd }) medusaProcess = await setupServer({ cwd, verbose: false })
}) })
afterAll(async () => { afterAll(async () => {
@@ -1,6 +1,6 @@
import { IsString } from "class-validator" import { IsString } from "class-validator"
import { defaultAdminProductFields, defaultAdminProductRelations } from "." import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { ProductService } from "../../../../services" import { ProductService, PricingService } from "../../../../services"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
/** /**
@@ -42,13 +42,16 @@ export default async (req, res) => {
) )
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const pricingService: PricingService = req.scope.resolve("pricingService")
await productService.addOption(id, validated.title) await productService.addOption(id, validated.title)
const product = await productService.retrieve(id, { const rawProduct = await productService.retrieve(id, {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
}) })
const [product] = await pricingService.setProductPrices([rawProduct])
res.json({ product }) res.json({ product })
} }
@@ -13,6 +13,7 @@ import { EntityManager } from "typeorm"
import { defaultAdminProductFields, defaultAdminProductRelations } from "." import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { import {
ProductService, ProductService,
PricingService,
ProductVariantService, ProductVariantService,
ShippingProfileService, ShippingProfileService,
} from "../../../../services" } from "../../../../services"
@@ -211,6 +212,7 @@ export default async (req, res) => {
const validated = await validator(AdminPostProductsReq, req.body) const validated = await validator(AdminPostProductsReq, req.body)
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const pricingService: PricingService = req.scope.resolve("pricingService")
const productVariantService: ProductVariantService = req.scope.resolve( const productVariantService: ProductVariantService = req.scope.resolve(
"productVariantService" "productVariantService"
) )
@@ -270,11 +272,13 @@ export default async (req, res) => {
} }
}) })
const product = await productService.retrieve(newProduct.id, { const rawProduct = await productService.retrieve(newProduct.id, {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
}) })
const [product] = await pricingService.setProductPrices([rawProduct])
res.json({ product }) res.json({ product })
} }
@@ -1,5 +1,9 @@
import { defaultAdminProductFields, defaultAdminProductRelations } from "." import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { ProductService, ProductVariantService } from "../../../../services" import {
ProductService,
PricingService,
ProductVariantService,
} from "../../../../services"
/** /**
* @oas [delete] /products/{id}/variants/{variant_id} * @oas [delete] /products/{id}/variants/{variant_id}
@@ -35,6 +39,7 @@ export default async (req, res) => {
"productVariantService" "productVariantService"
) )
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const pricingService: PricingService = req.scope.resolve("pricingService")
await productVariantService.delete(variant_id) await productVariantService.delete(variant_id)
@@ -42,11 +47,12 @@ export default async (req, res) => {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
}) })
const [product] = await pricingService.setProductPrices([data])
res.json({ res.json({
variant_id, variant_id,
object: "product-variant", object: "product-variant",
deleted: true, deleted: true,
product: data, product,
}) })
} }
@@ -1,5 +1,5 @@
import { defaultAdminProductFields, defaultAdminProductRelations } from "." import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { ProductService } from "../../../../services" import { ProductService, PricingService } from "../../../../services"
/** /**
* @oas [get] /products/{id} * @oas [get] /products/{id}
@@ -25,11 +25,14 @@ export default async (req, res) => {
const { id } = req.params const { id } = req.params
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const pricingService: PricingService = req.scope.resolve("pricingService")
const product = await productService.retrieve(id, { const rawProduct = await productService.retrieve(id, {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
}) })
const [product] = await pricingService.setProductPrices([rawProduct])
res.json({ product }) res.json({ product })
} }
@@ -1,5 +1,6 @@
import { Router } from "express" import { Router } from "express"
import "reflect-metadata" import "reflect-metadata"
import { PricedProduct } from "../../../../types/pricing"
import { Product, ProductTag, ProductType } from "../../../.." import { Product, ProductTag, ProductType } from "../../../.."
import { PaginatedResponse } from "../../../../types/common" import { PaginatedResponse } from "../../../../types/common"
import middlewares from "../../../middlewares" import middlewares from "../../../middlewares"
@@ -102,10 +103,7 @@ export const defaultAdminProductFields = [
"metadata", "metadata",
] ]
export const defaultAdminGetProductsVariantsFields = [ export const defaultAdminGetProductsVariantsFields = ["id", "product_id"]
"id",
"product_id"
]
export const allowedAdminProductFields = [ export const allowedAdminProductFields = [
"id", "id",
@@ -166,7 +164,7 @@ export type AdminProductsDeleteRes = {
} }
export type AdminProductsListRes = PaginatedResponse & { export type AdminProductsListRes = PaginatedResponse & {
products: Product[] products: (PricedProduct | Product)[]
} }
export type AdminProductsListTypesRes = { export type AdminProductsListTypesRes = {
@@ -17,7 +17,7 @@ import {
defaultAdminProductRelations, defaultAdminProductRelations,
ProductStatus, ProductStatus,
} from "." } from "."
import { ProductService } from "../../../../services" import { ProductService, PricingService } from "../../../../services"
import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant" import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -210,13 +210,15 @@ export default async (req, res) => {
const validated = await validator(AdminPostProductsProductReq, req.body) const validated = await validator(AdminPostProductsProductReq, req.body)
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const pricingService: PricingService = req.scope.resolve("pricingService")
await productService.update(id, validated) await productService.update(id, validated)
const product = await productService.retrieve(id, { const rawProduct = await productService.retrieve(id, {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
}) })
const [product] = await pricingService.setProductPrices([rawProduct])
res.json({ product }) res.json({ product })
} }
@@ -9,7 +9,11 @@ import {
ValidateNested, ValidateNested,
} from "class-validator" } from "class-validator"
import { defaultAdminProductFields, defaultAdminProductRelations } from "." import { defaultAdminProductFields, defaultAdminProductRelations } from "."
import { ProductService, ProductVariantService } from "../../../../services" import {
ProductService,
PricingService,
ProductVariantService,
} from "../../../../services"
import { PriceSelectionParams } from "../../../../types/price-selection" import { PriceSelectionParams } from "../../../../types/price-selection"
import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant" import { ProductVariantPricesUpdateReq } from "../../../../types/product-variant"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -134,6 +138,7 @@ export default async (req, res) => {
const validatedQueryParams = await validator(PriceSelectionParams, req.query) const validatedQueryParams = await validator(PriceSelectionParams, req.query)
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const pricingService: PricingService = req.scope.resolve("pricingService")
const productVariantService: ProductVariantService = req.scope.resolve( const productVariantService: ProductVariantService = req.scope.resolve(
"productVariantService" "productVariantService"
) )
@@ -143,14 +148,14 @@ export default async (req, res) => {
...validated, ...validated,
}) })
await productVariantService.retrieve(variant_id) const rawProduct = await productService.retrieve(id, {
const product = await productService.retrieve(id, {
select: defaultAdminProductFields, select: defaultAdminProductFields,
relations: defaultAdminProductRelations, relations: defaultAdminProductRelations,
...validatedQueryParams, ...validatedQueryParams,
}) })
const [product] = await pricingService.setProductPrices([rawProduct])
res.json({ product }) res.json({ product })
} }
@@ -23,10 +23,6 @@ describe("Get product by id", () => {
IdMap.getId("product1"), IdMap.getId("product1"),
{ {
relations: defaultStoreProductsRelations, relations: defaultStoreProductsRelations,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
} }
) )
}) })
@@ -59,10 +55,6 @@ describe("Get product by id", () => {
IdMap.getId("variantsWithPrices"), IdMap.getId("variantsWithPrices"),
{ {
relations: defaultStoreProductsRelations, relations: defaultStoreProductsRelations,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
} }
) )
}) })
@@ -23,10 +23,6 @@ describe("GET /store/products", () => {
relations: defaultStoreProductsRelations, relations: defaultStoreProductsRelations,
skip: 0, skip: 0,
take: 100, take: 100,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
} }
) )
}) })
@@ -54,10 +50,6 @@ describe("GET /store/products", () => {
relations: defaultStoreProductsRelations, relations: defaultStoreProductsRelations,
skip: 0, skip: 0,
take: 100, take: 100,
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
} }
) )
}) })
@@ -1,5 +1,10 @@
import { defaultStoreProductsRelations } from "." import { defaultStoreProductsRelations } from "."
import { ProductService } from "../../../../services" import {
ProductService,
PricingService,
CartService,
RegionService,
} from "../../../../services"
import { PriceSelectionParams } from "../../../../types/price-selection" import { PriceSelectionParams } from "../../../../types/price-selection"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -30,12 +35,31 @@ export default async (req, res) => {
const customer_id = req.user?.customer_id const customer_id = req.user?.customer_id
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const product = await productService.retrieve(id, { const pricingService: PricingService = req.scope.resolve("pricingService")
const cartService: CartService = req.scope.resolve("cartService")
const regionService: RegionService = req.scope.resolve("regionService")
const rawProduct = await productService.retrieve(id, {
relations: defaultStoreProductsRelations, relations: defaultStoreProductsRelations,
})
let regionId = validated.region_id
let currencyCode = validated.currency_code
if (validated.cart_id) {
const cart = await cartService.retrieve(validated.cart_id, {
select: ["id", "region_id"],
})
const region = await regionService.retrieve(cart.region_id, {
select: ["id", "currency_code"],
})
regionId = region.id
currencyCode = region.currency_code
}
const [product] = await pricingService.setProductPrices([rawProduct], {
cart_id: validated.cart_id, cart_id: validated.cart_id,
customer_id: customer_id, customer_id: customer_id,
region_id: validated.region_id, region_id: regionId,
currency_code: validated.currency_code, currency_code: currencyCode,
include_discount_prices: true, include_discount_prices: true,
}) })
@@ -9,7 +9,12 @@ import {
} from "class-validator" } from "class-validator"
import { omit, pickBy } from "lodash" import { omit, pickBy } from "lodash"
import { defaultStoreProductsRelations } from "." import { defaultStoreProductsRelations } from "."
import { ProductService } from "../../../../services" import {
ProductService,
RegionService,
CartService,
} from "../../../../services"
import PricingService from "../../../../services/pricing"
import { DateComparisonOperator } from "../../../../types/common" import { DateComparisonOperator } from "../../../../types/common"
import { PriceSelectionParams } from "../../../../types/price-selection" import { PriceSelectionParams } from "../../../../types/price-selection"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -61,6 +66,9 @@ import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
*/ */
export default async (req, res) => { export default async (req, res) => {
const productService: ProductService = req.scope.resolve("productService") const productService: ProductService = req.scope.resolve("productService")
const pricingService: PricingService = req.scope.resolve("pricingService")
const cartService: CartService = req.scope.resolve("cartService")
const regionService: RegionService = req.scope.resolve("regionService")
const validated = await validator(StoreGetProductsParams, req.query) const validated = await validator(StoreGetProductsParams, req.query)
@@ -96,18 +104,34 @@ export default async (req, res) => {
: defaultStoreProductsRelations, : defaultStoreProductsRelations,
skip: validated.offset, skip: validated.offset,
take: validated.limit, take: validated.limit,
cart_id: validated.cart_id,
region_id: validated.region_id,
currency_code: validated.currency_code,
customer_id: req.user?.customer_id,
include_discount_prices: true,
} }
const [products, count] = await productService.listAndCount( const [rawProducts, count] = await productService.listAndCount(
pickBy(filterableFields, (val) => typeof val !== "undefined"), pickBy(filterableFields, (val) => typeof val !== "undefined"),
listConfig listConfig
) )
let regionId = validated.region_id
let currencyCode = validated.currency_code
if (validated.cart_id) {
const cart = await cartService.retrieve(validated.cart_id, {
select: ["id", "region_id"],
})
const region = await regionService.retrieve(cart.region_id, {
select: ["id", "currency_code"],
})
regionId = region.id
currencyCode = region.currency_code
}
const products = await pricingService.setProductPrices(rawProducts, {
cart_id: validated.cart_id,
region_id: regionId,
currency_code: currencyCode,
customer_id: req.user?.customer_id,
include_discount_prices: true,
})
res.json({ res.json({
products, products,
count, count,
@@ -16,10 +16,6 @@ describe("Get variant by id", () => {
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1) expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", { expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", {
relations: ["prices", "options"], relations: ["prices", "options"],
cart_id: undefined,
currency_code: undefined,
region_id: undefined,
include_discount_prices: true,
}) })
}) })
@@ -1,5 +1,10 @@
import { defaultStoreVariantRelations } from "." import { defaultStoreVariantRelations } from "."
import ProductVariantService from "../../../../services/product-variant" import {
CartService,
RegionService,
ProductVariantService,
PricingService,
} from "../../../../services"
import { PriceSelectionParams } from "../../../../types/price-selection" import { PriceSelectionParams } from "../../../../types/price-selection"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
@@ -30,15 +35,34 @@ export default async (req, res) => {
const variantService: ProductVariantService = req.scope.resolve( const variantService: ProductVariantService = req.scope.resolve(
"productVariantService" "productVariantService"
) )
const pricingService: PricingService = req.scope.resolve("pricingService")
const cartService: CartService = req.scope.resolve("cartService")
const regionService: RegionService = req.scope.resolve("regionService")
const customer_id = req.user?.customer_id const customer_id = req.user?.customer_id
const variant = await variantService.retrieve(id, { const rawVariant = await variantService.retrieve(id, {
relations: defaultStoreVariantRelations, relations: defaultStoreVariantRelations,
})
let regionId = validated.region_id
let currencyCode = validated.currency_code
if (validated.cart_id) {
const cart = await cartService.retrieve(validated.cart_id, {
select: ["id", "region_id"],
})
const region = await regionService.retrieve(cart.region_id, {
select: ["id", "currency_code"],
})
regionId = region.id
currencyCode = region.currency_code
}
const [variant] = await pricingService.setVariantPrices([rawVariant], {
cart_id: validated.cart_id, cart_id: validated.cart_id,
customer_id: customer_id, customer_id: customer_id,
region_id: validated.region_id, region_id: regionId,
currency_code: validated.currency_code, currency_code: currencyCode,
include_discount_prices: true, include_discount_prices: true,
}) })
@@ -3,7 +3,12 @@ import { omit } from "lodash"
import { IsInt, IsOptional, IsString } from "class-validator" import { IsInt, IsOptional, IsString } from "class-validator"
import { defaultStoreVariantRelations } from "." import { defaultStoreVariantRelations } from "."
import { FilterableProductVariantProps } from "../../../../types/product-variant" import { FilterableProductVariantProps } from "../../../../types/product-variant"
import ProductVariantService from "../../../../services/product-variant" import {
CartService,
RegionService,
ProductVariantService,
PricingService,
} from "../../../../services"
import { validator } from "../../../../utils/validator" import { validator } from "../../../../utils/validator"
import { IsType } from "../../../../utils/validators/is-type" import { IsType } from "../../../../utils/validators/is-type"
import { NumericalComparisonOperator } from "../../../../types/common" import { NumericalComparisonOperator } from "../../../../types/common"
@@ -50,11 +55,6 @@ export default async (req, res) => {
: defaultStoreVariantRelations, : defaultStoreVariantRelations,
skip: offset, skip: offset,
take: limit, take: limit,
cart_id: validated.cart_id,
region_id: validated.region_id,
currency_code: validated.currency_code,
customer_id: customer_id,
include_discount_prices: true,
} }
const filterableFields: FilterableProductVariantProps = omit(validated, [ const filterableFields: FilterableProductVariantProps = omit(validated, [
@@ -71,10 +71,35 @@ export default async (req, res) => {
filterableFields.id = validated.ids.split(",") filterableFields.id = validated.ids.split(",")
} }
const pricingService: PricingService = req.scope.resolve("pricingService")
const variantService: ProductVariantService = req.scope.resolve( const variantService: ProductVariantService = req.scope.resolve(
"productVariantService" "productVariantService"
) )
const variants = await variantService.list(filterableFields, listConfig) const cartService: CartService = req.scope.resolve("cartService")
const regionService: RegionService = req.scope.resolve("regionService")
const rawVariants = await variantService.list(filterableFields, listConfig)
let regionId = validated.region_id
let currencyCode = validated.currency_code
if (validated.cart_id) {
const cart = await cartService.retrieve(validated.cart_id, {
select: ["id", "region_id"],
})
const region = await regionService.retrieve(cart.region_id, {
select: ["id", "currency_code"],
})
regionId = region.id
currencyCode = region.currency_code
}
const variants = await pricingService.setVariantPrices(rawVariants, {
cart_id: validated.cart_id,
region_id: regionId,
currency_code: currencyCode,
customer_id: customer_id,
include_discount_prices: true,
})
res.json({ variants }) res.json({ variants })
} }
@@ -3,7 +3,7 @@ import { AdminProductsListRes } from "../../api"
import { pickBy } from "lodash" import { pickBy } from "lodash"
import { MedusaError } from "medusa-core-utils" import { MedusaError } from "medusa-core-utils"
import { Product } from "../../models/product" import { Product } from "../../models/product"
import { ProductService } from "../../services" import { ProductService, PricingService } from "../../services"
import { getListConfig } from "../../utils/get-query-config" import { getListConfig } from "../../utils/get-query-config"
import { FilterableProductProps } from "../../types/product" import { FilterableProductProps } from "../../types/product"
@@ -24,10 +24,11 @@ const listAndCount = async (
body?: object, body?: object,
context: ListContext = { limit: 50, offset: 0 } context: ListContext = { limit: 50, offset: 0 }
): Promise<AdminProductsListRes> => { ): Promise<AdminProductsListRes> => {
const productService: ProductService = scope.resolve("productService")
const { limit, offset, allowedFields, defaultFields, defaultRelations } = const { limit, offset, allowedFields, defaultFields, defaultRelations } =
context context
const productService: ProductService = scope.resolve("productService")
const pricingService: PricingService = scope.resolve("pricingService")
let includeFields: (keyof Product)[] | undefined let includeFields: (keyof Product)[] | undefined
if (context.fields) { if (context.fields) {
includeFields = context.fields.split(",") as (keyof Product)[] includeFields = context.fields.split(",") as (keyof Product)[]
@@ -67,11 +68,20 @@ const listAndCount = async (
orderBy orderBy
) )
const [products, count] = await productService.listAndCount( const [rawProducts, count] = await productService.listAndCount(
pickBy(query, (val) => typeof val !== "undefined"), pickBy(query, (val) => typeof val !== "undefined"),
listConfig listConfig
) )
let products = rawProducts
const includesPricing = ["variants", "variants.prices"].every((relation) =>
listConfig?.relations?.includes(relation)
)
if (includesPricing) {
products = await pricingService.setProductPrices(rawProducts)
}
return { return {
products, products,
count, count,
@@ -82,8 +82,7 @@ export class ProductVariantRepository extends Repository<ProductVariant> {
): Promise<ProductVariant[]> { ): Promise<ProductVariant[]> {
const entitiesIdsWithRelations = await Promise.all( const entitiesIdsWithRelations = await Promise.all(
Object.entries(groupedRelations).map(([toplevel, rels]) => { Object.entries(groupedRelations).map(([toplevel, rels]) => {
let querybuilder = this.createQueryBuilder("pv") let querybuilder = this.createQueryBuilder("pv").leftJoinAndSelect(
querybuilder = querybuilder.leftJoinAndSelect(
`pv.${toplevel}`, `pv.${toplevel}`,
toplevel toplevel
) )
@@ -94,20 +93,20 @@ export class ProductVariantRepository extends Repository<ProductVariant> {
continue continue
} }
// Regex matches all '.' except the rightmost // Regex matches all '.' except the rightmost
querybuilder = querybuilder.leftJoinAndSelect( querybuilder.leftJoinAndSelect(
rel.replace(/\.(?=[^.]*\.)/g, "__"), rel.replace(/\.(?=[^.]*\.)/g, "__"),
rel.replace(".", "__") rel.replace(".", "__")
) )
} }
if (withDeleted) { if (withDeleted) {
querybuilder = querybuilder querybuilder
.where("pv.id IN (:...entitiesIds)", { .where("pv.id IN (:...entitiesIds)", {
entitiesIds: entityIds, entitiesIds: entityIds,
}) })
.withDeleted() .withDeleted()
} else { } else {
querybuilder = querybuilder.where( querybuilder.where(
"pv.deleted_at IS NULL AND pv.id IN (:...entitiesIds)", "pv.deleted_at IS NULL AND pv.id IN (:...entitiesIds)",
{ {
entitiesIds: entityIds, entitiesIds: entityIds,
@@ -0,0 +1,17 @@
export const PricingServiceMock = {
withTransaction: function () {
return this
},
setProductPrices: jest.fn().mockImplementation((prod) => {
return Promise.resolve(prod)
}),
setVariantPrices: jest.fn().mockImplementation((variant) => {
return Promise.resolve(variant)
}),
}
const mock = jest.fn().mockImplementation(() => {
return PricingServiceMock
})
export default mock
@@ -4,7 +4,7 @@ import LineItemService from "../line-item"
describe("LineItemService", () => { describe("LineItemService", () => {
describe("create", () => { describe("create", () => {
const lineItemRepository = MockRepository({ const lineItemRepository = MockRepository({
create: (data) => data create: (data) => data,
}) })
const cartRepository = MockRepository({ const cartRepository = MockRepository({
@@ -15,7 +15,7 @@ describe("LineItemService", () => {
}) })
const regionService = { const regionService = {
withTransaction: function() { withTransaction: function () {
return this return this
}, },
retrieve: () => { retrieve: () => {
@@ -26,10 +26,10 @@ describe("LineItemService", () => {
} }
const productVariantService = { const productVariantService = {
withTransaction: function() { withTransaction: function () {
return this return this
}, },
retrieve: query => { retrieve: (query) => {
if (query === IdMap.getId("test-giftcard")) { if (query === IdMap.getId("test-giftcard")) {
return { return {
id: IdMap.getId("test-giftcard"), id: IdMap.getId("test-giftcard"),
@@ -54,8 +54,25 @@ describe("LineItemService", () => {
getRegionPrice: () => 100, getRegionPrice: () => 100,
} }
const pricingService = {
withTransaction: function () {
return this
},
getProductVariantPricingById: () => {
return {
calculated_price: 100,
}
},
getProductVariantPricing: () => {
return {
calculated_price: 100,
}
},
}
const lineItemService = new LineItemService({ const lineItemService = new LineItemService({
manager: MockManager, manager: MockManager,
pricingService,
lineItemRepository, lineItemRepository,
productVariantService, productVariantService,
regionService, regionService,
@@ -119,19 +136,22 @@ describe("LineItemService", () => {
}) })
expect(lineItemRepository.create).toHaveBeenCalledTimes(2) expect(lineItemRepository.create).toHaveBeenCalledTimes(2)
expect(lineItemRepository.create).toHaveBeenNthCalledWith(2, expect.objectContaining({ expect(lineItemRepository.create).toHaveBeenNthCalledWith(
allow_discounts: false, 2,
variant_id: IdMap.getId("test-giftcard"), expect.objectContaining({
cart_id: IdMap.getId("test-cart"), allow_discounts: false,
title: "Test product", variant_id: IdMap.getId("test-giftcard"),
description: "Test variant", cart_id: IdMap.getId("test-cart"),
thumbnail: "", title: "Test product",
unit_price: 100, description: "Test variant",
quantity: 1, thumbnail: "",
is_giftcard: true, unit_price: 100,
should_merge: true, quantity: 1,
metadata: {}, is_giftcard: true,
})) should_merge: true,
metadata: {},
})
)
}) })
}) })
+1
View File
@@ -39,3 +39,4 @@ export { default as UserService } from "./user"
export { default as TaxRateService } from "./tax-rate" export { default as TaxRateService } from "./tax-rate"
export { default as TaxProviderService } from "./tax-provider" export { default as TaxProviderService } from "./tax-provider"
export { default as ProductTypeService } from "./product-type" export { default as ProductTypeService } from "./product-type"
export { default as PricingService } from "./pricing"
+15 -6
View File
@@ -3,7 +3,12 @@ import { BaseService } from "medusa-interfaces"
import { EntityManager } from "typeorm" import { EntityManager } from "typeorm"
import { LineItemRepository } from "../repositories/line-item" import { LineItemRepository } from "../repositories/line-item"
import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line" import { LineItemTaxLineRepository } from "../repositories/line-item-tax-line"
import { ProductService, RegionService, ProductVariantService } from "./index" import {
PricingService,
ProductService,
RegionService,
ProductVariantService,
} from "./index"
import { CartRepository } from "../repositories/cart" import { CartRepository } from "../repositories/cart"
import { LineItem } from "../models/line-item" import { LineItem } from "../models/line-item"
import LineItemAdjustmentService from "./line-item-adjustment" import LineItemAdjustmentService from "./line-item-adjustment"
@@ -17,6 +22,7 @@ type InjectedDependencies = {
cartRepository: typeof CartRepository cartRepository: typeof CartRepository
productVariantService: ProductVariantService productVariantService: ProductVariantService
productService: ProductService productService: ProductService
pricingService: PricingService
regionService: RegionService regionService: RegionService
lineItemAdjustmentService: LineItemAdjustmentService lineItemAdjustmentService: LineItemAdjustmentService
} }
@@ -41,6 +47,7 @@ class LineItemService extends BaseService {
lineItemTaxLineRepository, lineItemTaxLineRepository,
productVariantService, productVariantService,
productService, productService,
pricingService,
regionService, regionService,
cartRepository, cartRepository,
lineItemAdjustmentService, lineItemAdjustmentService,
@@ -52,6 +59,7 @@ class LineItemService extends BaseService {
this.itemTaxLineRepo_ = lineItemTaxLineRepository this.itemTaxLineRepo_ = lineItemTaxLineRepository
this.productVariantService_ = productVariantService this.productVariantService_ = productVariantService
this.productService_ = productService this.productService_ = productService
this.pricingService_ = pricingService
this.regionService_ = regionService this.regionService_ = regionService
this.cartRepository_ = cartRepository this.cartRepository_ = cartRepository
this.lineItemAdjustmentService_ = lineItemAdjustmentService this.lineItemAdjustmentService_ = lineItemAdjustmentService
@@ -68,6 +76,7 @@ class LineItemService extends BaseService {
lineItemTaxLineRepository: this.itemTaxLineRepo_, lineItemTaxLineRepository: this.itemTaxLineRepo_,
productVariantService: this.productVariantService_, productVariantService: this.productVariantService_,
productService: this.productService_, productService: this.productService_,
pricingService: this.pricingService_,
regionService: this.regionService_, regionService: this.regionService_,
cartRepository: this.cartRepository_, cartRepository: this.cartRepository_,
lineItemAdjustmentService: this.lineItemAdjustmentService_, lineItemAdjustmentService: this.lineItemAdjustmentService_,
@@ -201,7 +210,6 @@ class LineItemService extends BaseService {
.withTransaction(transactionManager) .withTransaction(transactionManager)
.retrieve(variantId, { .retrieve(variantId, {
relations: ["product"], relations: ["product"],
include_discount_prices: true,
}), }),
this.regionService_ this.regionService_
.withTransaction(transactionManager) .withTransaction(transactionManager)
@@ -213,18 +221,19 @@ class LineItemService extends BaseService {
if (context.unit_price === undefined || context.unit_price === null) { if (context.unit_price === undefined || context.unit_price === null) {
shouldMerge = true shouldMerge = true
unit_price = await this.productVariantService_ const variantPricing = await this.pricingService_
.withTransaction(transactionManager) .withTransaction(transactionManager)
.getRegionPrice(variant.id, { .getProductVariantPricingById(variant.id, {
regionId: region.id, region_id: region.id,
quantity: quantity, quantity: quantity,
customer_id: context?.customer_id, customer_id: context?.customer_id,
include_discount_prices: true, include_discount_prices: true,
}) })
unit_price = variantPricing.calculated_price
} }
const rawLineItem: Partial<LineItem> = { const rawLineItem: Partial<LineItem> = {
unit_price: unit_price as number, unit_price: unit_price,
title: variant.product.title, title: variant.product.title,
description: variant.title, description: variant.title,
thumbnail: variant.product.thumbnail, thumbnail: variant.product.thumbnail,
+389
View File
@@ -0,0 +1,389 @@
import { EntityManager } from "typeorm"
import { ProductVariantService, RegionService, TaxProviderService } from "."
import { Product, ProductVariant } from "../models"
import { TaxServiceRate } from "../types/tax-service"
import {
ProductVariantPricing,
TaxedPricing,
PricingContext,
PricedProduct,
PricedVariant,
} from "../types/pricing"
import { TransactionBaseService } from "../interfaces"
import {
IPriceSelectionStrategy,
PriceSelectionContext,
} from "../interfaces/price-selection-strategy"
type InjectedDependencies = {
manager: EntityManager
productVariantService: ProductVariantService
taxProviderService: TaxProviderService
regionService: RegionService
priceSelectionStrategy: IPriceSelectionStrategy
}
/**
* Allows retrieval of prices.
* @extends BaseService
*/
class PricingService extends TransactionBaseService<PricingService> {
protected manager_: EntityManager
protected transactionManager_: EntityManager | undefined
protected readonly regionService: RegionService
protected readonly taxProviderService: TaxProviderService
protected readonly priceSelectionStrategy: IPriceSelectionStrategy
protected readonly productVariantService: ProductVariantService
constructor({
manager,
productVariantService,
taxProviderService,
regionService,
priceSelectionStrategy,
}: InjectedDependencies) {
// eslint-disable-next-line prefer-rest-params
super(arguments[0])
this.manager_ = manager
this.regionService = regionService
this.taxProviderService = taxProviderService
this.priceSelectionStrategy = priceSelectionStrategy
this.productVariantService = productVariantService
}
/**
* Collects additional information neccessary for completing the price
* selection.
* @param context - the price selection context to use
* @return The pricing context
*/
async collectPricingContext(
context: PriceSelectionContext
): Promise<PricingContext> {
return await this.atomicPhase_(
async (transactionManager: EntityManager) => {
let automaticTaxes = false
let taxRate = null
let currencyCode = context.currency_code
if (context.region_id) {
const region = await this.regionService
.withTransaction(transactionManager)
.retrieve(context.region_id, {
select: ["id", "currency_code", "automatic_taxes", "tax_rate"],
})
currencyCode = region.currency_code
automaticTaxes = region.automatic_taxes
taxRate = region.tax_rate
}
return {
price_selection: {
...context,
currency_code: currencyCode,
},
automatic_taxes: automaticTaxes,
tax_rate: taxRate,
}
}
)
}
/**
* Gets the prices for a product variant
* @param variantPricing - the prices retrieved from a variant
* @param productRates - the tax rates that the product has applied
* @return The tax related variant prices.
*/
async calculateTaxes(
variantPricing: ProductVariantPricing,
productRates: TaxServiceRate[]
): Promise<TaxedPricing> {
const rate = productRates.reduce(
(accRate: number, nextTaxRate: TaxServiceRate) => {
return accRate + (nextTaxRate.rate || 0) / 100
},
0
)
const taxedPricing: TaxedPricing = {
original_tax: null,
calculated_tax: null,
original_price_incl_tax: null,
calculated_price_incl_tax: null,
tax_rates: productRates,
}
if (variantPricing.calculated_price !== null) {
const taxAmount = Math.round(variantPricing.calculated_price * rate)
taxedPricing.calculated_tax = taxAmount
taxedPricing.calculated_price_incl_tax =
variantPricing.calculated_price + taxAmount
}
if (variantPricing.original_price !== null) {
const taxAmount = Math.round(variantPricing.original_price * rate)
taxedPricing.original_tax = taxAmount
taxedPricing.original_price_incl_tax =
variantPricing.original_price + taxAmount
}
return taxedPricing
}
private async getProductVariantPricing_(
variantId: string,
taxRates: TaxServiceRate[],
context: PricingContext
): Promise<ProductVariantPricing> {
const transactionManager = this.transactionManager_ ?? this.manager_
const pricing = await this.priceSelectionStrategy
.withTransaction(transactionManager)
.calculateVariantPrice(variantId, context.price_selection)
const pricingResult: ProductVariantPricing = {
prices: pricing.prices,
original_price: pricing.originalPrice,
calculated_price: pricing.calculatedPrice,
calculated_price_type: pricing.calculatedPriceType,
original_price_incl_tax: null,
calculated_price_incl_tax: null,
original_tax: null,
calculated_tax: null,
tax_rates: null,
}
if (context.automatic_taxes && context.price_selection.region_id) {
const taxResults = await this.calculateTaxes(pricingResult, taxRates)
pricingResult.original_price_incl_tax = taxResults.original_price_incl_tax
pricingResult.calculated_price_incl_tax =
taxResults.calculated_price_incl_tax
pricingResult.original_tax = taxResults.original_tax
pricingResult.calculated_tax = taxResults.calculated_tax
pricingResult.tax_rates = taxResults.tax_rates
}
return pricingResult
}
/**
* Gets the prices for a product variant.
* @param variant - the id of the variant to get prices for
* @param context - the price selection context to use
* @return The product variant prices
*/
async getProductVariantPricing(
variant: Pick<ProductVariant, "id" | "product_id">,
context: PriceSelectionContext | PricingContext
): Promise<ProductVariantPricing> {
let pricingContext: PricingContext
if ("automatic_taxes" in context) {
pricingContext = context
} else {
pricingContext = await this.collectPricingContext(context)
}
let productRates: TaxServiceRate[] = []
if (
pricingContext.automatic_taxes &&
pricingContext.price_selection.region_id
) {
productRates = await this.taxProviderService.getRegionRatesForProduct(
variant.product_id,
{
id: pricingContext.price_selection.region_id,
tax_rate: pricingContext.tax_rate,
}
)
}
return await this.getProductVariantPricing_(
variant.id,
productRates,
pricingContext
)
}
/**
* Gets the prices for a product variant by a variant id.
* @param variantId - the id of the variant to get prices for
* @param context - the price selection context to use
* @return The product variant prices
*/
async getProductVariantPricingById(
variantId: string,
context: PriceSelectionContext | PricingContext
): Promise<ProductVariantPricing> {
let pricingContext: PricingContext
if ("automatic_taxes" in context) {
pricingContext = context
} else {
pricingContext = await this.collectPricingContext(context)
}
let productRates: TaxServiceRate[] = []
if (
pricingContext.automatic_taxes &&
pricingContext.price_selection.region_id
) {
const { product_id } = await this.productVariantService.retrieve(
variantId,
{ select: ["id", "product_id"] }
)
productRates = await this.taxProviderService.getRegionRatesForProduct(
product_id,
{
id: pricingContext.price_selection.region_id,
tax_rate: pricingContext.tax_rate,
}
)
}
return await this.getProductVariantPricing_(
variantId,
productRates,
pricingContext
)
}
private async getProductPricing_(
productId: string,
variants: ProductVariant[],
context: PricingContext
): Promise<Record<string, ProductVariantPricing>> {
const transactionManager = this.transactionManager_ ?? this.manager_
let taxRates: TaxServiceRate[] = []
if (context.automatic_taxes && context.price_selection.region_id) {
taxRates = await this.taxProviderService
.withTransaction(transactionManager)
.getRegionRatesForProduct(productId, {
id: context.price_selection.region_id,
tax_rate: context.tax_rate,
})
}
const pricings = {}
await Promise.all(
variants.map(async ({ id }) => {
const variantPricing = await this.getProductVariantPricing_(
id,
taxRates,
context
)
pricings[id] = variantPricing
})
)
return pricings
}
/**
* Gets all the variant prices for a product. All the product's variants will
* be fetched.
* @param product - the product to get pricing for.
* @param context - the price selection context to use
* @return A map of variant ids to their corresponding prices
*/
async getProductPricing(
product: Pick<Product, "id" | "variants">,
context: PriceSelectionContext
): Promise<Record<string, ProductVariantPricing>> {
const pricingContext = await this.collectPricingContext(context)
return await this.getProductPricing_(
product.id,
product.variants,
pricingContext
)
}
/**
* Gets all the variant prices for a product by the product id
* @param productId - the id of the product to get prices for
* @param context - the price selection context to use
* @return A map of variant ids to their corresponding prices
*/
async getProductPricingById(
productId: string,
context: PriceSelectionContext
): Promise<Record<string, ProductVariantPricing>> {
const pricingContext = await this.collectPricingContext(context)
const variants = await this.productVariantService.list(
{ product_id: productId },
{ select: ["id"] }
)
return await this.getProductPricing_(productId, variants, pricingContext)
}
/**
* Set additional prices on a list of product variants.
* @param variants - list of variants on which to set additional prices
* @param context - the price selection context to use
* @return A list of products with variants decorated with prices
*/
async setVariantPrices(
variants: ProductVariant[],
context: PriceSelectionContext
): Promise<PricedVariant[]> {
const pricingContext = await this.collectPricingContext(context)
return await Promise.all(
variants.map(async (variant) => {
const variantPricing = await this.getProductVariantPricing(
variant,
pricingContext
)
return {
...variant,
...variantPricing,
}
})
)
}
/**
* Set additional prices on a list of products.
* @param products - list of products on which to set additional prices
* @param context - the price selection context to use
* @return A list of products with variants decorated with prices
*/
async setProductPrices(
products: Product[],
context: PriceSelectionContext = {}
): Promise<(Product | PricedProduct)[]> {
const pricingContext = await this.collectPricingContext(context)
return await Promise.all(
products.map(async (product) => {
if (!product?.variants?.length) {
return product
}
const variantPricing = await this.getProductPricing_(
product.id,
product.variants,
pricingContext
)
const pricedVariants = product.variants.map(
(productVariant): PricedVariant => {
const pricing = variantPricing[productVariant.id]
return {
...productVariant,
...pricing,
}
}
)
const pricedProduct = {
...product,
variants: pricedVariants,
}
return pricedProduct
})
)
}
}
export default PricingService
+3 -133
View File
@@ -125,12 +125,6 @@ class ProductVariantService extends BaseService {
) )
const validatedId = this.validateId_(variantId) const validatedId = this.validateId_(variantId)
const priceIndex = config.relations?.indexOf("prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = this.buildQuery_({ id: validatedId }, config) const query = this.buildQuery_({ id: validatedId }, config)
const variant = await variantRepo.findOne(query) const variant = await variantRepo.findOne(query)
@@ -141,16 +135,7 @@ class ProductVariantService extends BaseService {
) )
} }
return priceIndex >= 0 return variant
? ((await this.setAdditionalPrices(
variant,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)) as ProductVariant)
: variant
} }
/** /**
@@ -185,16 +170,7 @@ class ProductVariantService extends BaseService {
) )
} }
return priceIndex >= 0 return variant
? ((await this.setAdditionalPrices(
variant,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)) as ProductVariant)
: variant
} }
/** /**
@@ -617,12 +593,6 @@ class ProductVariantService extends BaseService {
this.productVariantRepository_ this.productVariantRepository_
) )
const priceIndex = config.relations?.indexOf("prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const { q, query, relations } = this.prepareListQuery_(selector, config) const { q, query, relations } = this.prepareListQuery_(selector, config)
if (q) { if (q) {
@@ -634,17 +604,6 @@ class ProductVariantService extends BaseService {
raw.map((i) => i.id), raw.map((i) => i.id),
query.withDeleted ?? false query.withDeleted ?? false
) )
if (priceIndex >= 0) {
const res = await this.setAdditionalPrices(
variants,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
return [res as ProductVariant[], count]
}
return [variants, count] return [variants, count]
} }
@@ -654,18 +613,6 @@ class ProductVariantService extends BaseService {
query query
) )
if (priceIndex >= 0) {
const res = await this.setAdditionalPrices(
variants,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
return [res as ProductVariant[], count]
}
return [variants, count] return [variants, count]
} }
@@ -722,18 +669,7 @@ class ProductVariantService extends BaseService {
} }
} }
const variants = await productVariantRepo.find(query) return await productVariantRepo.find(query)
return priceIndex >= 0
? ((await this.setAdditionalPrices(
variants,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)) as ProductVariant[])
: variants
} }
/** /**
@@ -883,72 +819,6 @@ class ProductVariantService extends BaseService {
return qb return qb
} }
/**
* Set additional prices on a list of variants.
* @param {ProductVariant | ProductVariant[] } variant variant on which to set additional prices
* @param {string} currency_code currency code to fetch prices for
* @param {string} region_id region to fetch prices for
* @param {string} cart_id string of cart to use as a basis for getting currency and region
* @param {string} customer_id id of potentially logged in customer, used to get prices valid for their customer groups
* @param {boolean} include_discount_prices should result include discount pricing
* @return {Promise<ProductVariant | ProductVariant[]>} A list of variants with variants decorated with "additional_prices"
*/
async setAdditionalPrices(
variant,
currency_code,
region_id,
cart_id,
customer_id,
include_discount_prices = false
): Promise<ProductVariant | ProductVariant[]> {
return this.atomicPhase_(async (manager) => {
const cartRepo = manager.getCustomRepository(this.cartRepository_)
let regionId = region_id
let currencyCode = currency_code
if (cart_id) {
const cart = await cartRepo.findOne({
where: { id: cart_id },
relations: ["region"],
})
regionId = cart.region.id
currencyCode = cart.region.currency_code
}
const variantArray = Array.isArray(variant) ? variant : [variant]
const priceSelectionStrategy =
this.priceSelectionStrategy_.withTransaction(manager)
const variantsWithPrices = await Promise.all(
variantArray.map(async (v) => {
const prices = await priceSelectionStrategy.calculateVariantPrice(
v.id,
{
region_id: regionId,
currency_code: currencyCode,
cart_id: cart_id,
customer_id: customer_id,
include_discount_prices: include_discount_prices,
}
)
return {
...v,
prices: prices.prices,
original_price: prices.originalPrice,
calculated_price: prices.calculatedPrice,
calculated_price_type: prices.calculatedPriceType,
}
})
)
return Array.isArray(variant) ? variantsWithPrices : variantsWithPrices[0]
})
}
} }
export default ProductVariantService export default ProductVariantService
+7 -200
View File
@@ -22,13 +22,10 @@ class ProductService extends BaseService {
productOptionRepository, productOptionRepository,
eventBusService, eventBusService,
productVariantService, productVariantService,
productCollectionService,
productTypeRepository, productTypeRepository,
productTagRepository, productTagRepository,
imageRepository, imageRepository,
searchService, searchService,
cartRepository,
priceSelectionStrategy,
}) { }) {
super() super()
@@ -50,9 +47,6 @@ class ProductService extends BaseService {
/** @private @const {ProductVariantService} */ /** @private @const {ProductVariantService} */
this.productVariantService_ = productVariantService this.productVariantService_ = productVariantService
/** @private @const {ProductCollectionService} */
this.productCollectionService_ = productCollectionService
/** @private @const {ProductCollectionService} */ /** @private @const {ProductCollectionService} */
this.productTypeRepository_ = productTypeRepository this.productTypeRepository_ = productTypeRepository
@@ -64,12 +58,6 @@ class ProductService extends BaseService {
/** @private @const {SearchService} */ /** @private @const {SearchService} */
this.searchService_ = searchService this.searchService_ = searchService
/** @private @const {CartRepository} */
this.cartRepository_ = cartRepository
/** @private @const {IPriceSelectionStrategy} */
this.priceSelectionStrategy_ = priceSelectionStrategy
} }
withTransaction(transactionManager) { withTransaction(transactionManager) {
@@ -84,12 +72,9 @@ class ProductService extends BaseService {
productOptionRepository: this.productOptionRepository_, productOptionRepository: this.productOptionRepository_,
eventBusService: this.eventBus_, eventBusService: this.eventBus_,
productVariantService: this.productVariantService_, productVariantService: this.productVariantService_,
productCollectionService: this.productCollectionService_,
productTagRepository: this.productTagRepository_, productTagRepository: this.productTagRepository_,
productTypeRepository: this.productTypeRepository_, productTypeRepository: this.productTypeRepository_,
imageRepository: this.imageRepository_, imageRepository: this.imageRepository_,
cartRepository: this.cartRepository_,
priceSelectionStrategy: this.priceSelectionStrategy_,
}) })
cloned.transactionManager_ = transactionManager cloned.transactionManager_ = transactionManager
@@ -117,11 +102,6 @@ class ProductService extends BaseService {
const productRepo = this.manager_.getCustomRepository( const productRepo = this.manager_.getCustomRepository(
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const { q, query, relations } = this.prepareListQuery_(selector, config) const { q, query, relations } = this.prepareListQuery_(selector, config)
@@ -135,18 +115,7 @@ class ProductService extends BaseService {
return products return products
} }
const products = productRepo.findWithRelations(relations, query) return await productRepo.findWithRelations(relations, query)
return priceIndex > -1
? await this.setAdditionalPrices(
products,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: products
} }
/** /**
@@ -173,43 +142,17 @@ class ProductService extends BaseService {
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const { q, query, relations } = this.prepareListQuery_(selector, config) const { q, query, relations } = this.prepareListQuery_(selector, config)
let products
let count
if (q) { if (q) {
;[products, count] = await productRepo.getFreeTextSearchResultsAndCount( return await productRepo.getFreeTextSearchResultsAndCount(
q, q,
query, query,
relations relations
) )
} else {
;[products, count] = await productRepo.findWithRelationsAndCount(
relations,
query
)
} }
if (priceIndex > -1) { return await productRepo.findWithRelationsAndCount(relations, query)
const productsWithAdditionalPrices = await this.setAdditionalPrices(
products,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
return [productsWithAdditionalPrices, count]
} else {
return [products, count]
}
} }
/** /**
@@ -239,12 +182,6 @@ class ProductService extends BaseService {
) )
const validatedId = this.validateId_(productId) const validatedId = this.validateId_(productId)
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = { where: { id: validatedId } } const query = { where: { id: validatedId } }
if (config.relations && config.relations.length > 0) { if (config.relations && config.relations.length > 0) {
@@ -266,16 +203,7 @@ class ProductService extends BaseService {
) )
} }
return priceIndex > -1 return product
? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -290,12 +218,6 @@ class ProductService extends BaseService {
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = { where: { handle: productHandle } } const query = { where: { handle: productHandle } }
if (config.relations && config.relations.length > 0) { if (config.relations && config.relations.length > 0) {
@@ -317,16 +239,7 @@ class ProductService extends BaseService {
) )
} }
return priceIndex > -1 return product
? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -341,12 +254,6 @@ class ProductService extends BaseService {
this.productRepository_ this.productRepository_
) )
const priceIndex = config.relations?.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && config.relations) {
config.relations = [...config.relations]
config.relations.splice(priceIndex, 1)
}
const query = { where: { external_id: externalId } } const query = { where: { external_id: externalId } }
if (config.relations && config.relations.length > 0) { if (config.relations && config.relations.length > 0) {
@@ -368,16 +275,7 @@ class ProductService extends BaseService {
) )
} }
return priceIndex > -1 return product
? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -942,29 +840,12 @@ class ProductService extends BaseService {
async decorate(productId, fields = [], expandFields = [], config = {}) { async decorate(productId, fields = [], expandFields = [], config = {}) {
const requiredFields = ["id", "metadata"] const requiredFields = ["id", "metadata"]
const priceIndex = expandFields.indexOf("variants.prices") ?? -1
if (priceIndex >= 0 && expandFields.length) {
expandFields = [...expandFields]
expandFields.splice(priceIndex, 1)
}
fields = fields.concat(requiredFields) fields = fields.concat(requiredFields)
const product = await this.retrieve(productId, { return await this.retrieve(productId, {
select: fields, select: fields,
relations: expandFields, relations: expandFields,
}) })
return priceIndex > -1
? await this.setAdditionalPrices(
product,
config.currency_code,
config.region_id,
config.cart_id,
config.customer_id,
config.include_discount_prices
)
: product
} }
/** /**
@@ -1000,80 +881,6 @@ class ProductService extends BaseService {
q, q,
} }
} }
/**
* Set additional prices on a list of products.
* @param {Product[] | Product} products list of products on which to set additional prices
* @param {string} currency_code currency code to fetch prices for
* @param {string} region_id region to fetch prices for
* @param {string} cart_id string of cart to use as a basis for getting currency and region
* @param {string} customer_id id of potentially logged in customer, used to get prices valid for their customer groups
* @param {boolean} include_discount_prices indication wether or not to include sales prices in result
* @return {Promise<Product[]>} A list of products with variants decorated with "additional_prices"
*/
async setAdditionalPrices(
products,
currency_code,
region_id,
cart_id,
customer_id,
include_discount_prices = false
) {
return this.atomicPhase_(async (manager) => {
const cartRepo = this.manager_.getCustomRepository(this.cartRepository_)
let regionId = region_id
let currencyCode = currency_code
if (cart_id) {
const cart = await cartRepo.findOne({
where: { id: cart_id },
relations: ["region"],
})
regionId = cart.region.id
currencyCode = cart.region.currency_code
}
const productArray = Array.isArray(products) ? products : [products]
const priceSelectionStrategy =
this.priceSelectionStrategy_.withTransaction(manager)
const productsWithPrices = await Promise.all(
productArray.map(async (p) => {
if (p.variants?.length) {
p.variants = await Promise.all(
p.variants.map(async (v) => {
const prices =
await priceSelectionStrategy.calculateVariantPrice(v.id, {
region_id: regionId,
currency_code: currencyCode,
cart_id: cart_id,
customer_id: customer_id,
include_discount_prices,
})
return {
...v,
prices: prices.prices,
original_price: prices.originalPrice,
calculated_price: prices.calculatedPrice,
calculated_price_type: prices.calculatedPriceType,
}
})
)
}
return p
})
)
return Array.isArray(products)
? productsWithPrices
: productsWithPrices[0]
})
}
} }
export default ProductService export default ProductService
+12 -7
View File
@@ -28,6 +28,11 @@ import TaxRateService from "./tax-rate"
const CACHE_TIME = 30 // seconds const CACHE_TIME = 30 // seconds
type RegionDetails = {
id: string
tax_rate: number | null
}
/** /**
* Finds tax providers and assists in tax related operations. * Finds tax providers and assists in tax related operations.
*/ */
@@ -321,14 +326,14 @@ class TaxProviderService extends BaseService {
* Gets the tax rates configured for a shipping option. The rates are cached * Gets the tax rates configured for a shipping option. The rates are cached
* between calls. * between calls.
* @param optionId - the option id of the shipping method. * @param optionId - the option id of the shipping method.
* @param region - the region to get configured rates for. * @param regionDetails - the region to get configured rates for.
* @return the tax rates configured for the shipping option. * @return the tax rates configured for the shipping option.
*/ */
async getRegionRatesForShipping( async getRegionRatesForShipping(
optionId: string, optionId: string,
region: Region regionDetails: RegionDetails
): Promise<TaxServiceRate[]> { ): Promise<TaxServiceRate[]> {
const cacheHit = await this.getCacheEntry(optionId, region.id) const cacheHit = await this.getCacheEntry(optionId, regionDetails.id)
if (cacheHit) { if (cacheHit) {
return cacheHit return cacheHit
} }
@@ -336,7 +341,7 @@ class TaxProviderService extends BaseService {
let toReturn: TaxServiceRate[] = [] let toReturn: TaxServiceRate[] = []
const optionRates = await this.taxRateService_.listByShippingOption( const optionRates = await this.taxRateService_.listByShippingOption(
optionId, optionId,
{ region_id: region.id } { region_id: regionDetails.id }
) )
if (optionRates.length > 0) { if (optionRates.length > 0) {
@@ -352,14 +357,14 @@ class TaxProviderService extends BaseService {
if (toReturn.length === 0) { if (toReturn.length === 0) {
toReturn = [ toReturn = [
{ {
rate: region.tax_rate, rate: regionDetails.tax_rate,
name: "default", name: "default",
code: "default", code: "default",
}, },
] ]
} }
await this.setCache(optionId, region.id, toReturn) await this.setCache(optionId, regionDetails.id, toReturn)
return toReturn return toReturn
} }
@@ -373,7 +378,7 @@ class TaxProviderService extends BaseService {
*/ */
async getRegionRatesForProduct( async getRegionRatesForProduct(
productId: string, productId: string,
region: Region region: RegionDetails
): Promise<TaxServiceRate[]> { ): Promise<TaxServiceRate[]> {
const cacheHit = await this.getCacheEntry(productId, region.id) const cacheHit = await this.getCacheEntry(productId, region.id)
if (cacheHit) { if (cacheHit) {
+30
View File
@@ -0,0 +1,30 @@
import { MoneyAmount, ProductVariant, Product } from "../models"
import { TaxServiceRate } from "./tax-service"
import { PriceSelectionContext } from "../interfaces/price-selection-strategy"
export type ProductVariantPricing = {
prices: MoneyAmount[]
original_price: number | null
calculated_price: number | null
calculated_price_type?: string | null
} & TaxedPricing
export type TaxedPricing = {
original_price_incl_tax: number | null
calculated_price_incl_tax: number | null
original_tax: number | null
calculated_tax: number | null
tax_rates: TaxServiceRate[] | null
}
export type PricingContext = {
price_selection: PriceSelectionContext
automatic_taxes: boolean
tax_rate: number | null
}
export type PricedVariant = Partial<ProductVariant> & ProductVariantPricing
export type PricedProduct = Omit<Partial<Product>, "variants"> & {
variants: PricedVariant[]
}
+8 -1
View File
@@ -7507,7 +7507,7 @@
"@types/qs" "*" "@types/qs" "*"
"@types/range-parser" "*" "@types/range-parser" "*"
"@types/express@^4.17.13": "@types/express@*", "@types/express@^4.17.13":
version "4.17.13" version "4.17.13"
resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz"
integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
@@ -7744,6 +7744,13 @@
"@types/bson" "*" "@types/bson" "*"
"@types/node" "*" "@types/node" "*"
"@types/multer@^1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.7.tgz#89cf03547c28c7bbcc726f029e2a76a7232cc79e"
integrity sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==
dependencies:
"@types/express" "*"
"@types/node-fetch@2", "@types/node-fetch@^2.5.7": "@types/node-fetch@2", "@types/node-fetch@^2.5.7":
version "2.5.12" version "2.5.12"
resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz"