Untitled
unknown
plain_text
9 months ago
38 kB
12
Indexable
import os
import csv
from datetime import datetime, timedelta
from flask import render_template, redirect, url_for, flash, request, jsonify, send_file
from flask_login import login_user, logout_user, login_required, current_user
from werkzeug.utils import secure_filename
from sqlalchemy import func, desc
from app import app, db
from app.database_setup import User, Store, StoreStats, StoreAddition, StoreCategory, StoreGroup, Notification
from app.jj_scraper import run_scraper
from app.scripts.new_stores_checker import run_checker
# Helper functions
def allowed_file(filename):
"""Ελέγχει αν το αρχείο έχει επιτρεπόμενη κατάληξη"""
ALLOWED_EXTENSIONS = {'csv'}
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/login', methods=['GET', 'POST'])
def login():
"""Σελίδα σύνδεσης χρήστη"""
if current_user.is_authenticated:
next_page = url_for('dashboard')
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
remember = 'remember' in request.form
user = User.query.filter_by(username=username).first()
if user is None or not user.check_password(password):
flash('Λάθος όνομα χρήστη ή κωδικός πρόσβασης', 'danger')
return redirect(url_for('login'))
login_user(user, remember=remember)
next_page = request.args.get('next')
if not next_page or next_page.startswith('/'):
next_page = url_for('dashboard')
return redirect(next_page)
return render_template('login.html', title='Σύνδεση')
@app.route('/logout')
@login_required
def logout():
"""Αποσύνδεση χρήστη"""
logout_user()
flash('Έχετε αποσυνδεθεί επιτυχώς!', 'success')
return redirect(url_for('login'))
@app.route('/stores')
@login_required
def stores():
"""Λίστα καταστημάτων με φίλτρα και αναζήτηση"""
page = request.args.get('page', 1, type=int)
search = request.args.get('search', '')
sort = request.args.get('sort', 'name')
order = request.args.get('order', 'asc')
lovel_filter = request.args.get('lovel', '')
# Προετοιμασία του query
query = Store.query
# Εφαρμογή αναζήτησης
if search:
query = query.filter(Store.name.ilike(f'%{search}%') | Store.url.ilike(f'%{search}%'))
# Εφαρμογή φίλτρου Lovel
if lovel_filter == 'yes':
query = query.filter_by(is_lovel_store=True)
elif lovel_filter == 'no':
query = query.filter_by(is_lovel_store=False)
# Εφαρμογή ταξινόμησης
if sort == 'name':
query = query.order_by(Store.name.asc() if order == 'asc' else Store.name.desc())
elif sort == 'open_date':
query = query.order_by(Store.open_date.asc() if order == 'asc' else Store.open_date.desc())
elif sort == 'created_at':
query = query.order_by(Store.created_at.asc() if order == 'asc' else Store.created_at.desc())
# Εκτέλεση του query με σελιδοποίηση
stores = query.paginate(page=page, per_page=20)
# Λήψη όλων των ομάδων Lovel
lovel_groups = db.session.query(Store.lovel_group).filter(Store.lovel_group.isnot(None)).distinct().all()
lovel_groups = [group[0] for group in lovel_groups if group[0]]
return render_template('stores.html',
title='Καταστήματα',
stores=stores,
search=search,
sort=sort,
order=order,
lovel_filter=lovel_filter,
lovel_groups=lovel_groups)
@app.route('/store/<int:id>')
@login_required
def store_detail(id):
"""Προβολή λεπτομερειών καταστήματος"""
store = Store.query.get_or_404(id)
# Λήψη των στατιστικών του καταστήματος
stats = StoreStats.query.filter_by(store_id=id).order_by(StoreStats.scrape_date.desc()).all()
# Προετοιμασία δεδομένων για το γράφημα
dates = [stat.scrape_date.strftime('%d-%m-%Y') for stat in stats]
sales = [stat.sales for stat in stats]
products = [stat.products for stat in stats]
return render_template('store_detail.html',
title=f'Κατάστημα: {store.name}',
store=store,
stats=stats,
chart_dates=dates,
chart_sales=sales,
chart_products=products)
@app.route('/store/add', methods=['GET', 'POST'])
@login_required
def add_store():
"""Προσθήκη νέου καταστήματος"""
if request.method == 'POST':
name = request.form.get('name')
url = request.form.get('url')
open_date = request.form.get('open_date')
is_lovel_store = 'is_lovel_store' in request.form
lovel_group = request.form.get('lovel_group') if is_lovel_store else None
# Έλεγχοι εγκυρότητας
if not name or not url:
flash('Το όνομα και το URL είναι υποχρεωτικά πεδία!', 'danger')
return redirect(url_for('add_store'))
if Store.query.filter_by(url=url).first():
flash('Υπάρχει ήδη κατάστημα με αυτό το URL!', 'danger')
return redirect(url_for('add_store'))
# Δημιουργία νέου καταστήματος
store = Store(
name=name,
url=url,
open_date=open_date,
is_lovel_store=is_lovel_store,
lovel_group=lovel_group
)
db.session.add(store)
db.session.flush() # Για να λάβουμε το ID
# Καταγραφή της προσθήκης
store_addition = StoreAddition(
store_id=store.id,
addition_date=datetime.now().date(),
source='manual'
)
db.session.add(store_addition)
db.session.commit()
flash(f'Το κατάστημα {name} προστέθηκε επιτυχώς!', 'success')
return redirect(url_for('stores'))
# Λήψη των υπαρχουσών ομάδων Lovel για το dropdown
lovel_groups = db.session.query(Store.lovel_group).filter(Store.lovel_group.isnot(None)).distinct().all()
lovel_groups = [group[0] for group in lovel_groups if group[0]]
return render_template('store_form.html',
title='Προσθήκη Καταστήματος',
lovel_groups=lovel_groups)
@app.route('/store/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit_store(id):
"""Επεξεργασία καταστήματος"""
store = Store.query.get_or_404(id)
if request.method == 'POST':
name = request.form.get('name')
url = request.form.get('url')
open_date = request.form.get('open_date')
is_lovel_store = 'is_lovel_store' in request.form
lovel_group = request.form.get('lovel_group') if is_lovel_store else None
is_active = 'is_active' in request.form
# Έλεγχοι εγκυρότητας
if not name or not url:
flash('Το όνομα και το URL είναι υποχρεωτικά πεδία!', 'danger')
return redirect(url_for('edit_store', id=id))
# Έλεγχος αν το URL ανήκει σε άλλο κατάστημα
existing_store = Store.query.filter_by(url=url).first()
if existing_store and existing_store.id != id:
flash('Υπάρχει ήδη κατάστημα με αυτό το URL!', 'danger')
return redirect(url_for('edit_store', id=id))
# Ενημέρωση καταστήματος
store.name = name
store.url = url
store.open_date = open_date
store.is_lovel_store = is_lovel_store
store.lovel_group = lovel_group
store.is_active = is_active
db.session.commit()
flash(f'Το κατάστημα {name} ενημερώθηκε επιτυχώς!', 'success')
return redirect(url_for('store_detail', id=id))
# Λήψη των υπαρχουσών ομάδων Lovel για το dropdown
lovel_groups = db.session.query(Store.lovel_group).filter(Store.lovel_group.isnot(None)).distinct().all()
lovel_groups = [group[0] for group in lovel_groups if group[0]]
return render_template('store_form.html',
title='Επεξεργασία Καταστήματος',
store=store,
lovel_groups=lovel_groups,
edit=True)
@app.route('/store/delete/<int:id>', methods=['POST'])
@login_required
def delete_store(id):
"""Διαγραφή καταστήματος"""
store = Store.query.get_or_404(id)
store_name = store.name
db.session.delete(store)
db.session.commit()
flash(f'Το κατάστημα {store_name} διαγράφηκε επιτυχώς!', 'success')
return redirect(url_for('stores'))
@app.route('/stores/bulk-edit', methods=['POST'])
@login_required
def bulk_edit_stores():
"""Μαζική επεξεργασία καταστημάτων"""
# Λήψη λίστας ID καταστημάτων
store_ids = request.form.getlist('store_ids')
# Λήψη τιμών από τη φόρμα
is_lovel_store = 'is_lovel_store' in request.form
lovel_group = request.form.get('lovel_group', '').strip() if is_lovel_store else None
is_active = 'is_active' in request.form
if not store_ids:
flash('Δεν επιλέχθηκαν καταστήματα', 'warning')
return redirect(url_for('stores'))
# Ενημέρωση των επιλεγμένων καταστημάτων
update_values = {}
if 'is_lovel_store' in request.form:
update_values['is_lovel_store'] = is_lovel_store
update_values['lovel_group'] = lovel_group
if 'is_active' in request.form:
update_values['is_active'] = is_active
if update_values:
updated_count = Store.query.filter(Store.id.in_(store_ids)).update(
update_values,
synchronize_session=False
)
db.session.commit()
flash(f'Ενημερώθηκαν {updated_count} καταστήματα', 'success')
else:
flash('Δεν επιλέχθηκαν αλλαγές για εφαρμογή', 'warning')
return redirect(url_for('stores'))
@app.route('/statistics')
@login_required
def statistics():
"""Προβολή στατιστικών"""
# Παράμετροι φιλτραρίσματος
start_date_str = request.args.get('start_date')
end_date_str = request.args.get('end_date')
store_id = request.args.get('store_id', type=int)
sort = request.args.get('sort', 'date')
order = request.args.get('order', 'desc')
lovel_only = 'lovel_only' in request.args
# Προεπιλεγμένες ημερομηνίες (τελευταίες 7 ημέρες)
today = datetime.now().date()
default_start = today - timedelta(days=7)
default_end = today
# Μετατροπή ημερομηνιών
try:
start_date = datetime.strptime(start_date_str, '%Y-%m-%d').date() if start_date_str else default_start
end_date = datetime.strptime(end_date_str, '%Y-%m-%d').date() if end_date_str else default_end
except ValueError:
start_date = default_start
end_date = default_end
# Προετοιμασία του query για τα στατιστικά
query = db.session.query(
StoreStats.id,
Store.name.label('store_name'),
Store.id.label('store_id'),
StoreStats.sales,
StoreStats.products,
StoreStats.scrape_date
).join(Store)
# Εφαρμογή φίλτρων
query = query.filter(StoreStats.scrape_date.between(start_date, end_date))
if store_id:
query = query.filter(Store.id == store_id)
if lovel_only:
query = query.filter(Store.is_lovel_store == True)
# Εφαρμογή ταξινόμησης
if sort == 'store':
query = query.order_by(Store.name.asc() if order == 'asc' else Store.name.desc())
elif sort == 'sales':
query = query.order_by(StoreStats.sales.asc() if order == 'asc' else StoreStats.sales.desc())
elif sort == 'products':
query = query.order_by(StoreStats.products.asc() if order == 'asc' else StoreStats.products.desc())
else: # date
query = query.order_by(StoreStats.scrape_date.asc() if order == 'asc' else StoreStats.scrape_date.desc())
# Εκτέλεση του query
stats_data = query.all()
# Υπολογισμός συνολικών στοιχείων
totals = {
'sales': sum(row.sales for row in stats_data),
'products': sum(row.products for row in stats_data)
}
# Λίστα καταστημάτων για το dropdown
stores = Store.query.order_by(Store.name).all()
return render_template('statistics.html',
title='Στατιστικά',
stats=stats_data,
stores=stores,
selected_store_id=store_id,
start_date=start_date,
end_date=end_date,
sort=sort,
order=order,
lovel_only=lovel_only,
totals=totals)
@app.route('/lovel-report')
@login_required
def lovel_stores():
"""Έκθεση για τα καταστήματα Lovel"""
# Λίστα Lovel καταστημάτων
lovel_stores = Store.query.filter_by(is_lovel_store=True).order_by(Store.name).all()
# Ομαδοποίηση ανά Lovel group
lovel_groups = {}
for store in lovel_stores:
group = store.lovel_group or "Άλλα"
if group not in lovel_groups:
lovel_groups[group] = []
lovel_groups[group].append(store)
# Συγκεντρωτικά στατιστικά
today = datetime.now().date()
# Συνολικά στατιστικά Lovel καταστημάτων
lovel_stats = db.session.query(
func.sum(StoreStats.sales).label('total_sales'),
func.sum(StoreStats.products).label('total_products')
).join(
Store, StoreStats.store_id == Store.id
).filter(
Store.is_lovel_store == True,
StoreStats.scrape_date == today
).first()
lovel_sales = lovel_stats.total_sales if lovel_stats and lovel_stats.total_sales else 0
lovel_products = lovel_stats.total_products if lovel_stats and lovel_stats.total_products else 0
# Στατιστικά ανά ομάδα
group_stats = {}
for group, stores in lovel_groups.items():
store_ids = [store.id for store in stores]
group_data = db.session.query(
func.sum(StoreStats.sales).label('total_sales'),
func.sum(StoreStats.products).label('total_products')
).filter(
StoreStats.store_id.in_(store_ids),
StoreStats.scrape_date == today
).first()
group_stats[group] = {
'stores': len(stores),
'sales': group_data.total_sales if group_data and group_data.total_sales else 0,
'products': group_data.total_products if group_data and group_data.total_products else 0
}
return render_template('lovel_report.html',
title='Έκθεση Lovel',
lovel_stores=lovel_stores,
lovel_groups=lovel_groups,
lovel_sales=lovel_sales,
lovel_products=lovel_products,
group_stats=group_stats)
@app.route('/new-stores')
@login_required
def new_stores():
"""Προβολή νέων καταστημάτων"""
# Παράμετροι
days = request.args.get('days', 30, type=int)
# Υπολογισμός ημερομηνίας έναρξης
today = datetime.now().date()
start_date = today - timedelta(days=days)
# Εύρεση νέων καταστημάτων
new_stores_data = db.session.query(
Store, StoreAddition
).join(
StoreAddition, Store.id == StoreAddition.store_id
).filter(
StoreAddition.addition_date >= start_date
).order_by(
StoreAddition.addition_date.desc()
).all()
# Ομαδοποίηση ανά ημέρα
grouped_by_date = {}
for store, addition in new_stores_data:
date_str = addition.addition_date.strftime('%Y-%m-%d')
if date_str not in grouped_by_date:
grouped_by_date[date_str] = []
grouped_by_date[date_str].append((store, addition))
# Στατιστικά νέων καταστημάτων
total_new_stores = len(new_stores_data)
stores_by_source = {}
for _, addition in new_stores_data:
source = addition.source
if source not in stores_by_source:
stores_by_source[source] = 0
stores_by_source[source] += 1
return render_template('new_stores.html',
title='Νέα Καταστήματα',
grouped_by_date=grouped_by_date,
total_new_stores=total_new_stores,
stores_by_source=stores_by_source,
days=days)
@app.route('/upload', methods=['GET', 'POST'])
@login_required
def upload_file():
"""Ανέβασμα αρχείου CSV"""
if request.method == 'POST':
# Έλεγχος αν υπάρχει αρχείο στο request
if 'file' not in request.files:
flash('Δεν υπάρχει αρχείο', 'danger')
return redirect(request.url)
file = request.files['file']
# Έλεγχος αν δεν επιλέχθηκε αρχείο
if file.filename == '':
flash('Δεν επιλέχθηκε αρχείο', 'danger')
return redirect(request.url)
# Έλεγχος αν είναι επιτρεπόμενο αρχείο
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
try:
# Επεξεργασία αρχείου
added, updated = process_csv_file(file_path)
flash(f'Το αρχείο ανέβηκε και επεξεργάστηκε επιτυχώς! Προστέθηκαν {added} και ενημερώθηκαν {updated} καταστήματα.', 'success')
except Exception as e:
flash(f'Σφάλμα κατά την επεξεργασία του αρχείου: {str(e)}', 'danger')
return redirect(url_for('stores'))
flash('Μη επιτρεπόμενο αρχείο. Επιτρέπονται μόνο αρχεία CSV.', 'danger')
return redirect(request.url)
return render_template('upload.html', title='Ανέβασμα Αρχείου')
def process_csv_file(file_path):
"""Επεξεργασία αρχείου CSV"""
added_count = 0
updated_count = 0
with open(file_path, 'r', encoding='utf-8-sig') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
name = row.get('Store', '').strip()
url = row.get('URL', '').strip()
open_date = row.get('Open Date', '').strip()
# Προαιρετικά πεδία
sales = int(row.get('Sales', 0)) if row.get('Sales', '').strip() else 0
products = int(row.get('Products', 0)) if row.get('Products', '').strip() else 0
is_lovel_store = row.get('Lovel Store', '').strip().lower() in ['ναι', 'yes', 'true', '1']
lovel_group = row.get('Lovel Group', '').strip() if is_lovel_store else None
if not name or not url:
continue
# Έλεγχος αν υπάρχει ήδη το κατάστημα
store = Store.query.filter_by(url=url).first()
if store:
# Ενημέρωση υπάρχοντος καταστήματος
store.name = name
store.open_date = open_date
store.is_lovel_store = is_lovel_store
store.lovel_group = lovel_group
updated_count += 1
else:
# Δημιουργία νέου καταστήματος
store = Store(
name=name,
url=url,
open_date=open_date,
is_lovel_store=is_lovel_store,
lovel_group=lovel_group
)
db.session.add(store)
db.session.flush() # Για να πάρουμε το ID
# Καταγραφή της προσθήκης
store_addition = StoreAddition(
store_id=store.id,
addition_date=datetime.now().date(),
source='import'
)
db.session.add(store_addition)
added_count += 1
# Αν έχουμε πληροφορίες για πωλήσεις και προϊόντα, τα αποθηκεύουμε
if sales > 0 or products > 0:
# Έλεγχος αν υπάρχουν ήδη στατιστικά για σήμερα
today = datetime.now().date()
existing_stats = StoreStats.query.filter_by(
store_id=store.id,
scrape_date=today
).first()
if existing_stats:
# Ενημέρωση υπαρχόντων στατιστικών
existing_stats.sales = sales
existing_stats.products = products
existing_stats.last_update = datetime.now()
else:
# Δημιουργία νέων στατιστικών
stats = StoreStats(
store_id=store.id,
sales=sales,
products=products,
scrape_date=today,
last_update=datetime.now()
)
db.session.add(stats)
# Αποθήκευση όλων των αλλαγών
db.session.commit()
return added_count, updated_count
@app.route('/run-scraper', methods=['POST'])
@login_required
def run_scraper():
"""Εκτέλεση του scraper πωλήσεων/προϊόντων χειροκίνητα"""
try:
from app.jj_scraper import run_scraper as execute_scraper
result = execute_scraper()
flash(f'Το scraping ολοκληρώθηκε επιτυχώς! {result}', 'success')
except Exception as e:
flash(f'Σφάλμα κατά την εκτέλεση του scraper: {str(e)}', 'danger')
return redirect(url_for('dashboard'))
@app.route('/run-store-checker', methods=['POST'])
@login_required
def run_store_checker():
"""Εκτέλεση του ελέγχου νέων καταστημάτων χειροκίνητα"""
try:
from app.scripts.new_stores_checker import run_checker
result = run_checker()
flash(f'Ο έλεγχος νέων καταστημάτων ολοκληρώθηκε επιτυχώς! {result}', 'success')
except Exception as e:
flash(f'Σφάλμα κατά την εκτέλεση του ελέγχου νέων καταστημάτων: {str(e)}', 'danger')
return redirect(url_for('dashboard'))
@app.route('/')
@app.route('/index')
@login_required
def index():
# Στατιστικά για το dashboard
today = datetime.now().date()
yesterday = today - timedelta(days=1)
# Σύνολο καταστημάτων
total_stores = Store.query.count()
# Σιγουρευτείτε ότι έχετε αντικείμενα για τα στατιστικά
today_stats = {
'total_sales': 0,
'total_products': 0
}
yesterday_stats = {
'total_sales': 0,
'total_products': 0
}
# Στατιστικά τελευταίων ημερών
today_result = db.session.query(
func.sum(StoreStats.sales).label('total_sales'),
func.sum(StoreStats.products).label('total_products')
).filter(StoreStats.scrape_date == today).first()
yesterday_result = db.session.query(
func.sum(StoreStats.sales).label('total_sales'),
func.sum(StoreStats.products).label('total_products')
).filter(StoreStats.scrape_date == yesterday).first()
# Ενημέρωση στατιστικών αν έχουμε δεδομένα
if today_result and today_result.total_sales is not None:
today_stats['total_sales'] = today_result.total_sales
if today_result and today_result.total_products is not None:
today_stats['total_products'] = today_result.total_products
if yesterday_result and yesterday_result.total_sales is not None:
yesterday_stats['total_sales'] = yesterday_result.total_sales
if yesterday_result and yesterday_result.total_products is not None:
yesterday_stats['total_products'] = yesterday_result.total_products
# Πρόσφατα προστεθέντα καταστήματα
recent_stores = Store.query.order_by(Store.created_at.desc()).limit(5).all()
# Καταστήματα με τις περισσότερες πωλήσεις
top_selling_stores = []
try:
top_selling_stores = db.session.query(
Store.name,
Store.id,
func.max(StoreStats.sales).label('max_sales')
).join(StoreStats).group_by(Store.id, Store.name).order_by(desc('max_sales')).limit(5).all()
except Exception as e:
app.logger.error(f"Σφάλμα κατά την ανάκτηση των κορυφαίων καταστημάτων: {str(e)}")
# Νέα καταστήματα που προστέθηκαν πρόσφατα
new_stores_count = StoreAddition.query.filter(
StoreAddition.addition_date >= today - timedelta(days=7)
).count()
# Πληροφορίες για τα τελευταία scraping
last_scraping = StoreStats.query.order_by(StoreStats.created_at.desc()).first()
last_scraping_time = last_scraping.created_at if last_scraping else None
last_store_check = StoreAddition.query.order_by(StoreAddition.addition_date.desc()).first()
last_store_check_time = last_store_check.addition_date if last_store_check else None
# Υπολογισμός επόμενων προγραμματισμένων εκτελέσεων
next_scraping_time = datetime.now().replace(hour=23, minute=1, second=0)
if next_scraping_time < datetime.now():
next_scraping_time += timedelta(days=1)
next_stores_check_time = datetime.now().replace(hour=22, minute=45, second=0)
if next_stores_check_time < datetime.now():
next_stores_check_time += timedelta(days=1)
return render_template('index.html',
title='Dashboard',
total_stores=total_stores,
today_stats=today_stats,
yesterday_stats=yesterday_stats,
recent_stores=recent_stores,
top_selling_stores=top_selling_stores,
now=datetime.now(),
new_stores_count=new_stores_count,
last_scraping_time=last_scraping_time,
last_store_check_time=last_store_check_time,
next_scraping_time=next_scraping_time,
next_stores_check_time=next_stores_check_time)
import os
import sys
import subprocess
from datetime import datetime, timedelta
from flask import render_template, flash, redirect, url_for, request, jsonify
from app import app, db
from app.database_setup import Store, StoreStats, StoreAddition
@app.route('/')
@app.route('/index')
@app.route('/dashboard')
@login_required
def index():
today = datetime.now().date()
yesterday = today - timedelta(days=1)
# Βασικά στατιστικά
total_stores = Store.query.count()
# Υπολογισμός Λοβελ καταστημάτων
lovel_stores = Store.query.filter_by(is_lovel_store=True).count()
# Σημερινά στατιστικά
today_stats = db.session.query(
func.sum(StoreStats.sales).label('total_sales'),
func.sum(StoreStats.products).label('total_products')
).filter(StoreStats.scrape_date == today).first()
today_sales = today_stats.total_sales if today_stats and today_stats.total_sales else 0
today_products = today_stats.total_products if today_stats and today_stats.total_products else 0
# Πρόσφατα νέα καταστήματα
recent_new_stores = db.session.query(Store, StoreAddition)\
.join(StoreAddition, Store.id == StoreAddition.store_id)\
.order_by(StoreAddition.addition_date.desc())\
.limit(5).all()
# Μετατροπή σε λίστα από καταστήματα με επιπλέον πληροφορίες
recent_new_stores_data = []
for store, addition in recent_new_stores:
store_data = {
'id': store.id,
'name': store.name,
'url': store.url,
'open_date': store.open_date,
'is_lovel_store': store.is_lovel_store,
'added_date': addition.addition_date.strftime('%d/%m/%Y')
}
recent_new_stores_data.append(store_data)
# Top καταστήματα με βάση τις πωλήσεις
top_stores = db.session.query(
Store,
func.max(StoreStats.sales).label('max_sales'),
func.max(StoreStats.products).label('max_products')
).join(StoreStats).group_by(Store.id).order_by(db.desc('max_sales')).limit(5).all()
top_stores_data = []
for store, sales, products in top_stores:
store_data = {
'id': store.id,
'name': store.name,
'is_lovel_store': store.is_lovel_store,
'sales': sales,
'products': products
}
top_stores_data.append(store_data)
# Προγραμματισμένες εργασίες
now = datetime.now()
# Επόμενη εκτέλεση ελέγχου νέων καταστημάτων (22:45)
next_new_stores_check = now.replace(hour=22, minute=45, second=0)
if next_new_stores_check < now:
next_new_stores_check += timedelta(days=1)
# Επόμενη εκτέλεση scraping πωλήσεων/προϊόντων (23:01)
next_sales_scrape = now.replace(hour=23, minute=1, second=0)
if next_sales_scrape < now:
next_sales_scrape += timedelta(days=1)
# Πρόσφατη δραστηριότητα (παράδειγμα)
activity_log = [
{
'icon': 'sync-alt',
'level': 'primary',
'message': 'Ολοκληρώθηκε το scraping πωλήσεων/προϊόντων',
'timestamp': (now - timedelta(hours=2)).strftime('%d/%m/%Y %H:%M')
},
{
'icon': 'plus-circle',
'level': 'success',
'message': 'Προστέθηκαν 3 νέα καταστήματα',
'timestamp': (now - timedelta(days=1)).strftime('%d/%m/%Y %H:%M')
}
]
# Για AJAX αιτήματα
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return jsonify({
'success': True,
'data': {
'total_stores': total_stores,
'lovel_stores': lovel_stores,
'today_sales': today_sales,
'today_products': today_products
}
})
return render_template('dashboard.html',
title='Dashboard',
total_stores=total_stores,
lovel_stores=lovel_stores,
today_sales=today_sales,
today_products=today_products,
recent_new_stores=recent_new_stores_data,
top_stores=top_stores_data,
next_new_stores_check=next_new_stores_check.strftime('%d/%m/%Y %H:%M'),
next_sales_scrape=next_sales_scrape.strftime('%d/%m/%Y %H:%M'),
activity_log=activity_log)
@app.route('/run_scraper', methods=['POST'])
@login_required
def run_scraper():
"""Εκτελεί το scraper χειροκίνητα"""
try:
# Διαδρομή προς το jj_scraper.py
scraper_path = os.path.join(app.root_path, 'jj_scraper.py')
# Εκτέλεση του scraper ως υποδιεργασία
result = subprocess.run(
[sys.executable, scraper_path],
capture_output=True,
text=True,
timeout=300 # 5 λεπτά timeout
)
if result.returncode == 0:
flash(f'Το scraping ολοκληρώθηκε επιτυχώς!', 'success')
app.logger.info(f'Επιτυχής εκτέλεση του scraper. Output: {result.stdout}')
else:
flash(f'Προέκυψε σφάλμα κατά την εκτέλεση του scraper: {result.stderr}', 'danger')
app.logger.error(f'Σφάλμα κατά την εκτέλεση του scraper: {result.stderr}')
except subprocess.TimeoutExpired:
flash('Το scraping διήρκεσε πολύ και τερματίστηκε. Δοκιμάστε ξανά αργότερα.', 'warning')
app.logger.warning('Timeout κατά την εκτέλεση του scraper')
except Exception as e:
flash(f'Σφάλμα κατά την εκτέλεση του scraper: {str(e)}', 'danger')
app.logger.error(f'Εξαίρεση κατά την εκτέλεση του scraper: {str(e)}')
return redirect(url_for('index'))
@app.route('/run_stores_check', methods=['POST'])
@login_required
def run_stores_check():
"""Εκτελεί τον έλεγχο για νέα καταστήματα χειροκίνητα"""
try:
# Διαδρομή προς το new_stores_checker.py
checker_path = os.path.join(app.root_path, 'scripts', 'new_stores_checker.py')
# Εκτέλεση του checker ως υποδιεργασία
result = subprocess.run(
[sys.executable, checker_path],
capture_output=True,
text=True,
timeout=300 # 5 λεπτά timeout
)
if result.returncode == 0:
flash(f'Ο έλεγχος για νέα καταστήματα ολοκληρώθηκε επιτυχώς!', 'success')
app.logger.info(f'Επιτυχής εκτέλεση του new_stores_checker. Output: {result.stdout}')
else:
flash(f'Προέκυψε σφάλμα κατά τον έλεγχο για νέα καταστήματα: {result.stderr}', 'danger')
app.logger.error(f'Σφάλμα κατά την εκτέλεση του new_stores_checker: {result.stderr}')
except subprocess.TimeoutExpired:
flash('Ο έλεγχος διήρκεσε πολύ και τερματίστηκε. Δοκιμάστε ξανά αργότερα.', 'warning')
app.logger.warning('Timeout κατά την εκτέλεση του new_stores_checker')
except Exception as e:
flash(f'Σφάλμα κατά τον έλεγχο για νέα καταστήματα: {str(e)}', 'danger')
app.logger.error(f'Εξαίρεση κατά την εκτέλεση του new_stores_checker: {str(e)}')
return redirect(url_for('index'))Editor is loading...
Leave a Comment