Merge branch 'main' into Waybill

This commit is contained in:
2024-11-21 17:54:57 +03:00
52 changed files with 541 additions and 155 deletions

View File

@@ -9,14 +9,12 @@ def auth():
return render_template('auth.html') return render_template('auth.html')
else: else:
data = request.form.to_dict() data = request.form.to_dict()
data['table'] = 'internal_users' if 'internal' in data else 'external_users'
auth_data = auth_model(data) auth_data = auth_model(data)
if auth_data.status: if auth_data.status:
session.update({ session.update({
'login': auth_data.result[0]['login'], 'login': auth_data.result[0]['login'],
'access_user': data['access'],
'role': auth_data.result[0]['user_role'], '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 'permanent': True
}) })
return redirect(url_for('index')) return redirect(url_for('index'))

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from Database.select import select_list from .db.select import select_list
from Database.sql_provider import SQLProvider from .db.sql_provider import SQLProvider
from flask import current_app from flask import current_app
import os import os

13
App/Auth/description.txt Normal file
View 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 - шаблон для страницы авторизации

View File

@@ -1,4 +1,4 @@
SELECT login, user_role FROM $table SELECT login, user_role FROM $access
WHERE login = '$login' WHERE login = '$login'
AND password = '$password' AND password = '$password'
LIMIT 1; LIMIT 1;

View File

@@ -7,16 +7,22 @@
<link href="/static/css/main.css" type="text/css" rel="stylesheet"> <link href="/static/css/main.css" type="text/css" rel="stylesheet">
</head> </head>
<body> <body>
<h1>Авторизация</h1>
<div class="form"> <div class="form">
<form action="" method="post" name="auth"> <form action="" method="post" name="auth">
<label for="login">Логин: </label> <label for="login">Логин: </label>
<input type="text" name="login" required> <input type="text" name="login" required>
<label for="password">Пароль: </label> <label for="password">Пароль: </label>
<input type="password" name="password" required><br> <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="Вход"> <input type="submit" value="Вход">
</form> </form>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -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>

View File

@@ -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 checker import check_auth
from os import path from os import path
from .report_model import view_report, make_report
import json import json
with open(path.join(path.dirname(__file__), 'reports.json')) as f: 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 = Blueprint('report_bp', __name__, template_folder='templates')
@report_bp.route('/', methods=['GET', 'POST']) @report_bp.route('/menu')
@check_auth @check_auth
def menu(): def menu():
if request.method == 'GET': 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 @check_auth
def quaterly(): def create():
if request.method == 'GET': 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
View File

@@ -0,0 +1,7 @@
{
"title" : "Отчет о поставках заготовок",
"write" : ["Менеджер"],
"read" : ["Управляющий"],
"view" : "workpiece_report",
"procedure" : "report_workpiece"
}

7
App/Report/access/2.json Normal file
View File

@@ -0,0 +1,7 @@
{
"title" : "Отчет о поставках поставщиками",
"write" : ["Бухгалтер"],
"read" : ["Управляющий"],
"view" : "sellers_report",
"procedure" : "report_sellers"
}

View 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

View File

View 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
View 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

View 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 - шаблон для выбора просмотра/создания отчета

View 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)

View File

@@ -1,3 +1,4 @@
[ [
{"name": "Квартальный отчет передвижений заготовок", "url": "report_bp.quaterly"} {"id": 1, "name": "Заготовки"},
{"id": 2, "name": "Поставщики"}
] ]

View File

@@ -0,0 +1,4 @@
SELECT EXISTS (
SELECT 1 FROM reports
WHERE report_category_id = '$id' AND (month = '$month' AND year = '$year')
) AS exist;

View File

@@ -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;

View 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;

View 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;

View 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>

View File

@@ -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>

View 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>

View File

