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
+
+
+
+
+
+
+
+
+
+
+ * 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
+
+
+
+
+
+
+
+
+
+ ↗
+
+
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 %}
+
+ {{a.roleName}} |
+ {{a.numberofEmployees}} |
+
+ {% endfor %}
+
+
+ {% 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)