diff --git a/employee_self_service/mobile/v1/attendance/__init__.py b/employee_self_service/mobile/v1/attendance/__init__.py index 4504f7e..b048b90 100644 --- a/employee_self_service/mobile/v1/attendance/__init__.py +++ b/employee_self_service/mobile/v1/attendance/__init__.py @@ -5,7 +5,7 @@ get_holiday_list_for_employee, ) from frappe import _ -from frappe.utils import cint, getdate +from frappe.utils import add_days, cint, date_diff, flt, getdate, today from employee_self_service.mobile.v1.api_utils import ( convert_timezone, @@ -154,7 +154,6 @@ def get_ess_calendar_details(year=None, month=None): attendance_data = build_attendance_data( year, month, days_in_month, attendance_records, holidays ) - return gen_response( 200, "ESS calendar data fetched successfully", attendance_data ) @@ -179,7 +178,6 @@ def get_attendance_records(employee, start_date, end_date): def get_employee_holidays(employee, start_date, end_date): """Fetch holiday dates for a given employee and date range.""" holiday_list = get_holiday_list_for_employee(employee, raise_exception=False) - if not holiday_list: return set() @@ -213,3 +211,104 @@ def build_attendance_data(year, month, days_in_month, attendance_records, holida attendance_data[date_str] = "Holiday" if date in holidays else "No Record" return attendance_data + + +def _get_attendance_summary(employee, year, month): + """ + Compute present/absent/days-off counts for a given month using the same + data sources as get_ess_calendar_details (attendance records + holiday list). + + Returns a dict in the same shape as get_attendance_details in ess.py. + """ + days_in_month = monthrange(year, month)[1] + month_start = getdate(f"{year}-{month:02d}-01") + month_end = getdate(f"{year}-{month:02d}-{days_in_month}") + yesterday = getdate(add_days(today(), -1)) + + if yesterday < month_start: + till_date_days = 0 + elif yesterday > month_end: + till_date_days = days_in_month + else: + till_date_days = date_diff(yesterday, month_start) + 1 + + present_count = flt(0) + leave_count = flt(0) + holidays_till_date = flt(0) + + if till_date_days > 0: + till_date = min(yesterday, month_end) + + attendance_records = get_attendance_records( + employee, + month_start.strftime("%Y-%m-%d"), + month_end.strftime("%Y-%m-%d"), + ) + holidays = get_employee_holidays( + employee, + month_start.strftime("%Y-%m-%d"), + month_end.strftime("%Y-%m-%d"), + ) + + for record in attendance_records: + record_date = getdate(record["attendance_date"]) + if record_date > till_date: + continue + status = record["status"] + if status in ("Present", "Work From Home"): + present_count += 1 + elif status == "Half Day": + present_count += 0.5 + leave_count += 0.5 + elif status == "On Leave": + leave_count += 1 + + holidays_till_date = flt(sum(1 for h in holidays if h <= till_date)) + + days_off = leave_count + holidays_till_date + absent = max(flt(0), flt(till_date_days) - present_count - days_off) + + return { + "month_title": month_start.strftime("%B"), + "data": [ + {"type": "Total Days", "data": [till_date_days, days_in_month]}, + {"type": "Presents", "data": [present_count, till_date_days]}, + {"type": "Absents", "data": [absent, till_date_days]}, + {"type": "Days off", "data": [days_off, till_date_days]}, + ], + } + + +@frappe.whitelist() +def get_attendance_details_dashboard(): + """Current-month attendance summary for the dashboard.""" + try: + emp_data = get_employee_by_user(frappe.session.user, fields=["name", "company"]) + if not emp_data: + return gen_response(404, "Employee not found") + today_date = getdate(today()) + summary = _get_attendance_summary(emp_data["name"], today_date.year, today_date.month) + return gen_response(200, "Attendance data get successfully", summary) + except Exception as e: + return exception_handler(e) + + +@frappe.whitelist() +def get_attendance_details_by_month(year=None, month=None): + """Attendance summary for a given year and month.""" + try: + if not year or not month: + return gen_response(500, "year and month are required") + + year, month = cint(year), cint(month) + today_date = getdate(today()) + if (year, month) > (today_date.year, today_date.month): + return gen_response(400, "Cannot fetch attendance for a future month") + + emp_data = get_employee_by_user(frappe.session.user, fields=["name", "company"]) + if not emp_data: + return gen_response(404, "Employee not found") + summary = _get_attendance_summary(emp_data["name"], year, month) + return gen_response(200, "Attendance data get successfully", summary) + except Exception as e: + return exception_handler(e) \ No newline at end of file diff --git a/employee_self_service/mobile/v1/ess.py b/employee_self_service/mobile/v1/ess.py index 286e050..66d368d 100644 --- a/employee_self_service/mobile/v1/ess.py +++ b/employee_self_service/mobile/v1/ess.py @@ -27,6 +27,7 @@ nowdate, pretty_date, today, + cint, ) from employee_self_service.employee_self_service.doctype.push_notification.push_notification import ( @@ -46,6 +47,7 @@ get_till_date_holiday_month_wise, validate_employee_data, ) +from employee_self_service.mobile.v1.attendance import _get_attendance_summary from employee_self_service.mobile.v1.task import * from employee_self_service.mobile.v1.transactions import * from employee_self_service.utils import add_ess_comment @@ -274,7 +276,7 @@ def get_leave_type(from_date=None, to_date=None): from_date = today() emp_data = get_employee_by_user(frappe.session.user) leave_types = frappe.get_all( - "Leave Type", filters={}, fields=["name", "'0' as balance"] + "Leave Type", filters={}, fields=["name"] ) for leave_type in leave_types: leave_type["balance"] = get_leave_balance_on( @@ -312,26 +314,48 @@ def get_leave_application_list(): leave_application_fields = [ "name", "leave_type", - "DATE_FORMAT(from_date, '%d-%m-%Y') as from_date", - "DATE_FORMAT(to_date, '%d-%m-%Y') as to_date", + "from_date", + "to_date", "total_leave_days", "description", "status", - "DATE_FORMAT(posting_date, '%d-%m-%Y') as posting_date", + "posting_date", "half_day", - "DATE_FORMAT(half_day_date, '%d-%m-%Y') as half_day_date", + "half_day_date", ] upcoming_leaves = frappe.get_all( "Leave Application", filters={"from_date": [">", today()], "employee": emp_data.get("name")}, fields=leave_application_fields, ) + + # Format dates + for leave in upcoming_leaves: + if leave.get("from_date"): + leave["from_date"] = leave["from_date"].strftime("%d-%m-%Y") + if leave.get("to_date"): + leave["to_date"] = leave["to_date"].strftime("%d-%m-%Y") + if leave.get("posting_date"): + leave["posting_date"] = leave["posting_date"].strftime("%d-%m-%Y") + if leave.get("half_day_date"): + leave["half_day_date"] = leave["half_day_date"].strftime("%d-%m-%Y") taken_leaves = frappe.get_all( "Leave Application", fields=leave_application_fields, filters={"from_date": ["<=", today()], "employee": emp_data.get("name")}, ) + + # Format dates + for leave in taken_leaves: + if leave.get("from_date"): + leave["from_date"] = leave["from_date"].strftime("%d-%m-%Y") + if leave.get("to_date"): + leave["to_date"] = leave["to_date"].strftime("%d-%m-%Y") + if leave.get("posting_date"): + leave["posting_date"] = leave["posting_date"].strftime("%d-%m-%Y") + if leave.get("half_day_date"): + leave["half_day_date"] = leave["half_day_date"].strftime("%d-%m-%Y") fiscal_year = get_fiscal_year(nowdate())[0] if not fiscal_year: return gen_response(500, "Fiscal year not set") @@ -731,14 +755,16 @@ def get_leave_balance_dashboard(): return exception_handler(e) +#moved into attendance.py file @frappe.whitelist() def get_attendance_details_dashboard(): try: emp_data = get_employee_by_user(frappe.session.user, fields=["name", "company"]) - attendance_details = get_attendance_details(emp_data) - return gen_response( - 200, "Leave balance data get successfully", attendance_details - ) + if not emp_data: + return gen_response(404, "Employee not found") + today_date = getdate() + summary = _get_attendance_summary(emp_data["name"], today_date.year, today_date.month) + return gen_response(200, "Attendance data get successfully", summary) except Exception as e: return exception_handler(e) @@ -857,6 +883,7 @@ def get_attendance_details(emp_data, year=None, month=None): @frappe.whitelist() def run_attendance_report(employee, company): + from hrms.hr.report.monthly_attendance_sheet.monthly_attendance_sheet import execute filters = { "filter_based_on": "Month", "month": cstr(frappe.utils.getdate().month), @@ -865,11 +892,10 @@ def run_attendance_report(employee, company): "employee": employee, "summarized_view": 1, } - from frappe.desk.query_report import run - attendance_report = run("Monthly Attendance Sheet", filters=filters) - if attendance_report.get("result"): - return attendance_report.get("result")[0] + columns, data, *_ = execute(filters) + if data: + return data[0] def get_latest_leave(dashboard_data, employee): @@ -878,8 +904,8 @@ def get_latest_leave(dashboard_data, employee): filters={"employee": employee}, fields=[ "status", - "DATE_FORMAT(from_date, '%d-%m-%Y') AS from_date", - "DATE_FORMAT(to_date, '%d-%m-%Y') AS to_date", + "from_date", + "to_date", "name", "leave_type", "description", @@ -887,7 +913,13 @@ def get_latest_leave(dashboard_data, employee): order_by="modified desc", ) if len(leave_applications) >= 1: - dashboard_data["latest_leave"] = leave_applications[0] + latest = leave_applications[0] + # Format dates + if latest.get("from_date"): + latest["from_date"] = latest["from_date"].strftime("%d-%m-%Y") + if latest.get("to_date"): + latest["to_date"] = latest["to_date"].strftime("%d-%m-%Y") + dashboard_data["latest_leave"] = latest # def get_latest_expense(dashboard_data, employee): @@ -1195,7 +1227,7 @@ def get_attendance_list(year=None, month=None): }, fields=[ "name", - "DATE_FORMAT(attendance_date, '%d %W') AS attendance_date", + "attendance_date", "status", "working_hours", "in_time", @@ -1213,6 +1245,9 @@ def get_attendance_list(year=None, month=None): if user_time_zone != system_timezone: to_convert_timezone = True for attendance in employee_attendance_list: + # Format attendance date + if attendance.get("attendance_date"): + attendance["attendance_date"] = attendance["attendance_date"].strftime("%d %A") employee_checkin_details = [] if to_convert_timezone: if attendance["in_time"]: @@ -1236,8 +1271,12 @@ def get_attendance_list(year=None, month=None): employee_checkin_details = frappe.get_all( "Employee Checkin", filters={"attendance": attendance.get("name")}, - fields=["log_type", "time_format(time, '%h:%i%p') as time"], + fields=["log_type", "time"], ) + # Format time + for checkin in employee_checkin_details: + if checkin.get("time"): + checkin["time"] = checkin["time"].strftime("%I:%M%p") attendance["employee_checkin_detail"] = employee_checkin_details @@ -1258,7 +1297,7 @@ def get_attendance_list(year=None, month=None): "days_in_month": calendar.monthrange(int(year), int(month))[1], "present": present_count, "absent": absent_count, - "late": late_count, + "late": late_count } attendance_data = { "attendance_details": attendance_details, @@ -2195,14 +2234,17 @@ def get_transactions_old( @ess_validate(methods=["GET"]) def get_customer_list(start=0, page_length=10, filters=None): try: + if isinstance(filters, str): + filters = json.loads(filters) customer_list = frappe.get_list( "Customer", ["name", "customer_name", "mobile_no as phone"], - start=start, + start=cint(start), filters=filters, - page_length=page_length, + page_length=cint(page_length), order_by="modified desc", ) + frappe.log_error(title="Customer List",message=str(customer_list)) return gen_response(200, "Customer list get successfully", customer_list) except frappe.PermissionError: return gen_response(500, "Not permitted read customer") @@ -2403,14 +2445,19 @@ def get_hr_policies(): return exception_handler(e) +# moved into the attendance.py file @frappe.whitelist() def get_attendance_details_by_month(year, month): try: + year, month = cint(year), cint(month) + today_date = getdate() + if (year, month) > (today_date.year, today_date.month): + return gen_response(400, "Cannot fetch attendance for a future month") emp_data = get_employee_by_user(frappe.session.user, fields=["name", "company"]) - attendance_details = get_attendance_details(emp_data, year, month) - return gen_response( - 200, "Leave balance data get successfully", attendance_details - ) + if not emp_data: + return gen_response(404, "Employee not found") + summary = _get_attendance_summary(emp_data["name"], year, month) + return gen_response(200, "Attendance data get successfully", summary) except Exception as e: return exception_handler(e) @@ -2431,7 +2478,7 @@ def get_attendance_list_by_date(date=None): }, fields=[ "name", - "DATE_FORMAT(attendance_date, '%d %W') AS attendance_date", + "attendance_date", "status", "working_hours", "in_time", @@ -2443,6 +2490,16 @@ def get_attendance_list_by_date(date=None): if not employee_attendance_list: return gen_response(500, "no attendance found for this year and month", []) + user_time_zone = frappe.db.get_value("User", frappe.session.user, "time_zone") + system_timezone = get_system_timezone() + to_convert_timezone = False + if user_time_zone != system_timezone: + to_convert_timezone = True + for attendance in employee_attendance_list: + # Format attendance date + if attendance.get("attendance_date"): + attendance["attendance_date"] = attendance["attendance_date"].strftime("%d %A") + user_time_zone = frappe.db.get_value("User", frappe.session.user, "time_zone") system_timezone = get_system_timezone() to_convert_timezone = False @@ -2484,10 +2541,14 @@ def get_attendance_list_by_date(date=None): filters={"attendance": attendance.get("name")}, fields=[ "log_type", - "time_format(time, '%h:%i%p') as time", + "time", "location", ], ) + # Format time + for checkin in employee_checkin_details: + if checkin.get("time"): + checkin["time"] = checkin["time"].strftime("%I:%M%p") attendance["employee_checkin_detail"] = employee_checkin_details diff --git a/employee_self_service/mobile/v1/expense.py b/employee_self_service/mobile/v1/expense.py index 0392d56..86773c9 100644 --- a/employee_self_service/mobile/v1/expense.py +++ b/employee_self_service/mobile/v1/expense.py @@ -41,7 +41,7 @@ def get_expense_claims(): "`tabExpense Claim`.posting_date", "`tabExpense Claim`.company", "`tabExpense Claim Detail`.expense_type", - "count(`tabExpense Claim Detail`.expense_type) as total_expenses", + {"COUNT": "`tabExpense Claim Detail`.expense_type", "as": "total_expenses"}, ] claims = frappe.get_list( @@ -49,7 +49,7 @@ def get_expense_claims(): fields=fields, filters=filters, order_by="`tabExpense Claim`.posting_date desc", - group_by="`tabExpense Claim`.name", + group_by="`tabExpense Claim`.name" ) expense_data = {} for expense in claims: @@ -94,7 +94,7 @@ def get_expense_claims_list(): "`tabExpense Claim`.company", "`tabExpense Claim Detail`.expense_type", "`tabExpense Claim Detail`.description", - "count(`tabExpense Claim Detail`.expense_type) as total_expenses", + {"COUNT": "`tabExpense Claim Detail`.expense_type", "as": "total_expenses"}, ] claims = frappe.get_list( @@ -102,7 +102,7 @@ def get_expense_claims_list(): fields=fields, filters=filters, order_by="`tabExpense Claim`.posting_date desc", - group_by="`tabExpense Claim`.name", + group_by="`tabExpense Claim`.name" ) expense_data = {"pending": [], "other": []} for expense in claims: @@ -146,7 +146,7 @@ def get_expense_claim_type_totals(): "`tabExpense Claim`.employee", "`tabExpense Claim`.employee_name", "`tabExpense Claim Detail`.expense_type", - "sum(`tabExpense Claim Detail`.amount) as total_amount", + {"SUM": "`tabExpense Claim Detail`.amount", "as": "total_amount"}, ] claims = frappe.get_list( @@ -209,6 +209,7 @@ def apply_expense(**data): company=emp_data.get("company"), payable_account=payable_account, items=frappe.form_dict.items, + exchange_rate = 1 ) expense_doc.update(data) expense_doc.insert() diff --git a/employee_self_service/mobile/v1/hr_approval/__init__.py b/employee_self_service/mobile/v1/hr_approval/__init__.py index 12ea0a5..cc879c4 100644 --- a/employee_self_service/mobile/v1/hr_approval/__init__.py +++ b/employee_self_service/mobile/v1/hr_approval/__init__.py @@ -2,7 +2,7 @@ import frappe from frappe.model.workflow import get_transitions, get_workflow_name -from frappe.utils import fmt_money +from frappe.utils import fmt_money,cint from employee_self_service.mobile.v1.api_utils import ( check_workflow_exists, @@ -27,6 +27,7 @@ def get_workflow(doctype: str) -> dict: @ess_validate(methods=["GET"]) def get_team_leave_application(start=0, page_length=20): try: + frappe.log_error(title="Team Leave Application API Called",message="Called") workflow = check_workflow_exists("Leave Application") emp_data = get_employee_by_user(frappe.session.user) @@ -40,6 +41,7 @@ def get_team_leave_application(start=0, page_length=20): ["leave_approver", "=", frappe.session.user], ] ) + frappe.log_error(title="filters",message=filters) leave_applications = frappe.get_list( "Leave Application", filters=filters, @@ -54,12 +56,14 @@ def get_team_leave_application(start=0, page_length=20): "total_leave_days", "description", "status", - "'0' as 'workflow_active'", ], order_by="posting_date desc", start=start, page_length=page_length, ) + # Add workflow_active field to each result + for app in leave_applications: + app['workflow_active'] = "0" return gen_response( 200, "Leave Application Get Successfully", leave_applications ) @@ -80,13 +84,14 @@ def get_team_leave_application(start=0, page_length=20): "total_leave_days", "description", "workflow_state as 'status'", - "'1' as 'workflow_active'", ], order_by="posting_date desc", ) actual_leave_applications = [] for doc in leave_applications: + # Add workflow_active field + doc['workflow_active'] = "1" if doc.get("status"): transitions = get_transitions( frappe.get_doc("Leave Application", doc["name"]) @@ -114,35 +119,38 @@ def get_team_expenses(start=0, page_length=20): filters = [["employee", "!=", emp_data.name]] if not workflow: - filters.extend( - [ - ["docstatus", "=", 0], - ["status", "=", "Draft"], - ["expense_approver", "=", frappe.session.user], - ] - ) - fields = [ - "`tabExpense Claim`.name", - "`tabExpense Claim`.employee", - "`tabExpense Claim`.employee_name", - "`tabExpense Claim`.approval_status", - "`tabExpense Claim`.expense_approver", - "`tabExpense Claim`.total_claimed_amount", - "`tabExpense Claim`.posting_date", - "`tabExpense Claim`.company", - "`tabExpense Claim Detail`.expense_type", - "`tabExpense Claim Detail`.name as expense_detail_name", - "count(`tabExpense Claim Detail`.expense_type) as total_expenses", - ] - - claims = frappe.get_list( - "Expense Claim", - fields=fields, - filters=filters, - order_by="`tabExpense Claim`.posting_date desc", - group_by="`tabExpense Claim`.name", - start=start, - page_length=page_length, + # Use raw SQL for aggregate query with GROUP BY + claims = frappe.db.sql( + """ + SELECT + `tabExpense Claim`.name, + `tabExpense Claim`.employee, + `tabExpense Claim`.employee_name, + `tabExpense Claim`.approval_status, + `tabExpense Claim`.expense_approver, + `tabExpense Claim`.total_claimed_amount, + `tabExpense Claim`.posting_date, + `tabExpense Claim`.company, + `tabExpense Claim Detail`.expense_type, + `tabExpense Claim Detail`.name as expense_detail_name, + COUNT(`tabExpense Claim Detail`.expense_type) as total_expenses + FROM `tabExpense Claim` + LEFT JOIN `tabExpense Claim Detail` ON `tabExpense Claim Detail`.parent = `tabExpense Claim`.name + WHERE `tabExpense Claim`.employee != %(employee)s + AND `tabExpense Claim`.docstatus = 0 + AND `tabExpense Claim`.status = 'Draft' + AND `tabExpense Claim`.expense_approver = %(approver)s + GROUP BY `tabExpense Claim`.name + ORDER BY `tabExpense Claim`.posting_date DESC + LIMIT %(start)s, %(page_length)s + """, + { + "employee": emp_data.name, + "approver": frappe.session.user, + "start": start, + "page_length": page_length, + }, + as_dict=True, ) for claim in claims: @@ -155,27 +163,31 @@ def get_team_expenses(start=0, page_length=20): filters.extend([["docstatus", "!=", 2], ["workflow_state", "is", "set"]]) - # Workflow is enabled - fields = [ - "`tabExpense Claim`.name", - "`tabExpense Claim`.employee", - "`tabExpense Claim`.employee_name", - "`tabExpense Claim`.workflow_state as 'approval_status'", - "`tabExpense Claim`.expense_approver", - "`tabExpense Claim`.total_claimed_amount", - "`tabExpense Claim`.posting_date", - "`tabExpense Claim`.company", - "`tabExpense Claim Detail`.expense_type", - "`tabExpense Claim Detail`.name as expense_detail_name", - "count(`tabExpense Claim Detail`.expense_type) as total_expenses", - ] - - claims = frappe.get_list( - "Expense Claim", - fields=fields, - filters=filters, - order_by="`tabExpense Claim`.posting_date desc", - group_by="`tabExpense Claim`.name", + # Workflow is enabled - use raw SQL for aggregate query with GROUP BY + claims = frappe.db.sql( + """ + SELECT + `tabExpense Claim`.name, + `tabExpense Claim`.employee, + `tabExpense Claim`.employee_name, + `tabExpense Claim`.workflow_state as approval_status, + `tabExpense Claim`.expense_approver, + `tabExpense Claim`.total_claimed_amount, + `tabExpense Claim`.posting_date, + `tabExpense Claim`.company, + `tabExpense Claim Detail`.expense_type, + `tabExpense Claim Detail`.name as expense_detail_name, + COUNT(`tabExpense Claim Detail`.expense_type) as total_expenses + FROM `tabExpense Claim` + LEFT JOIN `tabExpense Claim Detail` ON `tabExpense Claim Detail`.parent = `tabExpense Claim`.name + WHERE `tabExpense Claim`.employee != %(employee)s + AND `tabExpense Claim`.docstatus != 2 + AND `tabExpense Claim`.workflow_state IS NOT NULL + GROUP BY `tabExpense Claim`.name + ORDER BY `tabExpense Claim`.posting_date DESC + """, + {"employee": emp_data.name}, + as_dict=True, ) updated_expense_claim_list = [] diff --git a/employee_self_service/mobile/v1/quotation.py b/employee_self_service/mobile/v1/quotation.py index 0de8774..cfc5413 100644 --- a/employee_self_service/mobile/v1/quotation.py +++ b/employee_self_service/mobile/v1/quotation.py @@ -66,7 +66,7 @@ def get_quotation_list( fields=[ "name", "customer_name", - "DATE_FORMAT(transaction_date, '%d-%m-%Y') as transaction_date", + "transaction_date", "grand_total", "status", "total_qty", @@ -77,6 +77,9 @@ def get_quotation_list( filters=updated_filters, ) for quotation in quotation_list: + # Format date + if quotation.get("transaction_date"): + quotation["transaction_date"] = quotation["transaction_date"].strftime("%d-%m-%Y") quotation["grand_total"] = fmt_money( quotation["grand_total"], currency=global_defaults.get("default_currency"), diff --git a/employee_self_service/mobile/v1/visit.py b/employee_self_service/mobile/v1/visit.py index b159a0f..f96666b 100644 --- a/employee_self_service/mobile/v1/visit.py +++ b/employee_self_service/mobile/v1/visit.py @@ -70,13 +70,19 @@ def get_visit_list(): fields=[ "name", "customer_name", - "DATE_FORMAT(date, '%d-%m-%Y') as date", - "time_format(time, '%h:%i:%s') as time", + "date", + "time", "visit_type", "description", "visit_proof", ], ) + # Format dates and times + for visit in visit_list: + if visit.get("date"): + visit["date"] = visit["date"].strftime("%d-%m-%Y") + if visit.get("time"): + visit["time"] = visit["time"].strftime("%I:%M:%S") return gen_response(200, "Visit list get successfully", visit_list) except frappe.PermissionError: return gen_response(500, "Not permitted read visit")