@@ -10,11 +10,10 @@
<a href="{{ url_for('logout') }}"><button>Выход</button></a> <a href="{{ url_for('logout') }}"><button>Выход</button></a>
</div> </div>
<h1>Выберите вариант отчетов</h1> <h1>Выберите вариант отчетов</h1>
<nav class="menu"> <div class="buttons_menu">
{% for point in options %} <a href="{{ url_for('report_bp.create') }}"><button>Создать отчет</button></a>
<a href="{{ url_for(point['url']) }}"><button>{{ point['name'] }}</button></a> <a href="{{ url_for('report_bp.view') }}"><button>Читать отчеты</button></a>
{% endfor %} </div>
</nav>
<div class="return"> <div class="return">
<a href="{{ url_for('index') }}"><button>Главное меню</button></a> <a href="{{ url_for('index') }}"><button>Главное меню</button></a>
</div> </div>

View File

@@ -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 os import path
from checker import check_auth from checker import check_auth
from .requests_model import sklad, materials_per_seller from .requests_model import sklad, materials_per_seller, sellers_names, materials_names
from datetime import date
import json import json
with open(path.join(path.dirname(__file__), 'zapros_menu.json')) as f: with open(path.join(path.dirname(__file__), 'zapros_menu.json')) as f:
@@ -20,47 +19,28 @@ def requests():
@check_auth @check_auth
def sklad_zapros(): def sklad_zapros():
if request.method == 'GET': if request.method == 'GET':
materials = ['Сталь', 'Золото', 'Дерево', 'Стекло', 'Медь', 'Цемент'] materials = materials_names()
return render_template('zagotovki.html', materials=materials, header='Количество заготовок на складе') return render_template('zagotovki.html', materials=materials.result)
else: else:
material = dict(request.form) material = dict(request.form)
zagotovki = sklad(material) zagotovki = sklad(material)
if zagotovki.status: if zagotovki.status:
header = f'Заготовки на складе из материала \'{material["material"]}\'' header = f'Заготовки на складе из материала \"{material["material"]}\"'
return render_template('output.html', items=zagotovki.result, object=header) return render_template('output.html', items=zagotovki.result, header=header)
else: else:
return render_template('error.html', error_message=zagotovki.error_message) 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']) @requests_bp.route('/shipments', methods=['GET', 'POST'])
@check_auth @check_auth
def sellers_ship(): def sellers_ship():
if request.method == 'GET': if request.method == 'GET':
sellers = ['Car and bikes', 'Doto', 'LPD', 'Neva', 'PGG', 'Robot', 'Rost'] sellers = sellers_names()
return render_template('sellers_ship.html', sellers=sellers, year_from='2000', year_to=str(date.today().year)) return render_template('sellers_ship.html', sellers=sellers.result)
else: else:
seller = dict(request.form) seller = dict(request.form)
zagotovki = materials_per_seller(seller) zagotovki = materials_per_seller(seller)
if zagotovki.status: if zagotovki.status:
header = f'Поставки от поставщика \'{seller['seller']}\'' header = f'Поставки от поставщика \"{seller["seller"]}\"'
return render_template('output.html', items=zagotovki.result, object=header) return render_template('output.html', items=zagotovki.result, header=header, link=url_for('requests_bp.requests'))
else: else:
return render_template('error.html', error_message=zagotovki.error_message) return render_template('error.html', error_message=zagotovki.error_message)

View 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

View File

12
App/Requests/db/select.py Normal file
View 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

View 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)

