аптечный склад
This commit is contained in:
commit
211f5a942b
BIN
Resource/categories_import.xlsx
Normal file
BIN
Resource/categories_import.xlsx
Normal file
Binary file not shown.
BIN
Resource/manufacturers_import.xlsx
Normal file
BIN
Resource/manufacturers_import.xlsx
Normal file
Binary file not shown.
BIN
Resource/masterpol.png
Normal file
BIN
Resource/masterpol.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
BIN
Resource/medicines_import.xlsx
Normal file
BIN
Resource/medicines_import.xlsx
Normal file
Binary file not shown.
BIN
Resource/Мастер пол.ico
Normal file
BIN
Resource/Мастер пол.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
196
app.py
Normal file
196
app.py
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
from flask import Flask, render_template, request, redirect, url_for, flash
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'apteka_secret'
|
||||
|
||||
def get_db():
|
||||
conn = sqlite3.connect('pharmacy.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def calculate_order_price(medicine_id, quantity):
|
||||
"""Расчет стоимости заказа с учетом срока годности и сезона"""
|
||||
conn = get_db()
|
||||
medicine = conn.execute('''
|
||||
SELECT price, expiration_date, season_coeff FROM medicines WHERE id = ?
|
||||
''', (medicine_id,)).fetchone()
|
||||
conn.close()
|
||||
|
||||
if not medicine:
|
||||
return 0
|
||||
|
||||
base = medicine['price'] * quantity
|
||||
|
||||
date_str = medicine['expiration_date'][:10]
|
||||
exp_date = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||
days_left = (exp_date - datetime.now().date()).days
|
||||
|
||||
if days_left < 0:
|
||||
exp_factor = 0.1
|
||||
elif days_left < 30:
|
||||
exp_factor = 0.7
|
||||
elif days_left < 90:
|
||||
exp_factor = 0.9
|
||||
else:
|
||||
exp_factor = 1.0
|
||||
|
||||
season_factor = medicine['season_coeff']
|
||||
|
||||
return round(base * exp_factor * season_factor, 2)
|
||||
|
||||
# ========== ГЛАВНАЯ ==========
|
||||
@app.route('/')
|
||||
def index():
|
||||
conn = get_db()
|
||||
medicines = conn.execute('''
|
||||
SELECT m.*, c.name as category_name, man.name as manufacturer_name
|
||||
FROM medicines m
|
||||
LEFT JOIN categories c ON m.category_id = c.id
|
||||
LEFT JOIN manufacturers man ON m.manufacturer_id = man.id
|
||||
ORDER BY m.name
|
||||
''').fetchall()
|
||||
conn.close()
|
||||
return render_template('index.html', medicines=medicines, calculate=calculate_order_price)
|
||||
|
||||
# ========== ПРОИЗВОДИТЕЛИ ==========
|
||||
@app.route('/manufacturers')
|
||||
def manufacturers():
|
||||
conn = get_db()
|
||||
mans = conn.execute('SELECT * FROM manufacturers ORDER BY name').fetchall()
|
||||
conn.close()
|
||||
return render_template('manufacturers.html', manufacturers=mans)
|
||||
|
||||
@app.route('/add_manufacturer', methods=['GET', 'POST'])
|
||||
def add_manufacturer():
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
country = request.form.get('country', '')
|
||||
phone = request.form.get('phone', '')
|
||||
email = request.form.get('email', '')
|
||||
|
||||
conn = get_db()
|
||||
conn.execute('''
|
||||
INSERT INTO manufacturers (name, country, phone, email)
|
||||
VALUES (?, ?, ?, ?)
|
||||
''', (name, country, phone, email))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash('Производитель добавлен!', 'success')
|
||||
return redirect(url_for('manufacturers'))
|
||||
|
||||
return render_template('add_manufacturer.html')
|
||||
|
||||
@app.route('/edit_manufacturer/<int:id>', methods=['GET', 'POST'])
|
||||
def edit_manufacturer(id):
|
||||
conn = get_db()
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
country = request.form.get('country', '')
|
||||
phone = request.form.get('phone', '')
|
||||
email = request.form.get('email', '')
|
||||
|
||||
conn.execute('''
|
||||
UPDATE manufacturers
|
||||
SET name=?, country=?, phone=?, email=?
|
||||
WHERE id=?
|
||||
''', (name, country, phone, email, id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash('Производитель обновлен!', 'success')
|
||||
return redirect(url_for('manufacturers'))
|
||||
|
||||
man = conn.execute('SELECT * FROM manufacturers WHERE id = ?', (id,)).fetchone()
|
||||
conn.close()
|
||||
return render_template('edit_manufacturer.html', manufacturer=man)
|
||||
|
||||
@app.route('/delete_manufacturer/<int:id>', methods=['POST'])
|
||||
def delete_manufacturer(id):
|
||||
conn = get_db()
|
||||
conn.execute('DELETE FROM manufacturers WHERE id = ?', (id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash('Производитель удален!', 'success')
|
||||
return redirect(url_for('manufacturers'))
|
||||
|
||||
# ========== КАТЕГОРИИ ==========
|
||||
@app.route('/categories')
|
||||
def categories():
|
||||
conn = get_db()
|
||||
cats = conn.execute('SELECT * FROM categories ORDER BY name').fetchall()
|
||||
conn.close()
|
||||
return render_template('categories.html', categories=cats)
|
||||
|
||||
@app.route('/add_category', methods=['GET', 'POST'])
|
||||
def add_category():
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
description = request.form.get('description', '')
|
||||
|
||||
conn = get_db()
|
||||
conn.execute('INSERT INTO categories (name, description) VALUES (?, ?)',
|
||||
(name, description))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash('Категория добавлена!', 'success')
|
||||
return redirect(url_for('categories'))
|
||||
|
||||
return render_template('add_category.html')
|
||||
|
||||
@app.route('/edit_category/<int:id>', methods=['GET', 'POST'])
|
||||
def edit_category(id):
|
||||
conn = get_db()
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
description = request.form.get('description', '')
|
||||
|
||||
conn.execute('UPDATE categories SET name=?, description=? WHERE id=?',
|
||||
(name, description, id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash('Категория обновлена!', 'success')
|
||||
return redirect(url_for('categories'))
|
||||
|
||||
category = conn.execute('SELECT * FROM categories WHERE id = ?', (id,)).fetchone()
|
||||
conn.close()
|
||||
return render_template('edit_category.html', category=category)
|
||||
|
||||
@app.route('/delete_category/<int:id>', methods=['POST'])
|
||||
def delete_category(id):
|
||||
conn = get_db()
|
||||
conn.execute('DELETE FROM categories WHERE id = ?', (id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash('Категория удалена!', 'success')
|
||||
return redirect(url_for('categories'))
|
||||
|
||||
# ========== ЛЕКАРСТВА ==========
|
||||
@app.route('/add_medicine', methods=['GET', 'POST'])
|
||||
def add_medicine():
|
||||
conn = get_db()
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
category_id = request.form['category_id']
|
||||
manufacturer_id = request.form['manufacturer_id']
|
||||
price = request.form['price']
|
||||
expiration_date = request.form['expiration_date']
|
||||
stock_quantity = request.form['stock_quantity']
|
||||
season_coeff = request.form.get('season_coeff', 1.0)
|
||||
|
||||
conn.execute('''
|
||||
INSERT INTO medicines (name, category_id, manufacturer_id, price, expiration_date, stock_quantity, season_coeff)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
''', (name, category_id, manufacturer_id, price, expiration_date, stock_quantity, season_coeff))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash('Лекарство добавлено!', 'success')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
categories = conn.execute('SELECT * FROM categories').fetchall()
|
||||
manufacturers = conn.execute('SELECT * FROM manufacturers').fetchall()
|
||||
conn.close()
|
||||
return render_template('add_medicine.html', categories=categories, manufacturers=manufacturers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
56
create_tables.py
Normal file
56
create_tables.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect('pharmacy.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Категории лекарств
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS categories (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
# Производители
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS manufacturers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
country TEXT,
|
||||
phone TEXT,
|
||||
email TEXT UNIQUE
|
||||
)
|
||||
''')
|
||||
|
||||
# Лекарства
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS medicines (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
category_id INTEGER,
|
||||
manufacturer_id INTEGER,
|
||||
price REAL NOT NULL,
|
||||
expiration_date DATE NOT NULL,
|
||||
stock_quantity INTEGER DEFAULT 0,
|
||||
season_coeff REAL DEFAULT 1.0,
|
||||
FOREIGN KEY (category_id) REFERENCES categories(id),
|
||||
FOREIGN KEY (manufacturer_id) REFERENCES manufacturers(id)
|
||||
)
|
||||
''')
|
||||
|
||||
# Заказы (для истории продаж)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
medicine_id INTEGER,
|
||||
quantity INTEGER NOT NULL,
|
||||
order_date DATE NOT NULL,
|
||||
total_price REAL,
|
||||
FOREIGN KEY (medicine_id) REFERENCES medicines(id)
|
||||
)
|
||||
''')
|
||||
|
||||
print("✅ Таблицы созданы")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
39
load_data.py
Normal file
39
load_data.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import sqlite3
|
||||
import pandas as pd
|
||||
|
||||
conn = sqlite3.connect('pharmacy.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Загрузка категорий
|
||||
print("📦 Загрузка категорий...")
|
||||
df = pd.read_excel('Resource/categories_import.xlsx')
|
||||
for _, row in df.iterrows():
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO categories (id, name, description)
|
||||
VALUES (?, ?, ?)
|
||||
''', (row['ID'], row['CategoryName'], row['Description']))
|
||||
|
||||
# Загрузка производителей
|
||||
print("🏭 Загрузка производителей...")
|
||||
df = pd.read_excel('Resource/manufacturers_import.xlsx')
|
||||
for _, row in df.iterrows():
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO manufacturers (id, name, country, phone, email)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
''', (row['ID'], row['ManufacturerName'], row['Country'], row['Phone'], row['Email']))
|
||||
|
||||
# Загрузка лекарств
|
||||
print("💊 Загрузка лекарств...")
|
||||
df = pd.read_excel('Resource/medicines_import.xlsx')
|
||||
for _, row in df.iterrows():
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO medicines
|
||||
(id, name, category_id, manufacturer_id, price, expiration_date, stock_quantity, season_coeff)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (row['ID'], row['MedicineName'], row['CategoryID'], row['ManufacturerID'],
|
||||
row['Price'], row['ExpirationDate'], row['StockQty'], row['SeasonCoeff']))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("✅ Данные загружены!")
|
||||
input("Нажми Enter для выхода...")
|
||||
BIN
pharmacy.db
Normal file
BIN
pharmacy.db
Normal file
Binary file not shown.
319
static/css/style.css
Normal file
319
static/css/style.css
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
/* Подключаем шрифт Segoe UI */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Segoe+UI:wght@300;400;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #F4E8D3;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
background-color: #ffffff;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #67BA80 0%, #4CAF50 100%);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 30px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 60px;
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 2.2em;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: #2c3e50;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.partner-card {
|
||||
border: 2px solid #67BA80;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4px 12px rgba(103, 186, 128, 0.15);
|
||||
}
|
||||
|
||||
.partner-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.partner-name-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.partner-type {
|
||||
font-size: 1.3em;
|
||||
font-weight: 600;
|
||||
color: #6c757d;
|
||||
background: #f8f9fa;
|
||||
padding: 6px 14px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.partner-name {
|
||||
font-size: 1.4em;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.divider {
|
||||
color: #dee2e6;
|
||||
font-weight: bold;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.discount-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.discount-badge {
|
||||
font-size: 1.1em;
|
||||
padding: 8px 16px;
|
||||
border-radius: 25px;
|
||||
background-color: #67BA80;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sales-amount {
|
||||
font-size: 1em;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.partner-info-row {
|
||||
margin-bottom: 0;
|
||||
padding: 6px 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.partner-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
min-width: 100px;
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.partner-value {
|
||||
font-size: 1em;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.partner-buttons-container {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-start;
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 2px solid #e9ecef;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
padding: 10px 20px;
|
||||
font-size: 0.95em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 180px;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #67BA80;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-add {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-history {
|
||||
background-color: #495057;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-action:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-new-partner {
|
||||
background-color: #67BA80;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.btn-new-partner:hover {
|
||||
background-color: #5aa870;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.rating-badge {
|
||||
font-size: 0.9em;
|
||||
padding: 6px 12px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
}
|
||||
.partner-info-row:last-child {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.volume-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.volume-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.volume-value {
|
||||
color: #2c3e50;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Стили для страницы истории продаж */
|
||||
/* Стили для таблицы продаж */
|
||||
.table-responsive {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #67BA80;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.table td {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgba(103, 186, 128, 0.05);
|
||||
}
|
||||
|
||||
.table-striped tbody tr:hover {
|
||||
background-color: rgba(103, 186, 128, 0.1);
|
||||
}
|
||||
|
||||
.table tfoot {
|
||||
background-color: #e8f5e8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.table tfoot td {
|
||||
border-top: 2px solid #67BA80;
|
||||
}
|
||||
|
||||
.text-end {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
/* Адаптивность для таблицы */
|
||||
@media (max-width: 768px) {
|
||||
.table-responsive {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.table th, .table td {
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
/* Кастомные кнопки под цветовую схему */
|
||||
.btn-custom-primary {
|
||||
background-color: #67BA80;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-weight: 600;
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-custom-primary:hover {
|
||||
background-color: #5aa870;
|
||||
color: white;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(103, 186, 128, 0.3);
|
||||
}
|
||||
|
||||
.btn-custom-edit {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn-custom-edit:hover {
|
||||
background-color: #5a6268;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Таблицы */
|
||||
.table th {
|
||||
background-color: #67BA80;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Бейджи популярности */
|
||||
.badge.bg-success {
|
||||
background-color: #67BA80 !important;
|
||||
}
|
||||
37
static/js/script.js
Normal file
37
static/js/script.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Кнопка нового партнера
|
||||
const newPartnerBtn = document.querySelector('.btn-new-partner');
|
||||
if (newPartnerBtn) {
|
||||
newPartnerBtn.addEventListener('click', function() {
|
||||
// Переход на страницу добавления партнера
|
||||
window.location.href = '/add_partner';
|
||||
});
|
||||
}
|
||||
|
||||
// Кнопка "Редактирование"
|
||||
document.querySelectorAll('.btn-edit').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const partnerId = this.getAttribute('data-partner-id');
|
||||
// Переход на страницу редактирования
|
||||
window.location.href = `/edit_partner/${partnerId}`;
|
||||
});
|
||||
});
|
||||
|
||||
// Кнопка "Добавление продажи"
|
||||
document.querySelectorAll('.btn-add').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const partnerId = this.getAttribute('data-partner-id');
|
||||
// Переход на страницу добавления продажи
|
||||
window.location.href = `/add_sale/${partnerId}`;
|
||||
});
|
||||
});
|
||||
|
||||
// Кнопка "История реализации"
|
||||
document.querySelectorAll('.btn-history').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const partnerId = this.getAttribute('data-partner-id');
|
||||
// Переход на страницу истории реализации
|
||||
window.location.href = `/sales_history/${partnerId}`;
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
static/masterpol.png
Normal file
BIN
static/masterpol.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 702 B |
18
templates/add_category.html
Normal file
18
templates/add_category.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Добавить категорию</h2>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label>Название категории</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Описание</label>
|
||||
<textarea name="description" class="form-control" rows="3"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Добавить</button>
|
||||
<a href="{{ url_for('categories') }}" class="btn btn-secondary">Отмена</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
26
templates/add_manufacturer.html
Normal file
26
templates/add_manufacturer.html
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Добавить производителя</h2>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label>Название компании</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Страна</label>
|
||||
<input type="text" name="country" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Телефон</label>
|
||||
<input type="text" name="phone" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Email</label>
|
||||
<input type="email" name="email" class="form-control">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Добавить</button>
|
||||
<a href="{{ url_for('manufacturers') }}" class="btn btn-secondary">Отмена</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
45
templates/add_medicine.html
Normal file
45
templates/add_medicine.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Добавить лекарство</h2>
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label>Название</label>
|
||||
<input type="text" name="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Категория</label>
|
||||
<select name="category_id" class="form-control" required>
|
||||
{% for c in categories %}
|
||||
<option value="{{ c.id }}">{{ c.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Производитель</label>
|
||||
<select name="manufacturer_id" class="form-control" required>
|
||||
{% for m in manufacturers %}
|
||||
<option value="{{ m.id }}">{{ m.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Цена</label>
|
||||
<input type="number" step="0.01" name="price" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Срок годности (ГГГГ-ММ-ДД)</label>
|
||||
<input type="date" name="expiration_date" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Количество на складе</label>
|
||||
<input type="number" name="stock_quantity" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Сезонный коэффициент</label>
|
||||
<input type="number" step="0.1" name="season_coeff" class="form-control" value="1.0">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Добавить</button>
|
||||
<a href="/" class="btn btn-secondary">Отмена</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
69
templates/base.html
Normal file
69
templates/base.html
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Аптечный склад{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.navbar {
|
||||
background-color: #E6F2FF !important;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #0066CC !important;
|
||||
border-color: #0066CC !important;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-color: #0052a3 !important;
|
||||
}
|
||||
.card-header {
|
||||
background-color: #E6F2FF !important;
|
||||
}
|
||||
footer {
|
||||
background-color: #E6F2FF;
|
||||
padding: 10px 0;
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img src="{{ url_for('static', filename='masterpol.png') }}" alt="Logo" height="40">
|
||||
Аптечный склад
|
||||
</a>
|
||||
<div>
|
||||
<a href="/" class="btn btn-outline-primary">Лекарства</a>
|
||||
<a href="/manufacturers" class="btn btn-outline-primary">Производители</a>
|
||||
<a href="/categories" class="btn btn-outline-primary">Категории</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else 'success' }} alert-dismissible">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<footer class="text-center">
|
||||
<p>Аптечный склад © 2026</p>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
30
templates/categories.html
Normal file
30
templates/categories.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Категории лекарств</h2>
|
||||
<a href="/add_category" class="btn btn-primary mb-3">Добавить категорию</a>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Описание</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in categories %}
|
||||
<tr>
|
||||
<td>{{ c.name }}</td>
|
||||
<td>{{ c.description or '-' }}</td>
|
||||
<td>
|
||||
<a href="/edit_category/{{ c.id }}" class="btn btn-sm btn-primary">✏️</a>
|
||||
<form action="/delete_category/{{ c.id }}" method="POST" style="display:inline;">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Удалить?')">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
6
templates/components/header.html
Normal file
6
templates/components/header.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<div class="header">
|
||||
<div class="logo-container">
|
||||
<img src="{{ url_for('static', filename='masterpol.png') }}" alt="MasterPol" class="logo">
|
||||
<h1 class="header-title">Управление партнерами</h1>
|
||||
</div>
|
||||
</div>
|
||||
73
templates/components/partner_card.html
Normal file
73
templates/components/partner_card.html
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<div class="col-12">
|
||||
<div class="partner-card">
|
||||
<!-- Первая строка: Тип | Наименование и Скидка -->
|
||||
<div class="partner-header">
|
||||
<div class="partner-name-line">
|
||||
<span class="partner-type">
|
||||
{% if partner.partner_type_name %}
|
||||
{{ partner.partner_type_name }}
|
||||
{% else %}
|
||||
Тип не указан
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="divider">|</span>
|
||||
<span class="partner-name">{{ partner.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="discount-info">
|
||||
<span class="discount-badge">
|
||||
Скидка {{ partner.discount_percent }}%
|
||||
</span>
|
||||
<span class="sales-amount">
|
||||
{{ partner.total_quantity }} ед.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Информация о партнере -->
|
||||
<div class="partner-info-row">
|
||||
<span class="partner-label">Директор:</span>
|
||||
<span class="partner-value">{{ partner.director or 'Не указан' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="partner-info-row">
|
||||
<span class="partner-label">Телефон:</span>
|
||||
<span class="partner-value">
|
||||
{% if partner.phone %}
|
||||
{{ partner.phone }}
|
||||
{% else %}
|
||||
Не указан
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="partner-info-row">
|
||||
<span class="partner-label">Рейтинг:</span>
|
||||
<span class="partner-value">
|
||||
<span class="badge rating-badge bg-{{ 'success' if partner.rating >= 4 else 'warning' if partner.rating >= 3 else 'danger' }}">
|
||||
{{ partner.rating }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="partner-info-row">
|
||||
<span class="partner-label">Объем продаж:</span>
|
||||
<span class="partner-value">
|
||||
{{ "%.2f"|format(partner.total_sales) }} руб.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Кнопки действий внизу -->
|
||||
<div class="partner-buttons-container">
|
||||
<button class="btn btn-action btn-edit" data-partner-id="{{ partner.id }}">
|
||||
<i class="fas fa-edit"></i> Редактирование
|
||||
</button>
|
||||
<button class="btn btn-action btn-add" data-partner-id="{{ partner.id }}">
|
||||
<i class="fas fa-plus-circle"></i> Добавление продажи
|
||||
</button>
|
||||
<button class="btn btn-action btn-history" data-partner-id="{{ partner.id }}">
|
||||
<i class="fas fa-history"></i> История реализации
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
18
templates/edit_category.html
Normal file
18
templates/edit_category.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Редактировать категорию</h2>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label>Название категории</label>
|
||||
<input type="text" name="name" class="form-control" value="{{ category.name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Описание</label>
|
||||
<textarea name="description" class="form-control" rows="3">{{ category.description or '' }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||||
<a href="{{ url_for('categories') }}" class="btn btn-secondary">Отмена</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
26
templates/edit_manufacturer.html
Normal file
26
templates/edit_manufacturer.html
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Редактировать производителя</h2>
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label>Название компании</label>
|
||||
<input type="text" name="name" class="form-control" value="{{ manufacturer.name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Страна</label>
|
||||
<input type="text" name="country" class="form-control" value="{{ manufacturer.country or '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Телефон</label>
|
||||
<input type="text" name="phone" class="form-control" value="{{ manufacturer.phone or '' }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Email</label>
|
||||
<input type="email" name="email" class="form-control" value="{{ manufacturer.email or '' }}">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||||
<a href="{{ url_for('manufacturers') }}" class="btn btn-secondary">Отмена</a>
|
||||
</form>
|
||||
{% endblock %}
|
||||
37
templates/index.html
Normal file
37
templates/index.html
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Список лекарств</h2>
|
||||
<a href="/add_medicine" class="btn btn-primary mb-3">Добавить лекарство</a>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Категория</th>
|
||||
<th>Производитель</th>
|
||||
<th>Цена</th>
|
||||
<th>Срок годности</th>
|
||||
<th>В наличии</th>
|
||||
<th>Тест заказа (10 шт)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in medicines %}
|
||||
<tr>
|
||||
<td>{{ m.name }}</td>
|
||||
<td>{{ m.category_name }}</td>
|
||||
<td>{{ m.manufacturer_name }}</td>
|
||||
<td>{{ m.price }} ₽</td>
|
||||
<td>{{ m.expiration_date }}</td>
|
||||
<td>{{ m.stock_quantity }}</td>
|
||||
<td>
|
||||
{% set total = calculate(m.id, 10) %}
|
||||
<strong>{{ total }} ₽</strong>
|
||||
<small>(с учетом скидок)</small>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
34
templates/manufacturers.html
Normal file
34
templates/manufacturers.html
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Производители</h2>
|
||||
<a href="/add_manufacturer" class="btn btn-primary mb-3">Добавить производителя</a>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Страна</th>
|
||||
<th>Телефон</th>
|
||||
<th>Email</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in manufacturers %}
|
||||
<tr>
|
||||
<td>{{ m.name }}</td>
|
||||
<td>{{ m.country or '-' }}</td>
|
||||
<td>{{ m.phone or '-' }}</td>
|
||||
<td>{{ m.email or '-' }}</td>
|
||||
<td>
|
||||
<a href="/edit_manufacturer/{{ m.id }}" class="btn btn-sm btn-primary">✏️</a>
|
||||
<form action="/delete_manufacturer/{{ m.id }}" method="POST" style="display:inline;">
|
||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Удалить?')">🗑️</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
Loading…
Reference in a new issue