Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions bwh_bot/handlers/wfh.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,25 @@ def _summary_buttons(self, data):
[InlineKeyboardButton("Confirm & Submit", callback_data=f"{p}:confirm")],
] + nav_buttons(p)

def _total_wfh_days(self, data):
days = frappe.utils.date_diff(data["to_date"], data["from_date"]) + 1
if data.get("half_day"):
days -= 0.5
return days

def _show_summary(self, state, chat_id, message_id):
data = self.get_data(state)
days = frappe.utils.date_diff(data["to_date"], data["from_date"]) + 1
half_day = data.get("half_day", False)

half_day_text = ""
if half_day:
half_day_text = f"\n<b>Half Day:</b> Yes ({data['from_date']})"
total_wfh_days = self._total_wfh_days(data)

edit_message_text(
chat_id, message_id,
(
f"<b>Work From Home Summary</b>\n\n"
f"<b>From:</b> {data['from_date']}\n"
f"<b>To:</b> {data['to_date']}\n"
f"<b>Days:</b> {days}"
f"{half_day_text}\n\n"
f"<b>Type:</b> {'Half Day' if half_day else 'Full Day'}\n"
f"<b>Total WFH Days:</b> {total_wfh_days:g}\n\n"
f"Confirm and submit?"
),
parse_mode="HTML",
Expand All @@ -165,16 +167,19 @@ def _handle_confirm(self, state, ctx):
chat_id, message_id, cqid = ctx["chat_id"], ctx["message_id"], ctx["callback_query_id"]

try:
total_wfh_days = self._total_wfh_days(data)

# Capture the WFH days in the custom field instead of ticking the
# Half Day checkbox — that checkbox would split the day into half
# present / half absent in attendance, which we don't want for WFH.
doc_data = {
"doctype": "Attendance Request",
"employee": data["employee"],
"from_date": data["from_date"],
"to_date": data["to_date"],
"reason": "Work From Home",
"custom_total_wfh_days": total_wfh_days,
}
if data.get("half_day"):
doc_data["half_day"] = 1
doc_data["half_day_date"] = data["from_date"]

frappe.db.savepoint("before_attendance_request")
doc = frappe.get_doc(doc_data)
Expand All @@ -191,6 +196,7 @@ def _handle_confirm(self, state, ctx):
f"<b>ID:</b> {doc.name}\n"
f"<b>From:</b> {data['from_date']}\n"
f"<b>To:</b> {data['to_date']}\n"
f"<b>Total WFH Days:</b> {total_wfh_days:g}\n"
f"<b>Status:</b> Submitted\n"
),
parse_mode="HTML",
Expand Down
18 changes: 17 additions & 1 deletion bwh_bot/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@
# ------------

# before_install = "bwh_bot.install.before_install"
# after_install = "bwh_bot.install.after_install"
after_install = "bwh_bot.install.after_install"

# Migration
# ---------
after_migrate = "bwh_bot.install.after_migrate"

# Uninstallation
# ------------
Expand Down Expand Up @@ -141,7 +145,19 @@
# Scheduled Tasks
# ---------------

# Cron times are evaluated in the server's timezone.
scheduler_events = {
"cron": {
# 10:30 AM — who is on leave and who is working from home today
"30 10 * * *": [
"bwh_bot.tasks.send_daily_leave_notification",
"bwh_bot.tasks.send_daily_wfh_notification",
],
# 3:00 PM — second WFH reminder for the day
"0 15 * * *": [
"bwh_bot.tasks.send_daily_wfh_notification",
],
},
"monthly": [
"bwh_bot.tasks.create_monthly_petty_cash_journal_entry"
],
Expand Down
32 changes: 32 additions & 0 deletions bwh_bot/install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields

CUSTOM_FIELDS = {
"Attendance Request": [
{
"fieldname": "custom_total_wfh_days",
"label": "Total WFH Days",
"fieldtype": "Float",
"precision": "1",
"insert_after": "half_day_date",
"description": (
"Total Work From Home days captured by BWH Bot. "
"For a half day this is 0.5; for a multi-day request it is the number of days. "
"This is used instead of the Half Day checkbox so WFH is not split into "
"half present / half absent in attendance."
),
}
],
}


def after_install():
_make_custom_fields()


def after_migrate():
_make_custom_fields()


def _make_custom_fields():
create_custom_fields(CUSTOM_FIELDS, ignore_validate=True)
65 changes: 65 additions & 0 deletions bwh_bot/tasks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,71 @@
import frappe
from frappe.utils import add_months, get_first_day, get_last_day, today

from bwh_bot.telegram_utils import broadcast_to_whitelisted_chats


def _format_date_range(from_date, to_date):
from_str = frappe.utils.formatdate(from_date, "d MMM")
if str(from_date) == str(to_date):
return from_str
return f"{from_str} → {frappe.utils.formatdate(to_date, 'd MMM')}"


def send_daily_leave_notification():
"""Daily cron: notify whitelisted chats about employees on approved leave today."""
current_day = today()
leaves = frappe.get_all(
"Leave Application",
filters={
"docstatus": 1,
"status": "Approved",
"from_date": ["<=", current_day],
"to_date": [">=", current_day],
},
fields=["employee_name", "leave_type", "from_date", "to_date", "half_day"],
order_by="employee_name asc",
)

if not leaves:
return

lines = []
for leave in leaves:
date_range = _format_date_range(leave.from_date, leave.to_date)
half_day_text = " · Half Day" if leave.half_day else ""
lines.append(f"• <b>{leave.employee_name}</b> — {leave.leave_type} ({date_range}){half_day_text}")

message = "🌴 <b>On Leave Today</b>\n\n" + "\n".join(lines)
broadcast_to_whitelisted_chats(message)


def send_daily_wfh_notification():
"""Daily cron: notify whitelisted chats about employees working from home today."""
current_day = today()
requests = frappe.get_all(
"Attendance Request",
filters={
"docstatus": 1,
"reason": "Work From Home",
"from_date": ["<=", current_day],
"to_date": [">=", current_day],
},
fields=["employee_name", "from_date", "to_date", "custom_total_wfh_days"],
order_by="employee_name asc",
)

if not requests:
return

lines = []
for req in requests:
date_range = _format_date_range(req.from_date, req.to_date)
half_day_text = " · Half Day" if req.custom_total_wfh_days == 0.5 else ""
lines.append(f"• <b>{req.employee_name}</b> ({date_range}){half_day_text}")

message = "🏠 <b>Working From Home Today</b>\n\n" + "\n".join(lines)
broadcast_to_whitelisted_chats(message)


def create_monthly_petty_cash_journal_entry():
"""Monthly cron: aggregate previous month's petty cash usage into a draft Journal Entry."""
Expand Down
10 changes: 10 additions & 0 deletions bwh_bot/telegram_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ def set_message_reaction(chat_id, message_id, emoji="👍"):
)


def broadcast_to_whitelisted_chats(text, parse_mode="HTML"):
"""Send a message to every whitelisted chat, logging (not raising) on failure."""
settings = frappe.get_single("BWH Bot Settings")
for chat in settings.whitelisted_chats:
try:
send_message(chat.chat_id, text, parse_mode=parse_mode)
except Exception:
frappe.log_error(f"Failed to send Telegram notification to chat {chat.chat_id}")


def is_whitelisted(chat_id):
settings = frappe.get_single("BWH Bot Settings")
chat_id = str(chat_id)
Expand Down
Loading