View 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 - файл с названиями запросов и их ссылками

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass from dataclasses import dataclass
from Database.select import select_list from .db.select import select_list
from Database.sql_provider import SQLProvider from .db.sql_provider import SQLProvider
from flask import current_app from flask import current_app
from os import path from os import path
@@ -11,8 +11,26 @@ class InfoRespronse:
error_message: str error_message: str
status: bool 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: 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) print("sql = ", _sql)
result = select_list(current_app.config['db_config'], _sql) result = select_list(current_app.config['db_config'], _sql)
if result is None: if result is None:
@@ -21,8 +39,9 @@ def sklad(input_data) -> InfoRespronse:
status=False) status=False)
return InfoRespronse(result, error_message='', status=True) return InfoRespronse(result, error_message='', status=True)
def materials_per_seller(input_data) -> InfoRespronse: 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) result = select_list(current_app.config['db_config'], _sql)
if result is None: if result is None:
return InfoRespronse((), return InfoRespronse((),

View File

@@ -0,0 +1 @@
SELECT DISTINCT material FROM workpiece;

View File

@@ -0,0 +1 @@
SELECT name FROM sellers;

View File

@@ -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 (SELECT waybill_id, SUM(count) AS cnt FROM waybill_lines wl GROUP BY waybill_id)wl USING (waybill_id)
JOIN sellers USING(sel_id) JOIN sellers USING(sel_id)
WHERE sellers.name = '${seller}' WHERE sellers.name = '${seller}'
AND YEAR(date_of_delivery) = '${date}'
GROUP BY date_of_delivery GROUP BY date_of_delivery

View File

@@ -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}';

View File

@@ -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>

View File

@@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="ru">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Поставки поставщиком за год</title> <title>Поставки поставщиком</title>
<link href="/static/css/main.css" type="text/css" rel="stylesheet"> <link href="/static/css/main.css" type="text/css" rel="stylesheet">
</head> </head>
<body> <body>
@@ -10,17 +10,15 @@
<a href="{{ url_for('logout') }}"><button>Выход</button></a> <a href="{{ url_for('logout') }}"><button>Выход</button></a>
</div> </div>
<!-- Input --> <!-- Input -->
<h1>Поставки поставщиком за год</h1> <h1>Поставки поставщиком</h1>
<div class="form"> <div class="form">
<p>Выберите поставщика</p> <p>Выберите поставщика</p>
<form action="" method="post"> <form action="" method="post">
<select name="seller"> <select name="seller">
{% for item in sellers %} {% for seller in sellers %}
<option value="{{ item }}">{{ item }}</option> <option value="{{ seller['name'] }}">{{ seller['name'] }}</option>
{% endfor %} {% endfor %}
</select> </select>
<p>Выберите год:</p>
<p><input type="number" name="date" required min={{ year_from }} max={{ year_to }} value="2024"></p>
<input type="submit" value="Отправить"> <input type="submit" value="Отправить">
</form> </form>
<div class="return"> <div class="return">

View File

@@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="ru">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{{ header }}</title> <title>Количество заготовок на складе</title>
<link href="/static/css/main.css" type="text/css" rel="stylesheet"> <link href="/static/css/main.css" type="text/css" rel="stylesheet">
</head> </head>
<body> <body>
@@ -10,14 +10,16 @@
<a href="{{ url_for('logout') }}"><button>Выход</button></a> <a href="{{ url_for('logout') }}"><button>Выход</button></a>
</div> </div>
<!-- Input --> <!-- Input -->
<h1>{{ header }}</h1> <h1>Количество заготовок на складе</h1>
<div class="form"> <div class="form">
<form action="" method="post" style="display: inline-block;"> <form action="" method="post" style="display: inline-block;">
<p>Выберите материал<p></p> <p>Выберите материал<p></p>
<select name="material"> <select name="material">
{% for item in materials %} <option value="Сталь">Сталь</option>
<option value="{{ item }}">{{ item }}</option> <option value="Алюминий">Алюминий</option>
{% endfor %} <option value="Медь">Медь</option>
<option value="Пластик">Пластик</option>
<option value="Дерево">Дерево</option>
</select> </select>
<input type="submit" value="Отправить"> <input type="submit" value="Отправить">
</form> </form>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="ru">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Запросы</title> <title>Запросы</title>

View File

@@ -1,5 +1,4 @@
[ [
{"name": "Количество заготовок на складе", "url": "requests_bp.sklad_zapros"}, {"name": "Количество заготовок на складе", "url": "requests_bp.sklad_zapros"},
{"name": "Поставки поставщиками", "url": "requests_bp.sellers_ship"}
{"name": "Поставки поставщиком за год", "url": "requests_bp.sellers_ship"}
] ]

View File

@@ -26,4 +26,6 @@ def index():
def logout(): def logout():
session.clear() session.clear()
return render_template('main_menu.html', ses=session) 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')

View File

@@ -1,5 +1,12 @@
{ {
"Менеджер": ["auth_bp", "requests_bp"], "Менеджер": [
"Управляющий": ["auth_bp", "requests_bp", "report_bp"], "requests_bp",
"Поставщик": ["auth_bp", "waybill_bp"] "report_bp"],
} "Управляющий": [
"requests_bp",
"report_bp"],
"Бухгалтер": [
"report_bp"],
"Поставщик": [
"waybill_bp"]
}

16
App/description.txt Normal file
View 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 - универсальный шаблон для вывода результатов

View File

@@ -1,4 +1,4 @@
h1,h2 { h1, h2 {
text-align: center; text-align: center;
} }
@@ -26,6 +26,11 @@ div.logout, div.login {
text-align: center; text-align: center;
} }
div.buttons_menu{
text-align: center;
margin: 20px 0;
}
div.logout button { div.logout button {
margin-top: -5px; margin-top: -5px;
background-color: #ff0000; background-color: #ff0000;
@@ -37,11 +42,12 @@ div.login button {
} }
div.return { div.return {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin-top: 15px;
} }
div.return button{ div.return button {
background-color: chocolate; background-color: chocolate;
} }
@@ -50,4 +56,35 @@ div.form {
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, -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;
}

View File

@@ -11,18 +11,15 @@
<a href="{{ url_for('logout') }}"><button>Выход</button></a> <a href="{{ url_for('logout') }}"><button>Выход</button></a>
</div> </div>
<h1>Здравствуйте, {{ ses['login'] }}!</h1> <h1>Здравствуйте, {{ ses['login'] }}!</h1>
<h2> Ваша роль: {{ ses['role'] }}</h2> {% if ses['access_user'] == 'internal_users' %}
{% if ses['access_user'] == 'in' %} <h2> Ваша роль: {{ ses['role'] }}</h2>
<!-- Not implemented --> <!-- Not implemented -->
<nav class="menu"> <nav class="menu">
<a href="{{ url_for('requests_bp.requests') }}"><button>Запросы</button></a> <a href="{{ url_for('requests_bp.requests') }}"><button>Запросы</button></a>
<a href="{{ url_for('report_bp.menu') }}"><button>Отчеты</button></a> <a href="{{ url_for('report_bp.menu') }}"><button>Отчеты</button></a>
</nav> </nav>
{% else %} {% else %}
<nav class="menu"> <p>Not implemented</p>
<!-- Not implemented -->
<a href="{{ url_for('waybill_bp.waybill') }}"><button>Новая накладная</button></a>
</nav>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="login"> <div class="login">

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <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/main.css" type="text/css" rel="stylesheet">
<link href="/static/css/output.css" type="text/css" rel="stylesheet"> <link href="/static/css/output.css" type="text/css" rel="stylesheet">
</head> </head>
@@ -10,7 +10,7 @@
<div class="logout"> <div class="logout">
<a href="{{ url_for('logout') }}"><button>Выход</button></a> <a href="{{ url_for('logout') }}"><button>Выход</button></a>
</div> </div>
<h1>{{ object }}</h1> <h1>{{ header }}</h1>
<!-- Output --> <!-- Output -->
{% if items %} {% if items %}
<table> <table>
@@ -31,6 +31,6 @@
<p>Информации не найдено</p> <p>Информации не найдено</p>
{% endif %} {% endif %}
<div class="return"> <div class="return">
<a href="{{ url_for('requests_bp.requests') }}"><button>Обратно в меню запросов</button></a> <a href="{{ link }}"><button>Вернуться в меню выбора</button></a>
</div></body> </div></body>
</html> </html>

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
cryptography==43.0.3
Flask==3.0.3
PyMySQL==1.1.1