Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -270,14 +270,14 @@ def create_invoices_from_template_with_logo(report_data, start_date, end_date):
|
|
| 270 |
cell = payments_ws.cell(row=2, column=col, value=header)
|
| 271 |
cell.font = Font(bold=True)
|
| 272 |
for row_idx, employee in enumerate(report_data[report_data['Row Labels'] != 'Grand Total'].itertuples(), 3):
|
| 273 |
-
name_parts = employee.
|
| 274 |
first_name = name_parts[0] if name_parts else ''
|
| 275 |
last_name = name_parts[1] if len(name_parts) > 1 else ''
|
| 276 |
payments_ws.cell(row=row_idx, column=1, value=first_name)
|
| 277 |
payments_ws.cell(row=row_idx, column=2, value=last_name)
|
| 278 |
-
payments_ws.cell(row=row_idx, column=3, value=employee
|
| 279 |
payments_ws.cell(row=row_idx, column=4, value='')
|
| 280 |
-
payments_ws.cell(row=row_idx, column=5, value=employee
|
| 281 |
payments_ws.cell(row=row_idx, column=6, value='')
|
| 282 |
# Create Pivot sheet
|
| 283 |
pivot_ws = wb.create_sheet(title="Pivot")
|
|
@@ -285,15 +285,19 @@ def create_invoices_from_template_with_logo(report_data, start_date, end_date):
|
|
| 285 |
pivot_ws['A1'].font = Font(bold=True)
|
| 286 |
due_date = (datetime.strptime(end_date, '%Y-%m-%d') + timedelta(days=1)).strftime('%d/%m/%Y')
|
| 287 |
pivot_ws['A2'] = f"Payment is due on {due_date}"
|
| 288 |
-
|
|
|
|
| 289 |
for col, header in enumerate(pivot_headers, 1):
|
| 290 |
cell = pivot_ws.cell(row=3, column=col, value=header)
|
| 291 |
cell.font = Font(bold=True)
|
| 292 |
-
for row_idx, employee in enumerate(report_data.itertuples(), 4):
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
pivot_ws.cell(row=row_idx, column=
|
| 296 |
-
pivot_ws.cell(row=row_idx, column=
|
|
|
|
|
|
|
|
|
|
| 297 |
# Create individual invoice sheets
|
| 298 |
individual_employees = report_data[report_data['Row Labels'] != 'Grand Total']
|
| 299 |
for _, employee in individual_employees.iterrows():
|
|
@@ -309,6 +313,9 @@ def process_invoice_files_with_professional_excel(file1, file2, file3, bookings_
|
|
| 309 |
return None, None, "β **Error:** Bookings CSV is required."
|
| 310 |
employee_dfs = [pd.read_csv(ef.name) for ef in employee_files]
|
| 311 |
all_employee_data = pd.concat(employee_dfs, ignore_index=True)
|
|
|
|
|
|
|
|
|
|
| 312 |
pdf_bookings = pd.read_csv(bookings_file.name)
|
| 313 |
all_employee_data['Date'] = pd.to_datetime(all_employee_data['Date'], dayfirst=True, errors='coerce')
|
| 314 |
pdf_bookings['Date'] = pd.to_datetime(pdf_bookings['Date'], dayfirst=True, errors='coerce')
|
|
@@ -316,47 +323,88 @@ def process_invoice_files_with_professional_excel(file1, file2, file3, bookings_
|
|
| 316 |
end_date_dt = pd.to_datetime(end_date)
|
| 317 |
filtered_data = all_employee_data[(all_employee_data['Date'] >= start_date_dt) & (all_employee_data['Date'] <= end_date_dt)].copy()
|
| 318 |
filtered_bookings = pdf_bookings[(pdf_bookings['Date'] >= start_date_dt) & (pdf_bookings['Date'] <= end_date_dt)].copy()
|
|
|
|
| 319 |
def extract_employee_name(team_string):
|
| 320 |
if pd.isna(team_string): return None
|
| 321 |
name = team_string.split(',')[0].strip()
|
| 322 |
return name.split('(')[0].strip()
|
|
|
|
| 323 |
filtered_bookings['Employee_Name'] = filtered_bookings['Teams Assigned (without IDs)'].apply(extract_employee_name)
|
| 324 |
tips_summary = filtered_bookings.groupby('Employee_Name')['Tip'].sum().reset_index()
|
| 325 |
tips_with_amount = tips_summary[tips_summary['Tip'] > 0]
|
|
|
|
| 326 |
if not tips_with_amount.empty:
|
| 327 |
-
|
|
|
|
| 328 |
tip_df = pd.DataFrame(tip_rows)
|
| 329 |
final_data = pd.concat([filtered_data, tip_df], ignore_index=True)
|
| 330 |
else:
|
| 331 |
final_data = filtered_data.copy()
|
| 332 |
-
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
report['Average of Hourly Rate'] = report['Average of Hourly Rate'].round(8)
|
| 335 |
report['Sum of Hours Worked'] = report['Sum of Hours Worked'].round(2)
|
| 336 |
report['Sum of Total'] = report['Sum of Total'].round(2)
|
|
|
|
| 337 |
total_hours = report['Sum of Hours Worked'].sum()
|
| 338 |
total_sum = report['Sum of Total'].sum()
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
final_invoice = pd.concat([report, grand_total], ignore_index=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
wb = create_invoices_from_template_with_logo(final_invoice, start_date, end_date)
|
|
|
|
| 342 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
|
| 343 |
wb.save(tmp.name)
|
| 344 |
temp_excel_file_path = tmp.name
|
|
|
|
| 345 |
# Create PDF files for individual cleaners
|
| 346 |
pdf_temp_dir = tempfile.mkdtemp()
|
| 347 |
-
individual_employees =
|
| 348 |
pdf_files = []
|
| 349 |
for _, employee in individual_employees.iterrows():
|
| 350 |
pdf_path = create_individual_pdf_invoice(employee, start_date, end_date, pdf_temp_dir)
|
| 351 |
pdf_files.append(pdf_path)
|
|
|
|
| 352 |
# Create a ZIP file containing all PDFs
|
| 353 |
zip_temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
|
| 354 |
with zipfile.ZipFile(zip_temp_file.name, 'w') as zipf:
|
| 355 |
for pdf_file in pdf_files:
|
| 356 |
zipf.write(pdf_file, os.path.basename(pdf_file))
|
| 357 |
-
|
|
|
|
| 358 |
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"
|
|
|
|
| 359 |
return temp_excel_file_path, zip_temp_file.name, summary_text
|
|
|
|
| 360 |
except Exception as e:
|
| 361 |
return None, None, f"β **An error occurred:**\n\n{str(e)}\n\nPlease check your input files and date formats (YYYY-MM-DD)."
|
| 362 |
|
|
@@ -400,4 +448,4 @@ with gr.Blocks(title="Complete Professional Invoice Generator", theme=gr.themes.
|
|
| 400 |
""")
|
| 401 |
|
| 402 |
if __name__ == "__main__":
|
| 403 |
-
interface.launch(debug=True)
|
|
|
|
| 270 |
cell = payments_ws.cell(row=2, column=col, value=header)
|
| 271 |
cell.font = Font(bold=True)
|
| 272 |
for row_idx, employee in enumerate(report_data[report_data['Row Labels'] != 'Grand Total'].itertuples(), 3):
|
| 273 |
+
name_parts = employee[1].split(' ', 1)
|
| 274 |
first_name = name_parts[0] if name_parts else ''
|
| 275 |
last_name = name_parts[1] if len(name_parts) > 1 else ''
|
| 276 |
payments_ws.cell(row=row_idx, column=1, value=first_name)
|
| 277 |
payments_ws.cell(row=row_idx, column=2, value=last_name)
|
| 278 |
+
payments_ws.cell(row=row_idx, column=3, value=employee[1])
|
| 279 |
payments_ws.cell(row=row_idx, column=4, value='')
|
| 280 |
+
payments_ws.cell(row=row_idx, column=5, value=employee[4]) # Sum of Total
|
| 281 |
payments_ws.cell(row=row_idx, column=6, value='')
|
| 282 |
# Create Pivot sheet
|
| 283 |
pivot_ws = wb.create_sheet(title="Pivot")
|
|
|
|
| 285 |
pivot_ws['A1'].font = Font(bold=True)
|
| 286 |
due_date = (datetime.strptime(end_date, '%Y-%m-%d') + timedelta(days=1)).strftime('%d/%m/%Y')
|
| 287 |
pivot_ws['A2'] = f"Payment is due on {due_date}"
|
| 288 |
+
# MODIFIED: Updated header name as requested
|
| 289 |
+
pivot_headers = ['Name', 'Fixed Hourly Rate(without TIP)', 'Average of Hourly Rate', 'Sum of Hours Worked', 'Sum of Total']
|
| 290 |
for col, header in enumerate(pivot_headers, 1):
|
| 291 |
cell = pivot_ws.cell(row=3, column=col, value=header)
|
| 292 |
cell.font = Font(bold=True)
|
| 293 |
+
for row_idx, employee in enumerate(report_data.itertuples(index=False), 4):
|
| 294 |
+
# The tuple index corresponds to the column order in the final_invoice DataFrame
|
| 295 |
+
# [0]: Row Labels, [1]: Avg Rate, [2]: Sum Hours, [3]: Sum Total, [4]: Fixed Rate
|
| 296 |
+
pivot_ws.cell(row=row_idx, column=1, value=employee[0]) # Name
|
| 297 |
+
pivot_ws.cell(row=row_idx, column=2, value=employee[4]) # Fixed Hourly Rate(without TIP)
|
| 298 |
+
pivot_ws.cell(row=row_idx, column=3, value=employee[1]) # Average of Hourly Rate
|
| 299 |
+
pivot_ws.cell(row=row_idx, column=4, value=employee[2]) # Sum of Hours Worked
|
| 300 |
+
pivot_ws.cell(row=row_idx, column=5, value=employee[3]) # Sum of Total
|
| 301 |
# Create individual invoice sheets
|
| 302 |
individual_employees = report_data[report_data['Row Labels'] != 'Grand Total']
|
| 303 |
for _, employee in individual_employees.iterrows():
|
|
|
|
| 313 |
return None, None, "β **Error:** Bookings CSV is required."
|
| 314 |
employee_dfs = [pd.read_csv(ef.name) for ef in employee_files]
|
| 315 |
all_employee_data = pd.concat(employee_dfs, ignore_index=True)
|
| 316 |
+
# Add the new Fixed Hour Rate column
|
| 317 |
+
all_employee_data['Fixed Hour Rate'] = all_employee_data['Hourly Rate']
|
| 318 |
+
|
| 319 |
pdf_bookings = pd.read_csv(bookings_file.name)
|
| 320 |
all_employee_data['Date'] = pd.to_datetime(all_employee_data['Date'], dayfirst=True, errors='coerce')
|
| 321 |
pdf_bookings['Date'] = pd.to_datetime(pdf_bookings['Date'], dayfirst=True, errors='coerce')
|
|
|
|
| 323 |
end_date_dt = pd.to_datetime(end_date)
|
| 324 |
filtered_data = all_employee_data[(all_employee_data['Date'] >= start_date_dt) & (all_employee_data['Date'] <= end_date_dt)].copy()
|
| 325 |
filtered_bookings = pdf_bookings[(pdf_bookings['Date'] >= start_date_dt) & (pdf_bookings['Date'] <= end_date_dt)].copy()
|
| 326 |
+
|
| 327 |
def extract_employee_name(team_string):
|
| 328 |
if pd.isna(team_string): return None
|
| 329 |
name = team_string.split(',')[0].strip()
|
| 330 |
return name.split('(')[0].strip()
|
| 331 |
+
|
| 332 |
filtered_bookings['Employee_Name'] = filtered_bookings['Teams Assigned (without IDs)'].apply(extract_employee_name)
|
| 333 |
tips_summary = filtered_bookings.groupby('Employee_Name')['Tip'].sum().reset_index()
|
| 334 |
tips_with_amount = tips_summary[tips_summary['Tip'] > 0]
|
| 335 |
+
|
| 336 |
if not tips_with_amount.empty:
|
| 337 |
+
# Add Fixed Hour Rate to tip rows as 0
|
| 338 |
+
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()]
|
| 339 |
tip_df = pd.DataFrame(tip_rows)
|
| 340 |
final_data = pd.concat([filtered_data, tip_df], ignore_index=True)
|
| 341 |
else:
|
| 342 |
final_data = filtered_data.copy()
|
| 343 |
+
|
| 344 |
+
# Update aggregation to include Fixed Hour Rate
|
| 345 |
+
report = final_data.groupby('Name').agg({
|
| 346 |
+
'Hourly Rate': 'mean',
|
| 347 |
+
'Hours Worked': 'sum',
|
| 348 |
+
'Total': 'sum',
|
| 349 |
+
'Fixed Hour Rate': 'first' # Use 'first' to get the original rate
|
| 350 |
+
}).reset_index()
|
| 351 |
+
|
| 352 |
+
# MODIFIED: Update column renaming as requested
|
| 353 |
+
report = report.rename(columns={
|
| 354 |
+
'Name': 'Row Labels',
|
| 355 |
+
'Hourly Rate': 'Average of Hourly Rate',
|
| 356 |
+
'Hours Worked': 'Sum of Hours Worked',
|
| 357 |
+
'Total': 'Sum of Total',
|
| 358 |
+
'Fixed Hour Rate': 'Fixed Hourly Rate(without TIP)'
|
| 359 |
+
})
|
| 360 |
+
|
| 361 |
report['Average of Hourly Rate'] = report['Average of Hourly Rate'].round(8)
|
| 362 |
report['Sum of Hours Worked'] = report['Sum of Hours Worked'].round(2)
|
| 363 |
report['Sum of Total'] = report['Sum of Total'].round(2)
|
| 364 |
+
|
| 365 |
total_hours = report['Sum of Hours Worked'].sum()
|
| 366 |
total_sum = report['Sum of Total'].sum()
|
| 367 |
+
|
| 368 |
+
# MODIFIED: Update Grand Total DataFrame to use new column name
|
| 369 |
+
grand_total = pd.DataFrame({
|
| 370 |
+
'Row Labels': ['Grand Total'],
|
| 371 |
+
'Average of Hourly Rate': [total_sum / total_hours if total_hours > 0 else 0],
|
| 372 |
+
'Sum of Hours Worked': [total_hours],
|
| 373 |
+
'Sum of Total': [total_sum],
|
| 374 |
+
'Fixed Hourly Rate(without TIP)': [None]
|
| 375 |
+
})
|
| 376 |
+
|
| 377 |
final_invoice = pd.concat([report, grand_total], ignore_index=True)
|
| 378 |
+
|
| 379 |
+
# Reorder columns for the create_invoices function to process correctly
|
| 380 |
+
# The order here determines the order in `itertuples`
|
| 381 |
+
final_invoice = final_invoice[['Row Labels', 'Average of Hourly Rate', 'Sum of Hours Worked', 'Sum of Total', 'Fixed Hourly Rate(without TIP)']]
|
| 382 |
+
|
| 383 |
wb = create_invoices_from_template_with_logo(final_invoice, start_date, end_date)
|
| 384 |
+
|
| 385 |
with tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx') as tmp:
|
| 386 |
wb.save(tmp.name)
|
| 387 |
temp_excel_file_path = tmp.name
|
| 388 |
+
|
| 389 |
# Create PDF files for individual cleaners
|
| 390 |
pdf_temp_dir = tempfile.mkdtemp()
|
| 391 |
+
individual_employees = final_invoice[final_invoice['Row Labels'] != 'Grand Total']
|
| 392 |
pdf_files = []
|
| 393 |
for _, employee in individual_employees.iterrows():
|
| 394 |
pdf_path = create_individual_pdf_invoice(employee, start_date, end_date, pdf_temp_dir)
|
| 395 |
pdf_files.append(pdf_path)
|
| 396 |
+
|
| 397 |
# Create a ZIP file containing all PDFs
|
| 398 |
zip_temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.zip')
|
| 399 |
with zipfile.ZipFile(zip_temp_file.name, 'w') as zipf:
|
| 400 |
for pdf_file in pdf_files:
|
| 401 |
zipf.write(pdf_file, os.path.basename(pdf_file))
|
| 402 |
+
|
| 403 |
+
individual_employees_count = len(final_invoice[final_invoice['Row Labels'] != 'Grand Total'])
|
| 404 |
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"
|
| 405 |
+
|
| 406 |
return temp_excel_file_path, zip_temp_file.name, summary_text
|
| 407 |
+
|
| 408 |
except Exception as e:
|
| 409 |
return None, None, f"β **An error occurred:**\n\n{str(e)}\n\nPlease check your input files and date formats (YYYY-MM-DD)."
|
| 410 |
|
|
|
|
| 448 |
""")
|
| 449 |
|
| 450 |
if __name__ == "__main__":
|
| 451 |
+
interface.launch(debug=True)
|