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) <noreply@anthropic.com>
This commit is contained in:
Ali 2026-04-03 16:29:36 +04:00
parent 86b219a31e
commit 6ed4c94213
5 changed files with 191 additions and 8 deletions

View File

@ -210,13 +210,16 @@ def get_bolme2_hisse1_data(company, year, quarter):
quarter_num = int(quarter[0]) # "1. Rüb" -> 1 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] 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: for month in months:
last_day = calendar.monthrange(year, month)[1] last_day = calendar.monthrange(year, month)[1]
month_start = f"{year}-{month:02d}-01" month_start = f"{year}-{month:02d}-01"
month_end = f"{year}-{month:02d}-{last_day:02d}" 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` SELECT COUNT(*) FROM `tabEmployee`
WHERE company = %(company)s WHERE company = %(company)s
AND status != 'Inactive' AND status != 'Inactive'
@ -226,17 +229,34 @@ def get_bolme2_hisse1_data(company, year, quarter):
OR (relieving_date IS NULL AND status != 'Left') OR (relieving_date IS NULL AND status != 'Left')
) )
""", {"company": company, "month_start": month_start, "month_end": month_end})[0][0] """, {"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 { return {
"İşçilərin ümumi sayı": { "İşçilərin ümumi sayı": {
"1ciay": monthly_counts[0] if len(monthly_counts) > 0 else 0, "1ciay": total_counts[0] if len(total_counts) > 0 else 0,
"2ciay": monthly_counts[1] if len(monthly_counts) > 1 else 0, "2ciay": total_counts[1] if len(total_counts) > 1 else 0,
"3cüay": monthly_counts[2] if len(monthly_counts) > 2 else 0, "3cüay": total_counts[2] if len(total_counts) > 2 else 0,
"rübüzrəortasay": avg, "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,
}, },
} }

View File

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

View File

@ -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": []
}

View File

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