feat: provision to setup opening balances for earnings and deductions while creating SSA
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"max_working_hours_against_timesheet",
|
||||
"include_holidays_in_total_working_days",
|
||||
"disable_rounded_total",
|
||||
"define_opening_balance_for_earning_and_deductions",
|
||||
"column_break_11",
|
||||
"daily_wages_fraction_for_half_day",
|
||||
"email_salary_slip_to_employee",
|
||||
@@ -91,13 +92,20 @@
|
||||
"fieldname": "show_leave_balances_in_salary_slip",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Leave Balances in Salary Slip"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If checked, then the system will enable the provision to set the opening balance for earnings and deductions till date while creating a Salary Structure Assignment (if any)",
|
||||
"fieldname": "define_opening_balance_for_earning_and_deductions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Define Opening Balance for Earning and Deductions"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-03 17:49:59.579723",
|
||||
"modified": "2022-12-21 17:30:08.704247",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Payroll Settings",
|
||||
|
||||
@@ -1063,7 +1063,26 @@ class SalarySlip(TransactionBase):
|
||||
)
|
||||
exempted_amount = flt(exempted_amount[0][0]) if exempted_amount else 0
|
||||
|
||||
return taxable_earnings - exempted_amount
|
||||
opening_taxable_earning = self.get_opening_for(
|
||||
"taxable_earnings_till_date", start_date, end_date
|
||||
)
|
||||
|
||||
return (taxable_earnings + opening_taxable_earning) - exempted_amount
|
||||
|
||||
def get_opening_for(self, field_to_select, start_date, end_date):
|
||||
return (
|
||||
frappe.db.get_value(
|
||||
"Salary Structure Assignment",
|
||||
{
|
||||
"employee": self.employee,
|
||||
"salary_structure": self.salary_structure,
|
||||
"from_date": ["between", [start_date, end_date]],
|
||||
"docstatus": 1,
|
||||
},
|
||||
field_to_select,
|
||||
)
|
||||
or 0
|
||||
)
|
||||
|
||||
def get_tax_paid_in_period(self, start_date, end_date, tax_component):
|
||||
# find total_tax_paid, tax paid for benefit, additional_salary
|
||||
@@ -1092,7 +1111,9 @@ class SalarySlip(TransactionBase):
|
||||
)[0][0]
|
||||
)
|
||||
|
||||
return total_tax_paid
|
||||
tax_deducted_till_date = self.get_opening_for("tax_deducted_till_date", start_date, end_date)
|
||||
|
||||
return total_tax_paid + tax_deducted_till_date
|
||||
|
||||
def get_taxable_earnings(
|
||||
self, allow_tax_exemption=False, based_on_payment_days=0, payroll_period=None
|
||||
|
||||
@@ -1030,6 +1030,104 @@ class TestSalarySlip(FrappeTestCase):
|
||||
activity_type.wage_rate = 25
|
||||
activity_type.save()
|
||||
|
||||
def test_salary_slip_generation_against_opening_entries_in_ssa(self):
|
||||
import math
|
||||
|
||||
from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor
|
||||
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
|
||||
|
||||
payroll_period = frappe.db.get_value(
|
||||
"Payroll Period",
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"start_date": ["<=", "2023-03-31"],
|
||||
"end_date": [">=", "2022-04-01"],
|
||||
},
|
||||
"name",
|
||||
)
|
||||
|
||||
if not payroll_period:
|
||||
payroll_period = create_payroll_period(
|
||||
name="_Test Payroll Period for Tax",
|
||||
company="_Test Company",
|
||||
start_date="2022-04-01",
|
||||
end_date="2023-03-31",
|
||||
)
|
||||
else:
|
||||
payroll_period = frappe.get_cached_doc("Payroll Period", payroll_period)
|
||||
|
||||
emp = make_employee(
|
||||
"test_employee_ss_with_opening_balance@salary.com",
|
||||
company="_Test Company",
|
||||
**{"date_of_joining": "2021-12-01"},
|
||||
)
|
||||
employee_doc = frappe.get_doc("Employee", emp)
|
||||
|
||||
create_tax_slab(payroll_period, allow_tax_exemption=True)
|
||||
|
||||
salary_structure_name = "Test Salary Structure for Opening Balance"
|
||||
if not frappe.db.exists("Salary Structure", salary_structure_name):
|
||||
salary_structure_doc = make_salary_structure(
|
||||
salary_structure_name,
|
||||
"Monthly",
|
||||
company="_Test Company",
|
||||
employee=emp,
|
||||
from_date="2022-04-01",
|
||||
payroll_period=payroll_period,
|
||||
test_tax=True,
|
||||
)
|
||||
|
||||
# validate no salary slip exists for the employee
|
||||
self.assertTrue(
|
||||
frappe.db.count(
|
||||
"Salary Slip",
|
||||
{
|
||||
"employee": emp,
|
||||
"salary_structure": salary_structure_doc.name,
|
||||
"docstatus": 1,
|
||||
"start_date": [">=", "2022-04-01"],
|
||||
},
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
remaining_sub_periods = get_period_factor(
|
||||
emp,
|
||||
get_first_day("2022-10-01"),
|
||||
get_last_day("2022-10-01"),
|
||||
"Monthly",
|
||||
payroll_period,
|
||||
depends_on_payment_days=0,
|
||||
)[1]
|
||||
|
||||
prev_period = math.ceil(remaining_sub_periods)
|
||||
|
||||
annual_tax = 93036 # 89220 #data[0].get('applicable_tax')
|
||||
monthly_tax_amount = 7732.40 # 7435 #annual_tax/12
|
||||
annual_earnings = 933600 # data[0].get('ctc')
|
||||
monthly_earnings = 77800 # annual_earnings/12
|
||||
|
||||
# Get Salary Structure Assignment
|
||||
ssa = frappe.get_value(
|
||||
"Salary Structure Assignment",
|
||||
{"employee": emp, "salary_structure": salary_structure_doc.name},
|
||||
"name",
|
||||
)
|
||||
ssa_doc = frappe.get_doc("Salary Structure Assignment", ssa)
|
||||
|
||||
# Set opening balance for earning and tax deduction in Salary Structure Assignment
|
||||
ssa_doc.taxable_earnings_till_date = monthly_earnings * prev_period
|
||||
ssa_doc.tax_deducted_till_date = monthly_tax_amount * prev_period
|
||||
ssa_doc.save()
|
||||
|
||||
# Create Salary Slip
|
||||
salary_slip = make_salary_slip(
|
||||
salary_structure_doc.name, employee=employee_doc.name, posting_date=getdate("2022-10-01")
|
||||
)
|
||||
for deduction in salary_slip.deductions:
|
||||
if deduction.salary_component == "TDS":
|
||||
self.assertEqual(deduction.amount, rounded(monthly_tax_amount))
|
||||
|
||||
|
||||
def get_no_of_days():
|
||||
no_of_days_in_month = calendar.monthrange(getdate(nowdate()).year, getdate(nowdate()).month)
|
||||
|
||||
@@ -42,6 +42,13 @@ frappe.ui.form.on('Salary Structure Assignment', {
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.__onload){
|
||||
frm.unhide_earnings_and_taxation_section = frm.doc.__onload.earning_and_deduction_entries_does_not_exists;
|
||||
frm.trigger("set_earnings_and_taxation_section_visibility");
|
||||
}
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if(frm.doc.employee){
|
||||
frappe.call({
|
||||
@@ -59,6 +66,8 @@ frappe.ui.form.on('Salary Structure Assignment', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.trigger("valiadte_joining_date_and_salary_slips");
|
||||
}
|
||||
else{
|
||||
frm.set_value("company", null);
|
||||
@@ -71,5 +80,33 @@ frappe.ui.form.on('Salary Structure Assignment', {
|
||||
frm.set_value("payroll_payable_account", r.default_payroll_payable_account);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
valiadte_joining_date_and_salary_slips: function(frm) {
|
||||
frappe.call({
|
||||
method: "earning_and_deduction_entries_does_not_exists",
|
||||
doc: frm.doc,
|
||||
callback: function(data) {
|
||||
let earning_and_deduction_entries_does_not_exists = data.message;
|
||||
frm.unhide_earnings_and_taxation_section = earning_and_deduction_entries_does_not_exists;
|
||||
frm.trigger("set_earnings_and_taxation_section_visibility");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_earnings_and_taxation_section_visibility: function(frm) {
|
||||
if(frm.unhide_earnings_and_taxation_section){
|
||||
frm.set_df_property('earnings_and_taxation_section', 'hidden', 0);
|
||||
}
|
||||
else{
|
||||
frm.set_df_property('earnings_and_taxation_section', 'hidden', 1);
|
||||
}
|
||||
},
|
||||
|
||||
from_date: function(frm) {
|
||||
if (frm.doc.from_date) {
|
||||
frm.trigger("valiadte_joining_date_and_salary_slips" );
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
"base",
|
||||
"column_break_9",
|
||||
"variable",
|
||||
"earnings_and_taxation_section",
|
||||
"tax_deducted_till_date",
|
||||
"column_break_18",
|
||||
"taxable_earnings_till_date",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@@ -141,11 +145,31 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Payroll Payable Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "earnings_and_taxation_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_deducted_till_date",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Tax Deducted Till Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "taxable_earnings_till_date",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Taxable Earnings Till Date"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-31 22:44:46.267974",
|
||||
"modified": "2022-12-21 17:06:38.602361",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Salary Structure Assignment",
|
||||
|
||||
@@ -13,10 +13,36 @@ class DuplicateAssignment(frappe.ValidationError):
|
||||
|
||||
|
||||
class SalaryStructureAssignment(Document):
|
||||
def onload(self):
|
||||
if self.employee:
|
||||
self.set_onload(
|
||||
"earning_and_deduction_entries_exists", self.earning_and_deduction_entries_does_not_exists()
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_income_tax_slab()
|
||||
self.set_payroll_payable_account()
|
||||
self.valiadte_missing_taxable_earnings_and_deductions_till_date()
|
||||
|
||||
def valiadte_missing_taxable_earnings_and_deductions_till_date(self):
|
||||
if self.earning_and_deduction_entries_does_not_exists():
|
||||
if not self.taxable_earnings_till_date and not self.tax_deducted_till_date:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"""
|
||||
Not found any salary slip record(s) for the employee {0}. <br><br>
|
||||
Please specify {1} and {2} (if any),
|
||||
for the correct tax calculation in future salary slips.
|
||||
"""
|
||||
).format(
|
||||
self.employee,
|
||||
"<b>" + _("Taxable Earnings Till Date") + "</b>",
|
||||
"<b>" + _("Tax Deducted Till Date") + "</b>",
|
||||
),
|
||||
indicator="orange",
|
||||
title=_("Warning"),
|
||||
)
|
||||
|
||||
def validate_dates(self):
|
||||
joining_date, relieving_date = frappe.db.get_value(
|
||||
@@ -76,6 +102,56 @@ class SalaryStructureAssignment(Document):
|
||||
)
|
||||
self.payroll_payable_account = payroll_payable_account
|
||||
|
||||
@frappe.whitelist()
|
||||
def earning_and_deduction_entries_does_not_exists(self):
|
||||
if self.enabled_settings_to_specify_earnings_and_deductions_till_date():
|
||||
if not self.joined_in_the_same_month() and not self.have_salary_slips():
|
||||
return True
|
||||
else:
|
||||
if self.docstatus in [1, 2] and (
|
||||
self.taxable_earnings_till_date or self.tax_deducted_till_date
|
||||
):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def enabled_settings_to_specify_earnings_and_deductions_till_date(self):
|
||||
"""returns True if settings are enabled to specify earnings and deductions till date else False"""
|
||||
|
||||
if frappe.db.get_single_value(
|
||||
"Payroll Settings", "define_opening_balance_for_earning_and_deductions"
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def have_salary_slips(self):
|
||||
"""returns True if salary structure assignment has salary slips else False"""
|
||||
|
||||
salary_slip = frappe.db.get_value(
|
||||
"Salary Slip", filters={"employee": self.employee, "docstatus": 1}
|
||||
)
|
||||
|
||||
if salary_slip:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def joined_in_the_same_month(self):
|
||||
"""returns True if employee joined in same month as salary structure assignment from date else False"""
|
||||
|
||||
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
|
||||
from_date = getdate(self.from_date)
|
||||
|
||||
if not self.from_date or not date_of_joining:
|
||||
return False
|
||||
|
||||
elif date_of_joining.month == from_date.month:
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_assigned_salary_structure(employee, on_date):
|
||||
if not employee or not on_date:
|
||||
|
||||
Reference in New Issue
Block a user