From a446f519b77849c61737ad21357567c93ea7c359 Mon Sep 17 00:00:00 2001 From: Khushal P Soonderji Date: Mon, 23 Feb 2026 18:07:22 +0530 Subject: [PATCH] (20260223) Manual attendance script is ready. --- scripts/common.py | 1 + scripts/manual_attendance.py | 253 +++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 scripts/manual_attendance.py diff --git a/scripts/common.py b/scripts/common.py index 2293334..44b597d 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -91,6 +91,7 @@ TCAOFF_CREDS_FILE = os.path.join(CREDS_DIR, "tcaoff.json") MUSTER_ROLL_CACHE_FILE = os.path.join(CACHE_DIR, "muster_roll_cache.json") IN_OUT_SUMMARY_CACHE_FILE = os.path.join(CACHE_DIR, "in_out_summary_cache.json") PREV_DAY_IN_OUT_SUMMARY_CACHE_FILE = os.path.join(CACHE_DIR, "prev_day_in_out_summary_cache.json") +MANUAL_SUMMARY_CACHE_FILE = os.path.join(CACHE_DIR, "manual_in_out_summary_cache.json") CHROME_DRIVER_DIR = os.path.join(PROJ_DIR, "drivers", "chrome") USER_DATA_DIR = os.path.join(PROJ_DIR, "browser", "user_data") DOWNLOADS_DIR = os.path.join(PROJ_DIR, "downloads") diff --git a/scripts/manual_attendance.py b/scripts/manual_attendance.py new file mode 100644 index 0000000..cf5d86b --- /dev/null +++ b/scripts/manual_attendance.py @@ -0,0 +1,253 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + CREATED: Mon, 23rd Feb, 2026 + UPDATED: Mon, 23rd Feb, 2026 + + OBJECTIVE: + + To manually sync attendance for a specific time period. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For system-level activities: +import os +import copy + +# To work with date and time: +import time +import datetime + +# To work with tabulate data: +import pandas as pd + +# Cosec-related: +from cosec_web.cosec_web import CosecWeb + +# TCAOFF-related: +from tcaoff.async_tcaoff import AsyncTheCAOffice + +# My utils: +from utils_v2.system import files +from utils_v2.string import json +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with datatypes: +from typing import List, Dict, Any +from collections import defaultdict + +# To run a cron-like scheduler: +from apscheduler.schedulers.asyncio import AsyncIOScheduler + +# For async activities: +import asyncio + +# For debugging: +from icecream import IceCreamDebugger + +# Common and Shared: +from scripts import common + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# Debugging: +printer = IceCreamDebugger(prefix = "Manual | ", includeContext = True) +err_printer = IceCreamDebugger(prefix = "[ERR] Manual | ", includeContext = True) + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +async def manual_attendance( + cosec_creds: dict, + tcaoff_client: AsyncTheCAOffice, + from_date: date_time.datetime = None, + to_date: date_time.datetime = None, + test_mode: bool = False, +) -> None: + + printer("MANUAL ATTENDANCE") + + # Try the whole process once: + try: + + # Log in to TCAOFF: + success = await tcaoff_client.login() + if not success: raise RuntimeError("TCAOFF Login Failed!") + + # Try the part that needs COSEC: + try: + + # Figure out the dates: + now = date_time.get_current_ist_date_time() + if not from_date: from_date = now - datetime.timedelta(days = 1) + if not to_date: to_date = now + + # Set the correct sequence for the dates: + if from_date > to_date: from_date, to_date = to_date, from_date + + # Get the previous day's In/Out Summary and then wait + # for the driver's resources to get freed: + success = common.get_in_out_summary( + cosec_creds = cosec_creds, + from_dt = from_date, + to_dt = to_date, + cache_file = common.MANUAL_SUMMARY_CACHE_FILE, + test_mode = test_mode + ) + + # Sync data between Cosec and TCAOFF: + if success: + printer("IN/OUT SUMMARY: Sync'ing with TCAOFF") + await common.sync_attendance_to_tcaoff( + tcaoff_client = tcaoff_client, + cosec_in_out_summary = json.from_file(common.MANUAL_SUMMARY_CACHE_FILE), + chunk_size = 10 + ) + + # If something goes wrong in the COSEC step: + except Exception as exception: + err_printer(exception) + if test_mode: raise + + # Log out of TCAOFF: + success = await tcaoff_client.logout() + if not success: raise RuntimeError("TCAOFF Logout Failed!") + + # If something goes wrong: + except Exception as exception: + err_printer(exception) + await tcaoff_client.logout() + if test_mode: raise + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + # Parse args: + import argparse + ap = argparse.ArgumentParser() + ap.add_argument( + "--test", + action = "store_true", + default = False, + ) + ap.add_argument( + "--verbose", + action = "store_true", + default = False, + ) + ap.add_argument( + "--from-date", + help = "The date from which you want to sync attendance.", + default = date_time.get_current_ist_date_time() + ) + ap.add_argument( + "--to-date", + help = "The date till when you want to sync attendance.", + default = date_time.get_current_ist_date_time() + ) + args = ap.parse_args() + + # Explicitly mention the expected file paths for other devs to maintain: + print("PROJ. DIR. :", common.PROJ_DIR) + print("COSEC CREDS :", common.COSEC_CREDS_FILE) + print("TCAOFF CREDS:", common.TCAOFF_CREDS_FILE) + + # Read required credentials: + cosec_creds = json.from_file(common.COSEC_CREDS_FILE) + tcaoff_creds = json.from_file(common.TCAOFF_CREDS_FILE) + + # Date-handling: + args.from_date = date_time.parse_date_time(args.from_date, timezone = date_time.TIMEZONE_IST) + args.to_date = date_time.parse_date_time(args.to_date, timezone = date_time.TIMEZONE_IST) + if args.from_date > args.to_date: args.from_date, args.to_date = args.to_date, args.from_date + print("From Date:", args.from_date) + print(" To Date:", args.to_date) + # dates_list = [args.from_date] + # while args.from_date < args.to_date: + # args.from_date = args.from_date + datetime.timedelta(days = 1) + # dates_list.append(args.from_date) + # print(f"DATES ({len(dates_list)}):", dates_list) + + # Create the clients: + tcaoff_client = AsyncTheCAOffice( + username = tcaoff_creds["creds"]["username"], + password = tcaoff_creds["creds"]["password"], + debug = True, + debug_only_errors = False if args.verbose else True, + ) + + # Schedule the activities: + asyncio.run( + manual_attendance( + cosec_creds = cosec_creds, + tcaoff_client = tcaoff_client, + from_date = args.from_date, + to_date = args.to_date, + test_mode = args.test, + ) + )