[pricelist] first commit for pricelist plugin
This commit is contained in:
parent
29157dbc03
commit
a6a1de19de
|
@ -0,0 +1,127 @@
|
|||
"""Pricelist plugin"""
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
from http.client import NO_CONTENT
|
||||
|
||||
from flaschengeist.plugins import Plugin
|
||||
from flaschengeist.utils.decorators import login_required
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
from . import models
|
||||
from . import pricelist_controller, permissions
|
||||
|
||||
pricelist_bp = Blueprint("pricelist", __name__, url_prefix="/pricelist")
|
||||
|
||||
|
||||
class PriceListPlugin(Plugin):
|
||||
models = models
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(blueprint=pricelist_bp, permissions=permissions.permissions)
|
||||
config = {"discount": 0}
|
||||
config.update(cfg)
|
||||
|
||||
def install(self):
|
||||
from flaschengeist.database import db
|
||||
|
||||
db.create_all()
|
||||
|
||||
|
||||
@pricelist_bp.route("/drink-types", methods=["GET"])
|
||||
@pricelist_bp.route("/drink-types/<int:identifier>", methods=["GET"])
|
||||
def get_drink_types(identifier=None):
|
||||
if identifier:
|
||||
result = pricelist_controller.get_drink_type(identifier)
|
||||
else:
|
||||
result = pricelist_controller.get_drink_types()
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@pricelist_bp.route("/drink-types", methods=["POST"])
|
||||
@login_required(permission=permissions.CREATE_TYPE)
|
||||
def new_drink_type(current_session):
|
||||
data = request.get_json()
|
||||
if "name" not in data:
|
||||
raise BadRequest
|
||||
drink_type = pricelist_controller.create_drink_type(data["name"])
|
||||
return jsonify(drink_type)
|
||||
|
||||
|
||||
@pricelist_bp.route("/drink-types/<int:identifier>", methods=["PUT"])
|
||||
@login_required(permission=permissions.EDIT_TYPE)
|
||||
def update_drink_type(identifier, current_session):
|
||||
data = request.get_json()
|
||||
if "name" not in data:
|
||||
raise BadRequest
|
||||
drink_type = pricelist_controller.rename_drink_type(data["id"], data["name"])
|
||||
return jsonify(drink_type)
|
||||
|
||||
|
||||
@pricelist_bp.route("/drink-types/<int:identifier>", methods=["DELETE"])
|
||||
@login_required(permission=permissions.DELETE_TYPE)
|
||||
def delete_drink_type(identifier, current_session):
|
||||
pricelist_controller.delete_drink_type(identifier)
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
@pricelist_bp.route("/tags", methods=["GET"])
|
||||
@pricelist_bp.route("/tags/<int:identifier>", methods=["GET"])
|
||||
def get_tags(identifier=None):
|
||||
if identifier:
|
||||
result = pricelist_controller.get_tag(identifier)
|
||||
else:
|
||||
result = pricelist_controller.get_tags()
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@pricelist_bp.route("/tags", methods=["POST"])
|
||||
@login_required(permission=permissions.CREATE_TAG)
|
||||
def new_tag(current_session):
|
||||
data = request.get_json()
|
||||
if "name" not in data:
|
||||
raise BadRequest
|
||||
drink_type = pricelist_controller.create_tag(data["name"])
|
||||
return jsonify(drink_type)
|
||||
|
||||
|
||||
@pricelist_bp.route("/tags/<int:identifier>", methods=["PUT"])
|
||||
@login_required(permission=permissions.EDIT_TAG)
|
||||
def update_tag(identifier, current_session):
|
||||
data = request.get_json()
|
||||
if "name" not in data:
|
||||
raise BadRequest
|
||||
drink_type = pricelist_controller.rename_tag(data["name"])
|
||||
return jsonify(drink_type)
|
||||
|
||||
|
||||
@pricelist_bp.route("/tags/<int:identifier>", methods=["DELETE"])
|
||||
@login_required(permission=permissions.DELETE_TAG)
|
||||
def delete_tag(identifier, current_session):
|
||||
pricelist_controller.delete_tag(identifier)
|
||||
return "", NO_CONTENT
|
||||
|
||||
|
||||
@pricelist_bp.route("/drinks", methods=["GET"])
|
||||
@pricelist_bp.route("/drinks/<int:identifier>", methods=["GET"])
|
||||
def get_drinks(identifier=None):
|
||||
if identifier:
|
||||
result = pricelist_controller.get_drink(identifier)
|
||||
else:
|
||||
result = pricelist_controller.get_drinks()
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@pricelist_bp.route("/drinks/search/<string:name>", methods=["GET"])
|
||||
def search_drinks(name):
|
||||
return jsonify(pricelist_controller.get_drinks(name))
|
||||
|
||||
|
||||
@pricelist_bp.route("/drinks", methods=["POST"])
|
||||
@login_required(permission=permissions.CREATE)
|
||||
def create_drink(current_session):
|
||||
data = request.get_json()
|
||||
if not all(item in data for item in ["name", "volume", "cost_price"]) or not all(
|
||||
item in data for item in ["name", "ingredients"]
|
||||
):
|
||||
raise BadRequest("No correct Keys to create drink")
|
||||
return jsonify(pricelist_controller.create_drink(data))
|
|
@ -0,0 +1,90 @@
|
|||
from flaschengeist.database import db
|
||||
from flaschengeist.models import ModelSerializeMixin
|
||||
|
||||
from typing import Optional
|
||||
|
||||
drink_tag_association = db.Table(
|
||||
"drink_x_tag",
|
||||
db.Column("drink_id", db.Integer, db.ForeignKey("drink.id")),
|
||||
db.Column("tag_id", db.Integer, db.ForeignKey("drink_tag.id")),
|
||||
)
|
||||
|
||||
drink_type_association = db.Table(
|
||||
"drink_x_type",
|
||||
db.Column("drink_id", db.Integer, db.ForeignKey("drink.id")),
|
||||
db.Column("type_id", db.Integer, db.ForeignKey("drink_type.id")),
|
||||
)
|
||||
|
||||
|
||||
class Tag(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
Tag
|
||||
"""
|
||||
|
||||
__tablename__ = "drink_tag"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), nullable=False, unique=True)
|
||||
|
||||
|
||||
class DrinkType(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
DrinkType
|
||||
"""
|
||||
|
||||
__tablename__ = "drink_type"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(30), nullable=False, unique=True)
|
||||
|
||||
|
||||
class DrinkPrice(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
PriceFromVolume
|
||||
"""
|
||||
|
||||
__tablename__ = "drink_price"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False)
|
||||
price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
drink_id = db.Column("drink_id", db.Integer, db.ForeignKey("drink.id"))
|
||||
drink = db.relationship("Drink", back_populates="prices")
|
||||
no_auto: bool = db.Column(db.Boolean, default=False)
|
||||
public: bool = db.Column(db.Boolean, default=True)
|
||||
description: Optional[str] = db.Column(db.String(30))
|
||||
round_step: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False, default=0.5)
|
||||
|
||||
|
||||
class Ingredient(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
Drink Build
|
||||
"""
|
||||
|
||||
__tablename__ = "drink_ingredient"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False)
|
||||
drink_parent_id: int = db.Column("drink_parent_id", db.Integer, db.ForeignKey("drink.id"))
|
||||
drink_parent = db.relationship("Drink", foreign_keys=drink_parent_id)
|
||||
drink_ingredient_id: int = db.Column("drink_ingredient_id", db.Integer, db.ForeignKey("drink.id"))
|
||||
drink_ingredient: "Drink" = db.relationship("Drink", foreign_keys=drink_ingredient_id)
|
||||
|
||||
|
||||
class Drink(db.Model, ModelSerializeMixin):
|
||||
"""
|
||||
DrinkPrice
|
||||
"""
|
||||
|
||||
__tablename__ = "drink"
|
||||
id: int = db.Column("id", db.Integer, primary_key=True)
|
||||
name: str = db.Column(db.String(60), nullable=False)
|
||||
volume: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
cost_price: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False))
|
||||
discount: float = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), nullable=False)
|
||||
extra_charge: Optional[float] = db.Column(db.Numeric(precision=3, scale=2, asdecimal=False), default=0)
|
||||
prices: [DrinkPrice] = db.relationship(
|
||||
"DrinkPrice", back_populates="drink", cascade="all,delete,delete-orphan", order_by=[DrinkPrice.volume]
|
||||
)
|
||||
ingredients: [Ingredient] = db.relationship(
|
||||
"Ingredient", back_populates="drink_parent", foreign_keys=Ingredient.drink_parent_id
|
||||
)
|
||||
tags: [Optional[Tag]] = db.relationship("Tag", secondary=drink_tag_association, cascade="save-update, merge")
|
||||
type_id_ = db.Column("type_id", db.Integer, db.ForeignKey("drink_type.id"))
|
||||
type = db.relationship("DrinkType")
|
|
@ -0,0 +1,23 @@
|
|||
CREATE = "drink_create"
|
||||
"""Can create drinks"""
|
||||
|
||||
EDIT = "drink_edit"
|
||||
"""Can edit drinks"""
|
||||
|
||||
DELETE = "drink_delete"
|
||||
"""Can delete drinks"""
|
||||
|
||||
CREATE_TAG = "drink_tag_create"
|
||||
"""Can create and edit Tags"""
|
||||
|
||||
EDIT_TAG = "drink_tag_edit"
|
||||
|
||||
DELETE_TAG = "drink_tag_delete"
|
||||
|
||||
CREATE_TYPE = "drink_type_create"
|
||||
|
||||
EDIT_TYPE = "drink_type_edit"
|
||||
|
||||
DELETE_TYPE = "drink_type_delete"
|
||||
|
||||
permissions = [value for key, value in globals().items() if not key.startswith("_")]
|
|
@ -0,0 +1,209 @@
|
|||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.database import db
|
||||
from .models import Drink, DrinkPrice, Ingredient, Tag, DrinkType
|
||||
|
||||
from math import ceil
|
||||
|
||||
|
||||
def update():
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def get_tags():
|
||||
return Tag.query.all()
|
||||
|
||||
|
||||
def get_tag(identifier):
|
||||
if isinstance(identifier, int):
|
||||
retVal = Tag.query.get(identifier)
|
||||
elif isinstance(identifier, str):
|
||||
retVal = Tag.query.filter(Tag.name == identifier).one_or_none()
|
||||
else:
|
||||
logger.debug("Invalid identifier type for Tag")
|
||||
raise BadRequest
|
||||
if not retVal:
|
||||
raise NotFound
|
||||
return retVal
|
||||
|
||||
|
||||
def create_tag(name):
|
||||
try:
|
||||
tag = Tag(name=name)
|
||||
db.session.add(tag)
|
||||
update()
|
||||
return tag
|
||||
except IntegrityError:
|
||||
raise BadRequest("Name already exists")
|
||||
|
||||
|
||||
def rename_tag(identifier, new_name):
|
||||
tag = get_tag(identifier)
|
||||
tag.name = new_name
|
||||
try:
|
||||
update()
|
||||
except IntegrityError:
|
||||
raise BadRequest("Name already exists")
|
||||
|
||||
|
||||
def delete_tag(identifier):
|
||||
tag = get_tag(identifier)
|
||||
db.session.delete(tag)
|
||||
try:
|
||||
update()
|
||||
except IntegrityError:
|
||||
raise BadRequest("Tag still in use")
|
||||
|
||||
|
||||
def get_drink_types():
|
||||
return DrinkType.query.all()
|
||||
|
||||
|
||||
def get_drink_type(identifier):
|
||||
if isinstance(identifier, int):
|
||||
retVal = DrinkType.query.get(identifier)
|
||||
elif isinstance(identifier, str):
|
||||
retVal = DrinkType.query.filter(Tag.name == identifier).one_or_none()
|
||||
else:
|
||||
logger.debug("Invalid identifier type for DrinkType")
|
||||
raise BadRequest
|
||||
if not retVal:
|
||||
raise NotFound
|
||||
return retVal
|
||||
|
||||
|
||||
def create_drink_type(name):
|
||||
try:
|
||||
drinkType = DrinkType(name=name)
|
||||
db.session.add(drinkType)
|
||||
update()
|
||||
return drinkType
|
||||
except IntegrityError:
|
||||
raise BadRequest("Name already exists")
|
||||
|
||||
|
||||
def rename_drink_type(identifier, new_name):
|
||||
drink_type = get_drink_type(identifier)
|
||||
drink_type.name = new_name
|
||||
try:
|
||||
update()
|
||||
except IntegrityError:
|
||||
raise BadRequest("Name already exists")
|
||||
return drink_type
|
||||
|
||||
|
||||
def delete_drink_type(identifier):
|
||||
drinkType = get_drink_type(identifier)
|
||||
db.session.delete(drinkType)
|
||||
try:
|
||||
update()
|
||||
except IntegrityError:
|
||||
raise BadRequest("DrinkType still in use")
|
||||
|
||||
|
||||
def round_price(price, round_step):
|
||||
return round(ceil(float(price) / round_step) * round_step * 100) / 100
|
||||
|
||||
|
||||
def calc_prices(drink, prices):
|
||||
retVal = []
|
||||
if len(drink.ingredients) > 0:
|
||||
return calc_price_by_ingredients(drink, prices)
|
||||
allowed_keys = DrinkPrice().serialize().keys()
|
||||
for price in prices:
|
||||
values = {key: value for key, value in price.items() if key in allowed_keys}
|
||||
if values.get("no_auto"):
|
||||
retVal.append(DrinkPrice(**values))
|
||||
else:
|
||||
volume = float(values.pop("volume"))
|
||||
if "price" in values:
|
||||
values.pop("price")
|
||||
_price = float(drink.cost_price) / float(drink.volume) * volume
|
||||
_price += _price * float(drink.discount)
|
||||
if drink.extra_charge:
|
||||
_price += float(drink.extra_charge)
|
||||
_price = round_price(_price, float(price.get("round_step")))
|
||||
retVal.append(DrinkPrice(volume=volume, price=_price, **values))
|
||||
return retVal
|
||||
|
||||
|
||||
def calc_price_by_ingredients(drink, prices):
|
||||
allowed_keys = DrinkPrice().serialize().keys()
|
||||
retVal = []
|
||||
for price in prices:
|
||||
values = {key: value for key, value in price.items() if key in allowed_keys}
|
||||
if values.get("no_auto"):
|
||||
retVal.append(DrinkPrice(**values))
|
||||
else:
|
||||
volume = float(values.pop("volume"))
|
||||
if "price" in values:
|
||||
values.pop("price")
|
||||
_price = 0
|
||||
for ingredient in drink.ingredients:
|
||||
_price = (
|
||||
float(ingredient.drink_ingredient.cost_price)
|
||||
/ float(ingredient.drink_ingredient.volume)
|
||||
* float(ingredient.volume)
|
||||
)
|
||||
_price += _price * float(drink.discount) + float(drink.extra_charge)
|
||||
_price = round_price(_price, price.get("round_step"))
|
||||
retVal.append(DrinkPrice(volume=volume, price=_price, **values))
|
||||
return retVal
|
||||
|
||||
|
||||
def get_drinks(name=None):
|
||||
if name:
|
||||
return Drink.query.filter(Drink.name.contains(name)).all()
|
||||
return Drink.query.all()
|
||||
|
||||
|
||||
def get_drink(identifier):
|
||||
if isinstance(identifier, int):
|
||||
retVal = Drink.query.get(identifier)
|
||||
elif isinstance(identifier, str):
|
||||
retVal = Drink.query.filter(Tag.name == identifier).one_or_none()
|
||||
else:
|
||||
logger.debug("Invalid identifier type for Drink")
|
||||
raise BadRequest
|
||||
if not retVal:
|
||||
raise NotFound
|
||||
return retVal
|
||||
|
||||
|
||||
def add_prices(drink, prices):
|
||||
for price in prices:
|
||||
drink.prices.append(price)
|
||||
|
||||
|
||||
def add_ingredients(drink, ingredients):
|
||||
for identifier, volume in ingredients:
|
||||
ingredient = Ingredient(volume=volume, drink_ingredient=get_drink(identifier))
|
||||
drink.ingredients.append(ingredient)
|
||||
|
||||
|
||||
def create_drink(data):
|
||||
allowed_keys = Drink().serialize().keys()
|
||||
values = {key: value for key, value in data.items() if key in allowed_keys}
|
||||
prices = values.pop("prices", [])
|
||||
ingredients = values.pop("ingredients", [])
|
||||
if "id" in values:
|
||||
values.pop("id")
|
||||
|
||||
drink = Drink(**values)
|
||||
add_ingredients(drink, ingredients)
|
||||
drink.prices = calc_prices(drink, prices)
|
||||
db.session.add(drink)
|
||||
update()
|
||||
return drink
|
||||
|
||||
|
||||
def delete_drink(identifier):
|
||||
drink = get_drink(identifier)
|
||||
for price in drink.prices:
|
||||
db.session.delete(price)
|
||||
for ingredient in drink.ingredients:
|
||||
db.session.delete(ingredient)
|
||||
db.session.delete(drink)
|
||||
update()
|
Loading…
Reference in New Issue