Files
Tracker/Code/Tools/Samples/generators/xlsx_invoice.py
T
2026-02-21 21:55:42 +00:00

112 lines
3.7 KiB
Python

"""Generate XLSX: Invoice/billing schedule."""
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
def generate(output_path: Path, rows: list, firm: dict):
"""Generate an invoice schedule Excel file."""
wb = Workbook()
ws = wb.active
ws.title = "Invoice Schedule"
# Styles
header_font = Font(name="Arial", size=12, bold=True, color="003366")
col_header_font = Font(name="Arial", size=10, bold=True, color="FFFFFF")
col_header_fill = PatternFill(
start_color="003366", end_color="003366", fill_type="solid"
)
data_font = Font(name="Arial", size=10)
currency_format = "#,##0"
thin_border = Border(
left=Side(style="thin"),
right=Side(style="thin"),
top=Side(style="thin"),
bottom=Side(style="thin"),
)
# Title
ws.merge_cells("A1:G1")
ws["A1"] = f"{firm['name']} - Invoice Schedule"
ws["A1"].font = header_font
ws["A1"].alignment = Alignment(horizontal="center")
ws.merge_cells("A2:G2")
ws["A2"] = firm["address"]
ws["A2"].font = Font(name="Arial", size=8, color="666666")
ws["A2"].alignment = Alignment(horizontal="center")
# Column headers
headers = [
"Client",
"Matter Ref",
"TM Number",
"Description",
"Amount (HKD)",
"Due Date",
"Status",
]
col_widths = [25, 15, 14, 40, 15, 14, 12]
for i, (header, width) in enumerate(zip(headers, col_widths), start=1):
cell = ws.cell(row=4, column=i, value=header)
cell.font = col_header_font
cell.fill = col_header_fill
cell.alignment = Alignment(horizontal="center")
cell.border = thin_border
ws.column_dimensions[get_column_letter(i)].width = width
# Data rows
for row_idx, row_data in enumerate(rows, start=5):
ws.cell(row=row_idx, column=1, value=row_data["client"]).font = data_font
ws.cell(row=row_idx, column=2, value=row_data["matter_ref"]).font = data_font
ws.cell(row=row_idx, column=3, value=row_data["tm_number"]).font = data_font
ws.cell(row=row_idx, column=4, value=row_data["description"]).font = data_font
amount_cell = ws.cell(row=row_idx, column=5, value=row_data["amount_hkd"])
amount_cell.font = data_font
amount_cell.number_format = currency_format
amount_cell.alignment = Alignment(horizontal="right")
ws.cell(row=row_idx, column=6, value=row_data["due_date"]).font = data_font
status_cell = ws.cell(row=row_idx, column=7, value=row_data["status"])
status_cell.font = data_font
status_cell.alignment = Alignment(horizontal="center")
# Color-code status
status_colors = {
"Draft": "FFF3CD",
"Sent": "D1ECF1",
"Overdue": "F8D7DA",
"Paid": "D4EDDA",
}
if row_data["status"] in status_colors:
status_cell.fill = PatternFill(
start_color=status_colors[row_data["status"]],
end_color=status_colors[row_data["status"]],
fill_type="solid",
)
# Apply borders
for col in range(1, 8):
ws.cell(row=row_idx, column=col).border = thin_border
# Total row
total_row = 5 + len(rows)
ws.cell(row=total_row, column=4, value="TOTAL").font = Font(
name="Arial", size=10, bold=True
)
total_cell = ws.cell(
row=total_row,
column=5,
value=sum(r["amount_hkd"] for r in rows),
)
total_cell.font = Font(name="Arial", size=10, bold=True)
total_cell.number_format = currency_format
total_cell.alignment = Alignment(horizontal="right")
wb.save(str(output_path))