release-scripts/mailling/monthly_attendance_report.py

325 lines
13 KiB
Python

import json
import smtplib
import os
import datetime
import requests
import pandas as pd
from email.message import EmailMessage
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
# --- Config and color map ---
color_map = {
"all_ok": "4CAF50", #Green present
"act4_true": "009688", # teal Regularization is accepted
"act1_nullOut": "FFC107", #Amber Check out pending
"act2": "2196F3", # Bule Regularization pending
"act5": "B71C1C", #Dark Red Regularization rejected
"all_null": "F6635C", #Red Absent
"sunday":"FF0000"
}
# Add Legend sheet to the workbook
legend_data = {
"Color Description": [
"Present (All Ok)",
"Regularization Accepted",
"Check-out Pending",
"Regularization Pending",
"Regularization Rejected",
"Absent",
"Sundays"
],
"Hex Color": [
color_map["all_ok"],
color_map["act4_true"],
color_map["act1_nullOut"],
color_map["act2"],
color_map["act5"],
color_map["all_null"],
color_map["sunday"]
]
}
def login_api():
payload = {"username": API_USERNAME, "password": API_PASSWORD}
headers = {"Content-Type": "application/json"}
try:
response = requests.post(f"{BASE_URL}/auth/login", json=payload, headers=headers)
response.raise_for_status()
data = response.json()["data"]
jwt = data["token"]
print("API login successful.")
return jwt
except Exception as e:
print(f"Login API error: {e}")
return None
def select_tenant(jwt):
headers = {"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"}
try:
response = requests.post(f"{BASE_URL}/auth/select-tenant/{API_TENANT}", headers=headers)
response.raise_for_status()
data = response.json()["data"]
jwt = data["token"]
print("Tenant selected successful.")
return jwt
except Exception as e:
print(f"Select tenant error: {e}")
return None
def attendance_report(jwt,is_current_month):
headers = {"Authorization": f"Bearer {jwt}", "Content-Type": "application/json"}
response = requests.get(f"{BASE_URL}/report/report-attendance?isCurrentMonth={is_current_month}", headers=headers)
response.raise_for_status()
data = response.json()
projects = data.get('data', [])
# Get current date and time
today = datetime.datetime.now()
if not is_current_month:
first_day_current_month = today.replace(day=1)
last_month_last_day = first_day_current_month - datetime.timedelta(days=1)
month_name = last_month_last_day.strftime("%B")
year = last_month_last_day.year
else:
month_name = today.strftime("%B")
year = today.year
excel_file = f"{month_name}-{year}_attendance_report.xlsx"
writer = pd.ExcelWriter(excel_file, engine="openpyxl")
# Border style
thin = Side(border_style="thin", color="000000")
border = Border(left=thin, right=thin, top=thin, bottom=thin)
for proj_idx, project in enumerate(projects):
project_name = project.get('projectName', 'UnknownProject')[:31] # Sheet name max 31 chars
attendances = project.get('projectAttendance', [])
# Collect all unique dates in this project
all_dates = set()
for user in attendances:
for att in user.get('attendances', []):
all_dates.add(att.get('attendanceDate', '').split('T')[0])
all_dates = sorted(all_dates)
# Prepare rows
rows = []
for user in attendances:
row = {'Name': f"{user.get('firstName','')} {user.get('lastName','')}".strip()}
date_map = {a.get('attendanceDate', '').split('T')[0]: a for a in user.get('attendances', [])}
for date in all_dates:
att = date_map.get(date, {})
# Store also needed fields for coloring below
row[f"{date}_checkin"] = ''
row[f"{date}_checkout"] = ''
if att.get('checkIn'):
# Extract time part only, e.g. "09:30:00"
row[f"{date}_checkin"] = att.get('checkIn').split('T')[1]
if att.get('checkOut'):
# Extract time part only
row[f"{date}_checkout"] = att.get('checkOut').split('T')[1]
row[f"{date}_activity"] = att.get('activity', None)
row[f"{date}_isapproved"] = att.get('isApproved', None)
row['CheckInCheckOutDone'] = user.get('checkInCheckOutDone', 0)
row['CheckOutPending'] = user.get('checkOutPending', 0)
row['CheckInDone'] = user.get('checkInDone', 0)
row['AbsentAttendance'] = user.get('absentAttendance', 0)
row['RejectedRegularize'] = user.get('rejectedRegularize', 0)
rows.append(row)
legend_df = pd.DataFrame(legend_data)
# Use the same writer to add sheet after all sheets written
legend_df.to_excel(writer, sheet_name="Legend", index=False)
# Access Legend sheet for coloring the color sample cells
ws_legend = writer.book["Legend"]
# Assuming data starts from row 2 (header in row 1)
for row_idx in range(2, 2 + len(legend_data["Color Description"])):
# Color sample cell: B column (2nd col)
cell = ws_legend.cell(row=row_idx, column=2)
hex_color = cell.value
fill = PatternFill(start_color=hex_color, fill_type="solid")
cell.fill = fill
# Optional: bold and center description column
desc_cell = ws_legend.cell(row=row_idx, column=1)
desc_cell.font = Font(bold=True)
desc_cell.alignment = Alignment(horizontal="left", vertical="center")
# Optionally adjust headers and column width for visual clarity
for col in [1, 2]:
ws_legend.column_dimensions[get_column_letter(col)].width = 30
ws_legend["A1"].font = Font(bold=True)
ws_legend["B1"].font = Font(bold=True)
ws_legend["A1"].alignment = ws_legend["B1"].alignment = Alignment(horizontal="center")
# Columns: Name, checkin/checkout pairs for dates, and summary columns
columns = ['Name']
for date in all_dates:
columns.extend([f"{date}_checkin", f"{date}_checkout"])
columns.extend(['CheckInCheckOutDone', 'CheckInDone','CheckOutPending', 'AbsentAttendance', 'RejectedRegularize'])
df = pd.DataFrame(rows, columns=columns)
# Write to sheet
df.to_excel(writer, sheet_name=project_name, index=False, startrow=2)
# Post-process sheet using openpyxl
wb = writer.book
ws = wb[project_name]
# Merged header with date and subheaders
ws["A3"].value = "Name"
ws["A3"].font = Font(bold=True)
ws["A3"].alignment = Alignment(horizontal="center", vertical="center")
col = 2
for date in all_dates:
col_letter_1 = get_column_letter(col)
col_letter_2 = get_column_letter(col + 1)
ws.merge_cells(f"{col_letter_1}2:{col_letter_2}2")
ws[f"{col_letter_1}2"].value = pd.to_datetime(date).strftime("%d-%m-%Y")
ws[f"{col_letter_1}2"].font = Font(bold=True)
ws[f"{col_letter_1}2"].alignment = Alignment(horizontal="center", vertical="center")
ws[f"{col_letter_1}3"].value = "Check-in"
ws[f"{col_letter_2}3"].value = "Check-out"
ws[f"{col_letter_1}3"].alignment = ws[f"{col_letter_2}3"].alignment = Alignment(horizontal="center")
col += 2
# Add headers for summary columns
summary_headers = ['CheckIn-CheckOut Done', 'CheckIn Done', 'CheckOut Pending', 'Absent Attendance', 'Rejected Regularize']
summary_start_col = col
for i, header in enumerate(summary_headers):
col_letter = get_column_letter(summary_start_col + i)
ws.merge_cells(f"{col_letter}2:{col_letter}3")
ws[f"{col_letter}2"].value = header
ws[f"{col_letter}2"].font = Font(bold=True)
ws[f"{col_letter}2"].alignment = Alignment(horizontal="center", vertical="center")
# Apply borders and coloring
max_row = ws.max_row
max_col = ws.max_column
for row_idx in range(3, max_row + 1): # Starting from 3 to include header rows
for col_idx in range(1, max_col + 1):
cell = ws.cell(row=row_idx, column=col_idx)
cell.border = border
# Coloring according to rules for check-in/out pairs
for row_idx, user_row in enumerate(rows, start=4):
for date_idx, date in enumerate(all_dates):
checkin_col = 2 + date_idx * 2
checkout_col = checkin_col + 1
activity = user_row.get(f"{date}_activity")
isapproved = user_row.get(f"{date}_isapproved")
c_in = user_row.get(f"{date}_checkin")
c_out = user_row.get(f"{date}_checkout")
# --- Sunday RED coloring ---
dt = datetime.datetime.strptime(date, "%Y-%m-%d")
if dt.weekday() == 6: # Sunday
fill = PatternFill(start_color="FF0000", fill_type="solid")
ws.cell(row=row_idx, column=checkin_col).fill = fill
ws.cell(row=row_idx, column=checkout_col).fill = fill
continue # Skip further coloring for this date
fill_color = None
if not c_in and not c_out:
fill_color = color_map["all_null"]
elif c_in and c_out and activity == 4 and not bool(isapproved):
fill_color = color_map["all_ok"]
elif activity == 4 and bool(isapproved):
fill_color = color_map["act4_true"]
elif activity == 1 and not c_out:
fill_color = color_map["act1_nullOut"]
elif activity == 2:
fill_color = color_map["act2"]
elif activity == 5:
fill_color = color_map["act5"]
if fill_color:
fill = PatternFill(start_color=fill_color, fill_type="solid")
ws.cell(row=row_idx, column=checkin_col).fill = fill
ws.cell(row=row_idx, column=checkout_col).fill = fill
writer.close()
print(f"Excel '{excel_file}' generated with sheets per project, summary columns and conditional coloring.")
# --- 2. Compose the Email ----
msg = EmailMessage()
msg["Subject"] = f"Attendance Report for {month_name}-{year}"
msg["From"] = SENDER_EMAIL
msg["To"] = RECIPIENT_EMAILS
msg.set_content("Please find the attached Excel file.")
# Add the Excel attachment
with open(excel_file, "rb") as f:
file_data = f.read()
file_name = f.name
msg.add_attachment(file_data, maintype="application", subtype="vnd.openxmlformats-officedocument.spreadsheetml.sheet", filename=file_name)
# --- 3. Send the Email ----
with smtplib.SMTP(SMPTSERVER, PORT) as smtp:
smtp.ehlo()
smtp.starttls()
smtp.ehlo()
smtp.login(SENDER_EMAIL, SENDER_PASSWORD)
smtp.send_message(msg)
# After all operations (e.g., emailing) are done, delete the file
if os.path.exists(excel_file):
os.remove(excel_file)
print(f"Deleted file: {excel_file}")
else:
print("File not found, cannot delete.")
# --- Main execution logic ---
if __name__ == "__main__":
import sys
# Load your real config before calling any API
GLOBAL_CONFIG_PATH = "config.json"
try:
with open(GLOBAL_CONFIG_PATH, "r", encoding="utf-8") as f:
config = json.load(f)
except Exception as e:
print(f"Failed to load config: {e}")
sys.exit(1)
API_CONFIG = config.get("API", {})
BASE_URL = API_CONFIG.get("BASE_URL")
API_USERNAME = API_CONFIG.get("USERNAME")
API_PASSWORD = API_CONFIG.get("PASSWORD")
API_TENANT = API_CONFIG.get("TENANTID")
SMPT_CONFIG = config.get("SMPT", {})
SMPTSERVER = SMPT_CONFIG.get("SMPTSERVER")
PORT = SMPT_CONFIG.get("PORT")
SENDER_EMAIL = SMPT_CONFIG.get("SENDER_EMAIL")
SENDER_PASSWORD = SMPT_CONFIG.get("SENDER_PASSWORD")
RECIPIENT_EMAILS = SMPT_CONFIG.get("RECIPIENT_EMAILS")
token = login_api()
if not token:
print("Login failed, aborting.")
sys.exit(1)
jwt_token = select_tenant(token)
if not jwt_token:
print("Tenant selection failed, aborting.")
sys.exit(1)
attendance_report(jwt_token, False)