Natwar commited on
Commit
0c7025c
Β·
verified Β·
1 Parent(s): 402feb9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -16
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._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)
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
- pivot_headers = ['Name', 'Average of Hourly Rate', 'Sum of Hours Worked', 'Sum of Total']
 
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
- pivot_ws.cell(row=row_idx, column=1, value=employee._1)
294
- pivot_ws.cell(row=row_idx, column=2, value=employee._2)
295
- pivot_ws.cell(row=row_idx, column=3, value=employee._3)
296
- pivot_ws.cell(row=row_idx, column=4, value=employee._4)
 
 
 
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
- tip_rows = [{'Name': row['Employee_Name'], 'Hourly Rate': 0, 'Hours Worked': 0, 'Total': row['Tip']} for _, row in tips_with_amount.iterrows()]
 
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
- report = final_data.groupby('Name').agg({'Hourly Rate': 'mean', 'Hours Worked': 'sum', 'Total': 'sum'}).reset_index()
333
- report = report.rename(columns={'Name': 'Row Labels', 'Hourly Rate': 'Average of Hourly Rate', 'Hours Worked': 'Sum of Hours Worked', 'Total': 'Sum of Total'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- 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]})
 
 
 
 
 
 
 
 
 
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 = report[report['Row Labels'] != 'Grand Total']
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
- individual_employees_count = len(report[report['Row Labels'] != 'Grand Total'])
 
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)