From a0c83caa1480e96ff82dc4f991b2a7409b75e995 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Mon, 6 Oct 2025 14:31:51 +0530 Subject: [PATCH] Added new malling list --- mailling/config.json | 15 +- mailling/dpr.html | 603 ++++++++++++++++++++++++++ mailling/monthly_attendance_report.py | 324 ++++++++++++++ mailling/project_statistics_report.py | 119 +++++ 4 files changed, 1057 insertions(+), 4 deletions(-) create mode 100644 mailling/dpr.html create mode 100644 mailling/monthly_attendance_report.py create mode 100644 mailling/project_statistics_report.py diff --git a/mailling/config.json b/mailling/config.json index fba66ac..4eb096e 100644 --- a/mailling/config.json +++ b/mailling/config.json @@ -3,17 +3,24 @@ "SMPTSERVER": "smtp.gmail.com", "PORT": 587, "SENDER_EMAIL": "marcoioitsoft@gmail.com", - "SENDER_PASSWORD": "qrtq wfuj hwpp fhqr" + "SENDER_PASSWORD": "qrtq wfuj hwpp fhqr", + "RECIPIENT_EMAILS": "ashutosh.nehete@marcoaiot.com,vikas@marcoaiot.com" }, "API": { - "BASE_URL": "http://localhost:5032/api", + "BASE_URL": "https://stageapi.marcoaiot.com/api", "USERNAME": "admin@marcoaiot.com", - "PASSWORD": "User@123" + "PASSWORD": "User@123", + "TENANTID": "b3466e83-7e11-464c-b93a-daf047838b26" + }, + "WEB":{ + "BASE_URL": "https://stageapp.marcoaiot.com" }, "MONGODB":{ "MONGO_CONNECTION_STRING": "mongodb://localhost:27017", "DATABASE_NAME": "MarcoBMS_Caches", "COLLECTION_NAME": "ProjectReportMail" + }, + "UNIQUE_IDENTIFIER":{ + "PROJECT_IDS":"2618eb89-2823-11f0-9d9e-bc241163f504" } } - diff --git a/mailling/dpr.html b/mailling/dpr.html new file mode 100644 index 0000000..5b6ffd0 --- /dev/null +++ b/mailling/dpr.html @@ -0,0 +1,603 @@ + + + + + + Daily Progress Report + + + + +
+ +
+

Daily Progress Report

+
+ Project:{{projectName}}
+ Date: {{date}} +
+
+ + +
+ * Project Status Reported - Generated at {{timeStamp}} UTC +
+ + +
+
+ + + +

TODAY'S ATTENDANCE

+
+ + +
+ +
+ +

{{todaysAttendances}}

/

{{totalEmployees}}

+
+
+
+ + +
+
+ Today's Attendance +
+
+ Total Employees +
+ +
+ +
+
+ +
+ + + +

DAILY TASKS COMPLETED

+
+ + +
+ +
+ +

{{totalCompletedTask}}

/

{{totalPlannedTask}}

+
+
+ +
+ + +
+
+ Completed Work +
+
+ Planned Work +
+
+
+
+

*Today's total work

+
+
+ + +
+ + + +

PROJECT COMPLETION STATUS

+
+ + +
+ +
+ +

{{totalCompletedWork}}

/

{{totalPlannedWork}}

+
+
+ +
+ + +
+
+ Completed Work +
+
+ Planned Work +
+
+
+
+

*Project's total work

+
+
+ +
+ + + +

Regularization Pending

+

{{regularizationPending}}

+ +

Checkout Pending

+

{{checkoutPending}}

+
+ +
+ + + +

Activity Report Pending

+
+ + +
+ + +

{{reportPending}}

/

{{todaysAssignTasks}}

+
+
+ + +
+
+ Total Pending Tasks +
+
+ Today's Assigned Tasks +
+
+
+
+ + {% if teamOnSite and teamOnSite|length > 0 %} +
+ +
+

Team Strength on Site

