PW-567 add the old canvas formula

This commit is contained in:
Martin
2016-10-31 17:57:11 +01:00
parent cbee61b564
commit bf9fc3ae5a
10 changed files with 352 additions and 35 deletions
+52
View File
@@ -0,0 +1,52 @@
# coding=UTF-8
CANVAS = {
'max_long_side': 500,
'min_long_side': 20,
'max_short_side': 150,
'min_short_side': 20
}
CANVAS_FRAME = {
'max_long_side': 150,
'min_long_side': 40,
'max_short_side': 150,
'min_short_side': 40,
}
def calculate_canvas_frame_limits(width, height):
if width > height:
width = min(width, CANVAS_FRAME.get('max_long_side'))
width = max(width, CANVAS_FRAME.get('min_long_side'))
height = min(height, CANVAS_FRAME.get('max_short_side'))
height = max(height, CANVAS_FRAME.get('min_short_side'))
else:
height = min(height, CANVAS_FRAME.get('max_long_side'))
height = max(height, CANVAS_FRAME.get('min_long_side'))
width = min(width, CANVAS_FRAME.get('max_short_side'))
width = max(width, CANVAS_FRAME.get('min_short_side'))
# round to nearest 10
width = round(width, -1)
height = round(height, -1)
return width, height
def calculate_canvas_limits(width, height, framed=False):
if framed:
return calculate_canvas_frame_limits(width, height)
if width > height:
width = min(width, CANVAS.get('max_long_side'))
width = max(width, CANVAS.get('min_long_side'))
height = min(height, CANVAS.get('max_short_side'))
height = max(height, CANVAS.get('min_short_side'))
else:
height = min(height, CANVAS.get('max_long_side'))
height = max(height, CANVAS.get('min_long_side'))
width = min(width, CANVAS.get('max_short_side'))
width = max(width, CANVAS.get('min_short_side'))
return width, height
+81 -1
View File
@@ -1,7 +1,9 @@
# coding=UTF-8
""" Price formulas """
from api.lib.utils import round_half_up
from api.lib.limits import calculate_canvas_limits
def wallpaper_price(width, height, material_price, market, designer=None, reseller=None, inc_vat=True):
m2_price = material_price
@@ -96,3 +98,81 @@ def canvas_price(width, height, market, framed=True, designer=None, reseller=Non
price *= market.vat
return round(price)
def canvas_frame_price(width, height, stock_price, additional_price=None):
width, height = calculate_canvas_limits(width, height, framed=True)
circumference = (2 * width) + (2 * height)
circumference = max(circumference, 170)
price = stock_price * circumference
# if width or height goes above this limit additional costs are added
if additional_price:
additional_price_limit = 120
if width > additional_price_limit or height > additional_price_limit:
price += additional_price
return price
def old_canvas_diy_frame_price(width, height, market, reseller=None, inc_vat=True):
# todo: when product models are in place we should use stockproduct ""Do it yourself frame""
# instead of hardcoded values
stock_price = 1.61084
additional_price = 94.750000
price = canvas_frame_price(width, height, stock_price, additional_price)
# exchange to local currency
price *= market.exchange_rate
# multiply price with market price adjustments
price *= market.price_adjustments
# add reseller price premium
if reseller:
price *= (float(reseller.pricepremium) / 100) + 1
# add vat
if inc_vat:
price *= market.vat
return round_half_up(price)
def old_canvas_price(width, height, material_price, market, framed=True, designer=None, reseller=None, inc_vat=True):
width, height = calculate_canvas_limits(width, height, framed)
# calculate square meter
sqm = (width / 100) * (height / 100)
sqm = max(sqm, 0.2) # sqm can never go below 0.2
# calculate price per square meter
price = sqm * material_price
if framed:
# todo: when product models are in place we should use stockproduct
# "Canvasframe" instead of hardcoded values
stock_price = 1.04230824
additional_price = 94.750000
price += canvas_frame_price(width, height,
stock_price, additional_price)
# add designer price premium
if designer:
price *= (float(designer.pricepremium) / 100) + 1
# add reseller price premium
if reseller:
price *= (float(reseller.pricepremium) / 100) + 1
# exchange to local currency
price *= market.exchange_rate
# multiply price with market price adjustments
price *= market.price_adjustments
# add vat
if inc_vat:
price *= market.vat
return round_half_up(price)
+5
View File
@@ -0,0 +1,5 @@
from decimal import Decimal, ROUND_HALF_UP
def round_half_up(val):
return int(Decimal(val).quantize(0, ROUND_HALF_UP))
+15
View File
@@ -1,9 +1,24 @@
# coding=UTF-8
from flask_sqlalchemy import BaseQuery
from api import db
class ContractCustomerQuery(BaseQuery):
def reseller_store(self, url, territory):
return self.filter(
ContractCustomer.dealerstore == True,
ContractCustomer.dealerurl == url,
ContractCustomer.country == territory).first()
class ContractCustomer(db.Model):
__tablename__ = 'contract_customers'
query_class = ContractCustomerQuery
id = db.Column('contractcustomerid', db.Integer, primary_key=True)
country = db.Column(db.String(2))
pricepremium = db.Column(db.Numeric, nullable=False, default=0)
+1 -1
View File
@@ -78,7 +78,7 @@ _markets = [
Market('United kingdom', 'en_GB', 'GBP', 1.20, 'GBR'),
Market('Denmark', 'da_DK', 'DKK', 1.25, 'DNK'),
Market('Finland', 'fi_FI', 'EUR', 1.24, 'FIN'),
Market('Russia', 'ru_RU', 'RUB', 1, 'RUS'),
Market('Russia', 'ru_RU', 'EUR', 1, 'RUS'),
Market('Germany', 'de_DE', 'EUR', 1.19, 'DEU'),
Market('Netherlands', 'nl_NL', 'EUR', 1.21, 'NLD'),
Market('Austria', 'de_AT', 'EUR', 1.20, 'AUT'),
+46 -29
View File
@@ -2,33 +2,33 @@ from flask import Blueprint, request, jsonify, abort
from api.models.product import Material
from api.models.contract_customer import ContractCustomer
from api.models import market as market_model
from api.lib.prices import wallpaper_price, canvas_price
from api.lib.prices import wallpaper_price, old_canvas_price, old_canvas_diy_frame_price
from api.lib.limits import calculate_canvas_limits
from api.validators import validate_territory, validate_number, validate_keys_exists
mod = Blueprint('prices', __name__, url_prefix='/prices')
def get_reseller():
dealerurl = request.args.get('dealerurl')
territory = request.args.get('territory')
if dealerurl and territory:
return ContractCustomer.query.reseller_store(dealerurl, territory)
return None
@mod.route("/wallpaper", methods=['GET'])
def wallpaper():
validate_keys_exists(request.args, 'width', 'height', 'territory')
validate_number(request.args.get('width'), request.args.get('height'))
validate_territory(request.args.get('territory'))
width = request.args.get('width', type=int)
height = request.args.get('height', type=int)
market = market_model.from_territory(request.args.get('territory'))
params = {
'width': request.args.get('width', type=int),
'height': request.args.get('height', type=int),
'market': market,
}
dealerurl = request.args.get('dealerurl')
if dealerurl:
reseller = ContractCustomer.query.filter_by(
dealerstore=True, dealerurl=dealerurl, country=market.territory).first()
if reseller:
params['reseller'] = reseller
reseller = get_reseller()
materials = Material.query.wallpapers().all()
data = []
@@ -36,7 +36,7 @@ def wallpaper():
data.append({
'material': material.name,
'material_id': material.id,
'price': wallpaper_price(material_price=material.price, **params),
'price': wallpaper_price(width, height, material.price, market, reseller=reseller),
})
return jsonify(data=data)
@@ -47,28 +47,45 @@ def canvas():
validate_keys_exists(request.args, 'width', 'height', 'territory')
validate_number(request.args.get('width'), request.args.get('height'))
validate_territory(request.args.get('territory'))
market = market_model.from_territory(request.args.get('territory'))
params = {
'width': request.args.get('width', type=int),
'height': request.args.get('height', type=int),
'market': market
}
dealerurl = request.args.get('dealerurl')
if dealerurl:
reseller = ContractCustomer.query.filter_by(
dealerstore=True, dealerurl=dealerurl, country=market.territory).first()
if reseller:
params['reseller'] = reseller
reseller = get_reseller()
material = Material.query.canvas().first()
data = []
for framed in [True, False]:
width, height = calculate_canvas_limits(
request.args.get('width', type=int),
request.args.get('height', type=int),
framed
)
data.append({
'framed': framed,
'price': canvas_price(framed=framed, **params)
'width': width,
'height': height,
'price': old_canvas_price(width, height, material.price, market, reseller=reseller, framed=framed)
})
return jsonify(data=data)
@mod.route('/diy-frame', methods=['GET'])
def diy_frame():
validate_keys_exists(request.args, 'width', 'height', 'territory')
validate_number(request.args.get('width'), request.args.get('height'))
validate_territory(request.args.get('territory'))
market = market_model.from_territory(request.args.get('territory'))
reseller = get_reseller()
width, height = calculate_canvas_limits(
request.args.get('width', type=int),
request.args.get('height', type=int),
framed=True
)
data = [{
'width': width,
'height': height,
'price': old_canvas_diy_frame_price(width, height, market, reseller=reseller)
}]
return jsonify(data=data)
+37 -3
View File
@@ -43,7 +43,7 @@ Sample response
## Calculate canvas price
Calculate price inc VAT of a canvas product. The response will contain information for both framed/not framed canvases.
Calculate price inc VAT of a canvas product. The response will contain information for both framed/not framed canvases. Note that width and height parameters may be adjusted if they go over or under the limits that are valid for a canvas. The values that are used in the price-calculation are always returned in the response.
| Parameter | Description |
| --------- | -----| -------- | -
@@ -63,11 +63,45 @@ Sample response
"data": [
{
"framed": true,
"price": 284
"price": 284,
"width": 100,
"height": 50,
},
{
"framed": false,
"price": 249
"price": 249,
"width": 100,
"height": 50,
}
]
}
```
## Calculate "Do it yourself frame" price
Calculate the price for the "do it yourself frame" (DIY). Note that width and height parameters may be adjusted if they go over or under the limits that are valid for a canvas-frame. The values that are used in the price-calculation are always returned in the response.
| Parameter | Description |
| --------- | -----| -------- | -
| `width` | Canvas width in cm e.g `100'. |
| `height` | Canvas height in cm e.g `50` |
| `territory` | Two letter iso code e.g `SE`. Territory affect the price in different ways like VAT, currency exchanges etc. |
| `dealerurl` | Optional parameter e.g `ackes`. Should always be used when calling the api from a dealerstore. If a dealerstore exists with this url additional fees will be applied to the price. |
Sample request
`curl -X GET 'https://api2.photowall.com/prices/diy-frame?width=150&height=150&territory=SE`
Sample response
```
{
"data": [
{
"height": 150,
"price": 1327,
"width": 150
}
]
}
+22
View File
@@ -0,0 +1,22 @@
# coding=UTF-8
import unittest2
from api.lib.limits import calculate_canvas_limits, calculate_canvas_frame_limits
class TestCalculate(unittest2.TestCase):
def test_calculate_canvas_limits(self):
self.assertEqual((100, 100), calculate_canvas_limits(100, 100))
self.assertEqual((500, 100), calculate_canvas_limits(800, 100))
self.assertEqual((100, 500), calculate_canvas_limits(100, 800))
self.assertEqual((20, 20), calculate_canvas_limits(10, 10))
self.assertEqual((150, 50), calculate_canvas_limits(
400, 50, True), "Framed canvas")
def test_calculate_canvas_frame_limits(self):
self.assertEqual((100, 100), calculate_canvas_frame_limits(100, 100))
self.assertEqual((40, 40), calculate_canvas_frame_limits(1, 1))
self.assertEqual((150, 150), calculate_canvas_frame_limits(200, 200))
self.assertEqual((150, 40), calculate_canvas_frame_limits(200, 10))
self.assertEqual((110, 120), calculate_canvas_frame_limits(112, 117), "round to nearest 10")
@@ -1,7 +1,7 @@
# coding=UTF-8
import unittest2
from api.lib.prices import wallpaper_price, canvas_price, canvas_cm2_price
from api.lib.prices import *
from tests.factories import DesignerFactory, ContractCustomerFactory, InquiryFactory
from api.models import market
@@ -80,3 +80,46 @@ class TestPrice(unittest2.TestCase):
self.assertEqual(436, canvas_price(width=50, height=50,
market=denmark, framed=True, inc_vat=False))
def test_canvas_frame_price(self):
self.assertAlmostEqual(208.46, canvas_frame_price(
50, 50, stock_price=1.04230824, additional_price=94.750000), places=2)
self.assertAlmostEqual(177.19, canvas_frame_price(
1, 1, stock_price=1.04230824, additional_price=94.750000), places=2)
self.assertAlmostEqual(625.38, canvas_frame_price(
500, 500, stock_price=1.04230824, additional_price=None), places=2)
def test_old_canvas_diy_frame_price(self):
self.assertEqual(
403, old_canvas_diy_frame_price(50, 50, market=sweden))
self.assertEqual(801, old_canvas_diy_frame_price(
100, 110, market=denmark))
self.assertEqual(463, old_canvas_diy_frame_price(
50, 50, market=sweden, reseller=reseller))
self.assertEqual(1327, old_canvas_diy_frame_price(
500, 500, market=sweden))
def test_old_canvas_price(self):
self.assertEqual(250, old_canvas_price(
width=50, height=50, material_price=799.2, market=sweden, framed=False))
self.assertEqual(200, old_canvas_price(
width=1, height=1, material_price=799.2, market=sweden, framed=False))
self.assertEqual(7493, old_canvas_price(
width=500, height=500, material_price=799.2, market=sweden, framed=False))
self.assertEqual(5994, old_canvas_price(width=500, height=500,
material_price=799.2, market=sweden, framed=False, inc_vat=False))
self.assertEqual(7493, old_canvas_price(
width=500, height=500, material_price=799.2, market=sweden, framed=False))
self.assertEqual(236, old_canvas_price(
width=50, height=50, material_price=799.2, market=denmark, framed=False))
self.assertEqual(294, old_canvas_price(
width=50, height=50, material_price=799.2, market=sweden, designer=designer, framed=False))
self.assertEqual(510, old_canvas_price(
width=50, height=50, material_price=799.2, market=sweden, framed=True))
self.assertEqual(2972, old_canvas_price(
width=150, height=140, material_price=799.2, market=sweden, framed=True))
self.assertEqual(287, old_canvas_price(
width=50, height=50, material_price=799.2, market=sweden, framed=False, reseller=reseller))
+49
View File
@@ -0,0 +1,49 @@
import json
import flask_testing
from api import create_app, db
from api.models.product import Material, MaterialPrice
class TestEndpoints(flask_testing.TestCase):
def create_app(self):
return create_app('tests.config')
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
def _add_material(self, name, groupid, price):
material = Material(name=name)
material.material_price = MaterialPrice(price=price, groupid=groupid)
db.session.add(material)
db.session.commit()
return material
def test_canvas(self):
self._add_material('standard-canvas', 2, 799.9)
response = self.client.get(
'/prices/canvas?width=200&height=10&territory=SE')
self.assert200(response)
products = response.json['data']
for product in products:
if product['framed']:
self.assertEqual(150, product['width'])
self.assertEqual(40, product['height'])
self.assertEqual(1213, product['price'])
else:
self.assertEqual(200, product['width'])
self.assertEqual(20, product['height'])
self.assertEqual(400, product['price'])
def test_diy_frame(self):
response = self.client.get(
'/prices/diy-frame?width=200&height=50&territory=SE')
self.assert200(response)
product = response.json['data'][0]
self.assertEqual(150, product['width'], "it adjusts measurements")
self.assertEqual(50, product['height'])
self.assertEqual(924, product['price'])