1.started removal of CDNs.\n 2. Improved modular structure for all parts of project including database.
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -1,873 +0,0 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Business Object
|
||||
|
||||
Description:
|
||||
Business object for product
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit, Form_Filters_Permutation
|
||||
from business_objects.discount import Discount
|
||||
from business_objects.variation import Variation
|
||||
from business_objects.image import Image
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
from business_objects.stock_item import Stock_Item
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Enum_Status_Stock(Enum):
|
||||
OUT = 0
|
||||
LOW = 1
|
||||
IN = 99
|
||||
|
||||
def text(self):
|
||||
return Enum_Status_Stock.Enum_Status_Stock_Text(self)
|
||||
|
||||
def Enum_Status_Stock_Text(status):
|
||||
av.val_instance(status, 'category', 'Enum_Status_Stock_Text', Enum_Status_Stock)
|
||||
if status == Enum_Status_Stock.OUT:
|
||||
return 'Out of stock'
|
||||
elif status == Enum_Status_Stock.LOW:
|
||||
return 'Low on stock'
|
||||
else:
|
||||
return 'Fully stocked'
|
||||
|
||||
def get_member_by_text(text):
|
||||
return data_types.get_enum_member_by_text(Enum_Status_Stock, text.upper())
|
||||
|
||||
class Variation_Tree_Node():
|
||||
variation: Variation
|
||||
node_parent: None
|
||||
nodes_child: list
|
||||
def __init__(self):
|
||||
self.nodes_child = []
|
||||
def make_from_variation_and_node_parent(variation, node_parent):
|
||||
node = Variation_Tree_Node()
|
||||
node.variation = variation
|
||||
node.node_parent = node_parent
|
||||
return node
|
||||
def make_from_node_parent(node_parent):
|
||||
node = Variation_Tree_Node()
|
||||
node.node_parent = node_parent
|
||||
return node
|
||||
def add_child(self, node_child):
|
||||
self.nodes_child.append(node_child)
|
||||
def is_leaf(self):
|
||||
return (len(self.nodes_child) == 0)
|
||||
class Variation_Tree:
|
||||
node_root: Variation_Tree_Node
|
||||
def make_from_node_root(node_root):
|
||||
tree = Variation_Tree()
|
||||
tree.node_root = node_root
|
||||
return tree
|
||||
def get_variation_type_list(self):
|
||||
variation_types = []
|
||||
node = self.node_root
|
||||
at_leaf_node = node.is_leaf()
|
||||
while not at_leaf_node:
|
||||
variation_types.append(node.variation.name_variation_type)
|
||||
at_leaf_node = node.is_leaf()
|
||||
if not at_leaf_node:
|
||||
node = node.nodes_child[0]
|
||||
return variation_types
|
||||
def is_equal(self, tree):
|
||||
my_type_list = self.get_variation_type_list()
|
||||
sz_me = len(my_type_list)
|
||||
other_type_list = tree.get_variation_type_list()
|
||||
sz_other = len(other_type_list)
|
||||
is_equal = (sz_me == sz_other)
|
||||
if is_equal:
|
||||
for index_type in range(sz_me):
|
||||
if sz_me[index_type] != sz_other[index_type]:
|
||||
is_equal = False
|
||||
break
|
||||
return is_equal
|
||||
def make_from_product_permutation(product_permutation):
|
||||
depth_max = len(product_permutation.variations)
|
||||
node_root = Variation_Tree_Node.make_from_variation_and_node_parent(product_permutation.variations[0], None)
|
||||
node = node_root
|
||||
for depth in range(depth_max - 1):
|
||||
node = Variation_Tree_Node.make_from_variation_and_node_parent(product_permutation.variations[depth + 1], node)
|
||||
return Variation_Tree.make_from_node_root(node_root)
|
||||
|
||||
class Product(db.Model):
|
||||
ATTR_ID_CATEGORY: ClassVar[str] = Variation.ATTR_ID_CATEGORY # 'id-category'
|
||||
ATTR_ID_PRODUCT: ClassVar[str] = Variation.ATTR_ID_PRODUCT # 'id-product'
|
||||
ATTR_ID_PERMUTATION: ClassVar[str] = Variation.ATTR_ID_PERMUTATION # 'id-permutation'
|
||||
FLAG_VARIATIONS: ClassVar[str] = 'variations'
|
||||
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
id_category = db.Column(db.Integer)
|
||||
name = db.Column(db.String(255))
|
||||
display_order = db.Column(db.Integer)
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
# form_basket_add: Form_Basket_Add
|
||||
# form_basket_edit: Form_Basket_Edit
|
||||
# has_variations: bool
|
||||
# index_permutation_selected: int
|
||||
|
||||
def __init__(self):
|
||||
self.permutations = []
|
||||
self.permutation_index = {}
|
||||
self.variation_trees = []
|
||||
self.index_permutation_selected = None
|
||||
self.has_variations = False
|
||||
super().__init__()
|
||||
self.form_basket_add = Form_Basket_Add()
|
||||
self.form_basket_edit = Form_Basket_Edit()
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Product.make_from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
product = Product()
|
||||
product.id_product = query_row[0]
|
||||
product.id_category = query_row[5]
|
||||
product.name = query_row[2]
|
||||
product.has_variations = av.input_bool(query_row[4], "has_variations", _m, v_arg_type=v_arg_type)
|
||||
product.display_order = query_row[22]
|
||||
product.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
|
||||
product.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
|
||||
product.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
|
||||
return product
|
||||
"""
|
||||
def make_from_permutation(permutation, has_variations = False):
|
||||
_m = 'Product.make_from_permutation'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_instance(permutation, 'permutation', _m, Product_Permutation, v_arg_type=v_arg_type)
|
||||
product = Product()
|
||||
product.has_variations = has_variations
|
||||
product.index_permutation_selected = 0
|
||||
product.id_product = permutation.id_product
|
||||
product.id_category = permutation.id_category
|
||||
product.display_order = permutation.display_order
|
||||
product.can_view = permutation.can_view
|
||||
product.can_edit = permutation.can_edit
|
||||
product.can_admin = permutation.can_admin
|
||||
product.permutations.append(permutation)
|
||||
# product.get_variation_trees()
|
||||
return product
|
||||
"""
|
||||
def add_permutation(self, permutation):
|
||||
_m = 'Product.add_permutation'
|
||||
av.val_instance(permutation, 'permutation', _m, Product_Permutation)
|
||||
try:
|
||||
self.permutation_index[permutation.id_permutation]
|
||||
raise ValueError(f"{av.error_msg_str(permutation, 'permutation', _m, Product_Permutation)}\nPermutation ID already in product")
|
||||
except KeyError:
|
||||
self.permutation_index[permutation.id_permutation] = len(self.permutations)
|
||||
self.permutations.append(permutation)
|
||||
"""
|
||||
if self.has_variations:
|
||||
self.has_variations = False
|
||||
"""
|
||||
if self.index_permutation_selected is None:
|
||||
self.index_permutation_selected = self.permutation_index[permutation.id_permutation]
|
||||
print(f'setting selected permutation for product {self.id_product} to {self.index_permutation_selected}') # :\n{self.permutations[self.index_permutation_selected]}
|
||||
"""
|
||||
def make_from_permutations(permutations):
|
||||
_m = 'Product.make_from_permutations'
|
||||
v_arg_type = 'class attribute'
|
||||
if len(permutations) == 0:
|
||||
raise ValueError(av.error_msg_str(permutations, 'permutations', _m, list, v_arg_type=v_arg_type))
|
||||
product = Product()
|
||||
product.has_variations = True
|
||||
product.index_permutation_selected = 0
|
||||
product.id_product = permutations[0].id_product
|
||||
product.id_category = permutations[0].id_category
|
||||
product.display_order = permutations[0].display_order
|
||||
product.can_view = True
|
||||
product.can_edit = True
|
||||
product.can_admin = True
|
||||
for permutation in permutations:
|
||||
product.can_view &= permutation.can_view
|
||||
product.can_edit &= permutation.can_edit
|
||||
product.can_admin &= permutation.can_admin
|
||||
product.permutations.append(permutations)
|
||||
product.get_variation_trees()
|
||||
return product
|
||||
"""
|
||||
def get_variation_trees(self):
|
||||
for index_permutation in range(len(self.permutations)):
|
||||
variation_tree = Variation_Tree.make_from_product_permutation(self.permutations[index_permutation])
|
||||
found_variation_tree_match = False
|
||||
for index_tree in range(len(self.variation_trees)):
|
||||
if self.variation_trees[index_tree].is_equal(variation_tree):
|
||||
found_variation_tree_match = True
|
||||
break
|
||||
if not found_variation_tree_match:
|
||||
self.variation_trees.append(variation_tree)
|
||||
|
||||
def make_from_DB_Stripe_product(query_row):
|
||||
permutation = Product_Permutation.make_from_DB_Stripe_product(query_row)
|
||||
product = Product.make_from_permutation(permutation)
|
||||
return product
|
||||
|
||||
def make_from_DB_Stripe_price(query_row):
|
||||
permutation = Product_Permutation.make_from_DB_Stripe_price(query_row)
|
||||
product = Product.make_from_permutation(permutation)
|
||||
return product
|
||||
|
||||
def make_from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
permutation = Product_Permutation.make_from_json(json_basket_item, key_id_product, key_id_permutation)
|
||||
product = Product.make_from_permutation(permutation)
|
||||
return product
|
||||
|
||||
def get_permutation_selected(self):
|
||||
try:
|
||||
return self.permutations[self.index_permutation_selected]
|
||||
except:
|
||||
raise ValueError(f'list index {self.index_permutation_selected} out of range')
|
||||
def output_lead_time(self):
|
||||
return self.get_permutation_selected().output_lead_time()
|
||||
def output_delivery_date(self):
|
||||
return self.get_permutation_selected().output_delivery_date()
|
||||
def output_price(self, is_included_VAT):
|
||||
return self.get_permutation_selected().output_price(is_included_VAT)
|
||||
def output_price_VAT_incl(self):
|
||||
return self.get_permutation_selected().output_price(True)
|
||||
def output_price_VAT_excl(self):
|
||||
return self.get_permutation_selected().output_price(False)
|
||||
def get_price_local(self, is_included_VAT):
|
||||
if is_included_VAT:
|
||||
return self.get_price_local_VAT_incl()
|
||||
else:
|
||||
return self.get_price_local_VAT_excl()
|
||||
def get_price_local_VAT_incl(self):
|
||||
return self.get_permutation_selected().get_price_local_VAT_incl()
|
||||
def get_price_local_VAT_excl(self):
|
||||
return self.get_permutation_selected().get_price_local_VAT_excl()
|
||||
def get_quantity_min(self):
|
||||
return self.get_permutation_selected().quantity_min
|
||||
def get_id_permutation(self):
|
||||
return self.get_permutation_selected().id_permutation
|
||||
def get_image_from_index(self, index_image):
|
||||
return self.get_permutation_selected().images[index_image]
|
||||
def get_name(self):
|
||||
return self.get_permutation_selected().name
|
||||
def get_description(self):
|
||||
return self.get_permutation_selected().description
|
||||
def output_currency(self):
|
||||
return self.get_permutation_selected().get_price().symbol_currency
|
||||
"""
|
||||
def add_form_basket_add(self):
|
||||
self.form_basket_add = None
|
||||
|
||||
def add_form_basket_edit(self):
|
||||
self.form_basket_edit = None
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''Product
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
name: {self.name}
|
||||
display_order: {self.display_order}
|
||||
can_view: {self.can_view}
|
||||
can_edit: {self.can_edit}
|
||||
can_admin: {self.can_admin}
|
||||
has_variations: {self.has_variations}
|
||||
permutations: {self.permutations}
|
||||
variation trees: {self.variation_trees}
|
||||
'''
|
||||
"""
|
||||
def get_index_permutation_from_id(self, id_permutation):
|
||||
if id_permutation is None and not self.has_variations:
|
||||
return 0
|
||||
for index_permutation in range(len(self.permutations)):
|
||||
permutation = self.permutations[index_permutation]
|
||||
if permutation.id_permutation == id_permutation:
|
||||
return index_permutation
|
||||
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Product.get_index_permutation_from_id', int)}\nPermutation ID not found.")
|
||||
"""
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Product.add_variation', Variation)
|
||||
# print(f'variation: {variation}')
|
||||
index_permutation = self.permutation_index[variation.id_permutation] # self.get_index_permutation_from_id(variation.id_permutation)
|
||||
self.permutations[index_permutation].add_variation(variation)
|
||||
def add_price(self, price):
|
||||
av.val_instance(price, 'price', 'Product.add_price', Price)
|
||||
index_permutation = self.permutation_index[price.id_permutation] # self.get_index_permutation_from_id(price.id_permutation)
|
||||
self.permutations[index_permutation].add_price(price)
|
||||
def add_image(self, image):
|
||||
av.val_instance(image, 'image', 'Product.add_image', Image)
|
||||
index_permutation = self.permutation_index[image.id_permutation] # self.get_index_permutation_from_id(image.id_permutation)
|
||||
self.permutations[index_permutation].add_image(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Product.add_delivery_option', Delivery_Option)
|
||||
index_permutation = self.permutation_index[delivery_option.id_permutation] # self.get_index_permutation_from_id(delivery_option.id_permutation)
|
||||
self.permutations[index_permutation].add_delivery_option(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
av.val_instance(discount, 'discount', 'Product.add_discount', Discount)
|
||||
index_permutation = self.permutation_index[discount.id_permutation] # self.get_index_permutation_from_id(discount.id_permutation)
|
||||
self.permutations[index_permutation].add_discount(discount)
|
||||
|
||||
def add_stock_item(self, stock_item):
|
||||
av.val_instance(stock_item, 'stock_item', 'Product.add_stock_item', Stock_Item)
|
||||
index_permutation = self.permutation_index[stock_item.id_permutation]
|
||||
self.permutations[index_permutation].add_stock_item(stock_item)
|
||||
|
||||
def has_permutations(self):
|
||||
return len(self.permutations) > 0
|
||||
def is_available(self):
|
||||
if len(self.permutations) == 0:
|
||||
return False
|
||||
for permutation in self.permutations:
|
||||
if permutation.is_available():
|
||||
return True
|
||||
return False
|
||||
|
||||
def to_list_rows_permutation(self):
|
||||
list_rows = []
|
||||
for permutation in self.permutations:
|
||||
list_rows.append(permutation.to_row_permutation())
|
||||
return list_rows
|
||||
|
||||
|
||||
class Product_Permutation(db.Model):
|
||||
FLAG_QUANTITY_STOCK = 'quantity-stock'
|
||||
FLAG_QUANTITY_MIN = 'quantity-min'
|
||||
FLAG_QUANTITY_MAX = 'quantity-max'
|
||||
FLAG_COST_LOCAL = 'cost-local'
|
||||
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
id_permutation = db.Column(db.Integer, primary_key=True)
|
||||
# name = db.Column(db.String(255))
|
||||
description = db.Column(db.String(4000))
|
||||
# price_GBP_full = db.Column(db.Float)
|
||||
# price_GBP_min = db.Column(db.Float)
|
||||
id_currency_cost = db.Column(db.Integer)
|
||||
code_currency_cost = db.Column(db.String(3))
|
||||
symbol_currency_cost = db.Column(db.String(3))
|
||||
cost_local = db.Column(db.Float)
|
||||
has_variations = db.Column(db.Boolean)
|
||||
id_category = db.Column(db.Integer)
|
||||
latency_manufacture = db.Column(db.Integer)
|
||||
quantity_min = db.Column(db.Float)
|
||||
quantity_max = db.Column(db.Float)
|
||||
quantity_step = db.Column(db.Float)
|
||||
quantity_stock = db.Column(db.Float)
|
||||
id_stripe_product = db.Column(db.String(100))
|
||||
is_subscription = db.Column(db.Boolean)
|
||||
name_recurrence_interval = db.Column(db.String(255))
|
||||
name_plural_recurrence_interval = db.Column(db.String(256))
|
||||
count_recurrence_interval = db.Column(db.Integer)
|
||||
display_order = db.Column(db.Integer)
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
# form_basket_add: Form_Basket_Add
|
||||
# form_basket_edit: Form_Basket_Edit
|
||||
# is_unavailable_in_currency_or_region: bool
|
||||
# is_available: bool
|
||||
|
||||
def __init__(self):
|
||||
self.variations = []
|
||||
self.variation_index = {}
|
||||
self.prices = []
|
||||
self.price_index = {}
|
||||
self.images = []
|
||||
self.image_index = {}
|
||||
self.delivery_options = []
|
||||
self.delivery_option_index = {}
|
||||
self.discounts = []
|
||||
self.discount_index = {}
|
||||
self.stock_items = []
|
||||
self.stock_item_index = {}
|
||||
super().__init__()
|
||||
self.form_basket_add = Form_Basket_Add()
|
||||
self.form_basket_edit = Form_Basket_Edit()
|
||||
self.is_unavailable_in_currency_or_region = False
|
||||
# self.is_available = False
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Product_Permutation.make_from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
print(f'query_row: {query_row}')
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
permutation.id_permutation = query_row[1]
|
||||
# permutation.name = query_row[2]
|
||||
permutation.description = query_row[3]
|
||||
# permutation.price_GBP_full = query_row[4]
|
||||
# permutation.price_GBP_min = query_row[5]
|
||||
permutation.id_currency_cost = query_row[7]
|
||||
permutation.code_currency_cost = query_row[8]
|
||||
permutation.symbol_currency_cost = query_row[9]
|
||||
permutation.cost_local = query_row[6]
|
||||
permutation.has_variations = query_row[4]
|
||||
permutation.id_category = query_row[5]
|
||||
permutation.latency_manufacture = query_row[11]
|
||||
permutation.quantity_min = query_row[12]
|
||||
permutation.quantity_max = query_row[13]
|
||||
permutation.quantity_step = query_row[14]
|
||||
permutation.quantity_stock = query_row[15]
|
||||
permutation.id_stripe_product = query_row[16]
|
||||
permutation.is_subscription = av.input_bool(query_row[17], "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
permutation.name_recurrence_interval = query_row[18]
|
||||
permutation.name_plural_recurrence_interval = query_row[19]
|
||||
permutation.count_recurrence_interval = query_row[20]
|
||||
permutation.display_order = query_row[23]
|
||||
permutation.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
|
||||
permutation.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
|
||||
permutation.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
|
||||
return permutation
|
||||
|
||||
def make_from_DB_Stripe_product(query_row):
|
||||
_m = 'Product_Permutation.make_from_DB_Stripe_product'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
# permutation.name = query_row[1]
|
||||
permutation.description = query_row[2]
|
||||
return permutation
|
||||
|
||||
def make_from_DB_Stripe_price(query_row):
|
||||
_m = 'Product_Permutation.make_from_DB_Stripe_price'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
# permutation.price_GBP_full = query_row[1]
|
||||
permutation.id_stripe_product = query_row[2]
|
||||
permutation.is_subscription = av.input_bool(query_row[3], "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
permutation.name_recurrence_interval = query_row[4]
|
||||
permutation.count_recurrence_interval = query_row[5]
|
||||
return permutation
|
||||
|
||||
def make_from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
_m = 'Product_Permutation.make_from_json'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = json_basket_item[key_id_product]
|
||||
permutation.id_permutation = json_basket_item[key_id_permutation]
|
||||
return permutation
|
||||
|
||||
def from_json(jsonPermutation):
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_category = jsonPermutation[Product.ATTR_ID_CATEGORY]
|
||||
permutation.id_product = jsonPermutation[Product.ATTR_ID_PRODUCT]
|
||||
permutation.id_permutation = jsonPermutation[Product.ATTR_ID_PERMUTATION]
|
||||
permutation.has_variations = len(jsonPermutation[Product.FLAG_VARIATIONS]) > 0
|
||||
if permutation.has_variations:
|
||||
for jsonVariation in jsonPermutation[Product.FLAG_VARIATIONS]:
|
||||
variation = Variation.from_json(jsonVariation)
|
||||
permutation.add_variation(variation)
|
||||
permutation.quantity_stock = jsonPermutation[Product_Permutation.FLAG_QUANTITY_STOCK]
|
||||
permutation.quantity_min = jsonPermutation[Product_Permutation.FLAG_QUANTITY_MIN]
|
||||
permutation.quantity_max = jsonPermutation[Product_Permutation.FLAG_QUANTITY_MAX]
|
||||
return permutation
|
||||
|
||||
def is_available(self):
|
||||
return len(self.prices) > 0
|
||||
def get_price(self):
|
||||
return self.prices[0]
|
||||
def get_price_local_VAT_incl(self):
|
||||
price = self.get_price()
|
||||
return price.value_local_VAT_incl
|
||||
def get_price_local_VAT_excl(self):
|
||||
price = self.get_price()
|
||||
return price.value_local_VAT_excl
|
||||
|
||||
def output_lead_time(self):
|
||||
return '1 day' if self.latency_manufacture == 1 else f'{self.latency_manufacture} days'
|
||||
|
||||
def output_delivery_date(self):
|
||||
return (datetime.now() + timedelta(days=self.latency_manufacture)).strftime('%A, %d %B %Y')
|
||||
|
||||
def output_price(self, is_included_VAT):
|
||||
if self.is_unavailable_in_currency_or_region:
|
||||
return 'Not available in currency and region'
|
||||
if not self.is_available:
|
||||
return 'Not available'
|
||||
price = self.get_price()
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
if is_included_VAT:
|
||||
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_incl, grouping=True)}'
|
||||
else:
|
||||
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_excl, grouping=True)}'
|
||||
def output_currency(self):
|
||||
if not self.is_available:
|
||||
return ''
|
||||
"""
|
||||
price = self.get_price()
|
||||
return price.code_currency
|
||||
"""
|
||||
return self.code_currency_cost if self.symbol_currency_cost == '' else self.symbol_currency_cost
|
||||
def output_variations(self):
|
||||
if not self.has_variations: return ''
|
||||
return '\n'.join([f'{variation.name_variation_type}: {variation.name_variation}' for variation in self.variations])
|
||||
def output_variations_jsonify(self):
|
||||
if not self.has_variations: return ''
|
||||
return ','.join([f'{variation.id_type}: {variation.id_variation}' for variation in self.variations])
|
||||
"""
|
||||
def output_price_VAT_incl(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.price_GBP_VAT_incl, grouping=True)
|
||||
def output_price_VAT_excl(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.price_GBP_VAT_excl, grouping=True)
|
||||
def add_form_basket_add(self):
|
||||
self.form_basket_add = None
|
||||
|
||||
def add_form_basket_edit(self):
|
||||
self.form_basket_edit = None
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''Product_Permutation
|
||||
id_product: {self.id_product}
|
||||
id_permutation: {self.id_permutation}
|
||||
description: {self.description}
|
||||
id_category: {self.id_category}
|
||||
latency_manufacture: {self.latency_manufacture}
|
||||
quantity_min: {self.quantity_min}
|
||||
quantity_max: {self.quantity_max}
|
||||
quantity_step: {self.quantity_step}
|
||||
quantity_stock: {self.quantity_stock}
|
||||
id_stripe_product: {self.id_stripe_product}
|
||||
is_subscription: {self.is_subscription}
|
||||
name_recurrence_interval: {self.name_recurrence_interval}
|
||||
name_plural_recurrence_interval: {self.name_plural_recurrence_interval}
|
||||
count_recurrence_interval: {self.count_recurrence_interval}
|
||||
display_order: {self.display_order}
|
||||
can_view: {self.can_view}
|
||||
can_edit: {self.can_edit}
|
||||
can_admin: {self.can_admin}
|
||||
variations: {self.variations}
|
||||
images: {self.images}
|
||||
delivery_options: {self.delivery_options}
|
||||
prices: {self.prices}
|
||||
'''
|
||||
"""
|
||||
price_GBP_full: {self.price_GBP_full}
|
||||
price_GBP_min: {self.price_GBP_min}
|
||||
"""
|
||||
|
||||
def add_variation(self, variation):
|
||||
_m = 'Product_Permutation.add_variation'
|
||||
av.val_instance(variation, 'variation', _m, Variation)
|
||||
try:
|
||||
self.variation_index[variation.id_variation]
|
||||
raise ValueError(f"{av.error_msg_str(variation, 'variation', _m, Variation)}\nVariation already in product.")
|
||||
except KeyError:
|
||||
self.variation_index[variation.id_variation] = len(self.variations)
|
||||
self.variations.append(variation)
|
||||
def add_price(self, price):
|
||||
_m = 'Product_Permutation.add_price'
|
||||
av.val_instance(price, 'price', _m, Price)
|
||||
try:
|
||||
self.price_index[price.display_order]
|
||||
raise ValueError(f"{av.error_msg_str(price, 'price', _m, Price)}\nPrice already in product.")
|
||||
except KeyError:
|
||||
self.price_index[price.display_order] = len(self.prices)
|
||||
self.prices.append(price)
|
||||
def add_image(self, image):
|
||||
_m = 'Product_Permutation.add_image'
|
||||
av.val_instance(image, 'image', _m, Image)
|
||||
try:
|
||||
self.image_index[image.id_image]
|
||||
raise ValueError(f"{av.error_msg_str(image, 'image', _m, Image)}\nImage already in product.")
|
||||
except KeyError:
|
||||
self.image_index[image.id_image] = len(self.images)
|
||||
self.images.append(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
_m = 'Product_Permutation.add_delivery_option'
|
||||
av.val_instance(delivery_option, 'delivery_option', _m, Delivery_Option)
|
||||
try:
|
||||
self.delivery_option_index[delivery_option.id_option]
|
||||
raise ValueError(f"{av.error_msg_str(delivery_option, 'delivery_option', _m, Delivery_Option)}\nDelivery_Option already in product.")
|
||||
except KeyError:
|
||||
self.delivery_option_index[delivery_option.id_option] = len(self.delivery_options)
|
||||
self.delivery_options.append(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
_m = 'Product_Permutation.add_discount'
|
||||
av.val_instance(discount, 'discount', _m, Discount)
|
||||
try:
|
||||
self.discount_index[discount.display_order]
|
||||
raise ValueError(f"{av.error_msg_str(discount, 'discount', _m, Discount)}\nDiscount already in product.")
|
||||
except KeyError:
|
||||
self.discount_index[discount.display_order] = len(self.discounts)
|
||||
self.discounts.append(discount)
|
||||
|
||||
def add_stock_item(self, stock_item):
|
||||
av.val_instance(stock_item, 'stock_item', 'Product_Permutation.add_stock_item', Stock_Item)
|
||||
self.stock_items.append(stock_item)
|
||||
|
||||
def get_image_from_index(self, index_image):
|
||||
try:
|
||||
return self.images[index_image]
|
||||
except:
|
||||
raise IndexError(f"Invalid image index: {index_image}")
|
||||
|
||||
def get_price_from_code_currency(self, code_currency):
|
||||
for price in self.prices:
|
||||
if price.code_currency == code_currency:
|
||||
return price
|
||||
|
||||
def to_row_permutation(self):
|
||||
a = {
|
||||
Product.ATTR_ID_CATEGORY: self.id_category,
|
||||
Product.ATTR_ID_PRODUCT: self.id_product,
|
||||
Product.FLAG_VARIATIONS: self.output_variations(),
|
||||
Product_Permutation.FLAG_QUANTITY_STOCK: self.quantity_stock,
|
||||
Product_Permutation.FLAG_QUANTITY_MIN: self.quantity_min,
|
||||
Product_Permutation.FLAG_QUANTITY_MAX: self.quantity_max,
|
||||
Product_Permutation.FLAG_COST_LOCAL: f"<strong>{self.symbol_currency_cost}</strong>{self.cost_local}"
|
||||
}
|
||||
print('permutation row: ', a)
|
||||
return a
|
||||
|
||||
"""
|
||||
class Product_Filters():
|
||||
ids: str # csv
|
||||
categories: str # csv
|
||||
|
||||
def __new__(cls, product_ids, product_categories):
|
||||
# Initialiser - validation
|
||||
_m = 'Product_Filters.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
# av.val_list_instances(product_ids, 'product_ids', _m, str, v_arg_type=v_arg_type)
|
||||
# av.val_list_instances(product_categories, 'product_categories', _m, Product_Category_Enum, v_arg_type=v_arg_type)
|
||||
av.val_str(product_ids, 'product_ids', _m, v_arg_type=v_arg_type)
|
||||
av.val_str(product_categories, 'product_categories', _m, v_arg_type=v_arg_type)
|
||||
return super(Product, cls).__new__(cls)
|
||||
|
||||
def __init__(self, product_ids, product_categories):
|
||||
# Constructor
|
||||
self.ids = product_ids
|
||||
self.categories = product_categories
|
||||
|
||||
class Price():
|
||||
product: Product
|
||||
currency: Currency_Enum
|
||||
|
||||
def make_from_product_currency(product, code_currency):
|
||||
name_method = 'make_from_product_currency'
|
||||
av.val_instance(product, 'product', name_method, Product)
|
||||
price = Price()
|
||||
price.product = product
|
||||
price.currency = Currency_Enum.get_member_by_text(code_currency)
|
||||
"""
|
||||
|
||||
class Price(db.Model):
|
||||
id_price = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
id_currency = db.Column(db.Integer)
|
||||
code_currency = db.Column(db.String)
|
||||
name_currency = db.Column(db.String)
|
||||
symbol_currency = db.Column(db.String)
|
||||
id_region = db.Column(db.Integer)
|
||||
value_local_VAT_incl = db.Column(db.Float)
|
||||
value_local_VAT_excl = db.Column(db.Float)
|
||||
display_order = db.Column(db.Float, primary_key=True)
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Price.make_from_DB_product'
|
||||
price = Price()
|
||||
price.id_price = query_row[0]
|
||||
price.id_permutation = query_row[1]
|
||||
price.id_product = query_row[2]
|
||||
price.id_category = query_row[3]
|
||||
price.id_currency = query_row[4]
|
||||
price.code_currency = query_row[5]
|
||||
price.name_currency = query_row[6]
|
||||
price.symbol_currency = query_row[7]
|
||||
price.id_region = query_row[8]
|
||||
price.value_local_VAT_incl = query_row[9]
|
||||
price.value_local_VAT_excl = query_row[10]
|
||||
price.display_order = query_row[11]
|
||||
return price
|
||||
|
||||
def __repr__(self):
|
||||
return f'''Price
|
||||
id: {self.id_price}
|
||||
id_permutation: {self.id_permutation}
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
id_currency: {self.id_currency}
|
||||
code_currency: {self.code_currency}
|
||||
name_currency: {self.name_currency}
|
||||
symbol_currency: {self.symbol_currency}
|
||||
id_region: {self.id_region}
|
||||
value_local (VAT incl): {self.value_local_VAT_incl}
|
||||
value_local (VAT excl): {self.value_local_VAT_excl}
|
||||
display_order (UID): {self.display_order}
|
||||
'''
|
||||
"""
|
||||
class Permutation_Variation_Link(db.Model):
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
id_variation = db.Column(db.Integer)
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Permutation_Variation_Link.make_from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
link = Permutation_Variation_Link()
|
||||
link.id_permutation = query_row[0]
|
||||
link.id_product = query_row[1]
|
||||
link.id_category = query_row[2]
|
||||
link.id_variation = query_row[3]
|
||||
return link
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class Product_Filters():
|
||||
# id_user: str
|
||||
get_all_category: bool
|
||||
get_inactive_category: bool
|
||||
get_first_category_only: bool
|
||||
ids_category: str
|
||||
get_all_product: bool
|
||||
get_inactive_product: bool
|
||||
get_first_product_only: bool
|
||||
ids_product: str
|
||||
get_all_permutation: bool
|
||||
get_inactive_permutation: bool
|
||||
get_first_permutation_only: bool
|
||||
ids_permutation: str
|
||||
get_all_image: bool
|
||||
get_inactive_image: bool
|
||||
get_first_image_only: bool
|
||||
ids_image: str
|
||||
get_all_region: bool
|
||||
get_inactive_region: bool
|
||||
get_first_region_only: bool
|
||||
ids_region: str
|
||||
get_all_currency: bool
|
||||
get_inactive_currency: bool
|
||||
get_first_currency_only: bool
|
||||
ids_currency: str
|
||||
get_all_discount: bool
|
||||
get_inactive_discount: bool
|
||||
ids_discount: str
|
||||
get_products_quantity_stock_below_min: bool
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'a_id_user': None,
|
||||
'a_get_all_category': self.get_all_category,
|
||||
'a_get_inactive_category': self.get_inactive_category,
|
||||
'a_get_first_category_only': self.get_first_category_only,
|
||||
'a_ids_category': self.ids_category,
|
||||
'a_get_all_product': self.get_all_product,
|
||||
'a_get_inactive_product': self.get_inactive_product,
|
||||
'a_get_first_product_only': self.get_first_product_only,
|
||||
'a_ids_product': self.ids_product,
|
||||
'a_get_all_permutation': self.get_all_permutation,
|
||||
'a_get_inactive_permutation': self.get_inactive_permutation,
|
||||
'a_get_first_permutation_only': self.get_first_permutation_only,
|
||||
'a_ids_permutation': self.ids_permutation,
|
||||
'a_get_all_image': self.get_all_image,
|
||||
'a_get_inactive_image': self.get_inactive_image,
|
||||
'a_get_first_image_only': self.get_first_image_only,
|
||||
'a_ids_image': self.ids_image,
|
||||
'a_get_all_delivery_region': self.get_all_region,
|
||||
'a_get_inactive_delivery_region': self.get_inactive_region,
|
||||
'a_get_first_delivery_region_only': self.get_first_region_only,
|
||||
'a_ids_delivery_region': self.ids_region,
|
||||
'a_get_all_currency': self.get_all_currency,
|
||||
'a_get_inactive_currency': self.get_inactive_currency,
|
||||
'a_get_first_currency_only': self.get_first_currency_only,
|
||||
'a_ids_currency': self.ids_currency,
|
||||
'a_get_all_discount': self.get_all_discount,
|
||||
'a_get_inactive_discount': self.get_inactive_discount,
|
||||
'a_ids_discount': self.ids_discount,
|
||||
'a_get_products_quantity_stock_below_min': self.get_products_quantity_stock_below_min
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_form(form):
|
||||
# if not (form is Form_Filters_Permutation): raise ValueError(f'Invalid form type: {type(form)}')
|
||||
av.val_instance(form, 'form', 'Product_Filters.from_form', Form_Filters_Permutation)
|
||||
has_category_filter = not (form.id_category.data == '0' or form.id_category.data == '')
|
||||
has_product_filter = not (form.id_product.data == '0' or form.id_product.data == '')
|
||||
get_permutations_stock_below_min = av.input_bool(form.is_out_of_stock.data, "is_out_of_stock", "Product_Filters.from_form")
|
||||
print(f'form question: {type(form.is_out_of_stock)}\nbool interpretted: {get_permutations_stock_below_min}\type form: {type(form)}')
|
||||
return Product_Filters(
|
||||
get_all_category = not has_category_filter,
|
||||
get_inactive_category = False,
|
||||
get_first_category_only = False,
|
||||
ids_category = form.id_category.data,
|
||||
get_all_product = not has_product_filter,
|
||||
get_inactive_product = False,
|
||||
get_first_product_only = False,
|
||||
ids_product = form.id_product.data,
|
||||
get_all_permutation = not get_permutations_stock_below_min,
|
||||
get_inactive_permutation = False,
|
||||
get_first_permutation_only = False,
|
||||
ids_permutation = '',
|
||||
get_all_image = False,
|
||||
get_inactive_image = False,
|
||||
get_first_image_only = False,
|
||||
ids_image = '',
|
||||
get_all_region = False,
|
||||
get_inactive_region = False,
|
||||
get_first_region_only = False,
|
||||
ids_region = '',
|
||||
get_all_currency = False,
|
||||
get_inactive_currency = False,
|
||||
get_first_currency_only = False,
|
||||
ids_currency = '',
|
||||
get_all_discount = False,
|
||||
get_inactive_discount = False,
|
||||
ids_discount = '',
|
||||
get_products_quantity_stock_below_min = get_permutations_stock_below_min
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_default():
|
||||
return Product_Filters(
|
||||
get_all_category = True,
|
||||
get_inactive_category = False,
|
||||
get_first_category_only = False,
|
||||
ids_category = '',
|
||||
get_all_product = True,
|
||||
get_inactive_product = False,
|
||||
get_first_product_only = False,
|
||||
ids_product = '',
|
||||
get_all_permutation = True,
|
||||
get_inactive_permutation = False,
|
||||
get_first_permutation_only = False,
|
||||
ids_permutation = '',
|
||||
get_all_image = True,
|
||||
get_inactive_image = False,
|
||||
get_first_image_only = False,
|
||||
ids_image = '',
|
||||
get_all_region = True,
|
||||
get_inactive_region = False,
|
||||
get_first_region_only = False,
|
||||
ids_region = '',
|
||||
get_all_currency = True,
|
||||
get_inactive_currency = False,
|
||||
get_first_currency_only = False,
|
||||
ids_currency = '',
|
||||
get_all_discount = True,
|
||||
get_inactive_discount = False,
|
||||
ids_discount = '',
|
||||
get_products_quantity_stock_below_min = True
|
||||
)
|
||||
@@ -55,7 +55,7 @@ class SQL_Error(db.Model):
|
||||
super().__init__()
|
||||
"""
|
||||
|
||||
def make_from_DB_record(record):
|
||||
def from_DB_record(record):
|
||||
error = SQL_Error()
|
||||
error.display_order = record[0]
|
||||
error.code = record[1]
|
||||
|
||||
BIN
business_objects/store/__pycache__/basket.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/basket.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/currency.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/currency.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
business_objects/store/__pycache__/discount.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/discount.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/image.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/image.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/order.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/order.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/product.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/product.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
business_objects/store/__pycache__/product_price.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/product_price.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
business_objects/store/__pycache__/stock_item.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/stock_item.cpython-312.pyc
Normal file
Binary file not shown.
BIN
business_objects/store/__pycache__/store_base.cpython-312.pyc
Normal file
BIN
business_objects/store/__pycache__/store_base.cpython-312.pyc
Normal file
Binary file not shown.
@@ -19,9 +19,9 @@ Business object for basket
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
# from lib import data_types
|
||||
from business_objects.product import Product, Product_Filters
|
||||
from business_objects.discount import Discount
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
from business_objects.store.product import Product #, Product_Filters
|
||||
from business_objects.store.discount import Discount
|
||||
from business_objects.store.delivery_option import Delivery_Option
|
||||
# from forms import Form_Product
|
||||
# from models.model_view_store import Model_View_Store # circular
|
||||
# from datastores.datastore_store import DataStore_Store # circular
|
||||
@@ -49,9 +49,9 @@ class Basket_Item():
|
||||
self.is_available = True
|
||||
"""
|
||||
|
||||
def make_from_product_and_quantity_and_VAT_included(product, quantity, is_included_VAT):
|
||||
def from_product_and_quantity_and_VAT_included(product, quantity, is_included_VAT):
|
||||
# Initialiser - validation
|
||||
_m = 'Basket_Item.make_from_product_and_quantity'
|
||||
_m = 'Basket_Item.from_product_and_quantity'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_instance(product, 'product', _m, Product, v_arg_type=v_arg_type)
|
||||
av.full_val_float(quantity, 'quantity', _m, product.get_quantity_min(), v_arg_type=v_arg_type)
|
||||
@@ -10,26 +10,10 @@ Description:
|
||||
Business object for product
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from extensions import db
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
# CLASSES
|
||||
"""
|
||||
@@ -56,15 +40,22 @@ class Currency_Enum(Enum):
|
||||
"""
|
||||
|
||||
class Currency(db.Model):
|
||||
ATTR_ID_CURRENCY: ClassVar[str] = 'id-currency'
|
||||
FLAG_CODE: ClassVar[str] = 'code-currency'
|
||||
FLAG_NAME: ClassVar[str] = 'name-currency'
|
||||
FLAG_SYMBOL: ClassVar[str] = 'symbol-currency'
|
||||
FLAG_FACTOR_FROM_GBP: ClassVar[str] = 'factor-from-GBP-currency'
|
||||
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-currency'
|
||||
|
||||
id_currency = db.Column(db.Integer, primary_key=True)
|
||||
code = db.Column(db.String)
|
||||
name = db.Column(db.String)
|
||||
symbol = db.Column(db.String)
|
||||
code = db.Column(db.String(50))
|
||||
name = db.Column(db.String(255))
|
||||
symbol = db.Column(db.String(50))
|
||||
factor_from_GBP = db.Column(db.Float)
|
||||
display_order = db.Column(db.Integer)
|
||||
|
||||
def make_from_DB_currency(query_row):
|
||||
# _m = 'Currency.make_from_DB_currency'
|
||||
def from_DB_currency(query_row):
|
||||
# _m = 'Currency.from_DB_currency'
|
||||
# v_arg_type = 'class attribute'
|
||||
currency = Currency()
|
||||
currency.id_currency = query_row[0]
|
||||
@@ -75,8 +66,8 @@ class Currency(db.Model):
|
||||
currency.display_order = query_row[5]
|
||||
return currency
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Currency.make_from_DB_product'
|
||||
def from_DB_product(query_row):
|
||||
_m = 'Currency.from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
currency = Currency()
|
||||
currency.id_permutation = query_row[0]
|
||||
@@ -93,4 +84,23 @@ class Currency(db.Model):
|
||||
symbol: {self.symbol}
|
||||
factor from GBP: {self.factor_from_GBP}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
'''
|
||||
def to_json(self):
|
||||
return {
|
||||
Currency.ATTR_ID_CURRENCY: self.id_currency,
|
||||
Currency.FLAG_CODE: self.code,
|
||||
Currency.FLAG_NAME: self.name,
|
||||
Currency.FLAG_SYMBOL: self.symbol,
|
||||
Currency.FLAG_FACTOR_FROM_GBP: self.factor_from_GBP,
|
||||
Currency.FLAG_DISPLAY_ORDER: self.display_order
|
||||
}
|
||||
@staticmethod
|
||||
def from_json(json_currency):
|
||||
currency = Currency()
|
||||
currency.id_currency = json_currency[Currency.ATTR_ID_CURRENCY]
|
||||
currency.code = json_currency[Currency.FLAG_CODE]
|
||||
currency.name = json_currency[Currency.FLAG_NAME]
|
||||
currency.symbol = json_currency[Currency.FLAG_SYMBOL]
|
||||
currency.factor_from_GBP = json_currency[Currency.FLAG_FACTOR_FROM_GBP]
|
||||
currency.display_order = json_currency[Currency.FLAG_DISPLAY_ORDER]
|
||||
return currency
|
||||
@@ -10,25 +10,8 @@ Description:
|
||||
Business object for delivery option
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
from extensions import db
|
||||
|
||||
|
||||
# CLASSES
|
||||
@@ -80,7 +63,7 @@ class Delivery_Option(db.Model):
|
||||
display_order = db.Column(db.Integer)
|
||||
def __init__(self):
|
||||
self.delivery_regions = []
|
||||
def make_from_DB_product(query_row):
|
||||
def from_DB_product(query_row):
|
||||
option = Delivery_Option()
|
||||
option.id_option = query_row[0]
|
||||
option.id_product = query_row[1]
|
||||
@@ -10,32 +10,23 @@ Description:
|
||||
Business object for delivery region
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from extensions import db
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Enum_Delivery_Region(Enum):
|
||||
UK = 0
|
||||
|
||||
class Delivery_Region(db.Model):
|
||||
ATTR_ID_REGION: ClassVar[str] = 'id-region'
|
||||
FLAG_CODE: ClassVar[str] = 'code-region'
|
||||
FLAG_NAME: ClassVar[str] = 'name-region'
|
||||
FLAG_ACTIVE: ClassVar[str] = 'active-region'
|
||||
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-region'
|
||||
|
||||
id_region = db.Column(db.Integer, primary_key=True)
|
||||
"""
|
||||
id_category = db.Column(db.Integer)
|
||||
@@ -68,14 +59,14 @@ class Delivery_Region(db.Model):
|
||||
self.code = code
|
||||
self.display_order = display_order
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
def from_DB_product(query_row):
|
||||
region = Delivery_Region()
|
||||
region.id_region = query_row[0]
|
||||
region.name = query_row[1]
|
||||
region.code = query_row[2]
|
||||
# self.display_order = query_row[3]
|
||||
return region
|
||||
def make_from_DB_region(query_row):
|
||||
def from_DB_region(query_row):
|
||||
region = Delivery_Region()
|
||||
region.id_region = query_row[0]
|
||||
region.code = query_row[1]
|
||||
@@ -91,6 +82,20 @@ class Delivery_Region(db.Model):
|
||||
active: {self.active}
|
||||
display_order: {self.display_order}
|
||||
'''
|
||||
|
||||
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
Delivery_Region.ATTR_ID_REGION: self.id_region,
|
||||
Delivery_Region.FLAG_CODE: self.code,
|
||||
Delivery_Region.FLAG_NAME: self.name,
|
||||
Delivery_Region.FLAG_ACTIVE: self.active,
|
||||
Delivery_Region.FLAG_DISPLAY_ORDER: self.display_order
|
||||
}
|
||||
@staticmethod
|
||||
def from_json(json_region):
|
||||
region = Delivery_Region()
|
||||
region.id_region = json_region[Delivery_Region.ATTR_ID_REGION]
|
||||
region.code = json_region[Delivery_Region.FLAG_CODE]
|
||||
region.name = json_region[Delivery_Region.FLAG_NAME]
|
||||
region.active = json_region[Delivery_Region.FLAG_ACTIVE]
|
||||
region.display_order = json_region[Delivery_Region.FLAG_DISPLAY_ORDER]
|
||||
return region
|
||||
@@ -10,25 +10,8 @@ Description:
|
||||
Business object for discount
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
from extensions import db
|
||||
|
||||
|
||||
# CLASSES
|
||||
@@ -54,7 +37,7 @@ class Discount(db.Model):
|
||||
|
||||
def __init__(self):
|
||||
self.delivery_regions = []
|
||||
def make_from_DB_product(query_row):
|
||||
def from_DB_product(query_row):
|
||||
discount = Discount()
|
||||
discount.id_discount = query_row[0]
|
||||
discount.id_category = query_row[1]
|
||||
@@ -10,28 +10,13 @@ Description:
|
||||
Business object for product image
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from extensions import db
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Resolution_Level_Enum(Enum):
|
||||
THUMBNAIL = 0
|
||||
LOW = 1
|
||||
@@ -88,8 +73,8 @@ class Image(db.Model):
|
||||
self.display_order = display_order
|
||||
super().__init__()
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
_m = 'Image.make_from_DB_product'
|
||||
def from_DB_product(query_row):
|
||||
_m = 'Image.from_DB_product'
|
||||
# print(f'image: {query_row}')
|
||||
image = Image()
|
||||
image.id_image = query_row[0]
|
||||
@@ -10,17 +10,11 @@ Description:
|
||||
Business object for order
|
||||
"""
|
||||
|
||||
# IMPORTS
|
||||
# VARIABLE INSTANTIATION
|
||||
# CLASSES
|
||||
# METHODS
|
||||
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
# from lib import data_types
|
||||
from business_objects.product import Product
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
from business_objects.store.product import Product
|
||||
from business_objects.store.delivery_option import Delivery_Option
|
||||
# from forms import Form_Product
|
||||
# from models.model_view_store import Model_View_Store # circular
|
||||
# external
|
||||
454
business_objects/store/product.py
Normal file
454
business_objects/store/product.py
Normal file
@@ -0,0 +1,454 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Business Object
|
||||
|
||||
Description:
|
||||
Business object for product
|
||||
"""
|
||||
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit, Form_Filters_Permutation
|
||||
from business_objects.store.delivery_option import Delivery_Option
|
||||
from business_objects.store.discount import Discount
|
||||
from business_objects.store.image import Image
|
||||
from business_objects.store.product_permutation import Product_Permutation
|
||||
from business_objects.store.product_price import Product_Price
|
||||
from business_objects.store.store_base import Store_Base
|
||||
from business_objects.store.stock_item import Stock_Item
|
||||
from business_objects.store.product_variation import Product_Variation
|
||||
from business_objects.store.product_variation_tree import Product_Variation_Tree
|
||||
from extensions import db
|
||||
# external
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, List
|
||||
"""
|
||||
class Enum_Status_Stock(Enum):
|
||||
OUT = 0
|
||||
LOW = 1
|
||||
IN = 99
|
||||
|
||||
def text(self):
|
||||
return Enum_Status_Stock.Enum_Status_Stock_Text(self)
|
||||
|
||||
def Enum_Status_Stock_Text(status):
|
||||
av.val_instance(status, 'category', 'Enum_Status_Stock_Text', Enum_Status_Stock)
|
||||
if status == Enum_Status_Stock.OUT:
|
||||
return 'Out of stock'
|
||||
elif status == Enum_Status_Stock.LOW:
|
||||
return 'Low on stock'
|
||||
else:
|
||||
return 'Fully stocked'
|
||||
|
||||
def get_member_by_text(text):
|
||||
return data_types.get_enum_member_by_text(Enum_Status_Stock, text.upper())
|
||||
|
||||
"""
|
||||
class Product(db.Model, Store_Base):
|
||||
FLAG_NAME: ClassVar[str] = 'name-product'
|
||||
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-product'
|
||||
FLAG_CAN_VIEW: ClassVar[str] = 'can-view-product'
|
||||
FLAG_CAN_EDIT: ClassVar[str] = 'can-edit-product'
|
||||
FLAG_CAN_ADMIN: ClassVar[str] = 'can-admin-product'
|
||||
FLAG_HAS_VARIATIONS: ClassVar[str] = 'has-variations-product'
|
||||
FLAG_INDEX_PERMUTATION_SELECTED: ClassVar[str] = 'index-permutation-selected'
|
||||
FLAG_VARIATION_TREES: ClassVar[str] = 'variation-trees'
|
||||
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
id_category = db.Column(db.Integer)
|
||||
name = db.Column(db.String(255))
|
||||
display_order = db.Column(db.Integer)
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
# form_basket_add: Form_Basket_Add
|
||||
# form_basket_edit: Form_Basket_Edit
|
||||
# has_variations: bool
|
||||
# index_permutation_selected: int
|
||||
|
||||
def __init__(self):
|
||||
self.permutations = []
|
||||
self.permutation_index = {}
|
||||
self.variation_trees = []
|
||||
self.index_permutation_selected = None
|
||||
self.has_variations = False
|
||||
super().__init__()
|
||||
Store_Base.__init__(self)
|
||||
self.form_basket_add = Form_Basket_Add()
|
||||
self.form_basket_edit = Form_Basket_Edit()
|
||||
|
||||
def from_DB_product(query_row):
|
||||
_m = 'Product.from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
product = Product()
|
||||
product.id_product = query_row[0]
|
||||
product.id_category = query_row[5]
|
||||
product.name = query_row[2]
|
||||
product.has_variations = av.input_bool(query_row[4], "has_variations", _m, v_arg_type=v_arg_type)
|
||||
product.display_order = query_row[22]
|
||||
product.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
|
||||
product.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
|
||||
product.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
|
||||
return product
|
||||
"""
|
||||
def from_permutation(permutation, has_variations = False):
|
||||
_m = 'Product.from_permutation'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_instance(permutation, 'permutation', _m, Product_Permutation, v_arg_type=v_arg_type)
|
||||
product = Product()
|
||||
product.has_variations = has_variations
|
||||
product.index_permutation_selected = 0
|
||||
product.id_product = permutation.id_product
|
||||
product.id_category = permutation.id_category
|
||||
product.display_order = permutation.display_order
|
||||
product.can_view = permutation.can_view
|
||||
product.can_edit = permutation.can_edit
|
||||
product.can_admin = permutation.can_admin
|
||||
product.permutations.append(permutation)
|
||||
# product.get_variation_trees()
|
||||
return product
|
||||
"""
|
||||
def add_permutation(self, permutation):
|
||||
_m = 'Product.add_permutation'
|
||||
av.val_instance(permutation, 'permutation', _m, Product_Permutation)
|
||||
try:
|
||||
self.permutation_index[permutation.id_permutation]
|
||||
raise ValueError(f"{av.error_msg_str(permutation, 'permutation', _m, Product_Permutation)}\nPermutation ID already in product")
|
||||
except KeyError:
|
||||
self.permutation_index[permutation.id_permutation] = len(self.permutations)
|
||||
self.permutations.append(permutation)
|
||||
"""
|
||||
if self.has_variations:
|
||||
self.has_variations = False
|
||||
"""
|
||||
if self.index_permutation_selected is None:
|
||||
self.index_permutation_selected = self.permutation_index[permutation.id_permutation]
|
||||
print(f'setting selected permutation for product {self.id_product} to {self.index_permutation_selected}') # :\n{self.permutations[self.index_permutation_selected]}
|
||||
"""
|
||||
def from_permutations(permutations):
|
||||
_m = 'Product.from_permutations'
|
||||
v_arg_type = 'class attribute'
|
||||
if len(permutations) == 0:
|
||||
raise ValueError(av.error_msg_str(permutations, 'permutations', _m, list, v_arg_type=v_arg_type))
|
||||
product = Product()
|
||||
product.has_variations = True
|
||||
product.index_permutation_selected = 0
|
||||
product.id_product = permutations[0].id_product
|
||||
product.id_category = permutations[0].id_category
|
||||
product.display_order = permutations[0].display_order
|
||||
product.can_view = True
|
||||
product.can_edit = True
|
||||
product.can_admin = True
|
||||
for permutation in permutations:
|
||||
product.can_view &= permutation.can_view
|
||||
product.can_edit &= permutation.can_edit
|
||||
product.can_admin &= permutation.can_admin
|
||||
product.permutations.append(permutations)
|
||||
product.get_variation_trees()
|
||||
return product
|
||||
"""
|
||||
def get_variation_trees(self):
|
||||
self.variation_trees = []
|
||||
for index_permutation in range(len(self.permutations)):
|
||||
variation_tree = Product_Variation_Tree.from_product_permutation(self.permutations[index_permutation])
|
||||
found_variation_tree_match = False
|
||||
for index_tree in range(len(self.variation_trees)):
|
||||
if self.variation_trees[index_tree].is_equal(variation_tree):
|
||||
found_variation_tree_match = True
|
||||
break
|
||||
if not found_variation_tree_match:
|
||||
self.variation_trees.append(variation_tree)
|
||||
def from_DB_Stripe_product(query_row):
|
||||
permutation = Product_Permutation.from_DB_Stripe_product(query_row)
|
||||
product = Product.from_permutation(permutation)
|
||||
return product
|
||||
def from_DB_Stripe_price(query_row):
|
||||
permutation = Product_Permutation.from_DB_Stripe_price(query_row)
|
||||
product = Product.from_permutation(permutation)
|
||||
return product
|
||||
def from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
permutation = Product_Permutation.from_json(json_basket_item, key_id_product, key_id_permutation)
|
||||
product = Product.from_permutation(permutation)
|
||||
return product
|
||||
def get_permutation_selected(self):
|
||||
try:
|
||||
return self.permutations[self.index_permutation_selected]
|
||||
except:
|
||||
raise ValueError(f'list index {self.index_permutation_selected} out of range')
|
||||
def output_lead_time(self):
|
||||
return self.get_permutation_selected().output_lead_time()
|
||||
def output_delivery_date(self):
|
||||
return self.get_permutation_selected().output_delivery_date()
|
||||
def output_price(self, is_included_VAT):
|
||||
return self.get_permutation_selected().output_price(is_included_VAT)
|
||||
def output_price_VAT_incl(self):
|
||||
return self.get_permutation_selected().output_price(True)
|
||||
def output_price_VAT_excl(self):
|
||||
return self.get_permutation_selected().output_price(False)
|
||||
def get_price_local(self, is_included_VAT):
|
||||
if is_included_VAT:
|
||||
return self.get_price_local_VAT_incl()
|
||||
else:
|
||||
return self.get_price_local_VAT_excl()
|
||||
def get_price_local_VAT_incl(self):
|
||||
return self.get_permutation_selected().get_price_local_VAT_incl()
|
||||
def get_price_local_VAT_excl(self):
|
||||
return self.get_permutation_selected().get_price_local_VAT_excl()
|
||||
def get_quantity_min(self):
|
||||
return self.get_permutation_selected().quantity_min
|
||||
def get_id_permutation(self):
|
||||
return self.get_permutation_selected().id_permutation
|
||||
def get_image_from_index(self, index_image):
|
||||
return self.get_permutation_selected().images[index_image]
|
||||
def get_name(self):
|
||||
return self.get_permutation_selected().name
|
||||
def get_description(self):
|
||||
return self.get_permutation_selected().description
|
||||
def output_currency(self):
|
||||
return self.get_permutation_selected().get_price().symbol_currency
|
||||
"""
|
||||
def add_form_basket_add(self):
|
||||
self.form_basket_add = None
|
||||
|
||||
def add_form_basket_edit(self):
|
||||
self.form_basket_edit = None
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''Product
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
name: {self.name}
|
||||
display_order: {self.display_order}
|
||||
can_view: {self.can_view}
|
||||
can_edit: {self.can_edit}
|
||||
can_admin: {self.can_admin}
|
||||
has_variations: {self.has_variations}
|
||||
permutations: {self.permutations}
|
||||
variation trees: {self.variation_trees}
|
||||
'''
|
||||
"""
|
||||
def get_index_permutation_from_id(self, id_permutation):
|
||||
if id_permutation is None and not self.has_variations:
|
||||
return 0
|
||||
for index_permutation in range(len(self.permutations)):
|
||||
permutation = self.permutations[index_permutation]
|
||||
if permutation.id_permutation == id_permutation:
|
||||
return index_permutation
|
||||
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Product.get_index_permutation_from_id', int)}\nPermutation ID not found.")
|
||||
"""
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Product.add_variation', Product_Variation)
|
||||
# print(f'variation: {variation}')
|
||||
index_permutation = self.permutation_index[variation.id_permutation] # self.get_index_permutation_from_id(variation.id_permutation)
|
||||
self.permutations[index_permutation].add_variation(variation)
|
||||
def add_price(self, price):
|
||||
av.val_instance(price, 'price', 'Product.add_price', Product_Price)
|
||||
index_permutation = self.permutation_index[price.id_permutation] # self.get_index_permutation_from_id(price.id_permutation)
|
||||
self.permutations[index_permutation].add_price(price)
|
||||
def add_image(self, image):
|
||||
av.val_instance(image, 'image', 'Product.add_image', Image)
|
||||
index_permutation = self.permutation_index[image.id_permutation] # self.get_index_permutation_from_id(image.id_permutation)
|
||||
self.permutations[index_permutation].add_image(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Product.add_delivery_option', Delivery_Option)
|
||||
index_permutation = self.permutation_index[delivery_option.id_permutation] # self.get_index_permutation_from_id(delivery_option.id_permutation)
|
||||
self.permutations[index_permutation].add_delivery_option(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
av.val_instance(discount, 'discount', 'Product.add_discount', Discount)
|
||||
index_permutation = self.permutation_index[discount.id_permutation] # self.get_index_permutation_from_id(discount.id_permutation)
|
||||
self.permutations[index_permutation].add_discount(discount)
|
||||
def add_stock_item(self, stock_item):
|
||||
av.val_instance(stock_item, 'stock_item', 'Product.add_stock_item', Stock_Item)
|
||||
index_permutation = self.permutation_index[stock_item.id_permutation]
|
||||
self.permutations[index_permutation].add_stock_item(stock_item)
|
||||
def has_permutations(self):
|
||||
return len(self.permutations) > 0
|
||||
def is_available(self):
|
||||
if len(self.permutations) == 0:
|
||||
return False
|
||||
for permutation in self.permutations:
|
||||
if permutation.is_available():
|
||||
return True
|
||||
return False
|
||||
def to_list_rows_permutation(self):
|
||||
list_rows = []
|
||||
for permutation in self.permutations:
|
||||
list_rows.append(permutation.to_row_permutation())
|
||||
return list_rows
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
product = cls()
|
||||
product.id_product = json[cls.ATTR_ID_PRODUCT]
|
||||
product.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
|
||||
product.name = json[cls.FLAG_NAME]
|
||||
product.display_order = json[cls.FLAG_DISPLAY_ORDER]
|
||||
product.can_view = json[cls.FLAG_CAN_VIEW]
|
||||
product.can_edit = json[cls.FLAG_CAN_EDIT]
|
||||
product.can_admin = json[cls.FLAG_CAN_ADMIN]
|
||||
product.has_variations = json[cls.FLAG_HAS_VARIATIONS]
|
||||
product.index_permutation_selected = json[cls.FLAG_INDEX_PERMUTATION_SELECTED]
|
||||
product.permutations = []
|
||||
for json_permutation in json[cls.ATTR_ID_PRODUCT_PERMUTATION]:
|
||||
product.permutations.append(Product_Permutation.from_json(json_permutation))
|
||||
product.variation_trees = []
|
||||
for json_tree in json[cls.FLAG_VARIATION_TREES]:
|
||||
product.variation_trees.append(Product_Variation_Tree.from_json(json_tree))
|
||||
return product
|
||||
def to_json(self):
|
||||
return {
|
||||
self.ATTR_ID_PRODUCT: self.id_product,
|
||||
self.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
|
||||
self.FLAG_NAME: self.name,
|
||||
self.FLAG_DISPLAY_ORDER: self.display_order,
|
||||
self.FLAG_CAN_VIEW: self.can_view,
|
||||
self.FLAG_CAN_EDIT: self.can_edit,
|
||||
self.FLAG_CAN_ADMIN: self.can_admin,
|
||||
self.FLAG_HAS_VARIATIONS: self.has_variations,
|
||||
self.FLAG_INDEX_PERMUTATION_SELECTED: self.index_permutation_selected,
|
||||
self.ATTR_ID_PRODUCT_PERMUTATION: [permutation.to_json() for permutation in self.permutations],
|
||||
self.FLAG_VARIATION_TREES: [tree.to_json() for tree in self.variation_trees]
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class Product_Filters():
|
||||
# id_user: str
|
||||
get_all_category: bool
|
||||
get_inactive_category: bool
|
||||
# get_first_category_only: bool
|
||||
ids_category: str
|
||||
get_all_product: bool
|
||||
get_inactive_product: bool
|
||||
# get_first_product_only: bool
|
||||
ids_product: str
|
||||
get_all_permutation: bool
|
||||
get_inactive_permutation: bool
|
||||
# get_first_permutation_only: bool
|
||||
ids_permutation: str
|
||||
get_all_image: bool
|
||||
get_inactive_image: bool
|
||||
# get_first_image_only: bool
|
||||
ids_image: str
|
||||
"""
|
||||
get_all_region: bool
|
||||
get_inactive_region: bool
|
||||
get_first_region_only: bool
|
||||
ids_region: str
|
||||
get_all_currency: bool
|
||||
get_inactive_currency: bool
|
||||
get_first_currency_only: bool
|
||||
ids_currency: str
|
||||
get_all_discount: bool
|
||||
get_inactive_discount: bool
|
||||
ids_discount: str
|
||||
"""
|
||||
get_products_quantity_stock_below_min: bool
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
'a_id_user': None,
|
||||
'a_get_all_category': self.get_all_category,
|
||||
'a_get_inactive_category': self.get_inactive_category,
|
||||
# 'a_get_first_category_only': self.get_first_category_only,
|
||||
'a_ids_category': self.ids_category,
|
||||
'a_get_all_product': self.get_all_product,
|
||||
'a_get_inactive_product': self.get_inactive_product,
|
||||
# 'a_get_first_product_only': self.get_first_product_only,
|
||||
'a_ids_product': self.ids_product,
|
||||
'a_get_all_permutation': self.get_all_permutation,
|
||||
'a_get_inactive_permutation': self.get_inactive_permutation,
|
||||
# 'a_get_first_permutation_only': self.get_first_permutation_only,
|
||||
'a_ids_permutation': self.ids_permutation,
|
||||
'a_get_all_image': self.get_all_image,
|
||||
'a_get_inactive_image': self.get_inactive_image,
|
||||
# 'a_get_first_image_only': self.get_first_image_only,
|
||||
'a_ids_image': self.ids_image,
|
||||
# 'a_get_all_delivery_region': self.get_all_region,
|
||||
# 'a_get_inactive_delivery_region': self.get_inactive_region,
|
||||
# 'a_get_first_delivery_region_only': self.get_first_region_only,
|
||||
# 'a_ids_delivery_region': self.ids_region,
|
||||
# 'a_get_all_currency': self.get_all_currency,
|
||||
# 'a_get_inactive_currency': self.get_inactive_currency,
|
||||
# 'a_get_first_currency_only': self.get_first_currency_only,
|
||||
# 'a_ids_currency': self.ids_currency,
|
||||
# 'a_get_all_discount': self.get_all_discount,
|
||||
# 'a_get_inactive_discount': self.get_inactive_discount,
|
||||
# 'a_ids_discount': self.ids_discount,
|
||||
'a_get_products_quantity_stock_below_min': self.get_products_quantity_stock_below_min
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def from_form(form):
|
||||
# if not (form is Form_Filters_Permutation): raise ValueError(f'Invalid form type: {type(form)}')
|
||||
av.val_instance(form, 'form', 'Product_Filters.from_form', Form_Filters_Permutation)
|
||||
has_category_filter = not (form.id_category.data == '0' or form.id_category.data == '')
|
||||
has_product_filter = not (form.id_product.data == '0' or form.id_product.data == '')
|
||||
get_permutations_stock_below_min = av.input_bool(form.is_out_of_stock.data, "is_out_of_stock", "Product_Filters.from_form")
|
||||
print(f'form question: {type(form.is_out_of_stock)}\nbool interpretted: {get_permutations_stock_below_min}\type form: {type(form)}')
|
||||
return Product_Filters(
|
||||
get_all_category = not has_category_filter,
|
||||
get_inactive_category = False,
|
||||
# get_first_category_only = False,
|
||||
ids_category = form.id_category.data,
|
||||
get_all_product = not has_product_filter,
|
||||
get_inactive_product = False,
|
||||
# get_first_product_only = False,
|
||||
ids_product = form.id_product.data,
|
||||
get_all_permutation = not get_permutations_stock_below_min,
|
||||
get_inactive_permutation = False,
|
||||
# get_first_permutation_only = False,
|
||||
ids_permutation = '',
|
||||
get_all_image = False,
|
||||
get_inactive_image = False,
|
||||
# get_first_image_only = False,
|
||||
ids_image = '',
|
||||
# get_all_region = False,
|
||||
# get_inactive_region = False,
|
||||
# get_first_region_only = False,
|
||||
# ids_region = '',
|
||||
# get_all_currency = False,
|
||||
# get_inactive_currency = False,
|
||||
# get_first_currency_only = False,
|
||||
# ids_currency = '',
|
||||
# get_all_discount = False,
|
||||
# get_inactive_discount = False,
|
||||
# ids_discount = '',
|
||||
get_products_quantity_stock_below_min = get_permutations_stock_below_min
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_default():
|
||||
return Product_Filters(
|
||||
get_all_category = True,
|
||||
get_inactive_category = False,
|
||||
# get_first_category_only = False,
|
||||
ids_category = '',
|
||||
get_all_product = True,
|
||||
get_inactive_product = False,
|
||||
# get_first_product_only = False,
|
||||
ids_product = '',
|
||||
get_all_permutation = True,
|
||||
get_inactive_permutation = False,
|
||||
# get_first_permutation_only = False,
|
||||
ids_permutation = '',
|
||||
get_all_image = True,
|
||||
get_inactive_image = False,
|
||||
# get_first_image_only = False,
|
||||
ids_image = '',
|
||||
# get_all_region = True,
|
||||
# et_inactive_region = False,
|
||||
# get_first_region_only = False,
|
||||
# ids_region = '',
|
||||
# get_all_currency = True,
|
||||
# get_inactive_currency = False,
|
||||
# get_first_currency_only = False,
|
||||
# ids_currency = '',
|
||||
# get_all_discount = True,
|
||||
# get_inactive_discount = False,
|
||||
# ids_discount = '',
|
||||
get_products_quantity_stock_below_min = True
|
||||
)
|
||||
@@ -18,26 +18,19 @@ Business object for product
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from business_objects.product import Product, Product_Permutation, Price
|
||||
from business_objects.variation import Variation
|
||||
from business_objects.image import Image
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
from business_objects.discount import Discount
|
||||
from business_objects.stock_item import Stock_Item
|
||||
from business_objects.store.product import Product, Product_Permutation, Product_Price
|
||||
from business_objects.store.product_variation import Product_Variation
|
||||
from business_objects.store.image import Image
|
||||
from business_objects.store.delivery_option import Delivery_Option
|
||||
from business_objects.store.discount import Discount
|
||||
from business_objects.store.stock_item import Stock_Item
|
||||
from business_objects.store.store_base import Store_Base
|
||||
from extensions import db
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from pydantic import BaseModel
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
"""
|
||||
class Enum_Product_Category(Enum):
|
||||
ASSISTIVE_DEVICES = 0
|
||||
HOME_DECOR = 1
|
||||
@@ -58,10 +51,16 @@ class Enum_Product_Category(Enum):
|
||||
def get_member_by_text(text):
|
||||
av.val_str(text, 'text', 'Enum_Product_Category.get_member_by_text')
|
||||
return data_types.get_enum_member_by_text(Enum_Product_Category, text.upper())
|
||||
|
||||
"""
|
||||
|
||||
class Product_Category(db.Model, Store_Base):
|
||||
ATTR_CODE_CATEGORY: ClassVar[str] = 'code-category'
|
||||
ATTR_NAME_CATEGORY: ClassVar[str] = 'name-category'
|
||||
ATTR_DESCRIPTION_CATEGORY: ClassVar[str] = 'description-category'
|
||||
ATTR_DISPLAY_ORDER_CATEGORY: ClassVar[str] = 'display-order-category'
|
||||
|
||||
class Category(db.Model):
|
||||
id_category = db.Column(db.Integer, primary_key=True)
|
||||
code = db.Column(db.String(50))
|
||||
name = db.Column(db.String(255))
|
||||
description = db.Column(db.String(4000))
|
||||
display_order = db.Column(db.Integer)
|
||||
@@ -85,9 +84,9 @@ class Category(db.Model):
|
||||
self.products = []
|
||||
self.product_index = {}
|
||||
super().__init__()
|
||||
|
||||
def make_from_DB_product(query_row):
|
||||
category = Category()
|
||||
Store_Base.__init__(self)
|
||||
def from_DB_product(query_row):
|
||||
category = Product_Category()
|
||||
category.id_category = query_row[0]
|
||||
category.name = query_row[1]
|
||||
category.description = query_row[2]
|
||||
@@ -115,7 +114,6 @@ class Category(db.Model):
|
||||
except:
|
||||
pass
|
||||
raise KeyError(f'permutation id: {id_permutation} not in category id: {self.id_category}')
|
||||
|
||||
def add_product(self, product):
|
||||
_m = 'Category.add_product'
|
||||
av.val_instance(product, 'product', _m, Product)
|
||||
@@ -136,11 +134,11 @@ class Category(db.Model):
|
||||
# index_product = self.product_index[permutation.id_product]
|
||||
self.products[index_product].add_permutation(permutation)
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Category.add_variation', Variation)
|
||||
av.val_instance(variation, 'variation', 'Category.add_variation', Product_Variation)
|
||||
index_product = self.get_index_product_from_id(variation.id_product)
|
||||
self.products[index_product].add_variation(variation)
|
||||
def add_price(self, price):
|
||||
av.val_instance(price, 'price', 'Category.add_price', Price)
|
||||
av.val_instance(price, 'price', 'Category.add_price', Product_Price)
|
||||
index_product = self.get_index_product_from_id(price.id_product)
|
||||
self.products[index_product].add_price(price)
|
||||
def add_image(self, image):
|
||||
@@ -155,18 +153,15 @@ class Category(db.Model):
|
||||
av.val_instance(discount, 'discount', 'Category.add_discount', Discount)
|
||||
index_product = self.get_index_product_from_id(discount.id_product)
|
||||
self.products[index_product].add_discount(discount)
|
||||
|
||||
def add_stock_item(self, stock_item):
|
||||
av.val_instance(stock_item, 'stock_item', 'Category.add_stock_item', Stock_Item)
|
||||
index_product = self.get_index_product_from_id(stock_item.id_product)
|
||||
self.products[index_product].add_stock_item(stock_item)
|
||||
|
||||
def get_all_variation_trees(self):
|
||||
for product in self.products:
|
||||
if product.has_variations:
|
||||
print(f'product with id:{product.id_product} has variations')
|
||||
product.get_variation_trees()
|
||||
|
||||
"""
|
||||
def index_product_from_ids_product_permutation(self, id_product, id_permutation):
|
||||
key = Category.key_product_index_from_ids_product_permutation(id_product, id_permutation)
|
||||
@@ -185,12 +180,10 @@ class Category(db.Model):
|
||||
display_order: {self.display_order}
|
||||
products: {self.products}
|
||||
'''
|
||||
|
||||
def get_permutation_first(self):
|
||||
if not (len(self.products) == 0):
|
||||
print(f'getting first permutation from product')
|
||||
return None if len(self.products) == 0 else self.products[0].get_permutation_selected()
|
||||
|
||||
def is_available(self):
|
||||
if len(self.products) == 0:
|
||||
return False
|
||||
@@ -198,52 +191,68 @@ class Category(db.Model):
|
||||
if product.is_available():
|
||||
return True
|
||||
return False
|
||||
|
||||
def to_list_rows_permutation(self):
|
||||
list_rows = []
|
||||
for product in self.products:
|
||||
list_rows += product.to_list_rows_permutation()
|
||||
return list_rows
|
||||
|
||||
def to_list_products(self):
|
||||
list_products = []
|
||||
for product in self.products:
|
||||
list_products.append({'value': product.id_product, 'text': product.name})
|
||||
return list_products
|
||||
def to_json(self):
|
||||
return {
|
||||
self.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
|
||||
self.ATTR_CODE_CATEGORY: self.code,
|
||||
self.ATTR_NAME_CATEGORY: self.name,
|
||||
self.ATTR_DESCRIPTION_CATEGORY: self.description,
|
||||
self.ATTR_DISPLAY_ORDER_CATEGORY: self.display_order
|
||||
}
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
category = cls()
|
||||
category.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY],
|
||||
category.code = json[cls.ATTR_CODE_CATEGORY],
|
||||
category.name = json[cls.ATTR_NAME_CATEGORY],
|
||||
category.description = json[cls.ATTR_DESCRIPTION_CATEGORY],
|
||||
category.display_order = json[cls.ATTR_DISPLAY_ORDER_CATEGORY]
|
||||
return category
|
||||
|
||||
|
||||
class Product_Category_Filters():
|
||||
category_ids: str # csv
|
||||
product_ids: str # csv
|
||||
|
||||
class Filters_Product_Category(BaseModel, Store_Base):
|
||||
ids_product_category: str
|
||||
ids_product: str
|
||||
"""
|
||||
def __new__(cls, product_ids, product_categories):
|
||||
# Initialiser - validation
|
||||
_m = 'Product_Filters.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
# av.val_list_instances(product_ids, 'product_ids', _m, str, v_arg_type=v_arg_type)
|
||||
# av.val_list_instances(product_categories, 'product_categories', _m, Product_Category_Enum, v_arg_type=v_arg_type)
|
||||
av.val_str(product_ids, 'product_ids', _m, v_arg_type=v_arg_type)
|
||||
av.val_str(product_categories, 'product_categories', _m, v_arg_type=v_arg_type)
|
||||
return super(Product_Category_Filters, cls).__new__(cls)
|
||||
|
||||
def __init__(self, product_ids, product_categories):
|
||||
return super(Filters_Product_Category, cls).__new__(cls)
|
||||
"""
|
||||
def __init__(self, ids_product, ids_product_category):
|
||||
super().__init__(ids_product=ids_product, ids_product_category=ids_product_category)
|
||||
"""
|
||||
# Constructor
|
||||
self.ids = product_ids
|
||||
self.categories = product_categories
|
||||
"""
|
||||
|
||||
class Category_List():
|
||||
class Container_Product_Category(Store_Base):
|
||||
categories: list
|
||||
def __init__(self):
|
||||
self.categories = []
|
||||
def add_category(self, category):
|
||||
av.val_instance(category, 'category', 'Category_List.add_category', Category)
|
||||
av.val_instance(category, 'category', 'Container_Product_Categories.add_category', Product_Category)
|
||||
self.categories.append(category)
|
||||
def get_index_category_from_id(self, id_category):
|
||||
for index_category in range(len(self.categories)):
|
||||
category = self.categories[index_category]
|
||||
if category.id_category == id_category:
|
||||
return index_category
|
||||
raise ValueError(f"{av.error_msg_str(id_category, 'id_category', 'Category_List.get_index_category_from_id', int)}\nID not in list")
|
||||
raise ValueError(f"{av.error_msg_str(id_category, 'id_category', 'Container_Product_Categories.get_index_category_from_id', int)}\nID not in list")
|
||||
def get_index_category_from_id_permutation(self, id_permutation):
|
||||
for index_category in range(len(self.categories)):
|
||||
category = self.categories[index_category]
|
||||
@@ -252,77 +261,68 @@ class Category_List():
|
||||
return index_category
|
||||
except:
|
||||
pass
|
||||
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Category_List.get_index_category_from_id_permutation', int)}. Permutation ID not in list")
|
||||
raise ValueError(f"{av.error_msg_str(id_permutation, 'id_permutation', 'Container_Product_Categories.get_index_category_from_id_permutation', int)}. Permutation ID not in list")
|
||||
def add_product(self, product):
|
||||
av.val_instance(product, 'product', 'Category_List.add_product', Product)
|
||||
av.val_instance(product, 'product', 'Container_Product_Categories.add_product', Product)
|
||||
index_category = self.get_index_category_from_id(product.id_category)
|
||||
self.categories[index_category].add_product(product)
|
||||
def add_permutation(self, permutation):
|
||||
av.val_instance(permutation, 'permutation', 'Category_List.add_permutation', Product_Permutation)
|
||||
av.val_instance(permutation, 'permutation', 'Container_Product_Categories.add_permutation', Product_Permutation)
|
||||
index_category = self.get_index_category_from_id(permutation.id_category)
|
||||
self.categories[index_category].add_permutation(permutation)
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Category.add_variation', Variation)
|
||||
av.val_instance(variation, 'variation', 'Container_Product_Categories.add_variation', Product_Variation)
|
||||
index_category = self.get_index_category_from_id(variation.id_category)
|
||||
self.categories[index_category].add_variation(variation)
|
||||
def add_price(self, price):
|
||||
av.val_instance(price, 'price', 'Category.add_price', Price)
|
||||
av.val_instance(price, 'price', 'Container_Product_Categories.add_price', Product_Price)
|
||||
index_category = self.get_index_category_from_id(price.id_category)
|
||||
self.categories[index_category].add_price(price)
|
||||
def add_image(self, image):
|
||||
av.val_instance(image, 'image', 'Category.add_image', Image)
|
||||
av.val_instance(image, 'image', 'Container_Product_Categories.add_image', Image)
|
||||
index_category = self.get_index_category_from_id(image.id_category)
|
||||
self.categories[index_category].add_image(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Category.add_delivery_option', Delivery_Option)
|
||||
av.val_instance(delivery_option, 'delivery_option', 'Container_Product_Categories.add_delivery_option', Delivery_Option)
|
||||
index_category = self.get_index_category_from_id(delivery_option.id_category)
|
||||
self.categories[index_category].add_delivery_option(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
av.val_instance(discount, 'discount', 'Category.add_discount', Discount)
|
||||
av.val_instance(discount, 'discount', 'Container_Product_Categories.add_discount', Discount)
|
||||
index_category = self.get_index_category_from_id(discount.id_category)
|
||||
self.categories[index_category].add_discount(discount)
|
||||
|
||||
def add_stock_item(self, stock_item):
|
||||
av.val_instance(stock_item, 'stock_item', 'Category.add_stock_item', Stock_Item)
|
||||
av.val_instance(stock_item, 'stock_item', 'Container_Product_Categories.add_stock_item', Stock_Item)
|
||||
index_category = self.get_index_category_from_id(stock_item.id_category)
|
||||
self.categories[index_category].add_stock_item(stock_item)
|
||||
|
||||
def get_all_variation_trees(self):
|
||||
for category in self.categories:
|
||||
category.get_all_variation_trees()
|
||||
|
||||
def __repr__(self):
|
||||
return f'categories: {self.categories}'
|
||||
|
||||
def get_permutation_first(self):
|
||||
print(f'getting first permutation from category list')
|
||||
if not (len(self.categories) == 0):
|
||||
print(f'getting first permutation from category')
|
||||
return None if len(self.categories) == 0 else self.categories[0].get_permutation_first()
|
||||
|
||||
def get_count_categories(self):
|
||||
return len(self.categories)
|
||||
|
||||
def to_list_rows_permutation(self):
|
||||
list_rows = []
|
||||
for category in self.categories:
|
||||
list_rows += category.to_list_rows_permutation()
|
||||
return list_rows
|
||||
|
||||
def to_list_categories(self):
|
||||
list_categories = []
|
||||
for category in self.categories:
|
||||
list_categories.append({'value': category.id_category, 'text': category.name})
|
||||
return list_categories
|
||||
|
||||
def to_list_products(self):
|
||||
list_products = []
|
||||
for category in self.categories:
|
||||
# list_products.append(category.to_list_products())
|
||||
for product in category.products:
|
||||
list_products.append({'value': product.id_product, 'text': product.name, Product.ATTR_ID_CATEGORY: product.id_category})
|
||||
list_products.append({'value': product.id_product, 'text': product.name, Product.ATTR_ID_PRODUCT_CATEGORY: product.id_category})
|
||||
return list_products
|
||||
|
||||
def to_dict_lists_products(self):
|
||||
dict_lists_products = {}
|
||||
for category in self.categories:
|
||||
365
business_objects/store/product_permutation.py
Normal file
365
business_objects/store/product_permutation.py
Normal file
@@ -0,0 +1,365 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Permutation Business Object
|
||||
|
||||
Description:
|
||||
Business object for product permutation
|
||||
"""
|
||||
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit, Form_Filters_Permutation
|
||||
from business_objects.store.delivery_option import Delivery_Option
|
||||
from business_objects.store.discount import Discount
|
||||
from business_objects.store.image import Image
|
||||
from business_objects.store.product_price import Product_Price
|
||||
from business_objects.store.stock_item import Stock_Item
|
||||
from business_objects.store.store_base import Store_Base
|
||||
from business_objects.store.product_variation import Product_Variation
|
||||
from extensions import db
|
||||
# external
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from dataclasses import dataclass
|
||||
|
||||
class Product_Permutation(db.Model, Store_Base):
|
||||
FLAG_QUANTITY_STOCK = 'quantity-stock'
|
||||
FLAG_QUANTITY_MIN = 'quantity-min'
|
||||
FLAG_QUANTITY_MAX = 'quantity-max'
|
||||
FLAG_COST_LOCAL = 'cost-local'
|
||||
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
id_permutation = db.Column(db.Integer, primary_key=True)
|
||||
# name = db.Column(db.String(255))
|
||||
description = db.Column(db.String(4000))
|
||||
# price_GBP_full = db.Column(db.Float)
|
||||
# price_GBP_min = db.Column(db.Float)
|
||||
id_currency_cost = db.Column(db.Integer)
|
||||
code_currency_cost = db.Column(db.String(3))
|
||||
symbol_currency_cost = db.Column(db.String(3))
|
||||
cost_local = db.Column(db.Float)
|
||||
has_variations = db.Column(db.Boolean)
|
||||
id_category = db.Column(db.Integer)
|
||||
latency_manufacture = db.Column(db.Integer)
|
||||
quantity_min = db.Column(db.Float)
|
||||
quantity_max = db.Column(db.Float)
|
||||
quantity_step = db.Column(db.Float)
|
||||
quantity_stock = db.Column(db.Float)
|
||||
id_stripe_product = db.Column(db.String(100))
|
||||
is_subscription = db.Column(db.Boolean)
|
||||
name_recurrence_interval = db.Column(db.String(255))
|
||||
name_plural_recurrence_interval = db.Column(db.String(256))
|
||||
count_recurrence_interval = db.Column(db.Integer)
|
||||
display_order = db.Column(db.Integer)
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
# form_basket_add: Form_Basket_Add
|
||||
# form_basket_edit: Form_Basket_Edit
|
||||
# is_unavailable_in_currency_or_region: bool
|
||||
# is_available: bool
|
||||
|
||||
def __init__(self):
|
||||
self.variations = []
|
||||
self.variation_index = {}
|
||||
self.prices = []
|
||||
self.price_index = {}
|
||||
self.images = []
|
||||
self.image_index = {}
|
||||
self.delivery_options = []
|
||||
self.delivery_option_index = {}
|
||||
self.discounts = []
|
||||
self.discount_index = {}
|
||||
self.stock_items = []
|
||||
self.stock_item_index = {}
|
||||
super().__init__()
|
||||
Store_Base.__init__(self)
|
||||
self.form_basket_add = Form_Basket_Add()
|
||||
self.form_basket_edit = Form_Basket_Edit()
|
||||
self.is_unavailable_in_currency_or_region = False
|
||||
# self.is_available = False
|
||||
|
||||
def from_DB_product(query_row):
|
||||
_m = 'Product_Permutation.from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
print(f'query_row: {query_row}')
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
permutation.id_permutation = query_row[1]
|
||||
# permutation.name = query_row[2]
|
||||
permutation.description = query_row[3]
|
||||
# permutation.price_GBP_full = query_row[4]
|
||||
# permutation.price_GBP_min = query_row[5]
|
||||
permutation.id_currency_cost = query_row[7]
|
||||
permutation.code_currency_cost = query_row[8]
|
||||
permutation.symbol_currency_cost = query_row[9]
|
||||
permutation.cost_local = query_row[6]
|
||||
permutation.has_variations = query_row[4]
|
||||
permutation.id_category = query_row[5]
|
||||
permutation.latency_manufacture = query_row[11]
|
||||
permutation.quantity_min = query_row[12]
|
||||
permutation.quantity_max = query_row[13]
|
||||
permutation.quantity_step = query_row[14]
|
||||
permutation.quantity_stock = query_row[15]
|
||||
permutation.id_stripe_product = query_row[16]
|
||||
permutation.is_subscription = av.input_bool(query_row[17], "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
permutation.name_recurrence_interval = query_row[18]
|
||||
permutation.name_plural_recurrence_interval = query_row[19]
|
||||
permutation.count_recurrence_interval = query_row[20]
|
||||
permutation.display_order = query_row[23]
|
||||
permutation.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
|
||||
permutation.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
|
||||
permutation.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
|
||||
return permutation
|
||||
|
||||
def from_DB_Stripe_product(query_row):
|
||||
_m = 'Product_Permutation.from_DB_Stripe_product'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
# permutation.name = query_row[1]
|
||||
permutation.description = query_row[2]
|
||||
return permutation
|
||||
|
||||
def from_DB_Stripe_price(query_row):
|
||||
_m = 'Product_Permutation.from_DB_Stripe_price'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = query_row[0]
|
||||
# permutation.price_GBP_full = query_row[1]
|
||||
permutation.id_stripe_product = query_row[2]
|
||||
permutation.is_subscription = av.input_bool(query_row[3], "is_subscription", _m, v_arg_type=v_arg_type)
|
||||
permutation.name_recurrence_interval = query_row[4]
|
||||
permutation.count_recurrence_interval = query_row[5]
|
||||
return permutation
|
||||
"""
|
||||
def from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
_m = 'Product_Permutation.from_json'
|
||||
v_arg_type = 'class attribute'
|
||||
permutation = Product_Permutation()
|
||||
permutation.id_product = json_basket_item[key_id_product]
|
||||
permutation.id_permutation = json_basket_item[key_id_permutation]
|
||||
return permutation
|
||||
"""
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
permutation = cls()
|
||||
permutation.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
|
||||
permutation.id_product = json[cls.ATTR_ID_PRODUCT]
|
||||
permutation.id_permutation = json[cls.ATTR_ID_PRODUCT_PERMUTATION]
|
||||
permutation.has_variations = len(json[cls.ATTR_ID_PRODUCT_VARIATION]) > 0
|
||||
if permutation.has_variations:
|
||||
for jsonProductVariation in json[cls.ATTR_ID_PRODUCT_VARIATION]:
|
||||
variation = Product_Variation.from_json(jsonProductVariation)
|
||||
permutation.add_variation(variation)
|
||||
permutation.quantity_stock = json[cls.FLAG_QUANTITY_STOCK]
|
||||
permutation.quantity_min = json[cls.FLAG_QUANTITY_MIN]
|
||||
permutation.quantity_max = json[cls.FLAG_QUANTITY_MAX]
|
||||
return permutation
|
||||
def to_json(self):
|
||||
return {
|
||||
'id_product': {self.id_product},
|
||||
'id_permutation': {self.id_permutation},
|
||||
'description': {self.description},
|
||||
'id_category': {self.id_category},
|
||||
'latency_manufacture': {self.latency_manufacture},
|
||||
'quantity_min': {self.quantity_min},
|
||||
'quantity_max': {self.quantity_max},
|
||||
'quantity_step': {self.quantity_step},
|
||||
'quantity_stock': {self.quantity_stock},
|
||||
'id_stripe_product': {self.id_stripe_product},
|
||||
'is_subscription': {self.is_subscription},
|
||||
'name_recurrence_interval': {self.name_recurrence_interval},
|
||||
'name_plural_recurrence_interval': {self.name_plural_recurrence_interval},
|
||||
'count_recurrence_interval': {self.count_recurrence_interval},
|
||||
'display_order': {self.display_order},
|
||||
'can_view': {self.can_view},
|
||||
'can_edit': {self.can_edit},
|
||||
'can_admin': {self.can_admin},
|
||||
'variations': {self.variations},
|
||||
'images': {self.images},
|
||||
'delivery_options': {self.delivery_options},
|
||||
'prices': {self.prices}
|
||||
}
|
||||
def is_available(self):
|
||||
return len(self.prices) > 0
|
||||
def get_price(self):
|
||||
return self.prices[0]
|
||||
def get_price_local_VAT_incl(self):
|
||||
price = self.get_price()
|
||||
return price.value_local_VAT_incl
|
||||
def get_price_local_VAT_excl(self):
|
||||
price = self.get_price()
|
||||
return price.value_local_VAT_excl
|
||||
|
||||
def output_lead_time(self):
|
||||
return '1 day' if self.latency_manufacture == 1 else f'{self.latency_manufacture} days'
|
||||
|
||||
def output_delivery_date(self):
|
||||
return (datetime.now() + timedelta(days=self.latency_manufacture)).strftime('%A, %d %B %Y')
|
||||
|
||||
def output_price(self, is_included_VAT):
|
||||
if self.is_unavailable_in_currency_or_region:
|
||||
return 'Not available in currency and region'
|
||||
if not self.is_available:
|
||||
return 'Not available'
|
||||
price = self.get_price()
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
if is_included_VAT:
|
||||
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_incl, grouping=True)}'
|
||||
else:
|
||||
return f'{price.symbol_currency} {locale.format_string("%d", price.value_local_VAT_excl, grouping=True)}'
|
||||
def output_currency(self):
|
||||
if not self.is_available:
|
||||
return ''
|
||||
"""
|
||||
price = self.get_price()
|
||||
return price.code_currency
|
||||
"""
|
||||
return self.code_currency_cost if self.symbol_currency_cost == '' else self.symbol_currency_cost
|
||||
def output_variations(self):
|
||||
if not self.has_variations: return ''
|
||||
return '\n'.join([f'{variation.name_variation_type}: {variation.name_variation}' for variation in self.variations])
|
||||
def output_variations_jsonify(self):
|
||||
if not self.has_variations: return ''
|
||||
return ','.join([f'{variation.id_type}: {variation.id_variation}' for variation in self.variations])
|
||||
"""
|
||||
def output_price_VAT_incl(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.price_GBP_VAT_incl, grouping=True)
|
||||
def output_price_VAT_excl(self):
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
return locale.format_string("%d", self.price_GBP_VAT_excl, grouping=True)
|
||||
def add_form_basket_add(self):
|
||||
self.form_basket_add = None
|
||||
|
||||
def add_form_basket_edit(self):
|
||||
self.form_basket_edit = None
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f'''Product_Permutation
|
||||
id_product: {self.id_product}
|
||||
id_permutation: {self.id_permutation}
|
||||
description: {self.description}
|
||||
id_category: {self.id_category}
|
||||
latency_manufacture: {self.latency_manufacture}
|
||||
quantity_min: {self.quantity_min}
|
||||
quantity_max: {self.quantity_max}
|
||||
quantity_step: {self.quantity_step}
|
||||
quantity_stock: {self.quantity_stock}
|
||||
id_stripe_product: {self.id_stripe_product}
|
||||
is_subscription: {self.is_subscription}
|
||||
name_recurrence_interval: {self.name_recurrence_interval}
|
||||
name_plural_recurrence_interval: {self.name_plural_recurrence_interval}
|
||||
count_recurrence_interval: {self.count_recurrence_interval}
|
||||
display_order: {self.display_order}
|
||||
can_view: {self.can_view}
|
||||
can_edit: {self.can_edit}
|
||||
can_admin: {self.can_admin}
|
||||
variations: {self.variations}
|
||||
images: {self.images}
|
||||
delivery_options: {self.delivery_options}
|
||||
prices: {self.prices}
|
||||
'''
|
||||
"""
|
||||
price_GBP_full: {self.price_GBP_full}
|
||||
price_GBP_min: {self.price_GBP_min}
|
||||
"""
|
||||
|
||||
def add_variation(self, variation):
|
||||
_m = 'Product_Permutation.add_variation'
|
||||
av.val_instance(variation, 'variation', _m, Product_Variation)
|
||||
try:
|
||||
self.variation_index[variation.id_variation]
|
||||
raise ValueError(f"{av.error_msg_str(variation, 'variation', _m, Product_Variation)}\nProduct_Variation already in product.")
|
||||
except KeyError:
|
||||
self.variation_index[variation.id_variation] = len(self.variations)
|
||||
self.variations.append(variation)
|
||||
def add_price(self, price):
|
||||
_m = 'Product_Permutation.add_price'
|
||||
av.val_instance(price, 'price', _m, Product_Price)
|
||||
try:
|
||||
self.price_index[price.display_order]
|
||||
raise ValueError(f"{av.error_msg_str(price, 'price', _m, Product_Price)}\nPrice already in product.")
|
||||
except KeyError:
|
||||
self.price_index[price.display_order] = len(self.prices)
|
||||
self.prices.append(price)
|
||||
def add_image(self, image):
|
||||
_m = 'Product_Permutation.add_image'
|
||||
av.val_instance(image, 'image', _m, Image)
|
||||
try:
|
||||
self.image_index[image.id_image]
|
||||
raise ValueError(f"{av.error_msg_str(image, 'image', _m, Image)}\nImage already in product.")
|
||||
except KeyError:
|
||||
self.image_index[image.id_image] = len(self.images)
|
||||
self.images.append(image)
|
||||
def add_delivery_option(self, delivery_option):
|
||||
_m = 'Product_Permutation.add_delivery_option'
|
||||
av.val_instance(delivery_option, 'delivery_option', _m, Delivery_Option)
|
||||
try:
|
||||
self.delivery_option_index[delivery_option.id_option]
|
||||
raise ValueError(f"{av.error_msg_str(delivery_option, 'delivery_option', _m, Delivery_Option)}\nDelivery_Option already in product.")
|
||||
except KeyError:
|
||||
self.delivery_option_index[delivery_option.id_option] = len(self.delivery_options)
|
||||
self.delivery_options.append(delivery_option)
|
||||
def add_discount(self, discount):
|
||||
_m = 'Product_Permutation.add_discount'
|
||||
av.val_instance(discount, 'discount', _m, Discount)
|
||||
try:
|
||||
self.discount_index[discount.display_order]
|
||||
raise ValueError(f"{av.error_msg_str(discount, 'discount', _m, Discount)}\nDiscount already in product.")
|
||||
except KeyError:
|
||||
self.discount_index[discount.display_order] = len(self.discounts)
|
||||
self.discounts.append(discount)
|
||||
|
||||
def add_stock_item(self, stock_item):
|
||||
av.val_instance(stock_item, 'stock_item', 'Product_Permutation.add_stock_item', Stock_Item)
|
||||
self.stock_items.append(stock_item)
|
||||
|
||||
def get_image_from_index(self, index_image):
|
||||
try:
|
||||
return self.images[index_image]
|
||||
except:
|
||||
raise IndexError(f"Invalid image index: {index_image}")
|
||||
|
||||
def get_price_from_code_currency(self, code_currency):
|
||||
for price in self.prices:
|
||||
if price.code_currency == code_currency:
|
||||
return price
|
||||
|
||||
def to_row_permutation(self):
|
||||
a = {
|
||||
Product_Permutation.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
|
||||
Product_Permutation.ATTR_ID_PRODUCT: self.id_product,
|
||||
Product_Permutation.ATTR_ID_PRODUCT_VARIATION: self.output_variations(),
|
||||
Product_Permutation.FLAG_QUANTITY_STOCK: self.quantity_stock,
|
||||
Product_Permutation.FLAG_QUANTITY_MIN: self.quantity_min,
|
||||
Product_Permutation.FLAG_QUANTITY_MAX: self.quantity_max,
|
||||
Product_Permutation.FLAG_COST_LOCAL: f"<strong>{self.symbol_currency_cost}</strong>{self.cost_local}"
|
||||
}
|
||||
print('permutation row: ', a)
|
||||
return a
|
||||
|
||||
|
||||
"""
|
||||
class Permutation_Product_Variation_Link(db.Model):
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
id_variation = db.Column(db.Integer)
|
||||
|
||||
def from_DB_product(query_row):
|
||||
_m = 'Permutation_Product_Variation_Link.from_DB_product'
|
||||
v_arg_type = 'class attribute'
|
||||
link = Permutation_Product_Variation_Link()
|
||||
link.id_permutation = query_row[0]
|
||||
link.id_product = query_row[1]
|
||||
link.id_category = query_row[2]
|
||||
link.id_variation = query_row[3]
|
||||
return link
|
||||
"""
|
||||
109
business_objects/store/product_price.py
Normal file
109
business_objects/store/product_price.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Price Business Object
|
||||
|
||||
Description:
|
||||
Business object for product price
|
||||
"""
|
||||
|
||||
# internal
|
||||
from business_objects.store.currency import Currency
|
||||
from business_objects.store.delivery_region import Delivery_Region
|
||||
from business_objects.store.store_base import Store_Base
|
||||
from extensions import db
|
||||
# external
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
class Product_Price(db.Model, Store_Base):
|
||||
ATTR_ID_PRODUCT_PRICE: ClassVar[str] = 'id-price'
|
||||
FLAG_VALUE_LOCAL_VAT_INCL: ClassVar[str] = 'value-local-vat-incl'
|
||||
FLAG_VALUE_LOCAL_VAT_EXCL: ClassVar[str] = 'value-local-vat-excl'
|
||||
FLAG_DISPLAY_ORDER: ClassVar[str] = 'display-order-price'
|
||||
|
||||
id_price = db.Column(db.Integer, primary_key=True)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_product = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
id_currency = db.Column(db.Integer)
|
||||
code_currency = db.Column(db.String(50))
|
||||
name_currency = db.Column(db.String(255))
|
||||
symbol_currency = db.Column(db.String(50))
|
||||
id_region = db.Column(db.Integer)
|
||||
value_local_VAT_incl = db.Column(db.Float)
|
||||
value_local_VAT_excl = db.Column(db.Float)
|
||||
display_order = db.Column(db.Float)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
Store_Base.__init__(self)
|
||||
|
||||
def from_DB_product(query_row):
|
||||
# _m = 'Product_Price.from_DB_product'
|
||||
price = Product_Price()
|
||||
price.id_price = query_row[0]
|
||||
price.id_permutation = query_row[1]
|
||||
price.id_product = query_row[2]
|
||||
price.id_category = query_row[3]
|
||||
price.id_currency = query_row[4]
|
||||
price.code_currency = query_row[5]
|
||||
price.name_currency = query_row[6]
|
||||
price.symbol_currency = query_row[7]
|
||||
price.id_region = query_row[8]
|
||||
price.value_local_VAT_incl = query_row[9]
|
||||
price.value_local_VAT_excl = query_row[10]
|
||||
price.display_order = query_row[11]
|
||||
return price
|
||||
|
||||
def __repr__(self):
|
||||
return f'''Product_Price
|
||||
id: {self.id_price}
|
||||
id_permutation: {self.id_permutation}
|
||||
id_product: {self.id_product}
|
||||
id_category: {self.id_category}
|
||||
id_currency: {self.id_currency}
|
||||
code_currency: {self.code_currency}
|
||||
name_currency: {self.name_currency}
|
||||
symbol_currency: {self.symbol_currency}
|
||||
id_region: {self.id_region}
|
||||
value_local (VAT incl): {self.value_local_VAT_incl}
|
||||
value_local (VAT excl): {self.value_local_VAT_excl}
|
||||
display_order (UID): {self.display_order}
|
||||
'''
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
self.ATTR_ID_PRODUCT_PRICE: {self.id_price},
|
||||
self.ATTR_ID_PRODUCT_PERMUTATION: {self.id_permutation},
|
||||
self.ATTR_ID_PRODUCT: {self.id_product},
|
||||
self.ATTR_ID_PRODUCT_CATEGORY: {self.id_category},
|
||||
Currency.ATTR_ID_CURRENCY: {self.id_currency},
|
||||
Currency.FLAG_CODE: {self.code_currency},
|
||||
Currency.FLAG_NAME: {self.name_currency},
|
||||
Currency.FLAG_SYMBOL: {self.symbol_currency},
|
||||
Delivery_Region.ATTR_ID_REGION: {self.id_region},
|
||||
self.FLAG_VALUE_LOCAL_VAT_INCL: {self.value_local_VAT_incl},
|
||||
self.FLAG_VALUE_LOCAL_VAT_EXCL: {self.value_local_VAT_excl},
|
||||
self.FLAG_DISPLAY_ORDER: {self.display_order}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
price = cls()
|
||||
price.id_price = json[cls.ATTR_ID_PRODUCT_PRICE]
|
||||
price.id_permutation = json[cls.ATTR_ID_PRODUCT_PERMUTATION]
|
||||
price.id_product = json[cls.ATTR_ID_PRODUCT]
|
||||
price.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
|
||||
price.id_currency = json[Currency.ATTR_ID_CURRENCY]
|
||||
price.code_currency = json[Currency.FLAG_CODE]
|
||||
price.name_currency = json[Currency.FLAG_NAME]
|
||||
price.symbol_currency = json[Currency.FLAG_SYMBOL]
|
||||
price.id_region = json[Delivery_Region.ATTR_ID_REGION]
|
||||
price.value_local_VAT_incl = json[cls.FLAG_VALUE_LOCAL_VAT_INCL]
|
||||
price.value_local_VAT_excl = json[cls.FLAG_VALUE_LOCAL_VAT_EXCL]
|
||||
price.display_order = json[cls.FLAG_DISPLAY_ORDER]
|
||||
return price
|
||||
@@ -4,7 +4,7 @@ Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Variation Business Object
|
||||
Feature: Product Product_Variation Business Object
|
||||
|
||||
Description:
|
||||
Business object for product variation
|
||||
@@ -18,28 +18,16 @@ Business object for product variation
|
||||
# IMPORTS
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from business_objects.store.store_base import Store_Base
|
||||
from extensions import db
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
from pydantic import BaseModel
|
||||
|
||||
# VARIABLE INSTANTIATION
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
# CLASSES
|
||||
class Variation(db.Model):
|
||||
ATTR_ID_CATEGORY: ClassVar[str] = 'id_category'
|
||||
ATTR_ID_PERMUTATION: ClassVar[str] = 'id_permutation'
|
||||
ATTR_ID_PRODUCT: ClassVar[str] = 'id_product'
|
||||
ATTR_ID_VARIATION: ClassVar[str] = 'id_variation'
|
||||
ATTR_ID_VARIATION_TYPE: ClassVar[str] = 'id_variation_type'
|
||||
class Product_Variation(db.Model, Store_Base):
|
||||
KEY_ACTIVE_VARIATION: ClassVar[str] = 'active_variation'
|
||||
KEY_ACTIVE_VARIATION_TYPE: ClassVar[str] = 'active_variation_type'
|
||||
KEY_CODE_VARIATION: ClassVar[str] = 'code_variation'
|
||||
@@ -64,41 +52,20 @@ class Variation(db.Model):
|
||||
id_permutation = db.Column(db.Integer)
|
||||
id_category = db.Column(db.Integer)
|
||||
|
||||
"""
|
||||
def __new__(cls, id, id_product, id_category, name_type, code_type, name, code, display_order):
|
||||
_m = 'Variation.__new__'
|
||||
v_arg_type = 'class attribute'
|
||||
av.val_int(id, 'id', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(id_product, 'id_product', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_int(id_category, 'id_category', _m, 0, v_arg_type=v_arg_type)
|
||||
av.val_str(code_type, 'code_type', _m, max_len=50, v_arg_type=v_arg_type)
|
||||
av.val_str(name_type, 'name_type', _m, max_len=256, v_arg_type=v_arg_type)
|
||||
av.val_str(code, 'code', _m, max_len=50, v_arg_type=v_arg_type)
|
||||
av.val_str(name, 'name', _m, max_len=256, v_arg_type=v_arg_type)
|
||||
av.val_int(display_order, 'display_order', _m, v_arg_type=v_arg_type)
|
||||
return super(Variation, cls).__new__(cls)
|
||||
|
||||
def __init__(self, id, id_product, id_category, name_type, code_type, name, code, display_order):
|
||||
self.id_variation = id
|
||||
self.id_product = id_product
|
||||
self.id_category = id_category
|
||||
self.name_variation_type = name_type
|
||||
self.code_variation_type = code_type
|
||||
self.name_variation = name
|
||||
self.code_variation = code
|
||||
self.display_order = display_order
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
"""
|
||||
def make_from_DB_product(query_row):
|
||||
variation = Variation.make_from_DB_variation(query_row)
|
||||
Store_Base.__init__(self)
|
||||
|
||||
def from_DB_product(query_row):
|
||||
variation = Product_Variation.from_DB_variation(query_row)
|
||||
variation.id_product = query_row[11]
|
||||
variation.id_permutation = query_row[12]
|
||||
variation.id_category = query_row[13]
|
||||
return variation
|
||||
|
||||
def make_from_DB_variation(query_row):
|
||||
_m = 'Variation.make_from_DB_variation'
|
||||
variation = Variation()
|
||||
def from_DB_variation(query_row):
|
||||
_m = 'Product_Variation.from_DB_variation'
|
||||
variation = Product_Variation()
|
||||
variation.id_variation = query_row[0]
|
||||
variation.code_variation = query_row[1]
|
||||
variation.name_variation = query_row[2]
|
||||
@@ -112,14 +79,15 @@ class Variation(db.Model):
|
||||
variation.display_order_variation_type = query_row[10]
|
||||
return variation
|
||||
|
||||
def from_json(json):
|
||||
variation = Variation()
|
||||
variation.id_variation = json[Variation.ATTR_ID_VARIATION]
|
||||
variation.id_product = json[Variation.ATTR_ID_PRODUCT]
|
||||
variation.id_permutation = json[Variation.ATTR_ID_PERMUTATION]
|
||||
variation.id_category = json[Variation.ATTR_ID_CATEGORY]
|
||||
variation.name_variation_type = json[Variation.KEY_NAME_VARIATION_TYPE]
|
||||
variation.name_variation = json[Variation.KEY_NAME_VARIATION]
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
variation = cls()
|
||||
variation.id_variation = json[cls.ATTR_ID_VARIATION]
|
||||
variation.id_product = json[cls.ATTR_ID_PRODUCT]
|
||||
variation.id_permutation = json[cls.ATTR_ID_PERMUTATION]
|
||||
variation.id_category = json[cls.ATTR_ID_CATEGORY]
|
||||
variation.name_variation_type = json[cls.KEY_NAME_VARIATION_TYPE]
|
||||
variation.name_variation = json[cls.KEY_NAME_VARIATION]
|
||||
return variation
|
||||
|
||||
def __repr__(self):
|
||||
@@ -140,32 +108,32 @@ class Variation(db.Model):
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
Variation.ATTR_ID_VARIATION: self.id_variation,
|
||||
Variation.ATTR_ID_PRODUCT: self.id_product,
|
||||
Variation.ATTR_ID_PERMUTATION: self.id_permutation,
|
||||
Variation.ATTR_ID_CATEGORY: self.id_category,
|
||||
Variation.ATTR_ID_VARIATION_TYPE: self.id_type,
|
||||
Variation.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
|
||||
Variation.KEY_CODE_VARIATION: self.code_variation,
|
||||
Variation.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
|
||||
Variation.KEY_DISPLAY_ORDER_VARIATION: self.display_order_variation,
|
||||
Variation.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
|
||||
Variation.KEY_NAME_VARIATION: self.name_variation,
|
||||
Variation.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
|
||||
Variation.KEY_ACTIVE_VARIATION: self.active_variation,
|
||||
self.ATTR_ID_PRODUCT_VARIATION: self.id_variation,
|
||||
self.ATTR_ID_PRODUCT: self.id_product,
|
||||
self.ATTR_ID_PRODUCT_PERMUTATION: self.id_permutation,
|
||||
self.ATTR_ID_PRODUCT_CATEGORY: self.id_category,
|
||||
self.ATTR_ID_PRODUCT_VARIATION_TYPE: self.id_type,
|
||||
self.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
|
||||
self.KEY_CODE_VARIATION: self.code_variation,
|
||||
self.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
|
||||
self.KEY_DISPLAY_ORDER_VARIATION: self.display_order_variation,
|
||||
self.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
|
||||
self.KEY_NAME_VARIATION: self.name_variation,
|
||||
self.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
|
||||
self.KEY_ACTIVE_VARIATION: self.active_variation,
|
||||
}
|
||||
|
||||
def to_json_variation_type(self):
|
||||
return {
|
||||
Variation.ATTR_ID_VARIATION_TYPE: self.id_type,
|
||||
Variation.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
|
||||
Variation.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
|
||||
Variation.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
|
||||
Variation.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
|
||||
self.ATTR_ID_PRODUCT_VARIATION_TYPE: self.id_type,
|
||||
self.KEY_CODE_VARIATION_TYPE: self.code_variation_type,
|
||||
self.KEY_DISPLAY_ORDER_VARIATION_TYPE: self.display_order_variation_type,
|
||||
self.KEY_NAME_VARIATION_TYPE: self.name_variation_type,
|
||||
self.KEY_ACTIVE_VARIATION_TYPE: self.active_variation_type,
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class Variation_Filters():
|
||||
class Product_Variation_Filters():
|
||||
get_all_variation_type: bool
|
||||
get_inactive_variation_type: bool
|
||||
get_first_variation_type: bool
|
||||
@@ -190,7 +158,7 @@ class Variation_Filters():
|
||||
"""
|
||||
@staticmethod
|
||||
def from_form(form):
|
||||
av.val_instance(form, 'form', 'User_Filters.from_form', Form_Filters_Variation)
|
||||
av.val_instance(form, 'form', 'User_Filters.from_form', Form_Filters_Product_Variation)
|
||||
get_inactive = av.input_bool(form.active_only.data, "active_only", "User_Filters.from_form")
|
||||
id_user = form.id_user.data
|
||||
return User_Filters(
|
||||
@@ -215,7 +183,7 @@ class Variation_Filters():
|
||||
|
||||
@staticmethod
|
||||
def get_default():
|
||||
return Variation_Filters(
|
||||
return Product_Variation_Filters(
|
||||
get_all_variation_type = True,
|
||||
get_inactive_variation_type = False,
|
||||
get_first_variation_type = False,
|
||||
@@ -226,11 +194,11 @@ class Variation_Filters():
|
||||
ids_variation = ''
|
||||
)
|
||||
|
||||
class Variation_List(BaseModel):
|
||||
class Product_Variation_List(BaseModel):
|
||||
variations: list = []
|
||||
|
||||
def add_variation(self, variation):
|
||||
av.val_instance(variation, 'variation', 'Variation_List.add_variation', Variation)
|
||||
av.val_instance(variation, 'variation', 'Product_Variation_List.add_variation', Product_Variation)
|
||||
self.variations.append(variation)
|
||||
|
||||
def __repr__(self):
|
||||
73
business_objects/store/product_variation_tree.py
Normal file
73
business_objects/store/product_variation_tree.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Product Business Object
|
||||
|
||||
Description:
|
||||
Business object for product
|
||||
"""
|
||||
|
||||
# internal
|
||||
from business_objects.store.product_variation import Product_Variation
|
||||
from extensions import db
|
||||
# external
|
||||
|
||||
|
||||
class Product_Variation_Tree_Node():
|
||||
variation: Product_Variation
|
||||
node_parent: None
|
||||
nodes_child: list
|
||||
def __init__(self):
|
||||
self.nodes_child = []
|
||||
def from_variation_and_node_parent(variation, node_parent):
|
||||
node = Product_Variation_Tree_Node()
|
||||
node.variation = variation
|
||||
node.node_parent = node_parent
|
||||
return node
|
||||
def from_node_parent(node_parent):
|
||||
node = Product_Variation_Tree_Node()
|
||||
node.node_parent = node_parent
|
||||
return node
|
||||
def add_child(self, node_child):
|
||||
self.nodes_child.append(node_child)
|
||||
def is_leaf(self):
|
||||
return (len(self.nodes_child) == 0)
|
||||
|
||||
class Product_Variation_Tree():
|
||||
node_root: Product_Variation_Tree_Node
|
||||
def from_node_root(node_root):
|
||||
tree = Product_Variation_Tree()
|
||||
tree.node_root = node_root
|
||||
return tree
|
||||
def get_variation_type_list(self):
|
||||
variation_types = []
|
||||
node = self.node_root
|
||||
at_leaf_node = node.is_leaf()
|
||||
while not at_leaf_node:
|
||||
variation_types.append(node.variation.name_variation_type)
|
||||
at_leaf_node = node.is_leaf()
|
||||
if not at_leaf_node:
|
||||
node = node.nodes_child[0]
|
||||
return variation_types
|
||||
def is_equal(self, tree):
|
||||
my_type_list = self.get_variation_type_list()
|
||||
sz_me = len(my_type_list)
|
||||
other_type_list = tree.get_variation_type_list()
|
||||
sz_other = len(other_type_list)
|
||||
is_equal = (sz_me == sz_other)
|
||||
if is_equal:
|
||||
for index_type in range(sz_me):
|
||||
if sz_me[index_type] != sz_other[index_type]:
|
||||
is_equal = False
|
||||
break
|
||||
return is_equal
|
||||
def from_product_permutation(product_permutation):
|
||||
depth_max = len(product_permutation.variations)
|
||||
node_root = Product_Variation_Tree_Node.from_variation_and_node_parent(product_permutation.variations[0], None)
|
||||
node = node_root
|
||||
for depth in range(depth_max - 1):
|
||||
node = Product_Variation_Tree_Node.from_variation_and_node_parent(product_permutation.variations[depth + 1], node)
|
||||
return Product_Variation_Tree.from_node_root(node_root)
|
||||
@@ -12,21 +12,27 @@ Feature: Stock Item Business Object
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Filters_Stock_Item
|
||||
from business_objects.store.product_price import Product_Price
|
||||
# from business_objects.discount import Discount
|
||||
from business_objects.store.store_base import Store_Base
|
||||
from extensions import db
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
class Stock_Item(db.Model):
|
||||
ATTR_ID_STOCK_ITEM: ClassVar[str] = 'id_stock_item'
|
||||
class Stock_Item(db.Model, Store_Base):
|
||||
ATTR_ID_CURRENCY_COST: ClassVar[str] = f'{Store_Base.ATTR_ID_CURRENCY}-cost'
|
||||
FLAG_DATE_CONSUMED: ClassVar[str] = 'date-consumed'
|
||||
FLAG_DATE_EXPIRATION: ClassVar[str] = 'date-expiration'
|
||||
FLAG_DATE_PURCHASED: ClassVar[str] = 'date-purchased'
|
||||
FLAG_DATE_RECEIVED: ClassVar[str] = 'date-received'
|
||||
FLAG_DATE_UNSEALED: ClassVar[str] = 'date-unsealed'
|
||||
FLAG_IS_CONSUMED: ClassVar[str] = 'is-consumed'
|
||||
FLAG_IS_SEALED: ClassVar[str] = 'is-sealed'
|
||||
FLAG_VALUE_LOCAL_VAT_EXCL_COST: ClassVar[str] = f'{Product_Price.FLAG_VALUE_LOCAL_VAT_EXCL}-cost'
|
||||
FLAG_VALUE_LOCAL_VAT_INCL_COST: ClassVar[str] = f'{Product_Price.FLAG_VALUE_LOCAL_VAT_INCL}-cost'
|
||||
|
||||
id_stock = db.Column(db.Integer, primary_key=True)
|
||||
id_permutation = db.Column(db.Integer)
|
||||
@@ -46,15 +52,18 @@ class Stock_Item(db.Model):
|
||||
is_consumed = db.Column(db.Boolean)
|
||||
date_consumed = db.Column(db.DateTime)
|
||||
active = db.Column(db.Boolean)
|
||||
"""
|
||||
can_view = db.Column(db.Boolean)
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
Store_Base.__init__(self)
|
||||
|
||||
def make_from_DB_stock_item(query_row):
|
||||
_m = 'Product.make_from_DB_stock_item'
|
||||
def from_DB_stock_item(query_row):
|
||||
_m = 'Product.from_DB_stock_item'
|
||||
v_arg_type = 'class attribute'
|
||||
stock_item = Stock_Item()
|
||||
stock_item.id_stock = query_row[0]
|
||||
@@ -76,15 +85,42 @@ class Stock_Item(db.Model):
|
||||
stock_item.is_consumed = av.input_bool(query_row[18], "is_consumed", _m, v_arg_type=v_arg_type)
|
||||
stock_item.date_consumed = query_row[19]
|
||||
stock_item.active = av.input_bool(query_row[20], "active", _m, v_arg_type=v_arg_type)
|
||||
"""
|
||||
stock_item.can_view = av.input_bool(query_row[24], "can_view", _m, v_arg_type=v_arg_type)
|
||||
stock_item.can_edit = av.input_bool(query_row[25], "can_edit", _m, v_arg_type=v_arg_type)
|
||||
stock_item.can_admin = av.input_bool(query_row[26], "can_admin", _m, v_arg_type=v_arg_type)
|
||||
"""
|
||||
return stock_item
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
stock_item = cls()
|
||||
stock_item.id_stock = json[cls.ATTR_ID_STOCK_ITEM]
|
||||
stock_item.id_permutation = json[cls.ATTR_ID_PRODUCT_PERMUTATION]
|
||||
stock_item.id_product = json[cls.ATTR_ID_PRODUCT]
|
||||
stock_item.id_category = json[cls.ATTR_ID_PRODUCT_CATEGORY]
|
||||
stock_item.date_purchased = json[cls.FLAG_DATE_PURCHASED]
|
||||
stock_item.date_received = json[cls.FLAG_DATE_RECEIVED]
|
||||
stock_item.id_location_storage = json[cls.ATTR_ID_LOCATION_STORAGE]
|
||||
stock_item.id_currency_cost = json[cls.ATTR_ID_CURRENCY_COST]
|
||||
stock_item.cost_local_VAT_incl = json[cls.FLAG_VALUE_LOCAL_VAT_INCL_COST]
|
||||
stock_item.cost_local_VAT_excl = json[cls.FLAG_VALUE_LOCAL_VAT_EXCL_COST]
|
||||
stock_item.is_sealed = json[cls.FLAG_IS_SEALED]
|
||||
stock_item.date_unsealed = json[cls.FLAG_DATE_UNSEALED]
|
||||
stock_item.date_expiration = json[cls.FLAG_DATE_EXPIRATION]
|
||||
stock_item.is_consumed = json[cls.FLAG_IS_CONSUMED]
|
||||
stock_item.date_consumed = json[cls.FLAG_DATE_CONSUMED]
|
||||
stock_item.active = json[cls.FLAG_ACTIVE]
|
||||
"""
|
||||
stock_item.can_view = json[cls.FLAG_CAN_VIEW]
|
||||
stock_item.can_edit = json[cls.FLAG_CAN_EDIT]
|
||||
stock_item.can_admin = json[cls.FLAG_CAN_ADMIN]
|
||||
"""
|
||||
return stock_item
|
||||
"""
|
||||
def make_from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
permutation = Product_Permutation.make_from_json(json_basket_item, key_id_product, key_id_permutation)
|
||||
product = Product.make_from_permutation(permutation)
|
||||
def from_json(json_basket_item, key_id_product, key_id_permutation):
|
||||
permutation = Product_Permutation.from_json(json_basket_item, key_id_product, key_id_permutation)
|
||||
product = Product.from_permutation(permutation)
|
||||
return product
|
||||
|
||||
def output_lead_time(self):
|
||||
57
business_objects/store/store_base.py
Normal file
57
business_objects/store/store_base.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""
|
||||
Project: PARTS Website
|
||||
Author: Edward Middleton-Smith
|
||||
Precision And Research Technology Systems Limited
|
||||
|
||||
Technology: Business Objects
|
||||
Feature: Base Store Business Object
|
||||
|
||||
Description:
|
||||
Abstract business object for store objects
|
||||
"""
|
||||
|
||||
# internal
|
||||
# external
|
||||
from typing import ClassVar
|
||||
|
||||
class Store_Base():
|
||||
ATTR_ID_CURRENCY: ClassVar[str] = 'id-currency'
|
||||
# ATTR_ID_CURRENCY_COST: ClassVar[str] = 'id-currency-cost'
|
||||
ATTR_ID_DELIVERY_REGION: ClassVar[str] = 'id-delivery-region'
|
||||
ATTR_ID_DISCOUNT: ClassVar[str] = 'id-discount'
|
||||
ATTR_ID_IMAGE: ClassVar[str] = 'id-image'
|
||||
ATTR_ID_LOCATION_STORAGE: ClassVar[str] = 'id-location-storage'
|
||||
ATTR_ID_PRODUCT: ClassVar[str] = 'id-product'
|
||||
ATTR_ID_PRODUCT_CATEGORY: ClassVar[str] = 'id-category'
|
||||
ATTR_ID_PRODUCT_PERMUTATION: ClassVar[str] = 'id-permutation'
|
||||
ATTR_ID_PRODUCT_PRICE: ClassVar[str] = 'id-price'
|
||||
ATTR_ID_PRODUCT_VARIATION: ClassVar[str] = 'id-variation'
|
||||
ATTR_ID_PRODUCT_VARIATION_TYPE: ClassVar[str] = 'id_variation_type'
|
||||
ATTR_ID_STOCK_ITEM: ClassVar[str] = 'id-stock-item'
|
||||
FLAG_ACTIVE: ClassVar[str] = 'active'
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
for name, value in vars(Store_Base).items():
|
||||
if getattr(value, "__isabstractmethod__", False):
|
||||
if name not in cls.__dict__:
|
||||
raise TypeError(f"Can't instantiate class {cls.__name__} "
|
||||
f"without implementation of abstract method {name}")
|
||||
subclass_value = cls.__dict__[name]
|
||||
if (isinstance(value, (staticmethod, classmethod)) and
|
||||
not isinstance(subclass_value, type(value))):
|
||||
raise TypeError(f"Abstract {type(value).__name__} {name} in {cls.__name__} "
|
||||
f"must be implemented as a {type(value).__name__}")
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is Store_Base:
|
||||
raise TypeError("Can't instantiate abstract class Store_Base directly")
|
||||
return super().__new__(cls)
|
||||
|
||||
def __repr__(self):
|
||||
pass
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
pass
|
||||
def to_json(self):
|
||||
pass
|
||||
@@ -14,14 +14,11 @@ Business objects for Stripe
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Basket_Add, Form_Basket_Edit # Form_Product
|
||||
from extensions import db
|
||||
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Stripe_Product(db.Model):
|
||||
id_product = db.Column(db.Integer, primary_key=True)
|
||||
@@ -9,26 +9,13 @@ Feature: User Business Object
|
||||
|
||||
# internal
|
||||
import lib.argument_validation as av
|
||||
from lib import data_types
|
||||
from forms import Form_Filters_User
|
||||
from business_objects.product import Product, Product_Permutation, Price
|
||||
from business_objects.variation import Variation
|
||||
from business_objects.image import Image
|
||||
from business_objects.delivery_option import Delivery_Option
|
||||
from business_objects.discount import Discount
|
||||
from business_objects.stock_item import Stock_Item
|
||||
from extensions import db
|
||||
# external
|
||||
from enum import Enum
|
||||
from datetime import datetime, timedelta
|
||||
import locale
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
KEY_USER: ClassVar[str] = 'authorisedUser' # 'user' already used
|
||||
|
||||
@@ -50,8 +37,8 @@ class User(db.Model):
|
||||
def __init__(self):
|
||||
self.is_logged_in = False
|
||||
|
||||
def make_from_DB_user(query_row):
|
||||
_m = 'User.make_from_DB_user'
|
||||
def from_DB_user(query_row):
|
||||
_m = 'User.from_DB_user'
|
||||
user = User()
|
||||
user.id_user = query_row[0]
|
||||
user.id_user_auth0 = query_row[1]
|
||||
@@ -259,7 +246,7 @@ class User_Permission_Evaluation(db.Model):
|
||||
can_edit = db.Column(db.Boolean)
|
||||
can_admin = db.Column(db.Boolean)
|
||||
|
||||
def make_from_DB_user_eval(query_row):
|
||||
def from_DB_user_eval(query_row):
|
||||
user_permission_evaluation = User_Permission_Evaluation()
|
||||
user_permission_evaluation.id_evaluation = query_row[0]
|
||||
user_permission_evaluation.guid = query_row[1]
|
||||
|
||||
Reference in New Issue
Block a user