+
+ + {% for a in teamOnSite %} + + + + + {% endfor %} +
{{a.roleName}}{{a.numberofEmployees}}
+
+ {% endif %} + + + +
+
+ + +
+ You have received this email because it contains important information about your Marco PMS Account account. +
+ + + + \ No newline at end of file diff --git a/mailling/monthly_attendance_report.py b/mailling/monthly_attendance_report.py new file mode 100644 index 0000000..cd444cb --- /dev/null +++ b/mailling/monthly_attendance_report.py @@ -0,0 +1,324 @@ +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) diff --git a/mailling/project_statistics_report.py b/mailling/project_statistics_report.py new file mode 100644 index 0000000..62e39de --- /dev/null +++ b/mailling/project_statistics_report.py @@ -0,0 +1,119 @@ +import sys +import json +import smtplib +import datetime +import requests +from pathlib import Path +from numbers import Real +from email.message import EmailMessage +from jinja2 import Environment, FileSystemLoader, select_autoescape + +# 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) + +WEB_CONFIG = config.get("WEB", {}) +WEB_BASE_URL = WEB_CONFIG.get("BASE_URL") + +API_CONFIG = config.get("API", {}) +BASE_URL = API_CONFIG.get("BASE_URL") + +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") + +UNIQUE_IDENTIFIER_CONFIG = config.get("UNIQUE_IDENTIFIER", {}) +PROJECT_IDS = UNIQUE_IDENTIFIER_CONFIG.get("PROJECT_IDS") + + +def render_template_from_file(template_name,context): + base_dir = Path(__file__).parent + env = Environment( + loader=FileSystemLoader(searchpath=str(base_dir)), + autoescape=select_autoescape(["html", "xml"]) + ) + tmpl = env.get_template(template_name) + return tmpl.render(**context) + +def fetch_Project_report(project_id): + headers = {"Content-Type": "application/json"} + try: + response = requests.get(f"{BASE_URL}/market/get/project/report/{project_id}", headers=headers) + response.raise_for_status() + data = response.json()["data"] + print("Project report fetched successfully.") + return data + except Exception as e: + print(f"Select tenant error: {e}") + return None + +def get_percentage(part, whole, decimals: int = 2): + if not isinstance(part, Real) or not isinstance(whole, Real): + raise TypeError("part and whole must be numbers") + if whole == 0: + return 0.0 + return round((part / whole) * 100.0, decimals) + +if __name__ == "__main__": + template_name = "dpr.html" + project_ids = [p.strip() for p in PROJECT_IDS.split(",") if p.strip()] + + for project_id in project_ids: + + data = fetch_Project_report(project_id) + attendance_percentage = get_percentage(data["todaysAttendances"], data["totalEmployees"], 2) + task_percentage = get_percentage(data["totalCompletedTask"], data["totalPlannedTask"], 2) + web_url = f"{WEB_BASE_URL}/auth/login" + + context = { + "webUrl":web_url, + "date": data["date"], + "projectName": data["projectName"], + "timeStamp": data["timeStamp"], + "todaysAttendances": data["todaysAttendances"], + "totalEmployees": data["totalEmployees"], + "attendancePercentage":data["attendancePercentage"], + "taskPercentage":data["taskPercentage"], + "regularizationPending": data["regularizationPending"], + "checkoutPending": data["checkoutPending"], + "totalPlannedWork": data["totalPlannedWork"], + "totalCompletedWork": data["totalCompletedWork"], + "totalPlannedTask": data["totalPlannedTask"], + "totalCompletedTask": data["totalCompletedTask"], + "completionStatus": data["completionStatus"], + "reportPending": data["reportPending"], + "todaysAssignTasks": data["todaysAssignTasks"], + "teamOnSite": data["teamOnSite"], + "performedTasks": data["performedTasks"], + "performedAttendance": data["performedAttendance"] + } + + # print(context) + project_name = data["projectName"] + html = render_template_from_file(template_name,context) + + # print(html) + today = datetime.datetime.now() + formatted = today.strftime("%d-%b-%Y") + + msg = EmailMessage() + msg["Subject"] = f"DPR - {formatted} - {project_name}" + msg["From"] = SENDER_EMAIL + msg["To"] = RECIPIENT_EMAILS + msg.set_content("HTML version attached as alternative.") + msg.add_alternative(html, subtype="html") + + with smtplib.SMTP(SMPTSERVER, PORT) as smtp: + smtp.ehlo() + smtp.starttls() + smtp.ehlo() + smtp.login(SENDER_EMAIL, SENDER_PASSWORD) + smtp.send_message(msg)