Merge branch 'main' into Waybill
This commit is contained in:
@@ -9,14 +9,12 @@ def auth():
|
||||
return render_template('auth.html')
|
||||
else:
|
||||
data = request.form.to_dict()
|
||||
data['table'] = 'internal_users' if 'internal' in data else 'external_users'
|
||||
auth_data = auth_model(data)
|
||||
if auth_data.status:
|
||||
session.update({
|
||||
'login': auth_data.result[0]['login'],
|
||||
'access_user': data['access'],
|
||||
'role': auth_data.result[0]['user_role'],
|
||||
'db_config': current_app.config['db_config'],
|
||||
'access_user': 'in' if 'internal' in data else 'ext',
|
||||
'permanent': True
|
||||
})
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
from Database.select import select_list
|
||||
from Database.sql_provider import SQLProvider
|
||||
from .db.select import select_list
|
||||
from .db.sql_provider import SQLProvider
|
||||
from flask import current_app
|
||||
import os
|
||||
|
||||
|
||||
13
App/Auth/description.txt
Normal file
13
App/Auth/description.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
.
|
||||
├── auth_model.py - реализация модели авторизации
|
||||
├── db
|
||||
│ ├── DBconnect.py - коннектор к СУБД
|
||||
│ ├── __init__.py - файл для инициализации db как модуль
|
||||
│ ├── select.py - файл для выполнения select-запросов к СУБД
|
||||
│ └── sql_provider.py - SQL-провайдер для формирования запроса к СУБД
|
||||
├── __init__.py - файл для инициализации Auth как модуль
|
||||
├── sql
|
||||
│ └── auth.sql - sql-запрос, проверяющий наличие пользователя в СУБД
|
||||
└── templates
|
||||
└── auth.html - шаблон для страницы авторизации
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
SELECT login, user_role FROM $table
|
||||
SELECT login, user_role FROM $access
|
||||
WHERE login = '$login'
|
||||
AND password = '$password'
|
||||
LIMIT 1;
|
||||
@@ -7,16 +7,22 @@
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Авторизация</h1>
|
||||
<div class="form">
|
||||
<form action="" method="post" name="auth">
|
||||
<label for="login">Логин: </label>
|
||||
<input type="text" name="login" required>
|
||||
<label for="password">Пароль: </label>
|
||||
<input type="password" name="password" required><br>
|
||||
<p><input type="checkbox" name="internal">Внутренний пользователь</p>
|
||||
<p>
|
||||
Уровень доступа:
|
||||
<select name="access">
|
||||
<option value="internal_users">Внутренний</option>
|
||||
<option value="external_users">Внешний</option>
|
||||
</select>
|
||||
</p>
|
||||
<input type="submit" value="Вход">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ошибка</title>
|
||||
<link href="/static/css/auth.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Сожалеем</h1>
|
||||
<p>{{ error_message }}</p>
|
||||
<a href="{{ url_for('index') }}"><button>На главную страницу</button></a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,7 @@
|
||||
from flask import request, Blueprint, render_template
|
||||
from flask import request, Blueprint, render_template, session, url_for
|
||||
from checker import check_auth
|
||||
from os import path
|
||||
from .report_model import view_report, make_report
|
||||
import json
|
||||
|
||||
with open(path.join(path.dirname(__file__), 'reports.json')) as f:
|
||||
@@ -8,14 +9,62 @@ with open(path.join(path.dirname(__file__), 'reports.json')) as f:
|
||||
|
||||
report_bp = Blueprint('report_bp', __name__, template_folder='templates')
|
||||
|
||||
@report_bp.route('/', methods=['GET', 'POST'])
|
||||
@report_bp.route('/menu')
|
||||
@check_auth
|
||||
def menu():
|
||||
if request.method == 'GET':
|
||||
return render_template('report_menu.html', options=report_list)
|
||||
return render_template('report_menu.html')
|
||||
|
||||
@report_bp.route('/quaterly', methods=['GET', 'POST'])
|
||||
@report_bp.route('/create', methods=['GET', 'POST'])
|
||||
@check_auth
|
||||
def quaterly():
|
||||
def create():
|
||||
if request.method == 'GET':
|
||||
return render_template('quaterly.html')
|
||||
return render_template('report_basic.html',
|
||||
write=True,
|
||||
title='Создание отчета',
|
||||
items = report_list)
|
||||
else:
|
||||
data = dict(id=request.form.get('category'),
|
||||
month=request.form.get('month'),
|
||||
year=request.form.get('year'))
|
||||
|
||||
with open(path.join(path.dirname(__file__), f'access/{data['id']}.json')) as f:
|
||||
report_access = json.load(f)
|
||||
|
||||
if session['role'] in report_access['write']:
|
||||
proc_name = report_access['procedure']
|
||||
ready_report = make_report(data, proc_name)
|
||||
if ready_report.status:
|
||||
return render_template("OK.html")
|
||||
else:
|
||||
return render_template("error.html", error_message=ready_report.error_message)
|
||||
else:
|
||||
return render_template("error.html", error_message='Недостаточно прав для создания данного отчета!')
|
||||
|
||||
@report_bp.route('/view', methods=['GET', 'POST'])
|
||||
@check_auth
|
||||
def view():
|
||||
if request.method == 'GET':
|
||||
return render_template('report_basic.html',
|
||||
write=False,
|
||||
title='Просмотр отчета',
|
||||
items = report_list)
|
||||
else:
|
||||
data = dict(id=request.form.get('category'),
|
||||
month=request.form.get('month'),
|
||||
year=request.form.get('year'))
|
||||
|
||||
with open(path.join(path.dirname(__file__), f'access/{data['id']}.json')) as f:
|
||||
report_access = json.load(f)
|
||||
|
||||
if session['role'] in report_access['read']:
|
||||
ready_report = view_report(data, report_access['view'])
|
||||
if ready_report.status:
|
||||
title= f'{report_access["title"]} за {data["month"]}-{data["year"]}'
|
||||
return render_template("output.html", items=ready_report.result,
|
||||
header=title,
|
||||
link = url_for('report_bp.menu'))
|
||||
else:
|
||||
return render_template("error.html", error_message=ready_report.error_message)
|
||||
else:
|
||||
return render_template("error.html", error_message='Недосточно прав для чтения данного отчета!')
|
||||
7
App/Report/access/1.json
Normal file
7
App/Report/access/1.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"title" : "Отчет о поставках заготовок",
|
||||
"write" : ["Менеджер"],
|
||||
"read" : ["Управляющий"],
|
||||
"view" : "workpiece_report",
|
||||
"procedure" : "report_workpiece"
|
||||
}
|
||||
7
App/Report/access/2.json
Normal file
7
App/Report/access/2.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"title" : "Отчет о поставках поставщиками",
|
||||
"write" : ["Бухгалтер"],
|
||||
"read" : ["Управляющий"],
|
||||
"view" : "sellers_report",
|
||||
"procedure" : "report_sellers"
|
||||
}
|
||||
34
App/Report/db/DBconnect.py
Normal file
34
App/Report/db/DBconnect.py
Normal file
@@ -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
|
||||
|
||||
0
App/Report/db/__init__.py
Normal file
0
App/Report/db/__init__.py
Normal file
14
App/Report/db/sql_provider.py
Normal file
14
App/Report/db/sql_provider.py
Normal file
@@ -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)
|
||||
23
App/Report/db/work.py
Normal file
23
App/Report/db/work.py
Normal file
@@ -0,0 +1,23 @@
|
||||
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 procedure(db_config, name, args: tuple):
|
||||
with DBContextManager(db_config) as cursor:
|
||||
if cursor is None:
|
||||
raise ValueError("Cursor not created")
|
||||
else:
|
||||
cursor.callproc(name, args)
|
||||
result = cursor.fetchall()[0]
|
||||
schema = cursor.description[0]
|
||||
lst = dict(zip(schema, result))
|
||||
return lst
|
||||
20
App/Report/description.txt
Normal file
20
App/Report/description.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
.
|
||||
├── access - json-файлы доступа к вариантам для формирования отчета
|
||||
│ ├── 1.json
|
||||
│ └── 2.json
|
||||
├── db
|
||||
│ ├── DBconnect.py - коннектор к СУБД
|
||||
│ ├── __init__.py - файл для инициализации db как модуль
|
||||
│ ├── sql_provider.py - SQL-провайдер для формирования запроса к СУБД
|
||||
│ └── work.py - файл для выполнения select и call запросов
|
||||
├── __init__.py - файл для инициализации Report как модуль
|
||||
├── report_model.py - реализация модели варианта работы с отчетами
|
||||
├── reports.json - файл с названиями отчетов и их идентификаторы
|
||||
├── sql
|
||||
│ ├── check_report.sql - sql-запрос для проверки наличия отчета в БД
|
||||
│ ├── sellers_report.sql - sql-запрос для просмотра отчета о поставках поставщиков
|
||||
│ └── workpiece_report.sql - sql-запрос для просмотра отчета о поставках заготовок
|
||||
└── templates
|
||||
├── OK.html - шаблон для вывода информации об успешном добавлении отчета в БД
|
||||
├── report_basic.html - шаблон для ввода параметров просмотра/создания отчета
|
||||
└── report_menu.html - шаблон для выбора просмотра/создания отчета
|
||||
53
App/Report/report_model.py
Normal file
53
App/Report/report_model.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from dataclasses import dataclass
|
||||
from .db.work import select_list, procedure
|
||||
from .db.sql_provider import SQLProvider
|
||||
from flask import current_app
|
||||
from os import path
|
||||
|
||||
sql_provider = SQLProvider(path.join(path.dirname(__file__), 'sql'))
|
||||
@dataclass
|
||||
class InfoRespronse:
|
||||
result: tuple
|
||||
error_message: str
|
||||
status: bool
|
||||
|
||||
def check_report(input_data: dict) -> bool:
|
||||
_sql = sql_provider.get('check_report.sql', input_data)
|
||||
result = select_list(current_app.config['db_config'], _sql)
|
||||
if result is None or result[0]['exist'] == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
def view_report(input_data: dict, view_script: str) -> InfoRespronse:
|
||||
status = check_report(input_data)
|
||||
if not status:
|
||||
return InfoRespronse((),
|
||||
error_message = 'Отчет не найден',
|
||||
status=False)
|
||||
|
||||
_sql = sql_provider.get(f'{view_script}.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)
|
||||
|
||||
def make_report(input_data: dict, proc_name: str) -> InfoRespronse:
|
||||
status = check_report(input_data)
|
||||
if status:
|
||||
return InfoRespronse((),
|
||||
error_message = 'Отчет уже существует',
|
||||
status=False)
|
||||
|
||||
data = tuple(input_data.values())
|
||||
result = procedure(current_app.config['db_config'], proc_name, data)
|
||||
if result is None:
|
||||
return InfoRespronse((),
|
||||
error_message = 'Ошибка в подключении к базе данных. Свяжитесь с администратором',
|
||||
status=False)
|
||||
elif result['message'] != 'OK':
|
||||
return InfoRespronse((),
|
||||
error_message = 'Невозможно создать отчет (нет продаж за выбранный период)',
|
||||
status=False)
|
||||
return InfoRespronse((), error_message='', status=True)
|
||||
@@ -1,3 +1,4 @@
|
||||
[
|
||||
{"name": "Квартальный отчет передвижений заготовок", "url": "report_bp.quaterly"}
|
||||
{"id": 1, "name": "Заготовки"},
|
||||
{"id": 2, "name": "Поставщики"}
|
||||
]
|
||||
4
App/Report/sql/check_report.sql
Normal file
4
App/Report/sql/check_report.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM reports
|
||||
WHERE report_category_id = '$id' AND (month = '$month' AND year = '$year')
|
||||
) AS exist;
|
||||
@@ -1,11 +0,0 @@
|
||||
SELECT sellers.name AS 'Поставщик',
|
||||
w.date_of_delivery AS 'Дата поставки',
|
||||
SUM(wl.count) AS 'Общее количество заготовок',
|
||||
SUM(wl.price) AS 'Общая стоимость поставленных заготовок'
|
||||
FROM waybill w
|
||||
JOIN waybill_lines wl USING(waybill_id)
|
||||
JOIN workpiece USING(work_id)
|
||||
JOIN sellers USING(sel_id)
|
||||
WHERE workpiece.material = '${material}'
|
||||
AND (w.date_of_delivery BETWEEN '${date_from}' AND '${date_to}')
|
||||
GROUP BY sellers.name, w.date_of_delivery;
|
||||
8
App/Report/sql/sellers_report.sql
Normal file
8
App/Report/sql/sellers_report.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
SELECT
|
||||
s.name AS Поставщик,
|
||||
SUM(reports.count) AS 'Количество поставок',
|
||||
SUM(reports.sum) AS Сумма
|
||||
FROM reports
|
||||
JOIN sellers s ON reports.item_id = s.sel_id
|
||||
WHERE report_category_id = '$id' AND (month = '$month' AND year = '$year')
|
||||
GROUP BY s.name;
|
||||
8
App/Report/sql/workpiece_report.sql
Normal file
8
App/Report/sql/workpiece_report.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
SELECT w.name AS Наименование,
|
||||
w.material AS Материал,
|
||||
sum(sum) AS Сумма,
|
||||
sum(reports.count) AS Количество
|
||||
from reports
|
||||
JOIN workpiece w ON reports.item_id = w.work_id
|
||||
WHERE report_category_id = '$id' AND (month = '$month' AND year = '$year')
|
||||
GROUP BY w.material, w.name;
|
||||
15
App/Report/templates/OK.html
Normal file
15
App/Report/templates/OK.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Успех</title>
|
||||
<link href="/static/css/auth.css" type="text/css" rel="stylesheet">
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Успешно!</h1>
|
||||
<p>Отчет успешно добавлен в базу данных!</p>
|
||||
<a href="{{ url_for('index') }}"><button>На главную страницу</button></a>
|
||||
<a href="{{ url_for('report_bp.menu') }}"><button>В меню отчетов</button></a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Квартальный отчет</title>
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="logout">
|
||||
<a href="{{ url_for('logout') }}"><button>Выход</button></a>
|
||||
</div>
|
||||
<!-- Not implemented -->
|
||||
<h1>Заглушка для квартального отчета</h1>
|
||||
<div class="return">
|
||||
<a href="{{ url_for('index') }}"><button>Главное меню</button></a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
52
App/Report/templates/report_basic.html
Normal file
52
App/Report/templates/report_basic.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="logout">
|
||||
<a href="{{ url_for('logout') }}"><button>Выход</button></a>
|
||||
</div>
|
||||
<h1>{{ title }}</h1>
|
||||
<div class="form">
|
||||
<form action="" method="post">
|
||||
<label for="category">Выберите предмет формирования отчета</label>
|
||||
<select name="category" id="category" required>
|
||||
{% for item in items %}
|
||||
<option value="{{ item['id'] }}">{{ item['name'] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="month">Выберите отчетный период</label>
|
||||
<div class="period">
|
||||
<select name="month" id="month" required>
|
||||
<option value="1">Январь</option>
|
||||
<option value="2">Февраль</option>
|
||||
<option value="3">Март</option>
|
||||
<option value="4">Апрель</option>
|
||||
<option value="5">Май</option>
|
||||
<option value="6">Июнь</option>
|
||||
<option value="7">Июль</option>
|
||||
<option value="8">Август</option>
|
||||
<option value="9">Сентябрь</option>
|
||||
<option value="10">Октябрь</option>
|
||||
<option value="11">Ноябрь</option>
|
||||
<option value="12">Декабрь</option>
|
||||
</select>
|
||||
<input type="number" name="year" value="2024" min="1990" max="2100" required>
|
||||
</div>
|
||||
|
||||
{% if write %}
|
||||
<button type="submit" value="write">Создать</button>
|
||||
{% else %}
|
||||
<button type="submit" value="read">Просмотр</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="return">
|
||||
<a href="{{ url_for('index') }}"><button>Главное меню</button></a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,11 +10,10 @@
|
||||
<a href="{{ url_for('logout') }}"><button>Выход</button></a>
|
||||
</div>
|
||||
<h1>Выберите вариант отчетов</h1>
|
||||
<nav class="menu">
|
||||
{% for point in options %}
|
||||
<a href="{{ url_for(point['url']) }}"><button>{{ point['name'] }}</button></a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
<div class="buttons_menu">
|
||||
<a href="{{ url_for('report_bp.create') }}"><button>Создать отчет</button></a>
|
||||
<a href="{{ url_for('report_bp.view') }}"><button>Читать отчеты</button></a>
|
||||
</div>
|
||||
<div class="return">
|
||||
<a href="{{ url_for('index') }}"><button>Главное меню</button></a>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from flask import request, Blueprint, render_template
|
||||
from flask import request, Blueprint, render_template, url_for
|
||||
from os import path
|
||||
from checker import check_auth
|
||||
from .requests_model import sklad, materials_per_seller
|
||||
from datetime import date
|
||||
from .requests_model import sklad, materials_per_seller, sellers_names, materials_names
|
||||
import json
|
||||
|
||||
with open(path.join(path.dirname(__file__), 'zapros_menu.json')) as f:
|
||||
@@ -20,47 +19,28 @@ def requests():
|
||||
@check_auth
|
||||
def sklad_zapros():
|
||||
if request.method == 'GET':
|
||||
materials = ['Сталь', 'Золото', 'Дерево', 'Стекло', 'Медь', 'Цемент']
|
||||
return render_template('zagotovki.html', materials=materials, header='Количество заготовок на складе')
|
||||
materials = materials_names()
|
||||
return render_template('zagotovki.html', materials=materials.result)
|
||||
else:
|
||||
material = dict(request.form)
|
||||
zagotovki = sklad(material)
|
||||
if zagotovki.status:
|
||||
header = f'Заготовки на складе из материала \'{material["material"]}\''
|
||||
return render_template('output.html', items=zagotovki.result, object=header)
|
||||
header = f'Заготовки на складе из материала \"{material["material"]}\"'
|
||||
return render_template('output.html', items=zagotovki.result, header=header)
|
||||
else:
|
||||
return render_template('error.html', error_message=zagotovki.error_message)
|
||||
|
||||
# Под вопросом
|
||||
""" @requests_bp.route('/req2', methods=['GET', 'POST'])
|
||||
@check_auth
|
||||
def zagotovki_ship():
|
||||
if request.method == 'GET':
|
||||
zagotovki = get_goods()
|
||||
if zagotovki.status:
|
||||
return render_template('zagotovki.html', materials=zagotovki.result, header='Поставки заготовок')
|
||||
else:
|
||||
return render_template('error.html', error_message=zagotovki.error_message)
|
||||
else:
|
||||
material = dict(request.form)
|
||||
zagotovki = route(material, 'zapros2.sql')
|
||||
if zagotovki.status:
|
||||
header = f'Поставки заготовок из материала \'{material['material']}\''
|
||||
return render_template('output.html', items=zagotovki.result, object=header)
|
||||
else:
|
||||
return render_template('error.html', error_message=zagotovki.error_message) """
|
||||
|
||||
@requests_bp.route('/shipments', methods=['GET', 'POST'])
|
||||
@check_auth
|
||||
def sellers_ship():
|
||||
if request.method == 'GET':
|
||||
sellers = ['Car and bikes', 'Doto', 'LPD', 'Neva', 'PGG', 'Robot', 'Rost']
|
||||
return render_template('sellers_ship.html', sellers=sellers, year_from='2000', year_to=str(date.today().year))
|
||||
sellers = sellers_names()
|
||||
return render_template('sellers_ship.html', sellers=sellers.result)
|
||||
else:
|
||||
seller = dict(request.form)
|
||||
zagotovki = materials_per_seller(seller)
|
||||
if zagotovki.status:
|
||||
header = f'Поставки от поставщика \'{seller['seller']}\''
|
||||
return render_template('output.html', items=zagotovki.result, object=header)
|
||||
header = f'Поставки от поставщика \"{seller["seller"]}\"'
|
||||
return render_template('output.html', items=zagotovki.result, header=header, link=url_for('requests_bp.requests'))
|
||||
else:
|
||||
return render_template('error.html', error_message=zagotovki.error_message)
|
||||
34
App/Requests/db/DBconnect.py
Normal file
34
App/Requests/db/DBconnect.py
Normal file
@@ -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
|
||||
|
||||
0
App/Requests/db/__init__.py
Normal file
0
App/Requests/db/__init__.py
Normal file
12
App/Requests/db/select.py
Normal file
12
App/Requests/db/select.py
Normal file
@@ -0,0 +1,12 @@
|
||||
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
|
||||
14
App/Requests/db/sql_provider.py
Normal file
14
App/Requests/db/sql_provider.py
Normal file
@@ -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)
|
||||
18
App/Requests/description.txt
Normal file
18
App/Requests/description.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
.
|
||||
├── db
|
||||
│ ├── DBconnect.py - коннектор к СУБД
|
||||
│ ├── __init__.py - файл для инициализации db как модуль
|
||||
│ ├── select.py - файл для выполнения select-запросов к СУБД
|
||||
│ └── sql_provider.py - SQL-провайдер для формирования запроса к СУБД
|
||||
├── __init__.py - файл для инициализации Requests как модуль
|
||||
├── requests_model.py - реализация модели варианта работы с запросами
|
||||
├── sql
|
||||
│ ├── materials_names.sql - sql-запрос для получения списка материалоа заготовок
|
||||
│ ├── sellers_names.sql - sql-запрос для получения списка поставщиков
|
||||
│ ├── ship_seller.sql - sql-запрос для получения списка поставок поставщиком
|
||||
│ └── sklad_material.sql - sql-запрос для получения списка заготовок на складе
|
||||
├── templates
|
||||
│ ├── sellers_ship.html - шаблон для формы передачи параметров для запроса всех поставок выбранного поставщика
|
||||
│ ├── zagotovki.html - шаблон для формы передачи параметров для запроса количества заготовок на складе
|
||||
│ └── zapros_menu.html - шаблон для выбора варианта отчёта
|
||||
└── zapros_menu.json - файл с названиями запросов и их ссылками
|
||||
@@ -1,6 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
from Database.select import select_list
|
||||
from Database.sql_provider import SQLProvider
|
||||
from .db.select import select_list
|
||||
from .db.sql_provider import SQLProvider
|
||||
from flask import current_app
|
||||
from os import path
|
||||
|
||||
@@ -11,8 +11,26 @@ class InfoRespronse:
|
||||
error_message: str
|
||||
status: bool
|
||||
|
||||
def sellers_names() -> InfoRespronse:
|
||||
_sql = sql_provider.get('sellers_names.sql', {})
|
||||
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)
|
||||
|
||||
def materials_names() -> InfoRespronse:
|
||||
_sql = sql_provider.get('materials_names.sql', {})
|
||||
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)
|
||||
|
||||
def sklad(input_data) -> InfoRespronse:
|
||||
_sql = sql_provider.get('zapros1.sql', input_data)
|
||||
_sql = sql_provider.get('sklad_material.sql', input_data)
|
||||
print("sql = ", _sql)
|
||||
result = select_list(current_app.config['db_config'], _sql)
|
||||
if result is None:
|
||||
@@ -21,8 +39,9 @@ def sklad(input_data) -> InfoRespronse:
|
||||
status=False)
|
||||
return InfoRespronse(result, error_message='', status=True)
|
||||
|
||||
|
||||
def materials_per_seller(input_data) -> InfoRespronse:
|
||||
_sql = sql_provider.get('zapros3.sql', input_data)
|
||||
_sql = sql_provider.get('ship_seller.sql', input_data)
|
||||
result = select_list(current_app.config['db_config'], _sql)
|
||||
if result is None:
|
||||
return InfoRespronse((),
|
||||
|
||||
1
App/Requests/sql/materials_names.sql
Normal file
1
App/Requests/sql/materials_names.sql
Normal file
@@ -0,0 +1 @@
|
||||
SELECT DISTINCT material FROM workpiece;
|
||||
1
App/Requests/sql/sellers_names.sql
Normal file
1
App/Requests/sql/sellers_names.sql
Normal file
@@ -0,0 +1 @@
|
||||
SELECT name FROM sellers;
|
||||
@@ -5,5 +5,4 @@ FROM waybill w
|
||||
JOIN (SELECT waybill_id, SUM(count) AS cnt FROM waybill_lines wl GROUP BY waybill_id)wl USING (waybill_id)
|
||||
JOIN sellers USING(sel_id)
|
||||
WHERE sellers.name = '${seller}'
|
||||
AND YEAR(date_of_delivery) = '${date}'
|
||||
GROUP BY date_of_delivery
|
||||
@@ -1,9 +0,0 @@
|
||||
SELECT w.date_of_delivery AS 'Дата поставки',
|
||||
name AS 'Поставщик',
|
||||
wl.count AS 'Количество',
|
||||
wl.price * wl.count AS 'Общая сумма'
|
||||
FROM waybill w
|
||||
JOIN waybill_lines wl USING(waybill_id)
|
||||
JOIN workpiece wp USING(work_id)
|
||||
JOIN sellers USING(sel_id)
|
||||
WHERE material = '${material}';
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Ошибка</title>
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Произошла ошибка</h1>
|
||||
<p>{{ error_message }}.</p>
|
||||
<a href="{{ url_for('index') }}"><button>На главную страницу</button></a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Поставки поставщиком за год</title>
|
||||
<title>Поставки поставщиком</title>
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
@@ -10,17 +10,15 @@
|
||||
<a href="{{ url_for('logout') }}"><button>Выход</button></a>
|
||||
</div>
|
||||
<!-- Input -->
|
||||
<h1>Поставки поставщиком за год</h1>
|
||||
<h1>Поставки поставщиком</h1>
|
||||
<div class="form">
|
||||
<p>Выберите поставщика</p>
|
||||
<form action="" method="post">
|
||||
<select name="seller">
|
||||
{% for item in sellers %}
|
||||
<option value="{{ item }}">{{ item }}</option>
|
||||
{% for seller in sellers %}
|
||||
<option value="{{ seller['name'] }}">{{ seller['name'] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p>Выберите год:</p>
|
||||
<p><input type="number" name="date" required min={{ year_from }} max={{ year_to }} value="2024"></p>
|
||||
<input type="submit" value="Отправить">
|
||||
</form>
|
||||
<div class="return">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ header }}</title>
|
||||
<title>Количество заготовок на складе</title>
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
@@ -10,14 +10,16 @@
|
||||
<a href="{{ url_for('logout') }}"><button>Выход</button></a>
|
||||
</div>
|
||||
<!-- Input -->
|
||||
<h1>{{ header }}</h1>
|
||||
<h1>Количество заготовок на складе</h1>
|
||||
<div class="form">
|
||||
<form action="" method="post" style="display: inline-block;">
|
||||
<p>Выберите материал<p></p>
|
||||
<select name="material">
|
||||
{% for item in materials %}
|
||||
<option value="{{ item }}">{{ item }}</option>
|
||||
{% endfor %}
|
||||
<option value="Сталь">Сталь</option>
|
||||
<option value="Алюминий">Алюминий</option>
|
||||
<option value="Медь">Медь</option>
|
||||
<option value="Пластик">Пластик</option>
|
||||
<option value="Дерево">Дерево</option>
|
||||
</select>
|
||||
<input type="submit" value="Отправить">
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Запросы</title>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
[
|
||||
{"name": "Количество заготовок на складе", "url": "requests_bp.sklad_zapros"},
|
||||
|
||||
{"name": "Поставки поставщиком за год", "url": "requests_bp.sellers_ship"}
|
||||
{"name": "Поставки поставщиками", "url": "requests_bp.sellers_ship"}
|
||||
]
|
||||
@@ -26,4 +26,6 @@ def index():
|
||||
def logout():
|
||||
session.clear()
|
||||
return render_template('main_menu.html', ses=session)
|
||||
app.run(port=5001, debug=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5002, host='0.0.0.0')
|
||||
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"Менеджер": ["auth_bp", "requests_bp"],
|
||||
"Управляющий": ["auth_bp", "requests_bp", "report_bp"],
|
||||
"Поставщик": ["auth_bp", "waybill_bp"]
|
||||
}
|
||||
"Менеджер": [
|
||||
"requests_bp",
|
||||
"report_bp"],
|
||||
"Управляющий": [
|
||||
"requests_bp",
|
||||
"report_bp"],
|
||||
"Бухгалтер": [
|
||||
"report_bp"],
|
||||
"Поставщик": [
|
||||
"waybill_bp"]
|
||||
}
|
||||
16
App/description.txt
Normal file
16
App/description.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
.
|
||||
├── app.py - основное приложение
|
||||
├── checker.py - декораторы
|
||||
├── data
|
||||
│ ├── config.json - конфигурация для подключения к СУБД
|
||||
│ └── db_access.json - доступ пользователей к вариантам использования ИС
|
||||
├── static
|
||||
│ └── css - стили для страниц
|
||||
│ ├── auth.css
|
||||
│ ├── main.css
|
||||
│ └── output.css
|
||||
└── templates
|
||||
├── error.html - универсальный шаблон для ошибок
|
||||
├── main_menu.html - шаблон главного меню
|
||||
└── output.html - универсальный шаблон для вывода результатов
|
||||
@@ -1,4 +1,4 @@
|
||||
h1,h2 {
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ div.logout, div.login {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.buttons_menu{
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
div.logout button {
|
||||
margin-top: -5px;
|
||||
background-color: #ff0000;
|
||||
@@ -39,9 +44,10 @@ div.login button {
|
||||
div.return {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
div.return button{
|
||||
div.return button {
|
||||
background-color: chocolate;
|
||||
}
|
||||
|
||||
@@ -50,4 +56,35 @@ div.form {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
div.form label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
div.form select, div.form input[type=number] {
|
||||
margin-bottom: 15px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.form .period {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
div.form button {
|
||||
margin-top: 15px;
|
||||
}
|
||||
@@ -11,18 +11,15 @@
|
||||
<a href="{{ url_for('logout') }}"><button>Выход</button></a>
|
||||
</div>
|
||||
<h1>Здравствуйте, {{ ses['login'] }}!</h1>
|
||||
{% if ses['access_user'] == 'internal_users' %}
|
||||
<h2> Ваша роль: {{ ses['role'] }}</h2>
|
||||
{% if ses['access_user'] == 'in' %}
|
||||
<!-- Not implemented -->
|
||||
<nav class="menu">
|
||||
<a href="{{ url_for('requests_bp.requests') }}"><button>Запросы</button></a>
|
||||
<a href="{{ url_for('report_bp.menu') }}"><button>Отчеты</button></a>
|
||||
</nav>
|
||||
{% else %}
|
||||
<nav class="menu">
|
||||
<!-- Not implemented -->
|
||||
<a href="{{ url_for('waybill_bp.waybill') }}"><button>Новая накладная</button></a>
|
||||
</nav>
|
||||
<p>Not implemented</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="login">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ object }}</title>
|
||||
<title>{{ header }}</title>
|
||||
<link href="/static/css/main.css" type="text/css" rel="stylesheet">
|
||||
<link href="/static/css/output.css" type="text/css" rel="stylesheet">
|
||||
</head>
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="logout">
|
||||
<a href="{{ url_for('logout') }}"><button>Выход</button></a>
|
||||
</div>
|
||||
<h1>{{ object }}</h1>
|
||||
<h1>{{ header }}</h1>
|
||||
<!-- Output -->
|
||||
{% if items %}
|
||||
<table>
|
||||
@@ -31,6 +31,6 @@
|
||||
<p>Информации не найдено</p>
|
||||
{% endif %}
|
||||
<div class="return">
|
||||
<a href="{{ url_for('requests_bp.requests') }}"><button>Обратно в меню запросов</button></a>
|
||||
<a href="{{ link }}"><button>Вернуться в меню выбора</button></a>
|
||||
</div></body>
|
||||
</html>
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
cryptography==43.0.3
|
||||
Flask==3.0.3
|
||||
PyMySQL==1.1.1
|
||||
Reference in New Issue
Block a user