diff --git a/App/Auth/__init__.py b/App/Auth/__init__.py index 3e426da..402607f 100644 --- a/App/Auth/__init__.py +++ b/App/Auth/__init__.py @@ -12,6 +12,7 @@ def auth(): auth_data = auth_model(data) if auth_data.status: session.update({ + 'user_id': auth_data.result[0]['user_ID'], 'login': auth_data.result[0]['login'], 'access_user': data['access'], 'role': auth_data.result[0]['user_role'], diff --git a/App/Auth/sql/auth.sql b/App/Auth/sql/auth.sql index 4fb90bf..56c956a 100644 --- a/App/Auth/sql/auth.sql +++ b/App/Auth/sql/auth.sql @@ -1,4 +1,4 @@ -SELECT login, user_role FROM $access +SELECT user_ID, login, user_role FROM $access WHERE login = '$login' AND password = '$password' LIMIT 1; \ No newline at end of file diff --git a/App/Auth/templates/auth.html b/App/Auth/templates/auth.html index 986bdaa..f5f4cb8 100644 --- a/App/Auth/templates/auth.html +++ b/App/Auth/templates/auth.html @@ -1,5 +1,5 @@ - + diff --git a/App/Waybill/__init__.py b/App/Waybill/__init__.py index 66fdc1b..55276bc 100644 --- a/App/Waybill/__init__.py +++ b/App/Waybill/__init__.py @@ -1,11 +1,47 @@ -from flask import request, Blueprint, render_template, session, current_app, redirect, url_for +from flask import request, Blueprint, render_template, session, redirect, url_for from checker import check_auth -waybill_bp = Blueprint('waybill_bp', __name__, template_folder='templates') +from .model import index_waybill, form_waybill, clear, button_click, transaction_order_model +from datetime import date -@waybill_bp.route('/', methods=['GET', 'POST']) +waybill_bp = Blueprint('waybill_bp', __name__, template_folder='templates', static_folder='static') + +@waybill_bp.route('/', methods=['GET']) @check_auth -def waybill(): - if request.method == 'GET': - return render_template('waybill.html') - print(request.form) - return 'OK' \ No newline at end of file +def index(): + lst = index_waybill() + if lst is not None: + waybill = form_waybill() + return render_template('waybill.html', items=lst, waybill=waybill) + else: + return render_template('error.html', error_message="Ошибка в подключении к СУБД") + +@waybill_bp.route('/', methods=['POST']) +@check_auth +def waybill_main(): + status = button_click(request) + if status: + return redirect(url_for('waybill_bp.index')) + else: + return render_template("error.html", error_message="Товар не был добавлен в корзину") + +@waybill_bp.route('/clear', methods=['GET']) +@check_auth +def clear_waybill(): + clear() + return redirect(url_for('waybill_bp.index')) + +@waybill_bp.route('/save_order') +@check_auth +def save_order(): + if not session.get('waybill',{}): + return redirect(url_for('waybill_bp.index')) + + user_id = session.get('user_id',"") + current_date = date.today().strftime("%Y-%m-%d") + ' ' + date.today().strftime("%H:%M:%S") + + result = transaction_order_model(user_id, current_date) + if result.status: + print("Order success") + return render_template("order_finish.html", order_id = result.result[0]) + else: + return render_template("error.html", error_message=result.error_message) \ No newline at end of file diff --git a/App/Waybill/cache/__init__.py b/App/Waybill/cache/__init__.py new file mode 100644 index 0000000..d902592 --- /dev/null +++ b/App/Waybill/cache/__init__.py @@ -0,0 +1,42 @@ +import json +from redis import Redis, ConnectionError, DataError + +class RedisCache: + def __init__(self, config: dict): + self.config = config + self.conn = self._connect() + + def _connect(self): + try: + conn = Redis(**self.config) + return conn + except DataError as err: + print(err) + return None + + def set_value(self, name: str, value_dict: dict, ttl: int): + try: + value_js = json.dumps(value_dict) + self.conn.set(name=name, value=value_js) + if ttl > 0: + self.conn.expire(name, ttl) + return True + except ConnectionError as err: + print(err) + return False + + def get_value(self, name: str): + try: + value_js = self.conn.get(name) + except: + return None + value_js = self.conn.get(name) + if value_js: + value_dict = json.loads(value_js) + return value_dict + else: + return None + + def __exit__(self, exc_type, exc_val, exc_tb): + self.conn.close() + return True \ No newline at end of file diff --git a/App/Waybill/cache/wrapper.py b/App/Waybill/cache/wrapper.py new file mode 100644 index 0000000..ff38803 --- /dev/null +++ b/App/Waybill/cache/wrapper.py @@ -0,0 +1,26 @@ +from functools import wraps +from Waybill.cache import RedisCache + +def fetch_from_cache(cache_name: str, cache_config: dict): + cache_conn = RedisCache(cache_config['redis']) + ttl = cache_config['ttl'] + def decorator(f): + # как оно работает + # лезем в кэш и смотрим, есть ли там что-то + # если есть, то возвращаем кэшированную информацию + # если нет, то запускаем декорируемую функцию + # достаём оттуда информацию + # заносим её в кэш + # возвращаем кэшированную информацию + @wraps(f) + def wrapper(*args, **kwargs): + cached_value = cache_conn.get_value(cache_name) + print("cached_value=", cached_value) + if cached_value: + return cached_value + response = f(*args, **kwargs) + print("response=", response) + cache_conn.set_value(cache_name,response,ttl) + return response + return wrapper + return decorator \ No newline at end of file diff --git a/App/Waybill/db/DBconnect.py b/App/Waybill/db/DBconnect.py new file mode 100644 index 0000000..b335654 --- /dev/null +++ b/App/Waybill/db/DBconnect.py @@ -0,0 +1,34 @@ +import pymysql +from pymysql.err import * +class DBContextManager: + def __init__(self, db_config : dict): + self.db_config = db_config + self.connection = None + self.cursor = None + + def __enter__(self): + try: + self.connection = pymysql.connect( + host=self.db_config['host'], + port=self.db_config['port'], + user=self.db_config['user'], + password=self.db_config['password'], + db=self.db_config['db'] + ) + self.cursor = self.connection.cursor() + return self.cursor + except (OperationalError, KeyError) as err: + print(err.args) + return None + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.connection and self.cursor: + if exc_type: + print(exc_type, '\n', exc_val) + self.connection.rollback() + else: + self.connection.commit() + self.cursor.close() + self.connection.close() + return True + \ No newline at end of file diff --git a/App/Waybill/db/__init__.py b/App/Waybill/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/App/Waybill/db/sql_provider.py b/App/Waybill/db/sql_provider.py new file mode 100644 index 0000000..a3a1855 --- /dev/null +++ b/App/Waybill/db/sql_provider.py @@ -0,0 +1,14 @@ +import os +from string import Template + +class SQLProvider: + def __init__(self, file_path): + self.scripts = {} + for file in os.listdir(file_path): + _sql = open(f'{file_path}/{file}').read() + self.scripts[file] = Template(_sql) + + def get(self, name, params) -> dict: + if name not in self.scripts: + raise ValueError(f'SQL template {name} not found') + return self.scripts[name].substitute(**params) \ No newline at end of file diff --git a/App/Waybill/db/work.py b/App/Waybill/db/work.py new file mode 100644 index 0000000..5a7f039 --- /dev/null +++ b/App/Waybill/db/work.py @@ -0,0 +1,16 @@ +from .DBconnect import DBContextManager + +def select_list(db_config, sql) -> list: + with DBContextManager(db_config) as cursor: + if cursor is None: + raise ValueError("Cursor not created") + else: + cursor.execute(sql) + result = cursor.fetchall() + schema = [item[0] for item in cursor.description] + lst = [dict(zip(schema, row)) for row in result] + return lst + +def transaction(cursor, sql): + cursor.execute(sql) + return True \ No newline at end of file diff --git a/App/Waybill/model.py b/App/Waybill/model.py new file mode 100644 index 0000000..01162b1 --- /dev/null +++ b/App/Waybill/model.py @@ -0,0 +1,124 @@ +from .db.sql_provider import SQLProvider +from .db.work import select_list, transaction +from .db.DBconnect import DBContextManager + +from flask import current_app, session +from dataclasses import dataclass +from datetime import date +from .cache.wrapper import fetch_from_cache +import os + +@dataclass +class InfoRespronse: + result: tuple + error_message: str + status: bool + +sql_provider = SQLProvider(os.path.join(os.path.dirname(__file__), 'sql')) + +def clear(): + if session.get('waybill',{}): + session.pop('waybill') + +def form_waybill() -> list: + current_waybill = session.get('waybill',{}) + waybill = [] + for k,v in current_waybill.items(): + _sql = sql_provider.get('one_good.sql', dict(work_id=k)) + product = select_list(current_app.config['db_config'], _sql)[0] + product['amount'] = v + waybill.append(product) + return waybill + +def index_waybill() -> list: + db_config = current_app.config['db_config'] + cache_config = current_app.config['cache_config'] + + cache_select = fetch_from_cache('items_cached', cache_config)(select_list) + _sql = sql_provider.get('goods.sql', {}) + products = cache_select(db_config, _sql) + + return products + +def button_click(request): + db_config = current_app.config['db_config'] + data = dict(work_id=int(request.form['product_display'])) + + _sql = sql_provider.get('one_good.sql', data) + result = select_list(db_config, _sql) + if result == None: + return False + + product = result[0] + + if request.form.get('add'): + if 'waybill' not in session: + session['waybill'] = dict() + session['total'] = '0' + + if str(product['work_id']) in session['waybill']: + pr_id = product['work_id'] + price = product['price'] + amount = int(session['waybill'][str(pr_id)]) + session['waybill'][str(pr_id)] = str(amount+1) + session['total'] = str(int(session['total']) + price) + session.modified = True + else: + print("NEW WORKPIECE") + pr_id = product['work_id'] + price = product['price'] + session['waybill'][str(pr_id)] = '1' + session['total'] = str(int(session['total']) + price) + print(session['waybill']) + session.modified = True + + # elif request.form.get('product_display_plus'): + # # increasing count in waybill + + # amount = int(session['waybill'][str(product['work_id'])]) + # session['waybill'][str(product['work_id'])] = str(amount + 1) + # session.modified = True + + elif request.form.get('product_display_minus'): + # decreasing count in waybill + + amount = int(session['waybill'][str(product['work_id'])]) + if amount == 1: + session['waybill'].pop(str(product['work_id'])) + else: + session['waybill'][str(product['work_id'])] = str(amount-1) + session['total'] = str(int(session['total']) - product['price']) + session.modified = True + return True + +def transaction_order_model(user_id: int, current_date: date): + + db_config = current_app.config['db_config'] + waybill = session.get('waybill',{}) + total = session.get('total', 0) + + # Чтобы всё это шло как одна транзакция + with DBContextManager(db_config) as cursor: + + data = dict(e_user_id=user_id, e_order_date=current_date, e_total=total) + + _sql = sql_provider.get('create_order.sql', data) + try: + cursor.execute(_sql) + except: + return InfoRespronse((), error_message="Заказ не был создан", status=False) + + order_id = cursor.lastrowid + for key, value in waybill.items(): + _sql = sql_provider.get('insert_order_line.sql', + dict(e_order_id = order_id, + e_prod_id = int(key), + e_amount = int(value))) + try: + cursor.execute(_sql) + except: + return InfoRespronse((), error_message="Заказ не был создан", status=False) + + result = tuple([order_id]) + clear() + return InfoRespronse(result, error_message="", status=True) \ No newline at end of file diff --git a/App/Waybill/sql/create_order.sql b/App/Waybill/sql/create_order.sql new file mode 100644 index 0000000..0b5e11c --- /dev/null +++ b/App/Waybill/sql/create_order.sql @@ -0,0 +1 @@ +INSERT INTO `waybill` (`user_id`,`waybill_date`, `total`) VALUES ($e_user_id, '$e_order_date', $e_total); \ No newline at end of file diff --git a/App/Waybill/sql/goods.sql b/App/Waybill/sql/goods.sql new file mode 100644 index 0000000..9a18e9b --- /dev/null +++ b/App/Waybill/sql/goods.sql @@ -0,0 +1 @@ +SELECT work_id, name, price, material, count FROM workpiece \ No newline at end of file diff --git a/App/Waybill/sql/insert_order_line.sql b/App/Waybill/sql/insert_order_line.sql new file mode 100644 index 0000000..26091a8 --- /dev/null +++ b/App/Waybill/sql/insert_order_line.sql @@ -0,0 +1,2 @@ +INSERT INTO `waybill_lines` VALUES ($e_order_id, $e_prod_id, +(SELECT price FROM workpiece WHERE work_id = $e_prod_id), $e_amount); \ No newline at end of file diff --git a/App/Waybill/sql/one_good.sql b/App/Waybill/sql/one_good.sql new file mode 100644 index 0000000..d81e194 --- /dev/null +++ b/App/Waybill/sql/one_good.sql @@ -0,0 +1,3 @@ +SELECT `work_id`, `name`, `price`, `weight` +FROM `workpiece` +WHERE `work_id` = $work_id; \ No newline at end of file diff --git a/App/Waybill/static/css/waybill.css b/App/Waybill/static/css/waybill.css new file mode 100644 index 0000000..b7d39da --- /dev/null +++ b/App/Waybill/static/css/waybill.css @@ -0,0 +1,10 @@ +.card { + background: #fff; + border: 1px solid #ddd; + color: black; + border-radius: 10px; + width: 250px; + padding: 15px; + margin-bottom: 15px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} \ No newline at end of file diff --git a/App/Waybill/templates/card.html b/App/Waybill/templates/card.html new file mode 100644 index 0000000..b6248d6 --- /dev/null +++ b/App/Waybill/templates/card.html @@ -0,0 +1,24 @@ +{% macro render_item(item, show_amount = False, show_form = False) %} + +
+
+
{{ item['name'] }}
+

Цена: {{ item['price'] }} ₽

+ {% if show_amount %} + Количество: {{item['amount']}}
+
+ + + +
+ {% endif %} + {% if show_form %} +
+ + +
+ {% endif %} +
+
+ +{% endmacro %} \ No newline at end of file diff --git a/App/Waybill/templates/order_finish.html b/App/Waybill/templates/order_finish.html new file mode 100644 index 0000000..dd7b64f --- /dev/null +++ b/App/Waybill/templates/order_finish.html @@ -0,0 +1,20 @@ + + + + + Накладная оформлена + + + + +
+
+

Накладная оформлена

+

Номер вашей накладной: {{ order_id }}

+ +
+
+ + diff --git a/App/Waybill/templates/waybill.html b/App/Waybill/templates/waybill.html index 529270f..9b0603a 100644 --- a/App/Waybill/templates/waybill.html +++ b/App/Waybill/templates/waybill.html @@ -1,48 +1,48 @@ +{% import 'card.html' as card %} - + - Авторизация - - + Оформление накладной + + + + -

Заглушка для примера составления накладной

-
-
- -
- Фото товара -

Название товара 1

-

Цена: 500 руб.

-

Количество: 10 шт.

-
- +
+
+ +
+

Список заготовок

+
+ {% for item in items %} +
+ {{ card.render_item(item, show_form = True, show_amount = False) }} +
+ {% endfor %}
-
- Фото товара -

Название товара 2

-

Цена: 1200 руб.

-

Количество: 5 шт.

-
- + + +
+

Накладная

+ {% if waybill %} + {% for item in waybill %} + {{ card.render_item(item, show_form = False, show_amount = True) }} + {% endfor %} + -
-
- Фото товара -

Название товара 3

-

Цена: 800 руб.

-

Количество: 7 шт.

-
- + {% else %} + Ваша накладная пуста + + {% endif %}
-
- - -
- +
- \ No newline at end of file + diff --git a/App/Waybill/waybill_model.py b/App/Waybill/waybill_model.py deleted file mode 100644 index 2928f24..0000000 --- a/App/Waybill/waybill_model.py +++ /dev/null @@ -1,23 +0,0 @@ -from Database.sql_provider import SQLProvider -from Database.select import select_list -from flask import current_app -from dataclasses import dataclass -import os - -@dataclass -class InfoRespronse: - result: tuple - error_message: str - status: bool - -sql_provider = SQLProvider(os.path.join(os.path.dirname(__file__), 'sql')) - - -def waybill_model(input_data) -> InfoRespronse: - _sql = sql_provider.get('waybill.sql', input_data) - result = select_list(current_app.config['db_config'], _sql) - if result is None: - return InfoRespronse((), - error_message = 'Ошибка в подключении к базе данных. Свяжитесь с администратором', - status=False) - return InfoRespronse(result, error_message='', status=True) \ No newline at end of file diff --git a/App/app.py b/App/app.py index cd4ad61..fe5e3d3 100644 --- a/App/app.py +++ b/App/app.py @@ -10,7 +10,8 @@ app.secret_key = 'suplex' app.config.update( db_config=json.load(open(os.path.join(os.path.dirname(__file__), 'data/config.json'))), - db_access=json.load(open(os.path.join(os.path.dirname(__file__), 'data/db_access.json'))) + db_access=json.load(open(os.path.join(os.path.dirname(__file__), 'data/db_access.json'))), + cache_config=json.load(open(os.path.join(os.path.dirname(__file__), 'data/redis_config.json'))) ) app.register_blueprint(requests_bp, url_prefix='/requests') diff --git a/App/data/config.json b/App/data/config.json index 75428e2..8b7b193 100644 --- a/App/data/config.json +++ b/App/data/config.json @@ -1,5 +1,5 @@ { - "host": "localhost", + "host": "89.46.42.74", "port": 3306, "user": "manager", "password": "ilikepizza", diff --git a/App/data/redis_config.json b/App/data/redis_config.json new file mode 100644 index 0000000..2739ead --- /dev/null +++ b/App/data/redis_config.json @@ -0,0 +1,8 @@ +{ + "redis": { + "host" :"89.46.42.74", + "port": 6379, + "db" : 0 + }, + "ttl": 60 +} \ No newline at end of file diff --git a/App/static/css/output.css b/App/static/css/output.css deleted file mode 100644 index e91e70a..0000000 --- a/App/static/css/output.css +++ /dev/null @@ -1,14 +0,0 @@ -table { - border: 1px solid black; - border-collapse: collapse; - background: white; - margin: 0 auto; -} -th { - background-color: #d3d3d3; -} -th, td { - border: 1px solid black; - padding: 5px; - text-align: center; -} diff --git a/App/static/css/waybill.css b/App/static/css/waybill.css deleted file mode 100644 index 048abb5..0000000 --- a/App/static/css/waybill.css +++ /dev/null @@ -1,48 +0,0 @@ -.container { - display: flex; - flex-wrap: wrap; - gap: 20px; - padding: 20px; - justify-content: center; -} -.card { - background: #fff; - border: 1px solid #ddd; - border-radius: 10px; - width: 250px; - padding: 15px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); -} -.card img { - width: 100%; - height: 150px; - object-fit: cover; - border-radius: 5px; -} -.card h3 { - margin: 10px 0; - font-size: 18px; -} -.price { - font-weight: bold; - color: #28a745; -} -.quantity { - margin: 10px 0; - font-weight: bold; - color: #007bff; -} -.input-container { - margin: 10px 0; -} -.input-container input { - width: 80%; - padding: 8px; - font-size: 14px; - border: 1px solid #ddd; - border-radius: 5px; -} -.button-container { - text-align: center; - margin: 20px 0; -} \ No newline at end of file diff --git a/App/templates/main_menu.html b/App/templates/main_menu.html index 8fd8fc8..473c62e 100644 --- a/App/templates/main_menu.html +++ b/App/templates/main_menu.html @@ -35,7 +35,20 @@
- {% if ses['access_user'] == 'internal_users' %} + {% if 'role' not in ses %} +
+

Для доступа к функциям системы необходимо авторизоваться

+
+ {% endif %} + {% if 'role' in ses and ses['access_user'] == 'external_users' %} +
+

Новая накладная

+ + + +
+ {% endif %} + {% if 'role' in ses and ses['access_user'] == 'internal_users' %}

Запросы

- @@ -57,49 +65,14 @@ >

Отчёты

-
- {% else %} -
-
-
{% endif %}
- \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5684634..4e407bf 100644 Binary files a/requirements.txt and b/requirements.txt differ