From 4556155a8445eab417aaf2a93e7bcb7e7b8e1956 Mon Sep 17 00:00:00 2001 From: Fredrik Ringqvist <35255659+fredrikphotowall@users.noreply.github.com> Date: Fri, 5 May 2023 10:33:02 +0200 Subject: [PATCH] Add new pricing system (#65) --- api/lib/prices.py | 125 +++++++++++++++++++++++++++ api/models/external_print_product.py | 13 ++- api/models/inquiry.py | 1 + api/models/prices.py | 105 ++++++++++++++++++++++ api/resources/prices.py | 87 ++++++++++++++++--- 5 files changed, 319 insertions(+), 12 deletions(-) create mode 100644 api/models/prices.py diff --git a/api/lib/prices.py b/api/lib/prices.py index f659a68..501f5d6 100644 --- a/api/lib/prices.py +++ b/api/lib/prices.py @@ -3,6 +3,33 @@ from api.lib.utils import round_half_up from api.models.external_print_product import get_external_print_product from api.models.price_adjustments import get_price_adjustment +from api.models.prices import get_wallpaper_m2_price, get_external_product_price, get_own_image_wallpaper_m2_price, get_external_product_own_image_price + + +def wallpaper_m2_price_new( + product_id, + material_id, + market, + inquiry=None, +): + own_image = inquiry is not None and inquiry.productId is None + + if own_image: + price_m2_incl_vat = get_own_image_wallpaper_m2_price(material_id, market.id) + else: + price_m2_incl_vat = get_wallpaper_m2_price(product_id, material_id, market.id) + + price_m2_excl_vat = price_m2_incl_vat / market.vat + + if inquiry: + inquiry_premium = (float(inquiry.pricepremium) / 100) + 1 + price_m2_excl_vat = price_m2_excl_vat * inquiry_premium + price_m2_incl_vat = price_m2_excl_vat * market.vat + + return { + "incl_vat": price_m2_incl_vat, + "excl_vat": price_m2_excl_vat, + } def wallpaper_m2_price( @@ -41,6 +68,34 @@ def wallpaper_m2_price( return price +def wallpaper_price_new( + product_id, + width, + height, + material, + market, + inquiry=None, +): + m2_prices = wallpaper_m2_price_new(product_id, material.id, market, inquiry) + + sqm = (width / 100) * (height / 100) + + min_sqm = 1 + + sqm = max(sqm, min_sqm) + + price_excl_vat = m2_prices["excl_vat"] * sqm + + price_incl_vat = price_excl_vat * market.vat + + return { + "incl_vat": price_incl_vat, + "excl_vat": price_excl_vat, + "m2_price_incl_vat": m2_prices["incl_vat"], + "m2_price_excl_vat": m2_prices["excl_vat"], + } + + def wallpaper_price( width, height, @@ -86,6 +141,28 @@ def _designer_pricepremium(designer, group_id): raise ValueError("Unexpected group id: {}".format(group_id)) +def external_product_own_image_price(external_product_id, market): + price_incl_vat = get_external_product_own_image_price(external_product_id, market.id) + + price_excl_vat = price_incl_vat / market.vat + + return { + 'incl_vat': price_incl_vat, + 'excl_vat': price_excl_vat, + } + + +def external_product_price_new(product_id, external_product_id, market): + price_incl_vat = get_external_product_price(product_id, external_product_id, market.id) + + price_excl_vat = price_incl_vat / market.vat + + return { + 'incl_vat': price_incl_vat, + 'excl_vat': price_excl_vat, + } + + def external_product_price( id, market, designer=None, inquiry=None, rounded=True, inc_vat=True ): @@ -112,6 +189,54 @@ def external_product_price( return price +def inquiry_price_new( + inquiry, **kwargs +): + width = kwargs.get("width", inquiry.width) + height = kwargs.get("height", inquiry.height) + material = kwargs.get("material", inquiry.material) + external_id = kwargs.get("external_id") + + product_id = inquiry.productId + is_own_image = product_id is None + + if external_id: + if not is_own_image: + raise ValueError("Manual inquiries only available for wallpapers") + prices = external_product_own_image_price(external_id, inquiry.market) + return prices + elif inquiry.product_group == "photo-wallpaper": + if is_own_image: + prices = wallpaper_price_new(None, width, height, material, inquiry.market, inquiry) + else: + prices = wallpaper_price_new(product_id, width, height, material, inquiry.market, inquiry) + else: + raise ValueError("Invalid product group: {}".format(inquiry.product_group)) + + price_excl_vat = prices["excl_vat"] + effect_price = inquiry.price_effect + if effect_price: + price_excl_vat += float(effect_price) + + price_excl_retouch_excl_vat = price_excl_vat + price_excl_retouch_incl_vat = price_excl_retouch_excl_vat * inquiry.market.vat + + retouch_price = inquiry.local_price_retouch(rounded=False, inc_vat=False) + if retouch_price: + price_excl_vat += retouch_price + + price_incl_vat = price_excl_vat * inquiry.market.vat + + return { + "incl_vat": price_incl_vat, + "excl_vat": price_excl_vat, + "excl_retouch_incl_vat": price_excl_retouch_incl_vat, + "excl_retouch_excl_vat": price_excl_retouch_excl_vat, + "m2_price_incl_vat": prices["m2_price_incl_vat"], + "m2_price_excl_vat": prices["m2_price_excl_vat"], + } + + def inquiry_price( inquiry, inc_price_retouch=True, rounded=True, inc_vat=True, **kwargs ): diff --git a/api/models/external_print_product.py b/api/models/external_print_product.py index 4582a49..480694d 100644 --- a/api/models/external_print_product.py +++ b/api/models/external_print_product.py @@ -30,7 +30,13 @@ def get_external_print_product(id): return external_product -def get_cheapest_canvas_product(): +def get_cheapest_canvas_product(new_version): + if new_version: + # We simply return the smallest size, assuming that is also the cheapest. + # It is technically possible that a larger size is cheaper, but unlikely, so we avoid the extra complexity + # of calculating the guaranteed cheapest product. + return 'canvas_200x300-mm-8x12-inch_canvas_wood-fsc-slim_4-0_ver' + sql = "select external_product_id from external_print_products where group_id = 2 order by price_sek limit 1" result = db.session.execute(sql) external_id = result.first()[0] @@ -38,7 +44,10 @@ def get_cheapest_canvas_product(): return external_id -def get_cheapest_poster_product(): +def get_cheapest_poster_product(new_version): + if new_version: + return 'flat_250x250-mm-10x10-inch_200-gsm-80lb-uncoated_4-0_ver' + sql = "select external_product_id from external_print_products where group_id in (7,8) order by price_sek limit 1" result = db.session.execute(sql) external_id = result.first()[0] diff --git a/api/models/inquiry.py b/api/models/inquiry.py index 2d4d6b9..6c72bd5 100644 --- a/api/models/inquiry.py +++ b/api/models/inquiry.py @@ -42,6 +42,7 @@ class Inquiry(db.Model): return 0 price = float(self.price_retouch) price *= self.market.exchange_rate + # Todo: review if the retouch price should no longer use the price_adjustments value price *= self.market.price_adjustments if inc_vat: price *= self.market.vat diff --git a/api/models/prices.py b/api/models/prices.py new file mode 100644 index 0000000..a2617ef --- /dev/null +++ b/api/models/prices.py @@ -0,0 +1,105 @@ +# coding=UTF-8 + +from api.extensions import db + + +def get_wallpaper_m2_price(product_id, material_id, market_id): + sql = """ + select + price + from + v_price_product_segments v + join prices_wallpaper_m2 p on v.segment_id = p.segment_id + where + productid = :product_id and market_id = :market_id and material_id = :material_id + order by priority desc + limit 1; + """ + + result = db.session.execute(sql, { + "product_id": product_id, + "market_id": market_id, + "material_id": material_id, + }) + + rows = result.fetchall() + for row in rows: + price = row[0] + return float(price) + + raise Exception("Failed reading wallpaper m2 price") + + +def get_external_product_price(product_id, external_product_id, market_id): + sql = """ + select + price + from + v_price_product_segments v + join prices_external_products p on v.segment_id = p.segment_id + where + productid = :product_id and market_id = :market_id and external_product_id = :external_id + order by priority desc + limit 1; + """ + + result = db.session.execute(sql, { + "product_id": product_id, + "market_id": market_id, + "external_id": external_product_id, + }) + + rows = result.fetchall() + for row in rows: + price = row[0] + return float(price) + + raise Exception("Failed reading external product price") + + +def get_own_image_wallpaper_m2_price(material_id, market_id): + sql = """ + select + price + from + prices_wallpaper_m2 + where + market_id = :market_id and material_id = :material_id + and segment_id = (select id from price_segments where name = 'OWN-IMAGE') + """ + + result = db.session.execute(sql, { + "market_id": market_id, + "material_id": material_id, + }) + + rows = result.fetchall() + for row in rows: + price = row[0] + return float(price) + + raise Exception("Failed reading own image wallpaper m2 price") + + +def get_external_product_own_image_price(external_product_id, market_id): + sql = """ + select + price + from + prices_external_products + where + market_id = :market_id and external_product_id = :external_id + and segment_id = (select id from price_segments where name = 'OWN-IMAGE') + """ + + result = db.session.execute(sql, { + "market_id": market_id, + "external_id": external_product_id, + }) + + rows = result.fetchall() + for row in rows: + price = row[0] + return float(price) + + raise Exception("Failed reading external product own image price") \ No newline at end of file diff --git a/api/resources/prices.py b/api/resources/prices.py index 78abd8a..0feb3b1 100644 --- a/api/resources/prices.py +++ b/api/resources/prices.py @@ -15,6 +15,9 @@ from api.lib.prices import ( inquiry_price, wallpaper_kit_price, external_product_price, + wallpaper_price_new, + external_product_price_new, + inquiry_price_new, ) mod = Blueprint("prices", __name__, url_prefix="/prices") @@ -45,6 +48,8 @@ def wallpaper(): width = request.args.get("width", type=int) height = request.args.get("height", type=int) market_id = request.args.get("market", type=int) + product_id = request.args.get("product", type=int) + new_version = request.args.get("new_version") is not None market = Market.query.get_or_404(market_id) designer = get_designer() reseller = get_reseller() @@ -55,6 +60,20 @@ def wallpaper(): materials = Material.query.wallpapers().all() data = [] for material in materials: + if new_version: + prices = wallpaper_price_new(product_id, width, height, material, market) + data.append( + { + "material": material.name, + "material_id": material.id, + "m2_price": prices["m2_price_incl_vat"], + "m2_price_excl_vat": prices["m2_price_excl_vat"], + "price": prices["incl_vat"], + "price_excl_vat": prices["excl_vat"], + } + ) + continue + data.append( { "material": material.name, @@ -139,7 +158,7 @@ def get_inquiry_measurements(inquiry): return width, height -def inquiry_wallpaper(inquiry): +def inquiry_wallpaper(inquiry, new_version=False): width, height = get_inquiry_measurements(inquiry) data = { @@ -152,6 +171,22 @@ def inquiry_wallpaper(inquiry): materials = Material.query.wallpapers().all() prices = [] for material in materials: + if new_version: + price = inquiry_price_new(inquiry, material=material, width=width, height=height) + prices.append( + { + "material": material.name, + "material_id": material.id, + "m2_price": price["m2_price_incl_vat"], + "m2_price_excl_vat": price["m2_price_excl_vat"], + "inquiry_price": price["incl_vat"], + "inquiry_price_excl_vat": price["excl_vat"], + "inquiry_price_excl_retouch": price["excl_retouch_incl_vat"], + "inquiry_price_excl_retouch_excl_vat": price["excl_retouch_excl_vat"], + } + ) + continue + prices.append( { "material": material.name, @@ -215,7 +250,24 @@ def inquiry_wallpaper(inquiry): return data -def inquiry_external_product(inquiry, external_id): +def inquiry_external_product(inquiry, external_id, new_version): + if new_version: + result = inquiry_price_new(inquiry, external_id=external_id) + data = { + "id": inquiry.id, + # No manual inquiries for external products, hence no retouch price + "retouch_price": 0, + "prices": [ + { + "inquiry_price": result["incl_vat"], + "inquiry_price_excl_vat": result["excl_vat"], + "inquiry_price_excl_retouch": result["incl_vat"], + "inquiry_price_excl_retouch_excl_vat": result["excl_vat"], + } + ], + } + return data + data = { "id": inquiry.id, "retouch_price": inquiry.local_price_retouch(rounded=False, inc_vat=True), @@ -245,19 +297,20 @@ def inquiry(inquiry_id): inquiry = Inquiry.query.get_or_404(inquiry_id) external_product_id = request.args.get("external_product_id") listprice = request.args.get("listprice") + new_version = request.args.get("new_version") is not None - if (listprice): - if (listprice == 'canvas'): - external_product_id = get_cheapest_canvas_product() - elif (listprice == 'poster'): - external_product_id = get_cheapest_poster_product() + if listprice: + if listprice == 'canvas': + external_product_id = get_cheapest_canvas_product(new_version) + elif listprice == 'poster': + external_product_id = get_cheapest_poster_product(new_version) else: raise ValueError("Invalid listprice group {}".format(listprice)) if external_product_id: - data = inquiry_external_product(inquiry, external_product_id) + data = inquiry_external_product(inquiry, external_product_id, new_version) elif inquiry.product_group == "photo-wallpaper": - data = inquiry_wallpaper(inquiry) + data = inquiry_wallpaper(inquiry, new_version) else: raise ValueError("Invalid group {}".format(inquiry.product_group)) @@ -270,8 +323,22 @@ def inquiry(inquiry_id): def external_product(): market_id = request.args.get("market", type=int) market = Market.query.get_or_404(market_id) - designer = get_designer() id = request.args.get("id", type=str) + new_version = request.args.get("new_version") is not None + + if new_version: + product_id = request.args.get("product", type=int) + prices = external_product_price_new(product_id, id, market) + data = [ + { + "id": id, + "price": prices["incl_vat"], + "price_excl_vat": prices['excl_vat'] + } + ] + return jsonify(data=data) + + designer = get_designer() data = [ {