Spaces:
Sleeping
Sleeping
File size: 27,945 Bytes
2f7e39e 402feb9 2f7e39e cb24c49 bdba181 402feb9 bdba181 cb24c49 bdba181 402feb9 bdba181 0c7025c bdba181 0c7025c bdba181 0c7025c bdba181 0c7025c bdba181 0c7025c bdba181 cb24c49 402feb9 cb24c49 402feb9 bdba181 cb24c49 0c7025c cb24c49 bdba181 cb24c49 bdba181 0c7025c cb24c49 bdba181 0c7025c cb24c49 0c7025c cb24c49 0c7025c cb24c49 0c7025c cb24c49 0c7025c bdba181 0c7025c cb24c49 0c7025c bdba181 0c7025c bdba181 402feb9 0c7025c 402feb9 0c7025c 402feb9 0c7025c 402feb9 0c7025c 402feb9 0c7025c 402feb9 0c7025c cb24c49 402feb9 cb24c49 bdba181 cb24c49 bdba181 cb24c49 bdba181 cb24c49 bdba181 cb24c49 bdba181 402feb9 bdba181 cb24c49 bdba181 402feb9 cb24c49 bdba181 cb24c49 402feb9 cb24c49 bdba181 402feb9 cb24c49 0c7025c |
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 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 |
import subprocess
import sys
# Install openpyxl if not available
try:
import openpyxl
except ImportError:
subprocess.check_call([sys.executable, "-m", "pip", "install", "openpyxl"])
import openpyxl
# Install additional dependencies for PDF generation
try:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import inch
from reportlab.lib.colors import HexColor
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
except ImportError:
subprocess.check_call([sys.executable, "-m", "pip", "install", "reportlab"])
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import inch
from reportlab.lib.colors import HexColor
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
import gradio as gr
import pandas as pd
import tempfile
from datetime import datetime, timedelta
import openpyxl
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
from openpyxl.drawing.image import Image as ExcelImage
import random
import base64
from io import BytesIO
import os
import zipfile
def generate_random_invoice_number():
"""Generate random 6-digit invoice number"""
return str(random.randint(100000, 999999))
def create_professional_individual_invoice(workbook, employee_data, start_date, end_date):
"""Create individual invoice sheet with professional Glimmr template formatting and logo"""
# --- PASTE YOUR CLEANED BASE64 STRING HERE ---
GLIMMR_LOGO_BASE64 = """
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
"""
ws = workbook.create_sheet(title=employee_data['Row Labels'])
column_widths = {'A': 25, 'B': 15, 'C': 15, 'D': 15, 'E': 15, 'F': 12, 'G': 12, 'H': 12, 'I': 12}
for col, width in column_widths.items():
ws.column_dimensions[col].width = width
try:
if GLIMMR_LOGO_BASE64.strip() and "PASTE" not in GLIMMR_LOGO_BASE64 and "YOUR BASE64" not in GLIMMR_LOGO_BASE64:
logo_data = base64.b64decode(GLIMMR_LOGO_BASE64.strip())
logo_stream = BytesIO(logo_data)
img = ExcelImage(logo_stream)
img.width = 200
img.height = 100
ws.add_image(img, 'A1')
except Exception as e:
print(f"Logo embedding failed: {e}")
# Company name
ws['A6'] = 'Glimmr Ltd'
ws['A6'].font = Font(bold=True, size=16)
# --- CORRECTED HEADER STRUCTURE TO MATCH ORIGINAL ---
# Main header spanning wider (extended one cell left)
ws.merge_cells('E2:I2')
ws['E2'] = 'CLEANER INVOICE'
ws['E2'].font = Font(bold=True, size=14, color='FFFFFF')
ws['E2'].alignment = Alignment(horizontal='center', vertical='center')
ws['E2'].fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
# Invoice # header (left side)
ws.merge_cells('E6:F6')
ws['E6'] = 'Invoice #'
ws['E6'].font = Font(bold=True, color='FFFFFF')
ws['E6'].fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
ws['E6'].alignment = Alignment(horizontal='center', vertical='center')
# Date header (right side)
ws.merge_cells('G6:I6')
ws['G6'] = 'Date'
ws['G6'].font = Font(bold=True, color='FFFFFF')
ws['G6'].fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
ws['G6'].alignment = Alignment(horizontal='center', vertical='center')
# Invoice # value (below header)
ws.merge_cells('E7:F7')
ws['E7'] = generate_random_invoice_number()
ws['E7'].alignment = Alignment(horizontal='center', vertical='center')
# Date value (below header)
ws.merge_cells('G7:I7')
ws['G7'] = datetime.now().strftime('%d/%m/%y')
ws['G7'].alignment = Alignment(horizontal='center', vertical='center')
# Company address
ws['A8'] = '108 Westbourne Terrace'
ws['A9'] = 'London'
ws['A10'] = 'W2 6QJ'
ws['A11'] = 'support@glimmr.co.uk'
ws['A12'] = '020 8158 8505'
# Payee information
ws['A14'] = "Payee's Name"
ws['A14'].font = Font(bold=True)
ws['A15'] = employee_data['Row Labels']
ws['A15'].font = Font(size=11)
# Payment period header
ws.merge_cells('A20:D20')
ws['A20'] = 'Payment Period'
ws['A20'].font = Font(bold=True, color='FFFFFF')
ws['A20'].fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
ws['A20'].alignment = Alignment(horizontal='center')
# Table headers - adjusted positions
ws['E20'] = 'Hours'
ws['F20'] = 'Β£ / Hour'
ws.merge_cells('G20:I20')
ws['G20'] = 'Amount'
for cell in ['E20', 'F20', 'G20']:
ws[cell].font = Font(bold=True, color='FFFFFF')
ws[cell].fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
ws[cell].alignment = Alignment(horizontal='center')
# Payment period data with center alignment
ws.merge_cells('A21:D21')
ws['A21'] = f"{start_date} to {end_date}"
ws['A21'].alignment = Alignment(horizontal='center')
ws['E21'] = employee_data['Sum of Hours Worked']
ws['F21'] = f"Β£ {employee_data['Average of Hourly Rate']:.2f}"
ws.merge_cells('G21:I21')
ws['G21'] = f"Β£ {employee_data['Sum of Total']:.2f}"
# Total sections - adjusted positions
ws.merge_cells('F24:G24')
ws['F24'] = 'Total Labor'
ws['F24'].font = Font(bold=True)
ws.merge_cells('H24:I24')
ws['H24'] = f"Β£ {employee_data['Sum of Total']:.2f}"
ws.merge_cells('F26:G26')
ws['F26'] = 'Tax (Withholding)'
# Payment due date and total
due_date = (datetime.strptime(end_date, '%Y-%m-%d') + timedelta(days=1)).strftime('%d/%m/%Y')
ws['A27'] = f'Payment is due on {due_date}'
ws.merge_cells('F27:G27')
ws['F27'] = 'TOTAL'
ws['F27'].font = Font(bold=True)
ws.merge_cells('H27:I27')
ws['H27'] = f"Β£ {employee_data['Sum of Total']:.2f}"
ws['H27'].font = Font(bold=True)
# Comments section
ws['A28'] = 'Comments or Special Instructions:'
ws['A28'].font = Font(bold=True)
ws['A29'] = 'Payments to be made every other Saturday (or as close to these days).'
ws['A30'] = 'Please let us know immediately if there are any issues.'
# Apply borders - updated ranges
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin'))
# Border for payment period table
for row in range(20, 22):
for col in range(1, 10):
ws.cell(row=row, column=col).border = thin_border
# Border for header section
for row in range(6, 8):
for col_letter in ['E', 'F', 'G', 'H', 'I']:
ws[f'{col_letter}{row}'].border = thin_border
def create_individual_pdf_invoice(employee_data, start_date, end_date, output_dir):
"""Create individual PDF invoice for each cleaner"""
GLIMMR_LOGO_BASE64 = """
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
"""
clean_name = "".join(c for c in employee_data['Row Labels'] if c.isalnum() or c in (' ', '-', '_')).rstrip()
clean_name = clean_name.replace(' ', '_')
start_dt = datetime.strptime(start_date, '%Y-%m-%d')
end_dt = datetime.strptime(end_date, '%Y-%m-%d')
date_period = f"{start_dt.strftime('%d%b%Y')}-{end_dt.strftime('%d%b%Y')}"
filename = f"{clean_name}_{date_period}_Glimmr_Invoice.pdf"
filepath = os.path.join(output_dir, filename)
doc = SimpleDocTemplate(filepath, pagesize=A4, rightMargin=72, leftMargin=72, topMargin=72, bottomMargin=18)
story = []
try:
if GLIMMR_LOGO_BASE64.strip():
logo_data = base64.b64decode(GLIMMR_LOGO_BASE64.strip())
logo_stream = BytesIO(logo_data)
logo_img = Image(logo_stream)
logo_img.drawWidth = 2*inch
logo_img.drawHeight = 1*inch
story.append(logo_img)
story.append(Spacer(1, 12))
except Exception as e:
print(f"PDF Logo embedding failed: {e}")
styles = getSampleStyleSheet()
title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], fontSize=18, spaceAfter=30, textColor=HexColor('#4472C4'), alignment=1)
header_style = ParagraphStyle('CustomHeader', parent=styles['Heading2'], fontSize=14, spaceAfter=12, textColor=HexColor('#4472C4'))
normal_style = styles['Normal']
story.append(Paragraph('<b>Glimmr Ltd</b>', title_style))
address_text = "108 Westbourne Terrace<br/>London<br/>W2 6QJ<br/>support@glimmr.co.uk<br/>020 8158 8505"
story.append(Paragraph(address_text, normal_style))
story.append(Spacer(1, 20))
invoice_number = generate_random_invoice_number()
current_date = datetime.now().strftime('%d/%m/%Y')
invoice_header_data = [['CLEANER INVOICE', ''], ['Invoice #: ' + invoice_number, 'Date: ' + current_date]]
invoice_header_table = Table(invoice_header_data, colWidths=[3*inch, 3*inch])
invoice_header_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), HexColor('#4472C4')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 14),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 1), (-1, -1), 12),
('BOTTOMPADDING', (0, 0), (-1, -1), 12),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
story.append(invoice_header_table)
story.append(Spacer(1, 20))
story.append(Paragraph('<b>Payee\'s Name:</b>', header_style))
story.append(Paragraph(employee_data['Row Labels'], normal_style))
story.append(Spacer(1, 20))
due_date = (datetime.strptime(end_date, '%Y-%m-%d') + timedelta(days=1)).strftime('%d/%m/%Y')
payment_data = [
['Payment Period', 'Hours', 'Β£ / Hour', 'Amount'],
[f"{start_date} to {end_date}",
str(employee_data['Sum of Hours Worked']),
f"Β£ {employee_data['Average of Hourly Rate']:.2f}",
f"Β£ {employee_data['Sum of Total']:.2f}"]
]
payment_table = Table(payment_data, colWidths=[2*inch, 1*inch, 1*inch, 1.5*inch])
payment_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), HexColor('#4472C4')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
story.append(payment_table)
story.append(Spacer(1, 20))
total_data = [
['Total Labor', f"Β£ {employee_data['Sum of Total']:.2f}"],
['Tax (Withholding)', 'Β£ 0.00'],
['TOTAL', f"Β£ {employee_data['Sum of Total']:.2f}"]
]
total_table = Table(total_data, colWidths=[3*inch, 2*inch])
total_table.setStyle(TableStyle([
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 1), 'Helvetica'),
('FONTNAME', (0, 2), (-1, 2), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
('GRID', (0, 0), (-1, -1), 1, colors.black),
('BACKGROUND', (0, 2), (-1, 2), colors.lightgrey)
]))
story.append(total_table)
story.append(Spacer(1, 20))
story.append(Paragraph(f'<b>Payment is due on {due_date}</b>', normal_style))
story.append(Spacer(1, 20))
story.append(Paragraph('<b>Comments or Special Instructions:</b>', header_style))
story.append(Paragraph('Payments to be made every other Saturday (or as close to these days).', normal_style))
story.append(Paragraph('Please let us know immediately if there are any issues.', normal_style))
doc.build(story)
return filepath
def create_invoices_from_template_with_logo(report_data, start_date, end_date):
wb = openpyxl.Workbook()
wb.remove(wb.active)
# Create Payments sheet
payments_ws = wb.create_sheet(title="Payments")
payments_ws['C1'] = 'DO NOT ALTER THIS TEMPLATE'
payments_ws['C1'].font = Font(bold=True)
headers = ['First Name', 'Last Name', 'Stripe Name', 'Reference', 'Amount (GBP)', 'Remarks']
for col, header in enumerate(headers, 1):
cell = payments_ws.cell(row=2, column=col, value=header)
cell.font = Font(bold=True)
for row_idx, employee in enumerate(report_data[report_data['Row Labels'] != 'Grand Total'].itertuples(), 3):
name_parts = employee[1].split(' ', 1)
first_name = name_parts[0] if name_parts else ''
last_name = name_parts[1] if len(name_parts) > 1 else ''
payments_ws.cell(row=row_idx, column=1, value=first_name)
payments_ws.cell(row=row_idx, column=2, value=last_name)
payments_ws.cell(row=row_idx, column=3, value=employee[1])
payments_ws.cell(row=row_idx, column=4, value='')
payments_ws.cell(row=row_idx, column=5, value=employee[4]) # Sum of Total
payments_ws.cell(row=row_idx, column=6, value='')
# Create Pivot sheet
pivot_ws = wb.create_sheet(title="Pivot")
pivot_ws['A1'] = f"{start_date} to {end_date}"
pivot_ws['A1'].font = Font(bold=True)
due_date = (datetime.strptime(end_date, '%Y-%m-%d') + timedelta(days=1)).strftime('%d/%m/%Y')
pivot_ws['A2'] = f"Payment is due on {due_date}"
# MODIFIED: Updated header name as requested
pivot_headers = ['Name', 'Fixed Hourly Rate(without TIP)', 'Average of Hourly Rate', 'Sum of Hours Worked', 'Sum of Total']
for col, header in enumerate(pivot_headers, 1):
cell = pivot_ws.cell(row=3, column=col, value=header)
cell.font = Font(bold=True)
for row_idx, employee in enumerate(report_data.itertuples(index=False), 4):
# The tuple index corresponds to the column order in the final_invoice DataFrame
# [0]: Row Labels, [1]: Avg Rate, [2]: Sum Hours, [3]: Sum Total, [4]: Fixed Rate
pivot_ws.cell(row=row_idx, column=1, value=employee[0]) # Name
pivot_ws.cell(row=row_idx, column=2, value=employee[4]) # Fixed Hourly Rate(without TIP)
pivot_ws.cell(row=row_idx, column=3, value=employee[1]) # Average of Hourly Rate
pivot_ws.cell(row=row_idx, column=4, value=employee[2]) # Sum of Hours Worked
pivot_ws.cell(row=row_idx, column=5, value=employee[3]) # Sum of Total
# Create individual invoice sheets
individual_employees = report_data[report_data['Row Labels'] != 'Grand Total']
for _, employee in individual_employees.iterrows():
create_professional_individual_invoice(wb, employee, start_date, end_date)
return wb
def process_invoice_files_with_professional_excel(file1, file2, file3, bookings_file, start_date, end_date):
try:
employee_files = [f for f in [file1, file2, file3] if f is not None]
if not employee_files:
return None, None, "β **Error:** At least one Employee CSV must be uploaded."
if bookings_file is None:
return None, None, "β **Error:** Bookings CSV is required."
employee_dfs = [pd.read_csv(ef.name) for ef in employee_files]
all_employee_data = pd.concat(employee_dfs, ignore_index=True)
# Add the new Fixed Hour Rate column
all_employee_data['Fixed Hour Rate'] = all_employee_data['Hourly Rate']
pdf_bookings = pd.read_csv(bookings_file.name)
all_employee_data['Date'] = pd.to_datetime(all_employee_data['Date'], dayfirst=True, errors='coerce')
pdf_bookings['Date'] = pd.to_datetime(pdf_bookings['Date'], dayfirst=True, errors='coerce')
start_date_dt = pd.to_datetime(start_date)
end_date_dt = pd.to_datetime(end_date)
filtered_data = all_employee_data[(all_employee_data['Date'] >= start_date_dt) & (all_employee_data['Date'] <= end_date_dt)].copy()
filtered_bookings = pdf_bookings[(pdf_bookings['Date'] >= start_date_dt) & (pdf_bookings['Date'] <= end_date_dt)].copy()
def extract_employee_name(team_string):
if pd.isna(team_string): return None
name = team_string.split(',')[0].strip()
return name.split('(')[0].strip()
filtered_bookings['Employee_Name'] = filtered_bookings['Teams Assigned (without IDs)'].apply(extract_employee_name)
tips_summary = filtered_bookings.groupby('Employee_Name')['Tip'].sum().reset_index()
tips_with_amount = tips_summary[tips_summary['Tip'] > 0]
if not tips_with_amount.empty:
# Add Fixed Hour Rate to tip rows as 0
tip_rows = [{'Name': row['Employee_Name'], 'Hourly Rate': 0, 'Hours Worked': 0, 'Total': row['Tip'], 'Fixed Hour Rate': 0} for _, row in tips_with_amount.iterrows()]
tip_df = pd.DataFrame(tip_rows)
final_data = pd.concat([filtered_data, tip_df], ignore_index=True)
else:
final_data = filtered_data.copy()
# Update aggregation to include Fixed Hour Rate
report = final_data.groupby('Name').agg({
'Hourly Rate': 'mean',
'Hours Worked': 'sum',
'Total': 'sum',
'Fixed Hour Rate': 'first' # Use 'first' to get the original rate
}).reset_index()
# MODIFIED: Update column renaming as requested
report = report.rename(columns={
'Name': 'Row Labels',
'Hourly Rate': 'Average of Hourly Rate',
'Hours Worked': 'Sum of Hours Worked',
'Total': 'Sum of Total',
'Fixed Hour Rate': 'Fixed Hourly Rate(without TIP)'
})
report['Average of Hourly Rate'] = report['Average of Hourly Rate'].round(8)
report['Sum of Hours Worked'] = report['Sum of Hours Worked'].round(2)
report['Sum of Total'] = report['Sum of Total'].round(2)
total_hours = report['Sum of Hours Worked'].sum()
total_sum = report['Sum of Total'].sum()
# MODIFIED: Update Grand Total DataFrame to use new column name
grand_total = pd.DataFrame({
'Row Labels': ['Grand Total'],
'Average of Hourly Rate': [total_sum / total_hours if total_hours > 0 else 0],
'Sum of Hours Worked': [total_hours],
'Sum of Total': [total_sum],
'Fixed Hourly Rate(without TIP)': [None]
})
final_invoice = pd.concat([report, grand_total], ignore_index=True)
# Reorder columns for the create_invoices function to process correctly
# The order here determines the order in `itertuples`
final_invoice = final_invoice[['Row Labels', 'Average of Hourly Rate', 'Sum of Hours Worked', 'Sum of Total', 'Fixed Hourly Rate(without TIP)']]
wb = create_invoices_from_template_with_logo(final_invoice, start_date, end_date)
with tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
wb.save(tmp.name)
temp_excel_file_path = tmp.name
# Create PDF files for individual cleaners
pdf_temp_dir = tempfile.mkdtemp()
individual_employees = final_invoice[final_invoice['Row Labels'] != 'Grand Total']
pdf_files = []
for _, employee in individual_employees.iterrows():
pdf_path = create_individual_pdf_invoice(employee, start_date, end_date, pdf_temp_dir)
pdf_files.append(pdf_path)
# Create a ZIP file containing all PDFs
zip_temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
with zipfile.ZipFile(zip_temp_file.name, 'w') as zipf:
for pdf_file in pdf_files:
zipf.write(pdf_file, os.path.basename(pdf_file))
individual_employees_count = len(final_invoice[final_invoice['Row Labels'] != 'Grand Total'])
summary_text = f"π **Professional Invoice Generated Successfully!**\n\n- β
**Individual Professional Invoices** for each employee ({individual_employees_count} Excel sheets)\n- β
**Individual PDF Invoices** for each cleaner ({individual_employees_count} PDFs)\n- β
**Payments Summary & Pivot Data** sheets included\n- β
**Glimmr Logo & Branding** applied\n\n**Summary for {start_date} to {end_date}:**\n- Total hours: {total_hours:.2f}\n- Total amount: Β£{total_sum:.2f}\n\n**Files Generated:**\n- Excel file with all data and individual sheets\n- ZIP file containing individual PDF invoices for each cleaner"
return temp_excel_file_path, zip_temp_file.name, summary_text
except Exception as e:
return None, None, f"β **An error occurred:**\n\n{str(e)}\n\nPlease check your input files and date formats (YYYY-MM-DD)."
with gr.Blocks(title="Complete Professional Invoice Generator", theme=gr.themes.Soft()) as interface:
gr.Markdown("# π§Ύ Complete Professional Invoice Generator for Launch27 Data")
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### π Upload CSV Files")
file1 = gr.File(label="Upload 1.csv (Optional Employee Data)", file_types=[".csv"])
file2 = gr.File(label="Upload 2.csv (Optional Employee Data)", file_types=[".csv"])
file3 = gr.File(label="Upload 3.csv (Optional Employee Data)", file_types=[".csv"])
bookings_file = gr.File(label="Upload Bookings CSV (Required)", file_types=[".csv"])
with gr.Column(scale=1):
gr.Markdown("### π
Select Date Range")
start_date = gr.Textbox(label="Start Date (YYYY-MM-DD)", value="2025-08-09")
end_date = gr.Textbox(label="End Date (YYYY-MM-DD)", value="2025-08-22")
process_btn = gr.Button("π Generate Professional Invoice Excel & PDFs", variant="primary", size="lg")
gr.Markdown("---")
gr.Markdown("### π Results")
with gr.Row():
summary_output = gr.Markdown(label="Summary")
with gr.Row():
download_excel_file = gr.File(label="π₯ Download Professional Invoice Excel (.xlsx)")
download_pdf_zip = gr.File(label="π₯ Download Individual PDF Invoices (.zip)")
process_btn.click(
fn=process_invoice_files_with_professional_excel,
inputs=[file1, file2, file3, bookings_file, start_date, end_date],
outputs=[download_excel_file, download_pdf_zip, summary_output]
)
with gr.Accordion("π How to Use", open=False):
gr.Markdown("""
1. **Upload Files**: Upload 1-3 employee CSVs and the required bookings CSV.
2. **Select Dates**: Enter the date range in YYYY-MM-DD format.
3. **Generate**: Click the button.
4. **Download**: Your complete Excel file and ZIP of individual PDF invoices will appear for download.
**New Features:**
- Individual PDF invoices for each cleaner with the naming convention: `Cleaner_Name_Dateperiod_Glimmr_Invoice.pdf`
- All PDF files are packaged in a convenient ZIP file for download
- Professional PDF formatting matching the Excel invoice design
""")
if __name__ == "__main__":
interface.launch(debug=True) |