monthly report added
This commit is contained in:
158
declaratie/monthly_declaration_helper.py
Normal file
158
declaratie/monthly_declaration_helper.py
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Maandelijkse Declaratie Helper Script
|
||||
Maakt het genereren en versturen van rapporten eenvoudiger
|
||||
Werkt op zowel Windows als Linux/Mac
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import calendar
|
||||
|
||||
def get_previous_month():
|
||||
"""Bereken vorige maand en jaar"""
|
||||
today = datetime.now()
|
||||
first_day_current = today.replace(day=1)
|
||||
last_month = first_day_current - timedelta(days=1)
|
||||
return last_month.month, last_month.year
|
||||
|
||||
def show_header(month, year, email, user=None, summary=False):
|
||||
"""Toon header informatie"""
|
||||
print("=" * 50)
|
||||
print("Maandelijkse Declaratie Generator")
|
||||
print("=" * 50)
|
||||
print(f"Periode: {calendar.month_name[month]} {year}")
|
||||
print(f"Email: {email}")
|
||||
if user:
|
||||
print(f"Gebruiker: {user}")
|
||||
if summary:
|
||||
print("Modus: Alleen samenvatting")
|
||||
print("=" * 50)
|
||||
|
||||
def run_command(cmd, shell=False):
|
||||
"""Voer commando uit en toon output"""
|
||||
try:
|
||||
if os.name == 'nt': # Windows
|
||||
# Op Windows, gebruik shell=True voor complexe commando's
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, encoding='utf-8')
|
||||
else: # Linux/Mac
|
||||
result = subprocess.run(cmd, shell=shell, capture_output=True, text=True, encoding='utf-8')
|
||||
|
||||
if result.stdout:
|
||||
print(result.stdout)
|
||||
if result.stderr:
|
||||
print(result.stderr, file=sys.stderr)
|
||||
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
print(f"Fout bij uitvoeren commando: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
# Standaard waarden - vorige maand
|
||||
default_month, default_year = get_previous_month()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Helper script voor maandelijkse declaratie rapporten',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Voorbeelden:
|
||||
# Rapport vorige maand versturen
|
||||
python monthly_declaration_helper.py
|
||||
|
||||
# Oktober 2024
|
||||
python monthly_declaration_helper.py -m 10 -y 2024
|
||||
|
||||
# Toon rapport vorige maand op scherm
|
||||
python monthly_declaration_helper.py -d
|
||||
|
||||
# Verstuur alleen samenvatting
|
||||
python monthly_declaration_helper.py -s
|
||||
|
||||
# Rapport voor specifieke gebruiker
|
||||
python monthly_declaration_helper.py -u 04A2CBA2C43C80
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('-m', '--month', type=int, default=default_month,
|
||||
choices=range(1, 13),
|
||||
help=f'Maand (1-12). Standaard: {default_month} (vorige maand)')
|
||||
parser.add_argument('-y', '--year', type=int, default=default_year,
|
||||
help=f'Jaar. Standaard: {default_year}')
|
||||
parser.add_argument('-u', '--user',
|
||||
help='Filter op specifieke gebruiker/RFID tag (optioneel)')
|
||||
parser.add_argument('-e', '--email', default='mark@markkors.nl',
|
||||
help='Email adres (standaard: mark@markkors.nl)')
|
||||
parser.add_argument('-s', '--summary', action='store_true',
|
||||
help='Alleen samenvatting (geen details)')
|
||||
parser.add_argument('-d', '--display', action='store_true',
|
||||
help='Toon rapport op scherm (niet versturen)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Toon header
|
||||
show_header(args.month, args.year, args.email, args.user, args.summary)
|
||||
|
||||
# Database configuratie
|
||||
db_config = {
|
||||
'host': '192.168.178.201',
|
||||
'port': '3307',
|
||||
'user': 'steve',
|
||||
'password': 'kS9R*xp17ZwCD@CV&E^N',
|
||||
'price_user': 'alfen_user',
|
||||
'price_password': '5uVgr%f%s2P5GR@3q!'
|
||||
}
|
||||
|
||||
print("")
|
||||
if args.display:
|
||||
# Toon op scherm
|
||||
print("Genereer rapport...")
|
||||
|
||||
cmd = [
|
||||
'python', 'steve_monthly_declaration.py',
|
||||
'--host', db_config['host'],
|
||||
'--port', db_config['port'],
|
||||
'--user', db_config['user'],
|
||||
'--password', db_config['password'],
|
||||
'--price-user', db_config['price_user'],
|
||||
'--price-password', db_config['price_password'],
|
||||
'--month', str(args.month),
|
||||
'--year', str(args.year)
|
||||
]
|
||||
|
||||
if args.user:
|
||||
cmd.extend(['--user', args.user])
|
||||
if args.summary:
|
||||
cmd.append('--summary')
|
||||
|
||||
success = run_command(cmd)
|
||||
else:
|
||||
# Verstuur per email
|
||||
print("Genereer en verstuur rapport...")
|
||||
|
||||
cmd = [
|
||||
'python', 'send_monthly_declaration.py',
|
||||
'--month', str(args.month),
|
||||
'--year', str(args.year),
|
||||
'--email', args.email
|
||||
]
|
||||
|
||||
if args.user:
|
||||
cmd.extend(['--user', args.user])
|
||||
if args.summary:
|
||||
cmd.append('--summary')
|
||||
|
||||
success = run_command(cmd)
|
||||
|
||||
print("")
|
||||
if success:
|
||||
print("✓ Klaar!")
|
||||
else:
|
||||
print("✗ Er is een fout opgetreden")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
105
declaratie/send_monthly_declaration.py
Normal file
105
declaratie/send_monthly_declaration.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import smtplib
|
||||
import argparse
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from datetime import datetime
|
||||
import calendar
|
||||
|
||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(description='Genereer en verstuur maandelijkse declaratie rapport')
|
||||
parser.add_argument('--month', type=int, required=True, choices=range(1, 13),
|
||||
help='Maand (1-12)')
|
||||
parser.add_argument('--year', type=int, required=True,
|
||||
help='Jaar (bijv. 2024)')
|
||||
parser.add_argument('--user', help='Filter op specifieke RFID tag/gebruiker (optioneel)')
|
||||
parser.add_argument('--email', default='mark@markkors.nl',
|
||||
help='Email adres (default: mark@markkors.nl)')
|
||||
parser.add_argument('--summary', action='store_true',
|
||||
help='Stuur alleen samenvatting zonder transactie details')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Configuratie
|
||||
TO_EMAIL = args.email
|
||||
FROM_EMAIL = "laadpaal@raspberry.local"
|
||||
SUBJECT = f"Declaratie Laadpaal - {calendar.month_name[args.month]} {args.year}"
|
||||
|
||||
# SMTP configuratie
|
||||
SMTP_SERVER = "192.168.178.201"
|
||||
SMTP_PORT = 25
|
||||
|
||||
# Genereer rapport commando
|
||||
cmd = [
|
||||
'python', 'steve_monthly_declaration.py',
|
||||
'--host', '192.168.178.201',
|
||||
'--port', '3307',
|
||||
'--user', 'steve',
|
||||
'--password', 'kS9R*xp17ZwCD@CV&E^N',
|
||||
'--price-user', 'alfen_user',
|
||||
'--price-password', '5uVgr%f%s2P5GR@3q!',
|
||||
'--month', str(args.month),
|
||||
'--year', str(args.year)
|
||||
]
|
||||
|
||||
# Voeg optionele parameters toe
|
||||
if args.user:
|
||||
cmd.extend(['--user', args.user])
|
||||
SUBJECT += f" - Gebruiker: {args.user}"
|
||||
|
||||
if args.summary:
|
||||
cmd.append('--summary')
|
||||
|
||||
try:
|
||||
# Genereer rapport
|
||||
print(f"📊 Genereer declaratie rapport voor {calendar.month_name[args.month]} {args.year}...")
|
||||
report = subprocess.check_output(cmd, encoding='utf-8')
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Fout bij genereren rapport: {e}")
|
||||
exit(1)
|
||||
|
||||
# Email samenstellen
|
||||
msg = MIMEMultipart('alternative')
|
||||
msg['From'] = FROM_EMAIL
|
||||
msg['To'] = TO_EMAIL
|
||||
msg['Subject'] = SUBJECT
|
||||
|
||||
# Plain text versie
|
||||
text_part = MIMEText(report, 'plain', 'utf-8')
|
||||
msg.attach(text_part)
|
||||
|
||||
# HTML versie voor betere leesbaarheid in email clients
|
||||
html_report = f"""
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {{ font-family: 'Courier New', monospace; font-size: 12px; }}
|
||||
pre {{ white-space: pre-wrap; word-wrap: break-word; }}
|
||||
.header {{ font-weight: bold; color: #2c3e50; }}
|
||||
.total {{ font-weight: bold; background-color: #f8f9fa; padding: 5px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre>{report.replace('DECLARATIE RAPPORT LAADTRANSACTIES', '<span class="header">DECLARATIE RAPPORT LAADTRANSACTIES</span>')
|
||||
.replace('TOTAAL OVERZICHT', '<span class="header">TOTAAL OVERZICHT</span>')
|
||||
.replace('TOTALE KOSTEN:', '<span class="total">TOTALE KOSTEN:</span>')}</pre>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
html_part = MIMEText(html_report, 'html', 'utf-8')
|
||||
msg.attach(html_part)
|
||||
|
||||
# Verstuur email
|
||||
try:
|
||||
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
|
||||
server.send_message(msg)
|
||||
print(f"✔ Declaratie rapport verzonden naar {TO_EMAIL}")
|
||||
print(f" Periode: {calendar.month_name[args.month]} {args.year}")
|
||||
if args.user:
|
||||
print(f" Gebruiker: {args.user}")
|
||||
except Exception as e:
|
||||
print(f"✗ Fout bij verzenden: {e}")
|
||||
exit(1)
|
||||
440
declaratie/steve_monthly_declaration.py
Normal file
440
declaratie/steve_monthly_declaration.py
Normal file
@@ -0,0 +1,440 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SteVe Monthly Declaration Report Generator
|
||||
Genereert maandelijkse declaratie rapporten van laadtransacties
|
||||
"""
|
||||
|
||||
import mysql.connector
|
||||
from datetime import datetime, timedelta
|
||||
import argparse
|
||||
import sys
|
||||
import io
|
||||
import os
|
||||
from typing import List, Dict, Optional
|
||||
import calendar
|
||||
from collections import defaultdict
|
||||
|
||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||
|
||||
# Forceer UTF-8 encoding voor stdout/stderr op Windows
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
||||
|
||||
class MonthlyDeclarationReporter:
|
||||
def __init__(self, host: str, database: str, user: str, password: str, port: int = 3306,
|
||||
price_host: str = None, price_port: int = None, price_user: str = None, price_password: str = None):
|
||||
"""Initialiseer database connectie"""
|
||||
self.config = {
|
||||
'host': host,
|
||||
'database': database,
|
||||
'user': user,
|
||||
'password': password,
|
||||
'port': port
|
||||
}
|
||||
# Prijzen database config (gebruik SteVe config als fallback)
|
||||
self.price_config = {
|
||||
'host': price_host or host,
|
||||
'port': price_port or port,
|
||||
'user': price_user or user,
|
||||
'password': price_password or password
|
||||
}
|
||||
self.conn = None
|
||||
self.cursor = None
|
||||
self.price_conn = None
|
||||
self.price_cursor = None
|
||||
|
||||
def connect(self):
|
||||
"""Maak verbinding met de database"""
|
||||
try:
|
||||
self.conn = mysql.connector.connect(**self.config)
|
||||
self.cursor = self.conn.cursor(dictionary=True)
|
||||
print(f"✓ Verbonden met database {self.config['database']} op {self.config['host']}")
|
||||
except mysql.connector.Error as err:
|
||||
print(f"✗ Database fout: {err}")
|
||||
sys.exit(1)
|
||||
|
||||
def disconnect(self):
|
||||
"""Sluit database verbinding"""
|
||||
if self.cursor:
|
||||
self.cursor.close()
|
||||
if self.conn:
|
||||
self.conn.close()
|
||||
if hasattr(self, 'price_cursor') and self.price_cursor:
|
||||
self.price_cursor.close()
|
||||
if hasattr(self, 'price_conn') and self.price_conn:
|
||||
self.price_conn.close()
|
||||
|
||||
def get_monthly_transactions(self, year: int, month: int, user_filter: Optional[str] = None) -> List[Dict]:
|
||||
"""Haal alle transacties op voor een specifieke maand"""
|
||||
# Bereken eerste en laatste dag van de maand
|
||||
first_day = datetime(year, month, 1)
|
||||
last_day = datetime(year, month, calendar.monthrange(year, month)[1], 23, 59, 59)
|
||||
|
||||
query = """
|
||||
SELECT
|
||||
t.transaction_pk,
|
||||
t.id_tag,
|
||||
t.start_timestamp,
|
||||
t.stop_timestamp,
|
||||
t.start_value,
|
||||
t.stop_value,
|
||||
t.stop_reason,
|
||||
cb.charge_box_id,
|
||||
cb.description as charge_box_description,
|
||||
c.connector_id
|
||||
FROM transaction t
|
||||
JOIN connector c ON t.connector_pk = c.connector_pk
|
||||
JOIN charge_box cb ON c.charge_box_id = cb.charge_box_id
|
||||
WHERE t.start_timestamp >= %s AND t.start_timestamp <= %s
|
||||
AND t.stop_timestamp IS NOT NULL
|
||||
"""
|
||||
|
||||
params = [first_day, last_day]
|
||||
|
||||
if user_filter:
|
||||
query += " AND t.id_tag = %s"
|
||||
params.append(user_filter)
|
||||
|
||||
query += " ORDER BY t.start_timestamp ASC"
|
||||
|
||||
self.cursor.execute(query, params)
|
||||
return self.cursor.fetchall()
|
||||
|
||||
def get_user_info(self, id_tag: str) -> Optional[Dict]:
|
||||
"""Haal gebruikersinformatie op"""
|
||||
query = """
|
||||
SELECT u.first_name, u.last_name, u.e_mail
|
||||
FROM user u
|
||||
JOIN user_ocpp_tag uot ON u.user_pk = uot.user_pk
|
||||
JOIN ocpp_tag ot ON uot.ocpp_tag_pk = ot.ocpp_tag_pk
|
||||
WHERE ot.id_tag = %s
|
||||
"""
|
||||
self.cursor.execute(query, (id_tag,))
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def get_price(self, timestamp: datetime, provider: str, price_db: str) -> Optional[float]:
|
||||
"""Haal prijs op voor een specifieke timestamp en provider"""
|
||||
# Maak verbinding met prijzen database indien nodig
|
||||
if not hasattr(self, 'price_conn') or self.price_conn is None:
|
||||
try:
|
||||
price_config = self.price_config.copy()
|
||||
price_config['database'] = price_db
|
||||
self.price_conn = mysql.connector.connect(**price_config)
|
||||
self.price_cursor = self.price_conn.cursor(dictionary=True)
|
||||
except mysql.connector.Error:
|
||||
return None
|
||||
|
||||
# Round timestamp naar het uur
|
||||
hour_timestamp = timestamp.replace(minute=0, second=0, microsecond=0)
|
||||
|
||||
# Probeer eerst dynamic_price_data
|
||||
query = """
|
||||
SELECT price
|
||||
FROM dynamic_price_data
|
||||
WHERE datetime = %s AND provider_code = %s
|
||||
LIMIT 1
|
||||
"""
|
||||
try:
|
||||
self.price_cursor.execute(query, (hour_timestamp, provider))
|
||||
result = self.price_cursor.fetchone()
|
||||
if result:
|
||||
return float(result['price'])
|
||||
except mysql.connector.Error:
|
||||
pass
|
||||
|
||||
# Zo niet, probeer dynamic_price_data_tommorow (alleen NextEnergy)
|
||||
if provider == 'NE':
|
||||
query = """
|
||||
SELECT price
|
||||
FROM dynamic_price_data_tommorow
|
||||
WHERE datetime = %s
|
||||
LIMIT 1
|
||||
"""
|
||||
try:
|
||||
self.price_cursor.execute(query, (hour_timestamp,))
|
||||
result = self.price_cursor.fetchone()
|
||||
if result:
|
||||
return float(result['price'])
|
||||
except mysql.connector.Error:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def calculate_transaction_cost(self, transaction: Dict, provider: str, price_db: str) -> Dict:
|
||||
"""Bereken de kosten voor een transactie"""
|
||||
start_time = transaction['start_timestamp']
|
||||
stop_time = transaction['stop_timestamp']
|
||||
start_val = float(transaction['start_value']) if transaction['start_value'] else 0
|
||||
stop_val = float(transaction['stop_value']) if transaction['stop_value'] else 0
|
||||
total_kwh = stop_val - start_val
|
||||
|
||||
# Bereken kosten per uur
|
||||
current_hour = start_time.replace(minute=0, second=0, microsecond=0)
|
||||
total_cost = 0
|
||||
hours_with_price = 0
|
||||
hours_without_price = 0
|
||||
|
||||
while current_hour <= stop_time:
|
||||
# Bereken verbruik in dit uur
|
||||
hour_start = max(current_hour, start_time)
|
||||
hour_end = min(current_hour + timedelta(hours=1), stop_time)
|
||||
|
||||
if hour_end > hour_start:
|
||||
# Bereken fractie van het uur
|
||||
hour_fraction = (hour_end - hour_start).total_seconds() / 3600
|
||||
hour_kwh = total_kwh * hour_fraction * 3600 / (stop_time - start_time).total_seconds()
|
||||
|
||||
# Haal prijs op
|
||||
price = self.get_price(current_hour, provider, price_db)
|
||||
if price is not None:
|
||||
hour_cost = hour_kwh * price
|
||||
total_cost += hour_cost
|
||||
hours_with_price += 1
|
||||
else:
|
||||
hours_without_price += 1
|
||||
|
||||
current_hour += timedelta(hours=1)
|
||||
|
||||
return {
|
||||
'total_kwh': total_kwh,
|
||||
'total_cost': total_cost,
|
||||
'hours_with_price': hours_with_price,
|
||||
'hours_without_price': hours_without_price,
|
||||
'avg_price': total_cost / total_kwh if total_kwh > 0 and hours_with_price > 0 else 0
|
||||
}
|
||||
|
||||
def generate_monthly_report(self, year: int, month: int, provider: str = 'NE',
|
||||
price_db: str = 'alfen', user_filter: Optional[str] = None,
|
||||
format_type: str = 'detailed'):
|
||||
"""Genereer maandelijks declaratie rapport"""
|
||||
transactions = self.get_monthly_transactions(year, month, user_filter)
|
||||
|
||||
if not transactions:
|
||||
print(f"Geen transacties gevonden voor {calendar.month_name[month]} {year}")
|
||||
return
|
||||
|
||||
# Header
|
||||
print("=" * 100)
|
||||
print(f"DECLARATIE RAPPORT LAADTRANSACTIES")
|
||||
print(f"Periode: {calendar.month_name[month]} {year}")
|
||||
print("=" * 100)
|
||||
|
||||
# Groepeer transacties per gebruiker
|
||||
user_transactions = defaultdict(list)
|
||||
for trans in transactions:
|
||||
user_transactions[trans['id_tag']].append(trans)
|
||||
|
||||
# Totalen voor het hele rapport
|
||||
grand_total_sessions = 0
|
||||
grand_total_kwh = 0
|
||||
grand_total_cost = 0
|
||||
grand_total_duration = timedelta()
|
||||
|
||||
# Print per gebruiker
|
||||
for id_tag, user_trans in sorted(user_transactions.items()):
|
||||
# Gebruikersinfo
|
||||
user_info = self.get_user_info(id_tag)
|
||||
if user_info:
|
||||
print(f"\nGebruiker: {user_info['first_name']} {user_info['last_name']}")
|
||||
print(f"Email: {user_info['e_mail']}")
|
||||
print(f"RFID Tag: {id_tag}")
|
||||
print("-" * 100)
|
||||
|
||||
if format_type == 'detailed':
|
||||
# Gedetailleerde tabel
|
||||
print(f"{'Datum':<12} {'Start':<6} {'Einde':<6} {'Duur':<8} "
|
||||
f"{'kWh':>8} {'Kosten':>10} {'Gem. prijs':>12} {'Laadpaal':<20}")
|
||||
print("-" * 100)
|
||||
|
||||
user_total_sessions = 0
|
||||
user_total_kwh = 0
|
||||
user_total_cost = 0
|
||||
user_total_duration = timedelta()
|
||||
|
||||
for trans in sorted(user_trans, key=lambda x: x['start_timestamp']):
|
||||
# Bereken kosten
|
||||
cost_info = self.calculate_transaction_cost(trans, provider, price_db)
|
||||
|
||||
# Bereken duur
|
||||
duration = trans['stop_timestamp'] - trans['start_timestamp']
|
||||
hours = int(duration.total_seconds() // 3600)
|
||||
minutes = int((duration.total_seconds() % 3600) // 60)
|
||||
duration_str = f"{hours}:{minutes:02d}"
|
||||
|
||||
# Update totalen
|
||||
user_total_sessions += 1
|
||||
user_total_kwh += cost_info['total_kwh']
|
||||
user_total_cost += cost_info['total_cost']
|
||||
user_total_duration += duration
|
||||
|
||||
if format_type == 'detailed':
|
||||
# Print transactie regel
|
||||
print(f"{trans['start_timestamp'].strftime('%d-%m-%Y'):<12} "
|
||||
f"{trans['start_timestamp'].strftime('%H:%M'):<6} "
|
||||
f"{trans['stop_timestamp'].strftime('%H:%M'):<6} "
|
||||
f"{duration_str:<8} "
|
||||
f"{cost_info['total_kwh']:>8.2f} "
|
||||
f"€{cost_info['total_cost']:>9.2f} "
|
||||
f"€{cost_info['avg_price']:>11.4f} "
|
||||
f"{trans['charge_box_id'][:19]:<20}")
|
||||
|
||||
if cost_info['hours_without_price'] > 0:
|
||||
print(f" * Waarschuwing: {cost_info['hours_without_price']} uur zonder prijsdata")
|
||||
|
||||
# Gebruiker totalen
|
||||
print("-" * 100)
|
||||
total_hours = int(user_total_duration.total_seconds() // 3600)
|
||||
total_minutes = int((user_total_duration.total_seconds() % 3600) // 60)
|
||||
|
||||
print(f"Totaal gebruiker: {user_total_sessions} sessies | "
|
||||
f"{total_hours}:{total_minutes:02d} uur | "
|
||||
f"{user_total_kwh:.2f} kWh | "
|
||||
f"€ {user_total_cost:.2f}")
|
||||
|
||||
if user_total_kwh > 0:
|
||||
avg_price_user = user_total_cost / user_total_kwh
|
||||
print(f"Gemiddelde prijs: € {avg_price_user:.4f} per kWh")
|
||||
|
||||
# Update grand totalen
|
||||
grand_total_sessions += user_total_sessions
|
||||
grand_total_kwh += user_total_kwh
|
||||
grand_total_cost += user_total_cost
|
||||
grand_total_duration += user_total_duration
|
||||
|
||||
# Grand totalen
|
||||
print("\n" + "=" * 100)
|
||||
print("TOTAAL OVERZICHT")
|
||||
print("=" * 100)
|
||||
|
||||
total_hours = int(grand_total_duration.total_seconds() // 3600)
|
||||
total_minutes = int((grand_total_duration.total_seconds() % 3600) // 60)
|
||||
|
||||
print(f"Aantal gebruikers: {len(user_transactions)}")
|
||||
print(f"Totaal aantal sessies: {grand_total_sessions}")
|
||||
print(f"Totale laadtijd: {total_hours}:{total_minutes:02d} uur")
|
||||
print(f"Totaal verbruik: {grand_total_kwh:.2f} kWh")
|
||||
print(f"TOTALE KOSTEN: € {grand_total_cost:.2f}")
|
||||
|
||||
if grand_total_kwh > 0:
|
||||
avg_price_total = grand_total_cost / grand_total_kwh
|
||||
print(f"Gemiddelde prijs: € {avg_price_total:.4f} per kWh")
|
||||
|
||||
# Statistieken
|
||||
print(f"\nGemiddeld per sessie:")
|
||||
if grand_total_sessions > 0:
|
||||
avg_kwh = grand_total_kwh / grand_total_sessions
|
||||
avg_cost = grand_total_cost / grand_total_sessions
|
||||
avg_duration = grand_total_duration / grand_total_sessions
|
||||
avg_hours = int(avg_duration.total_seconds() // 3600)
|
||||
avg_minutes = int((avg_duration.total_seconds() % 3600) // 60)
|
||||
|
||||
print(f" Verbruik: {avg_kwh:.2f} kWh")
|
||||
print(f" Kosten: € {avg_cost:.2f}")
|
||||
print(f" Duur: {avg_hours}:{avg_minutes:02d} uur")
|
||||
|
||||
print("\n" + "=" * 100)
|
||||
print(f"Rapport gegenereerd op: {datetime.now().strftime('%d-%m-%Y %H:%M')}")
|
||||
print(f"Energieprovider: {provider}")
|
||||
print("=" * 100)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='SteVe Monthly Declaration Report Generator',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Voorbeelden:
|
||||
# Genereer rapport voor oktober 2024
|
||||
python3 steve_monthly_declaration.py --host 192.168.178.201 --port 3307 --password 'geheim' --month 10 --year 2024
|
||||
|
||||
# Genereer rapport voor specifieke gebruiker in november 2024
|
||||
python3 steve_monthly_declaration.py --host 192.168.178.201 --port 3307 --password 'geheim' --month 11 --year 2024 --user 04A2CBA2C43C80
|
||||
|
||||
# Gebruik aparte credentials voor prijzen database
|
||||
python3 steve_monthly_declaration.py --host 192.168.178.201 --port 3307 --password 'geheim' \\
|
||||
--price-user alfen_user --price-password 'geheim2' --month 10 --year 2024
|
||||
|
||||
# Genereer beknopt rapport (zonder details per transactie)
|
||||
python3 steve_monthly_declaration.py --host 192.168.178.201 --port 3307 --password 'geheim' --month 10 --year 2024 --summary
|
||||
"""
|
||||
)
|
||||
|
||||
# Database opties
|
||||
parser.add_argument('--host', default='localhost', help='Database host (default: localhost)')
|
||||
parser.add_argument('--port', type=int, default=3306, help='Database poort (default: 3306)')
|
||||
parser.add_argument('--database', default='stevedb', help='Database naam (default: stevedb)')
|
||||
parser.add_argument('--user', default='steve', help='Database gebruiker (default: steve)')
|
||||
parser.add_argument('--password', required=True, help='Database wachtwoord')
|
||||
|
||||
# Maand/jaar selectie
|
||||
parser.add_argument('--month', type=int, required=True, choices=range(1, 13),
|
||||
help='Maand (1-12)')
|
||||
parser.add_argument('--year', type=int, required=True,
|
||||
help='Jaar (bijv. 2024)')
|
||||
|
||||
# Filter opties
|
||||
parser.add_argument('--rfid', '--user', dest='user_filter',
|
||||
help='Filter op specifieke RFID tag/gebruiker')
|
||||
parser.add_argument('--summary', action='store_true',
|
||||
help='Toon alleen samenvattingen (geen transactie details)')
|
||||
|
||||
# Prijzen opties
|
||||
parser.add_argument('--provider', default='NE',
|
||||
help='Energie provider code (default: NE voor NextEnergy)')
|
||||
parser.add_argument('--price-db', default='alfen',
|
||||
help='Database naam voor prijzen (default: alfen)')
|
||||
parser.add_argument('--price-host', default=None,
|
||||
help='Database host voor prijzen (default: zelfde als --host)')
|
||||
parser.add_argument('--price-port', type=int, default=None,
|
||||
help='Database poort voor prijzen (default: zelfde als --port)')
|
||||
parser.add_argument('--price-user', default=None,
|
||||
help='Database gebruiker voor prijzen (default: zelfde als --user)')
|
||||
parser.add_argument('--price-password', default=None,
|
||||
help='Database wachtwoord voor prijzen (default: zelfde als --password)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Maak reporter object
|
||||
reporter = MonthlyDeclarationReporter(
|
||||
host=args.host,
|
||||
database=args.database,
|
||||
user=args.user,
|
||||
password=args.password,
|
||||
port=args.port,
|
||||
price_host=args.price_host,
|
||||
price_port=args.price_port,
|
||||
price_user=args.price_user,
|
||||
price_password=args.price_password
|
||||
)
|
||||
|
||||
try:
|
||||
reporter.connect()
|
||||
|
||||
# Bepaal format type
|
||||
format_type = 'summary' if args.summary else 'detailed'
|
||||
|
||||
# Genereer rapport
|
||||
reporter.generate_monthly_report(
|
||||
year=args.year,
|
||||
month=args.month,
|
||||
provider=args.provider,
|
||||
price_db=args.price_db,
|
||||
user_filter=args.user_filter,
|
||||
format_type=format_type
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nAfgebroken door gebruiker")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"✗ Fout: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
finally:
|
||||
reporter.disconnect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user