Added new malling list

This commit is contained in:
ashutosh.nehete 2025-10-06 14:31:51 +05:30
parent 2696003a09
commit a0c83caa14
4 changed files with 1057 additions and 4 deletions

View File

@ -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"
}
}

603
mailling/dpr.html Normal file
View File

@ -0,0 +1,603 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Daily Progress Report</title>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background: #f5f5f5;
color: #333;
}
.container {
max-width: 1100px;
margin: 20px auto;
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.header {
background: #49bf3c;
color: #fff;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.header h1 {
font-size: 22px;
margin: 0;
}
.header .project-info {
font-size: 14px;
text-align: right;
}
.status-note {
font-size: 12px;
color: #555;
padding: 15px 20px 0 20px;
}
.status-cards {
display: flex;
justify-content: space-between;
gap: 15px;
padding: 20px;
flex-wrap: wrap;
}
.card {
flex: 1;
min-width: 200px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
background: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
/* <-- added shadow */
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-top: 1px solid #e63946;
}
.card-link {
position: absolute;
top: 8px;
right: 8px;
width: 28px;
height: 28px;
display: none;
align-items: center;
justify-content: center;
border-radius: 6px;
text-decoration: none;
color: #49bf3c;
/* arrow color */
background: #f5f5f5;
/* badge background */
box-shadow: 0 1px 3px rgba(0, 0, 0, .15);
}
.card-link:hover {
background: #ececec;
}
.card-link:focus {
outline: 2px solid #9ca3af;
outline-offset: 2px;
}
.card-link .arrow {
font-size: 16px;
line-height: 1;
}
.card:hover .card-link {
display: inline-flex;
/* <— show only on this card */
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.card h3 {
font-size: 14px;
margin: 0 0 10px 0;
}
.card p {
margin: 5px 0;
}
.card .value {
font-size: 22px;
font-weight: bold;
}
.card-title {
font-size: 0.9rem;
text-transform: uppercase;
font-weight: 600;
color: #6c757d;
}
.attendance {
color: #49bf3c;
}
.tasks {
color: #007bff;
}
.completion {
color: #28a745;
}
.activities {
padding: 20px;
}
.activities h2 {
font-size: 18px;
margin-bottom: 10px;
}
.table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.table th,
.table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
.table th {
background: #f0f0f0;
}
.footer {
/* background: #b10000; */
background: #49bf3c;
color: #fff;
text-align: center;
padding: 15px;
font-size: 12px;
}
.footer a {
color: #fff;
margin: 0 8px;
text-decoration: none;
}
/* Responsive */
@media (max-width: 600px) {
.header {
flex-direction: column;
text-align: center;
}
.header .project-info {
text-align: center;
margin-top: 10px;
}
.status-cards {
flex-direction: column;
}
}
.legend {
margin-top: 10px;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
font-size: 12px;
color: #555;
}
.legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.legend-color {
width: 10px;
height: 10px;
border-radius: 2px;
display: inline-block;
}
.legend-red {
background: #49bf3c;
}
.legend-blue {
background: #007bff;
}
.legend-green {
background: #28a745;
}
.legend-gray {
background: #ccc;
}
.donut {
--percentage: 0;
/* Change this per chart */
--danger: #e63946;
--primary: #007bff;
--warning: #ffc107;
--success: #198754;
/* Fill color */
--track: #e9ecef;
/* Background track */
--size: 120px;
/* Default size */
--thickness: 20px;
/* Default thickness */
width: var(--size);
height: var(--size);
border-radius: 50%;
/* background: conic-gradient(var(--danger) calc(var(--percentage) * 1%),
var(--track) 0); */
background: conic-gradient(var(--track) 100%);
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
font-weight: bold;
color: #333;
}
.donut::before {
content: "";
position: absolute;
width: calc(var(--size) - var(--thickness));
height: calc(var(--size) - var(--thickness));
border-radius: 50%;
background: #fff;
/* Inner cut-out */
}
.donut span {
position: absolute;
font-size: calc(var(--size) / 6);
}
/* Variants */
.donut.thin {
--size: 80px;
--thickness: 12px;
}
.donut.medium {
--size: 120px;
--thickness: 25px;
}
.donut.large {
--size: 180px;
--thickness: 35px;
}
/* Color variants */
.donut-success {
background: conic-gradient(var(--success) calc(var(--percentage) * 1%),
var(--track) 0);
color: var(--success)
}
.donut-warning {
background: conic-gradient(var(--warning) calc(var(--percentage) * 1%),
var(--track) 0);
color: var(--warning)
}
.donut-danger {
background: conic-gradient(var(--danger) calc(var(--percentage) * 1%),
var(--track) 0);
color: var(--danger)
}
.donut-primary {
background: conic-gradient(var(--primary) calc(var(--percentage) * 1%),
var(--track) 0);
color: var(--primary)
}
.values {
display: flex;
flex-wrap: nowrap;
gap: 8px;
}
.values p {
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<div class="header">
<h1>Daily Progress Report</h1>
<div class="project-info">
<strong>Project:</strong>{{projectName}}<br>
<strong>Date:</strong> {{date}}
</div>
</div>
<!-- Status Note -->
<div class="status-note">
* Project Status Reported - Generated at {{timeStamp}} UTC
</div>
<!-- Status Cards -->
<div class="status-cards">
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website"
title="Open website" target="_blank" rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">TODAY'S ATTENDANCE</h4>
<div style="display:flex; flex-wrap:wrap;">
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<div class="donut thin donut-primary" style="--percentage: {{attendancePercentage}};">
<span class="values">
<p style="color:#007bff;">{{todaysAttendances}}</p> / <p style="color:#ccc;">{{totalEmployees}}</p>
</span>
</div>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-blue"></span> Today's Attendance
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Total Employees
</div>
</div>
</div>
</div>
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website"
title="Open website" target="_blank" rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">DAILY TASKS COMPLETED</h4>
<div style="display:flex; flex-wrap:wrap;">
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<div class="donut thin donut-primary" style="--percentage: {{taskPercentage}};">
<span class="values">
<p style="color:#007bff;">{{totalCompletedTask}}</p> / <p style="color:#ccc;">{{totalPlannedTask}}</p>
</span>
</div>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-blue"></span> Completed Work
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Planned Work
</div>
</div>
</div>
<div>
<p style="font-size: xx-small;">*Today's total work</p>
</div>
</div>
<!-- <div class="card">
<h4 class="card-title">DAILY TASKS COMPLETED</h4>
<p class="value tasks">20 / 30</p>
<p>Team member present</p>
<div class="legend">
<div class="legend-item"><span class="legend-color legend-blue"></span> Completed</div>
<div class="legend-item"><span class="legend-color legend-green"></span> In Progress</div>
<div class="legend-item"><span class="legend-color legend-gray"></span> Pending</div>
</div>
</div> -->
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website"
title="Open website" target="_blank" rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">PROJECT COMPLETION STATUS</h4>
<div style="display:flex; flex-wrap:wrap;">
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<div class="donut thin donut-primary" style="--percentage: {{completionStatus}};">
<span>
<p style="color:#007bff;">{{totalCompletedWork}}</p> / <p style="color:#ccc;">{{totalPlannedWork}}</p>
</span>
</div>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-blue"></span> Completed Work
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Planned Work
</div>
</div>
</div>
<div>
<p style="font-size: xx-small;">*Project's total work</p>
</div>
</div>
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website"
title="Open website" target="_blank" rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">Regularization Pending</h4>
<p class="value tasks">{{regularizationPending}}</p>
<h4 class="card-title">Checkout Pending</h4>
<p class="value tasks">{{checkoutPending}}</p>
</div>
<div class="card">
<a class="card-link" href={{webUrl}} aria-label="Open original website"
title="Open website" target="_blank" rel="noopener">
<span class="arrow">&#8599;</span>
</a>
<h4 class="card-title">Activity Report Pending</h4>
<div style="display:flex; flex-wrap:wrap;">
<!-- Left Column -->
<div style="width:50%; box-sizing:border-box;display:flex; justify-content:center; align-items:center;">
<!-- Medium -->
<span class="values">
<p style="color:#007bff;">{{reportPending}}</p> / <p style="color:#ccc;">{{todaysAssignTasks}}</p>
</span>
</div>
<!-- Right Column -->
<div class="legend" style="width:50%; padding:15px; box-sizing:border-box;">
<div class="legend-item"
style="margin-bottom:10px;text-align: left; display:left; justify-content:left; align-items:left!important;; ">
<span class="legend-color legend-blue"></span> Total Pending Tasks
</div>
<div class="legend-item"
style="margin-bottom:10px; text-align: left; display:left; justify-content:left; align-items:left!important;;">
<span class="legend-color legend-gray"></span> Today's Assigned Tasks
</div>
</div>
</div>
</div>
{% if teamOnSite and teamOnSite|length > 0 %}
<div class="card">
<!-- Row 1: Header -->
<div>
<h4 class="card-title">Team Strength on Site</h4>
</div>
<table style="width: 100%;">
{% for a in teamOnSite %}
<tr>
<td style="text-align: left;">{{a.roleName}}</td>
<td style="text-align: right;">{{a.numberofEmployees}}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %}
<!-- Activities
{% if performedTasks and performedTasks|length > 0 %}
<div class="activities">
<h2>Activities (Tasks) Performed {{date}}</h2>
<table class="table">
<thead>
<tr>
<th>Activity/Location</th>
<th>Assigned Today/Pending</th>
<th>Completed Today</th>
<th>Date</th>
<th>Team Members</th>
<th>Comment</th>
</tr>
</thead>
<tbody>
{% for a in performedTasks %}
<tr>
<td>{{a.activity}} {{a.location}}</td>
<td>{{a.assignedToday}} / {{a.pending}}</td>
<td>{{a.completedToday}}</td>
<td>{{date}}</td>
<td></td>
<td>{{a.comment}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %} -->
</div>
</div>
<!-- Footer -->
<div class="footer" style="display:flex; flex-wrap:wrap;">
<div style="width: 50%;text-align: left;">
Contact Us: contact[at]marcoaiot.com<br>
Marco AIoT technologies Pvt. Ltd. ©2025 All Rights Reserved
</div>
<div style="width: 50%; text-align: right;">
<!-- <a href="#">Instagram</a> | -->
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/google.png" style="height: 15px;" /></a> |
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/twitter.png" style="height: 15px;" /></a> |
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/facebook.png" style="height: 15px;" /></a> |
<a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/instagram.png" style="height: 15px;" /></a>
<!-- <a href="#"><img src="https://cdn.marcoaiot.com/icons/brands/youtube.png" style="height: 15px;" /></a> | <a
href="#">LinkedIn</a> |
<a href="#">YouTube</a> -->
</div>
</div>
<div style="text-align: center;width: 100%;background-color: #fff;margin:10px;font-size: small;color: #6c757d ;">
You have received this email because it contains important information about your Marco PMS Account account.
</div>
</div>
</body>
</html>

View File

@ -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)

View File

@ -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)