reporter
This commit is contained in:
320
2025-11-30 final reporter/30Report.py
Normal file
320
2025-11-30 final reporter/30Report.py
Normal file
@@ -0,0 +1,320 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import io
|
||||
|
||||
# Force UTF-8 output for Scheduled Tasks
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
"""
|
||||
FIO EXPORT SCRIPT — FULLY COMMENTED VERSION
|
||||
-------------------------------------------
|
||||
|
||||
This script connects to your MySQL "fio" database,
|
||||
reads all transactions, and exports them into a highly formatted
|
||||
Excel workbook.
|
||||
|
||||
Excel file includes:
|
||||
|
||||
• First sheet: "ALL" → contains ALL transactions
|
||||
• Additional sheets: one for each account from accounts.json
|
||||
• First 5 CZK sheets appear first in custom order
|
||||
• All formatting exactly preserved (colors, borders, widths, formulas)
|
||||
|
||||
Everything is generated automatically.
|
||||
"""
|
||||
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
from datetime import datetime
|
||||
import os
|
||||
import glob
|
||||
import json
|
||||
|
||||
# ======================================================
|
||||
# CONFIGURATION
|
||||
# ======================================================
|
||||
|
||||
# MySQL server parameters
|
||||
DB_HOST = "192.168.1.76"
|
||||
DB_PORT = 3307
|
||||
DB_USER = "root"
|
||||
DB_PASS = "Vlado9674+"
|
||||
DB_NAME = "fio"
|
||||
|
||||
# Where to save Excel files
|
||||
OUTPUT_DIR = r"Z:\Dropbox\!!!Days\Downloads Z230"
|
||||
|
||||
# JSON file with list of accounts (name + account_number)
|
||||
ACCOUNTS_JSON = r"C:\Users\vlado\PycharmProjects\FIO\accounts.json"
|
||||
|
||||
# Columns that MUST be written as TEXT in Excel using ="value"
|
||||
# to avoid Excel stripping zeros or changing formatting
|
||||
TEXT_COLUMNS = ["cislo_uctu", "protiucet", "kod_banky", "ks", "vs", "ss"]
|
||||
|
||||
|
||||
# ======================================================
|
||||
# REMOVE OLD EXPORT FILES
|
||||
# ======================================================
|
||||
|
||||
def cleanup_old_exports():
|
||||
"""
|
||||
Deletes older versions of exported XLSX files that match
|
||||
specific filename patterns. This keeps your folder clean,
|
||||
ensuring you only have the most recent export.
|
||||
"""
|
||||
patterns = [
|
||||
os.path.join(OUTPUT_DIR, "*FIO*transaction*.xlsx"),
|
||||
os.path.join(OUTPUT_DIR, "*FIO*transactions*.xlsx"),
|
||||
os.path.join(OUTPUT_DIR, "*FIO_transactions*.xlsx"),
|
||||
]
|
||||
|
||||
# Check each pattern
|
||||
for pattern in patterns:
|
||||
for file in glob.glob(pattern):
|
||||
try:
|
||||
os.remove(file)
|
||||
print(f"🗑 Deleted old export: {file}")
|
||||
except:
|
||||
# If file cannot be deleted (locked or permission denied),
|
||||
# simply skip it.
|
||||
pass
|
||||
|
||||
|
||||
# ======================================================
|
||||
# CORE EXCEL FORMATTING FUNCTION
|
||||
# ======================================================
|
||||
|
||||
def format_sheet(ws, rows, headers):
|
||||
"""
|
||||
Applies ALL formatting rules to a worksheet:
|
||||
- Writes headers
|
||||
- Writes all rows
|
||||
- Converts selected columns to Excel text formulas
|
||||
- Colors rows based on "objem" (red=negative, green=positive)
|
||||
- Sets fixed column widths
|
||||
- Adds borders to every cell
|
||||
- Center-aligns first 10 columns
|
||||
- Freezes header row and enables filtering
|
||||
"""
|
||||
|
||||
# -------------------------------
|
||||
# 1) Format HEADER row
|
||||
# -------------------------------
|
||||
for col_idx in range(1, len(headers) + 1):
|
||||
cell = ws.cell(row=1, column=col_idx)
|
||||
cell.font = Font(bold=True) # bold text
|
||||
cell.fill = PatternFill(start_color="FFFF00", fill_type="solid") # yellow background
|
||||
|
||||
# -------------------------------
|
||||
# 2) Write DATA rows
|
||||
# -------------------------------
|
||||
for row in rows:
|
||||
excel_row = []
|
||||
for h in headers:
|
||||
val = row[h]
|
||||
|
||||
# For text-sensitive columns, write ="value"
|
||||
# This prevents Excel from stripping zeros or treating them as numbers.
|
||||
if h in TEXT_COLUMNS and val is not None:
|
||||
excel_row.append(f'="{val}"')
|
||||
else:
|
||||
excel_row.append(val)
|
||||
|
||||
ws.append(excel_row)
|
||||
|
||||
# -------------------------------
|
||||
# 3) Background coloring by "objem"
|
||||
# -------------------------------
|
||||
# Light red (ARGB) = negative
|
||||
fill_red = PatternFill(start_color="FFFFDDDD", end_color="FFFFDDDD", fill_type="solid")
|
||||
# Light green (ARGB) = positive or zero
|
||||
fill_green = PatternFill(start_color="FFEEFFEE", end_color="FFEEFFEE", fill_type="solid")
|
||||
|
||||
# Find column index where "objem" is located
|
||||
objem_col_index = headers.index("objem") + 1
|
||||
|
||||
# Apply row coloring
|
||||
for row_idx in range(2, len(rows) + 2): # Start at row 2 (row 1 = header)
|
||||
cell_objem = ws.cell(row=row_idx, column=objem_col_index)
|
||||
|
||||
# Convert objem to float
|
||||
try:
|
||||
value = float(cell_objem.value)
|
||||
except:
|
||||
value = 0
|
||||
|
||||
# Choose correct color
|
||||
fill = fill_red if value < 0 else fill_green
|
||||
|
||||
# Apply fill to entire row
|
||||
for col_idx in range(1, len(headers) + 1):
|
||||
ws.cell(row=row_idx, column=col_idx).fill = fill
|
||||
|
||||
# -------------------------------
|
||||
# 4) Fixed column widths
|
||||
# -------------------------------
|
||||
fixed_widths = [
|
||||
6, 11, 11, 5, 14, 14, 8, 6, 13, 13,
|
||||
50, 53, 12, 12, 5, 49, 29, 5, 29, 16,
|
||||
15, 12, 49, 20
|
||||
]
|
||||
|
||||
# Apply width using A, B, C... column names
|
||||
for i, width in enumerate(fixed_widths, start=1):
|
||||
col_letter = chr(64 + i) # convert 1 → 'A', 2 → 'B', ...
|
||||
ws.column_dimensions[col_letter].width = width
|
||||
|
||||
# -------------------------------
|
||||
# 5) Add borders + alignment
|
||||
# -------------------------------
|
||||
thin = Side(border_style="thin", color="000000")
|
||||
border = Border(left=thin, right=thin, top=thin, bottom=thin)
|
||||
align_center = Alignment(horizontal="center")
|
||||
|
||||
total_rows = len(rows) + 1
|
||||
total_cols = len(headers)
|
||||
|
||||
for row_idx in range(1, total_rows + 1):
|
||||
for col_idx in range(1, total_cols + 1):
|
||||
cell = ws.cell(row=row_idx, column=col_idx)
|
||||
cell.border = border # add border
|
||||
|
||||
# Center-align ONLY first 10 columns
|
||||
if col_idx <= 10:
|
||||
cell.alignment = align_center
|
||||
|
||||
# Freeze header row so it stays visible while scrolling
|
||||
ws.freeze_panes = "A2"
|
||||
|
||||
# Enable auto filter on top row
|
||||
ws.auto_filter.ref = ws.dimensions
|
||||
|
||||
|
||||
# ======================================================
|
||||
# MAIN EXPORT PROCESS
|
||||
# ======================================================
|
||||
|
||||
def export_fio():
|
||||
print("Connecting to MySQL...")
|
||||
|
||||
# Connect to MySQL database
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=DB_HOST,
|
||||
port=DB_PORT,
|
||||
user=DB_USER,
|
||||
password=DB_PASS,
|
||||
database=DB_NAME
|
||||
)
|
||||
except Error as e:
|
||||
print("❌ Failed to connect:", e)
|
||||
return
|
||||
|
||||
cur = conn.cursor(dictionary=True)
|
||||
|
||||
# -------------------------------
|
||||
# Load accounts.json
|
||||
# -------------------------------
|
||||
with open(ACCOUNTS_JSON, "r", encoding="utf-8") as f:
|
||||
accounts = json.load(f)
|
||||
|
||||
# -------------------------------
|
||||
# Define priority first sheets
|
||||
# -------------------------------
|
||||
preferred_order = [
|
||||
"CZK rodina",
|
||||
"CZK ordinace",
|
||||
"CZK na jídlo",
|
||||
"CZK TrialHelp",
|
||||
"CZK maminka svojě věci"
|
||||
]
|
||||
|
||||
accounts_sorted = []
|
||||
|
||||
# Step 1: add priority accounts first
|
||||
for pref in preferred_order:
|
||||
for acc in accounts:
|
||||
if acc["name"] == pref:
|
||||
accounts_sorted.append(acc)
|
||||
|
||||
# Step 2: add remaining accounts afterward
|
||||
for acc in accounts:
|
||||
if acc not in accounts_sorted:
|
||||
accounts_sorted.append(acc)
|
||||
|
||||
# -------------------------------
|
||||
# Create a new Excel workbook
|
||||
# -------------------------------
|
||||
wb = Workbook()
|
||||
wb.remove(wb.active) # remove default empty sheet
|
||||
|
||||
# -------------------------------
|
||||
# FIRST SHEET: ALL TRANSACTIONS
|
||||
# -------------------------------
|
||||
cur.execute("SELECT * FROM transactions ORDER BY datum DESC")
|
||||
all_rows = cur.fetchall()
|
||||
|
||||
if all_rows:
|
||||
headers = list(all_rows[0].keys())
|
||||
ws_all = wb.create_sheet(title="ALL")
|
||||
ws_all.append(headers)
|
||||
format_sheet(ws_all, all_rows, headers)
|
||||
|
||||
# -------------------------------
|
||||
# INDIVIDUAL SHEETS PER ACCOUNT
|
||||
# -------------------------------
|
||||
for acc in accounts_sorted:
|
||||
acc_num = acc["account_number"]
|
||||
sheet_name = acc["name"][:31] # Excel sheet name limit
|
||||
|
||||
print(f"➡ Creating sheet: {sheet_name}")
|
||||
|
||||
query = f"""
|
||||
SELECT *
|
||||
FROM transactions
|
||||
WHERE cislo_uctu = '{acc_num}'
|
||||
ORDER BY datum DESC
|
||||
"""
|
||||
|
||||
cur.execute(query)
|
||||
rows = cur.fetchall()
|
||||
|
||||
if not rows:
|
||||
print(f"⚠ No data for {sheet_name}")
|
||||
continue
|
||||
|
||||
headers = list(rows[0].keys())
|
||||
ws = wb.create_sheet(title=sheet_name)
|
||||
ws.append(headers)
|
||||
|
||||
format_sheet(ws, rows, headers)
|
||||
|
||||
conn.close()
|
||||
|
||||
# -------------------------------
|
||||
# Save Excel file
|
||||
# -------------------------------
|
||||
|
||||
cleanup_old_exports()
|
||||
|
||||
# File name includes timestamp
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
filename = f"{timestamp} FIO transactions.xlsx"
|
||||
output_file = os.path.join(OUTPUT_DIR, filename)
|
||||
|
||||
wb.save(output_file)
|
||||
|
||||
print(f"✅ Export complete:\n{output_file}")
|
||||
|
||||
|
||||
# ======================================================
|
||||
# MAIN ENTRY POINT
|
||||
# ======================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
export_fio()
|
||||
Reference in New Issue
Block a user