add Employee Payroll Register report with hardcoded salary columns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e5df13a68b
commit
a8a01de845
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright (c) 2026, Taxes Az and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.query_reports["Employee Payroll Register"] = {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
fieldname: "from_date",
|
||||||
|
label: __("From"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||||
|
reqd: 1,
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "to_date",
|
||||||
|
label: __("To"),
|
||||||
|
fieldtype: "Date",
|
||||||
|
default: frappe.datetime.get_today(),
|
||||||
|
reqd: 1,
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "currency",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Currency",
|
||||||
|
label: __("Currency"),
|
||||||
|
default: erpnext.get_currency(frappe.defaults.get_default("Company")),
|
||||||
|
width: "50px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "employee",
|
||||||
|
label: __("Employee"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Employee",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "company",
|
||||||
|
label: __("Company"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Company",
|
||||||
|
default: frappe.defaults.get_user_default("Company"),
|
||||||
|
width: "100px",
|
||||||
|
reqd: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "docstatus",
|
||||||
|
label: __("Document Status"),
|
||||||
|
fieldtype: "Select",
|
||||||
|
options: ["Draft", "Submitted", "Cancelled"],
|
||||||
|
default: "Submitted",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "department",
|
||||||
|
label: __("Department"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Department",
|
||||||
|
width: "100px",
|
||||||
|
get_query: function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frappe.query_report.get_filter_value("company"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "designation",
|
||||||
|
label: __("Designation"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Designation",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "branch",
|
||||||
|
label: __("Branch"),
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Branch",
|
||||||
|
width: "100px",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"add_total_row": 1,
|
||||||
|
"creation": "2026-03-26 12:00:00.000000",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2026-03-26 12:00:00.000000",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Taxes Az",
|
||||||
|
"name": "Employee Payroll Register",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Salary Slip",
|
||||||
|
"report_name": "Employee Payroll Register",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "HR User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "HR Manager"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,303 @@
|
||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import flt
|
||||||
|
|
||||||
|
import erpnext
|
||||||
|
|
||||||
|
salary_slip = frappe.qb.DocType("Salary Slip")
|
||||||
|
salary_detail = frappe.qb.DocType("Salary Detail")
|
||||||
|
|
||||||
|
EARNING_TYPES = [
|
||||||
|
"DSMF (xərc şirkət)",
|
||||||
|
"Məzuniyyət Kompensasiyası",
|
||||||
|
"İcbari tibbi sığorta (xərc şirkət 2%)",
|
||||||
|
"İşsizlikdən sığorta haqqı (xərc şirkət 0,5%)",
|
||||||
|
"Əsas əmək haqqı",
|
||||||
|
]
|
||||||
|
|
||||||
|
DEDUCTION_TYPES = [
|
||||||
|
"DSMF (işçi)",
|
||||||
|
"DSMF (şirkət)",
|
||||||
|
"İcbari tibbi sığorta (işçi 2%)",
|
||||||
|
"İcbari tibbi sığorta (şirkət 2%)",
|
||||||
|
"İşsizlikdən sığorta haqqı (işçi 0,5%)",
|
||||||
|
"İşsizlikdən sığorta haqqı (şirkət 0,5%)",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
if not filters:
|
||||||
|
filters = {}
|
||||||
|
|
||||||
|
currency = filters.get("currency")
|
||||||
|
company_currency = erpnext.get_company_currency(filters.get("company"))
|
||||||
|
|
||||||
|
salary_slips = get_salary_slips(filters, company_currency)
|
||||||
|
if not salary_slips:
|
||||||
|
return get_columns(), []
|
||||||
|
|
||||||
|
columns = get_columns()
|
||||||
|
|
||||||
|
ss_earning_map = get_salary_slip_details(salary_slips, currency, company_currency, "earnings")
|
||||||
|
ss_ded_map = get_salary_slip_details(salary_slips, currency, company_currency, "deductions")
|
||||||
|
doj_map = get_employee_doj_map()
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for ss in salary_slips:
|
||||||
|
row = {
|
||||||
|
"salary_slip_id": ss.name,
|
||||||
|
"employee": ss.employee,
|
||||||
|
"employee_name": ss.employee_name,
|
||||||
|
"date_of_joining": doj_map.get(ss.employee),
|
||||||
|
"branch": ss.branch,
|
||||||
|
"department": ss.department,
|
||||||
|
"designation": ss.designation,
|
||||||
|
"company": ss.company,
|
||||||
|
"start_date": ss.start_date,
|
||||||
|
"end_date": ss.end_date,
|
||||||
|
"leave_without_pay": ss.leave_without_pay,
|
||||||
|
"absent_days": ss.absent_days,
|
||||||
|
"payment_days": ss.payment_days,
|
||||||
|
"currency": currency or company_currency,
|
||||||
|
}
|
||||||
|
|
||||||
|
for e in EARNING_TYPES:
|
||||||
|
row[frappe.scrub(e)] = flt(ss_earning_map.get(ss.name, {}).get(e, 0))
|
||||||
|
|
||||||
|
if currency == company_currency:
|
||||||
|
row["gross_pay"] = flt(ss.gross_pay) * flt(ss.exchange_rate)
|
||||||
|
else:
|
||||||
|
row["gross_pay"] = flt(ss.gross_pay)
|
||||||
|
|
||||||
|
for d in DEDUCTION_TYPES:
|
||||||
|
row[frappe.scrub(d)] = flt(ss_ded_map.get(ss.name, {}).get(d, 0))
|
||||||
|
|
||||||
|
if currency == company_currency:
|
||||||
|
row["total_deduction"] = flt(ss.total_deduction) * flt(ss.exchange_rate)
|
||||||
|
row["net_pay"] = flt(ss.net_pay) * flt(ss.exchange_rate)
|
||||||
|
else:
|
||||||
|
row["total_deduction"] = flt(ss.total_deduction)
|
||||||
|
row["net_pay"] = flt(ss.net_pay)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
"label": _("Salary Slip ID"),
|
||||||
|
"fieldname": "salary_slip_id",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Salary Slip",
|
||||||
|
"width": 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Employee"),
|
||||||
|
"fieldname": "employee",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Employee",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Employee Name"),
|
||||||
|
"fieldname": "employee_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Date of Joining"),
|
||||||
|
"fieldname": "date_of_joining",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Branch"),
|
||||||
|
"fieldname": "branch",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Branch",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Department"),
|
||||||
|
"fieldname": "department",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Department",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Designation"),
|
||||||
|
"fieldname": "designation",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Designation",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Company"),
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Start Date"),
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("End Date"),
|
||||||
|
"fieldname": "end_date",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Leave Without Pay"),
|
||||||
|
"fieldname": "leave_without_pay",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Absent Days"),
|
||||||
|
"fieldname": "absent_days",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Payment Days"),
|
||||||
|
"fieldname": "payment_days",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for earning in EARNING_TYPES:
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": earning,
|
||||||
|
"fieldname": frappe.scrub(earning),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": _("Gross Pay"),
|
||||||
|
"fieldname": "gross_pay",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for deduction in DEDUCTION_TYPES:
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": deduction,
|
||||||
|
"fieldname": frappe.scrub(deduction),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
columns.extend(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"label": _("Total Deduction"),
|
||||||
|
"fieldname": "total_deduction",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Net Pay"),
|
||||||
|
"fieldname": "net_pay",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "currency",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": _("Currency"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
|
||||||
|
def get_salary_slips(filters, company_currency):
|
||||||
|
doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
|
||||||
|
|
||||||
|
query = frappe.qb.from_(salary_slip).select(salary_slip.star)
|
||||||
|
|
||||||
|
if filters.get("docstatus"):
|
||||||
|
query = query.where(salary_slip.docstatus == doc_status[filters.get("docstatus")])
|
||||||
|
|
||||||
|
if filters.get("from_date"):
|
||||||
|
query = query.where(salary_slip.start_date >= filters.get("from_date"))
|
||||||
|
|
||||||
|
if filters.get("to_date"):
|
||||||
|
query = query.where(salary_slip.end_date <= filters.get("to_date"))
|
||||||
|
|
||||||
|
if filters.get("company"):
|
||||||
|
query = query.where(salary_slip.company == filters.get("company"))
|
||||||
|
|
||||||
|
if filters.get("employee"):
|
||||||
|
query = query.where(salary_slip.employee == filters.get("employee"))
|
||||||
|
|
||||||
|
if filters.get("currency") and filters.get("currency") != company_currency:
|
||||||
|
query = query.where(salary_slip.currency == filters.get("currency"))
|
||||||
|
|
||||||
|
if filters.get("department"):
|
||||||
|
query = query.where(salary_slip.department == filters["department"])
|
||||||
|
|
||||||
|
if filters.get("designation"):
|
||||||
|
query = query.where(salary_slip.designation == filters["designation"])
|
||||||
|
|
||||||
|
if filters.get("branch"):
|
||||||
|
query = query.where(salary_slip.branch == filters["branch"])
|
||||||
|
|
||||||
|
salary_slips = query.run(as_dict=1)
|
||||||
|
|
||||||
|
return salary_slips or []
|
||||||
|
|
||||||
|
|
||||||
|
def get_employee_doj_map():
|
||||||
|
employee = frappe.qb.DocType("Employee")
|
||||||
|
result = (frappe.qb.from_(employee).select(employee.name, employee.date_of_joining)).run()
|
||||||
|
return frappe._dict(result)
|
||||||
|
|
||||||
|
|
||||||
|
def get_salary_slip_details(salary_slips, currency, company_currency, component_type):
|
||||||
|
salary_slip_names = [ss.name for ss in salary_slips]
|
||||||
|
|
||||||
|
result = (
|
||||||
|
frappe.qb.from_(salary_slip)
|
||||||
|
.join(salary_detail)
|
||||||
|
.on(salary_slip.name == salary_detail.parent)
|
||||||
|
.where((salary_detail.parent.isin(salary_slip_names)) & (salary_detail.parentfield == component_type))
|
||||||
|
.select(
|
||||||
|
salary_detail.parent,
|
||||||
|
salary_detail.salary_component,
|
||||||
|
salary_detail.amount,
|
||||||
|
salary_slip.exchange_rate,
|
||||||
|
)
|
||||||
|
).run(as_dict=1)
|
||||||
|
|
||||||
|
ss_map = {}
|
||||||
|
for d in result:
|
||||||
|
ss_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
|
||||||
|
if currency == company_currency:
|
||||||
|
ss_map[d.parent][d.salary_component] += flt(d.amount) * flt(d.exchange_rate or 1)
|
||||||
|
else:
|
||||||
|
ss_map[d.parent][d.salary_component] += flt(d.amount)
|
||||||
|
|
||||||
|
return ss_map
|
||||||
Loading…
Reference in New Issue