Spaces:
Paused
Paused
File size: 7,871 Bytes
90756b2 4bf4e39 5c5d757 4bf4e39 90756b2 5c5d757 90756b2 4bf4e39 90756b2 4bf4e39 90756b2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | import os
import logging
from flask import Flask, render_template
from config import Config
from extensions import db, login_manager, csrf
from models import User, KpiDefinition
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def create_app():
app = Flask(__name__)
app.config.from_object(Config)
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Пожалуйста, войдите в систему.'
login_manager.login_message_category = 'info'
from routes.auth import auth_bp
from routes.main import main_bp
from routes.admin import admin_bp
from routes.api import api_bp
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(api_bp)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.errorhandler(404)
def not_found(e):
return render_template('errors/404.html'), 404
@app.errorhandler(403)
def forbidden(e):
return render_template('errors/403.html'), 403
@app.errorhandler(500)
def server_error(e):
return render_template('errors/500.html'), 500
@app.context_processor
def utility_processor():
from models import Notification
unread_count = 0
try:
from flask_login import current_user
if current_user.is_authenticated:
unread_count = Notification.query.filter_by(
user_id=current_user.id, is_read=False
).count()
except Exception:
pass
return dict(unread_notifications_count=unread_count)
with app.app_context():
init_database()
from services.scheduler import init_scheduler
init_scheduler(app)
@app.cli.command('sync-facts')
def sync_facts_command():
from services.platform_fetcher import fetch_all_facts
result = fetch_all_facts()
saved = result.get('values_saved', 0)
errors = result.get('errors', [])
print(f'Sync complete: {saved} values saved, {len(errors)} errors')
for err in errors:
print(f' ERROR: {err}')
return app
def init_database():
seed_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'seed')
# Restore from seed if running in cloud and target DB is missing
seed_db = os.path.join(seed_dir, 'kpi_dashboard.db')
if Config._cloud and os.path.exists(seed_db):
db_uri = Config.SQLALCHEMY_DATABASE_URI
if db_uri.startswith('sqlite:///'):
db_file = db_uri.replace('sqlite:///', '', 1)
if not os.path.exists(db_file) or os.path.getsize(db_file) == 0:
os.makedirs(os.path.dirname(db_file), exist_ok=True)
import shutil
shutil.copy2(seed_db, db_file)
logger.info('Restored seed database to %s', db_file)
db.session.remove()
db.engine.dispose()
db.create_all()
# Restore seed uploads in cloud mode
seed_uploads = os.path.join(seed_dir, 'uploads')
if Config._cloud and os.path.isdir(seed_uploads):
from flask import current_app
upload_dir = current_app.config['UPLOAD_FOLDER']
os.makedirs(upload_dir, exist_ok=True)
import shutil
for fname in os.listdir(seed_uploads):
src = os.path.join(seed_uploads, fname)
dst = os.path.join(upload_dir, fname)
if os.path.isfile(src) and not os.path.exists(dst):
shutil.copy2(src, dst)
logger.info('Restored upload: %s', fname)
# Ensure egor (SM) exists
sm = User.query.filter_by(username='egor').first()
if not sm:
sm = User(
username='egor', full_name='Егор', role='sm',
grade='SM', position='руководитель', is_active=True
)
sm.set_password('egor')
db.session.add(sm)
db.session.commit()
logger.info('SM created: egor / egor')
# Ensure polina (guest viewer) exists
polina = User.query.filter_by(username='polina').first()
if not polina:
polina = User(
username='polina', full_name='Полина', role='guest',
is_active=True, email='polina@kpi.local'
)
polina.set_password('polina')
db.session.add(polina)
db.session.commit()
logger.info('Guest viewer created: polina / polina')
# Migration: add training file columns for existing databases
try:
import sqlalchemy as sa
insp = sa.inspect(db.engine)
cols = [c['name'] for c in insp.get_columns('trainings')]
if 'original_name' not in cols:
db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN original_name VARCHAR(300)'))
if 'file_size' not in cols:
db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN file_size INTEGER DEFAULT 0'))
if 'uploaded_by' not in cols:
db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN uploaded_by INTEGER REFERENCES users(id)'))
if 'upload_date' not in cols:
db.session.execute(sa.text('ALTER TABLE trainings ADD COLUMN upload_date DATETIME'))
db.session.commit()
except Exception:
pass
db.session.commit()
demo_kpis = [
('Объем продаж', 'Сумма продаж за период', 'руб.', 'Продажи', 1000000),
('Выполнение плана', 'Процент выполнения плана продаж', '%', 'Продажи', 100),
('Средний чек', 'Средняя сумма покупки', 'руб.', 'Продажи', 5000),
('Кол-во клиентов', 'Количество обслуженных клиентов', 'шт.', 'Сервис', 100),
('Индекс лояльности NPS', 'Готовность рекомендовать бутик', '%', 'Сервис', 80),
('Возвраты', 'Процент возвратов товара', '%', 'Сервис', 5),
('Обучение', 'Пройдено обучений за период', 'шт.', 'Развитие', 3),
('Скорость обслуживания', 'Среднее время на клиента', 'мин', 'Сервис', 15),
('Доп. продажи', 'Количество дополнительных продаж', 'шт.', 'Продажи', 30),
('Выполнение KPI', 'Общий процент выполнения KPI', '%', 'Итог', 90),
('Работа с возражениями', 'Успешное закрытие возражений', '%', 'Сервис', 70),
('Знание продукта', 'Оценка знания ассортимента', 'балл', 'Развитие', 85),
]
for name, desc, unit, category, target in demo_kpis:
existing = KpiDefinition.query.filter_by(name=name).first()
if not existing:
kpi = KpiDefinition(
name=name, description=desc, unit=unit,
category=category, target_value=target, is_active=True
)
db.session.add(kpi)
db.session.commit()
app = create_app()
if __name__ == '__main__':
port = int(os.environ.get('PORT', 5000))
debug = os.environ.get('FLASK_ENV', 'production') == 'development'
app.run(host='0.0.0.0', port=port, debug=debug)
|