From 6ed4c94213125600fbb096ddac7058dd8a6eeec2 Mon Sep 17 00:00:00 2001 From: Ali <010109ali@gmail.com> Date: Fri, 3 Apr 2026 16:29:36 +0400 Subject: [PATCH] feat: add Monthly Present Report and bolme2_hisse1 row 2 (actual headcount) New report counts days present (Present + Half Day) per employee. bolme2_hisse1 row 2 counts unique employees who showed up per month. Co-Authored-By: Claude Opus 4.6 (1M context) --- ...lated_to_salaried_and_non_salaried_work.py | 36 +++++++-- .../report/monthly_present_report/__init__.py | 0 .../monthly_present_report.js | 80 +++++++++++++++++++ .../monthly_present_report.json | 20 +++++ .../monthly_present_report.py | 63 +++++++++++++++ 5 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 taxes_az/taxes_az/report/monthly_present_report/__init__.py create mode 100644 taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.js create mode 100644 taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.json create mode 100644 taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.py diff --git a/taxes_az/taxes_az/doctype/single_declaration_related_to_salaried_and_non_salaried_work/single_declaration_related_to_salaried_and_non_salaried_work.py b/taxes_az/taxes_az/doctype/single_declaration_related_to_salaried_and_non_salaried_work/single_declaration_related_to_salaried_and_non_salaried_work.py index b1e42fa..c6dfa28 100644 --- a/taxes_az/taxes_az/doctype/single_declaration_related_to_salaried_and_non_salaried_work/single_declaration_related_to_salaried_and_non_salaried_work.py +++ b/taxes_az/taxes_az/doctype/single_declaration_related_to_salaried_and_non_salaried_work/single_declaration_related_to_salaried_and_non_salaried_work.py @@ -210,13 +210,16 @@ def get_bolme2_hisse1_data(company, year, quarter): quarter_num = int(quarter[0]) # "1. Rüb" -> 1 months = [(quarter_num - 1) * 3 + i for i in range(1, 4)] # e.g. Q1 -> [1, 2, 3] - monthly_counts = [] + total_counts = [] + present_counts = [] + for month in months: last_day = calendar.monthrange(year, month)[1] month_start = f"{year}-{month:02d}-01" month_end = f"{year}-{month:02d}-{last_day:02d}" - count = frappe.db.sql(""" + # Row 1: total employees on payroll + total = frappe.db.sql(""" SELECT COUNT(*) FROM `tabEmployee` WHERE company = %(company)s AND status != 'Inactive' @@ -226,17 +229,34 @@ def get_bolme2_hisse1_data(company, year, quarter): OR (relieving_date IS NULL AND status != 'Left') ) """, {"company": company, "month_start": month_start, "month_end": month_end})[0][0] + total_counts.append(total) - monthly_counts.append(count) + # Row 2: unique employees who actually showed up + present = frappe.db.sql(""" + SELECT COUNT(DISTINCT a.employee) FROM `tabAttendance` a + WHERE a.company = %(company)s + AND a.docstatus = 1 + AND a.status IN ('Present', 'Half Day') + AND a.attendance_date >= %(month_start)s + AND a.attendance_date <= %(month_end)s + """, {"company": company, "month_start": month_start, "month_end": month_end})[0][0] + present_counts.append(present) - avg = round(sum(monthly_counts) / 3) if monthly_counts else 0 + total_avg = round(sum(total_counts) / 3) if total_counts else 0 + present_avg = round(sum(present_counts) / 3) if present_counts else 0 return { "İşçilərin ümumi sayı": { - "1ciay": monthly_counts[0] if len(monthly_counts) > 0 else 0, - "2ciay": monthly_counts[1] if len(monthly_counts) > 1 else 0, - "3cüay": monthly_counts[2] if len(monthly_counts) > 2 else 0, - "rübüzrəortasay": avg, + "1ciay": total_counts[0] if len(total_counts) > 0 else 0, + "2ciay": total_counts[1] if len(total_counts) > 1 else 0, + "3cüay": total_counts[2] if len(total_counts) > 2 else 0, + "rübüzrəortasay": total_avg, + }, + "Faktiki işə cəlb olunan işçilərin sayı": { + "1ciay": present_counts[0] if len(present_counts) > 0 else 0, + "2ciay": present_counts[1] if len(present_counts) > 1 else 0, + "3cüay": present_counts[2] if len(present_counts) > 2 else 0, + "rübüzrəortasay": present_avg, }, } diff --git a/taxes_az/taxes_az/report/monthly_present_report/__init__.py b/taxes_az/taxes_az/report/monthly_present_report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.js b/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.js new file mode 100644 index 0000000..65e7403 --- /dev/null +++ b/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.js @@ -0,0 +1,80 @@ +// Copyright (c) 2026, Jey Soft and contributors +// For license information, please see license.txt + +function get_quarter_dates(year, quarter) { + const quarters = { + "1": { from: year + "-01-01", to: year + "-03-31" }, + "2": { from: year + "-04-01", to: year + "-06-30" }, + "3": { from: year + "-07-01", to: year + "-09-30" }, + "4": { from: year + "-10-01", to: year + "-12-31" } + }; + return quarters[String(quarter)]; +} + +frappe.query_reports["Monthly Present Report"] = { + "filters": [ + { + fieldname: "company", + label: __("Şirkət"), + fieldtype: "Link", + options: "Company", + reqd: 1, + default: frappe.defaults.get_user_default("Company") + }, + { + fieldname: "year", + label: __("İl"), + fieldtype: "Int", + reqd: 1, + default: new Date().getFullYear(), + on_change: function() { + const year = frappe.query_report.get_filter_value("year"); + const quarter = frappe.query_report.get_filter_value("quarter"); + if (year && quarter) { + const dates = get_quarter_dates(year, quarter); + frappe.query_report.set_filter_value("from_date", dates.from); + frappe.query_report.set_filter_value("to_date", dates.to); + } + } + }, + { + fieldname: "quarter", + label: __("Rüb"), + fieldtype: "Select", + options: "1\n2\n3\n4", + reqd: 1, + default: String(Math.ceil((new Date().getMonth() + 1) / 3)), + on_change: function() { + const year = frappe.query_report.get_filter_value("year"); + const quarter = frappe.query_report.get_filter_value("quarter"); + if (year && quarter) { + const dates = get_quarter_dates(year, quarter); + frappe.query_report.set_filter_value("from_date", dates.from); + frappe.query_report.set_filter_value("to_date", dates.to); + } + } + }, + { + fieldname: "from_date", + label: __("Tarixdən"), + fieldtype: "Date", + hidden: 1 + }, + { + fieldname: "to_date", + label: __("Tarixə qədər"), + fieldtype: "Date", + hidden: 1 + } + ], + + "onload": function() { + const year = frappe.query_report.get_filter_value("year"); + const quarter = frappe.query_report.get_filter_value("quarter"); + if (year && quarter) { + const dates = get_quarter_dates(year, quarter); + frappe.query_report.set_filter_value("from_date", dates.from); + frappe.query_report.set_filter_value("to_date", dates.to); + } + } +}; diff --git a/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.json b/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.json new file mode 100644 index 0000000..a2d1648 --- /dev/null +++ b/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.json @@ -0,0 +1,20 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2026-04-03 00:00:00.000000", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2026-04-03 00:00:00.000000", + "modified_by": "Administrator", + "module": "Taxes Az", + "name": "Monthly Present Report", + "owner": "Administrator", + "ref_doctype": "Attendance", + "report_name": "Monthly Present Report", + "report_type": "Script Report", + "roles": [] +} diff --git a/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.py b/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.py new file mode 100644 index 0000000..e82c48f --- /dev/null +++ b/taxes_az/taxes_az/report/monthly_present_report/monthly_present_report.py @@ -0,0 +1,63 @@ +# Copyright (c) 2026, Jey Soft and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ + + +def execute(filters=None): + columns = get_columns() + data = get_data(filters) + return columns, data + + +def get_columns(): + return [ + { + "label": _("Soyad"), + "fieldname": "last_name", + "fieldtype": "Data", + "width": 140, + }, + { + "label": _("Ad"), + "fieldname": "first_name", + "fieldtype": "Data", + "width": 120, + }, + { + "label": _("FİN"), + "fieldname": "fin", + "fieldtype": "Data", + "width": 110, + }, + { + "label": _("İşçinin faktiki işə cəlb olunduğu iş günlərinin sayı"), + "fieldname": "days_present", + "fieldtype": "Int", + "width": 160, + }, + ] + + +def get_data(filters): + if not filters: + filters = {} + + return frappe.db.sql(""" + SELECT + e.last_name, + e.first_name, + e.passport_number AS fin, + COUNT(*) AS days_present + FROM `tabAttendance` a + LEFT JOIN `tabEmployee` e ON e.name = a.employee + WHERE + a.docstatus = 1 + AND a.status IN ('Present', 'Half Day') + AND a.company = %(company)s + AND a.attendance_date >= %(from_date)s + AND a.attendance_date <= %(to_date)s + GROUP BY a.employee + ORDER BY e.last_name ASC, e.first_name ASC + """, filters, as_dict=1)