Merge branch 'develop' into fix-salary-slip-bg-job

This commit is contained in:
Rucha Mahabal
2022-06-01 12:15:42 +05:30
committed by GitHub
99 changed files with 2872 additions and 1709 deletions

View File

@@ -33,7 +33,9 @@ class EmployeeTaxExemptionDeclaration(Document):
self.total_declared_amount += flt(d.amount)
def set_total_exemption_amount(self):
self.total_exemption_amount = get_total_exemption_amount(self.declarations)
self.total_exemption_amount = flt(
get_total_exemption_amount(self.declarations), self.precision("total_exemption_amount")
)
def calculate_hra_exemption(self):
self.salary_structure_hra, self.annual_hra_exemption, self.monthly_hra_exemption = 0, 0, 0
@@ -41,9 +43,18 @@ class EmployeeTaxExemptionDeclaration(Document):
hra_exemption = calculate_annual_eligible_hra_exemption(self)
if hra_exemption:
self.total_exemption_amount += hra_exemption["annual_exemption"]
self.salary_structure_hra = hra_exemption["hra_amount"]
self.annual_hra_exemption = hra_exemption["annual_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
self.total_exemption_amount = flt(
self.total_exemption_amount, self.precision("total_exemption_amount")
)
self.salary_structure_hra = flt(
hra_exemption["hra_amount"], self.precision("salary_structure_hra")
)
self.annual_hra_exemption = flt(
hra_exemption["annual_exemption"], self.precision("annual_hra_exemption")
)
self.monthly_hra_exemption = flt(
hra_exemption["monthly_exemption"], self.precision("monthly_hra_exemption")
)
@frappe.whitelist()

View File

@@ -4,25 +4,28 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_months, getdate
import erpnext
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.utils import DuplicateDeclarationError
class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
class TestEmployeeTaxExemptionDeclaration(FrappeTestCase):
def setUp(self):
make_employee("employee@taxexepmtion.com")
make_employee("employee1@taxexepmtion.com")
create_payroll_period()
make_employee("employee@taxexemption.com", company="_Test Company")
make_employee("employee1@taxexemption.com", company="_Test Company")
create_payroll_period(company="_Test Company")
create_exemption_category()
frappe.db.sql("""delete from `tabEmployee Tax Exemption Declaration`""")
frappe.db.delete("Employee Tax Exemption Declaration")
frappe.db.delete("Salary Structure Assignment")
def test_duplicate_category_in_declaration(self):
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"),
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"currency": erpnext.get_default_currency(),
@@ -46,7 +49,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"),
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"currency": erpnext.get_default_currency(),
@@ -68,7 +71,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
duplicate_declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"),
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"currency": erpnext.get_default_currency(),
@@ -83,7 +86,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
)
self.assertRaises(DuplicateDeclarationError, duplicate_declaration.insert)
duplicate_declaration.employee = frappe.get_value(
"Employee", {"user_id": "employee1@taxexepmtion.com"}, "name"
"Employee", {"user_id": "employee1@taxexemption.com"}, "name"
)
self.assertTrue(duplicate_declaration.insert)
@@ -91,7 +94,7 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexepmtion.com"}, "name"),
"employee": frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name"),
"company": erpnext.get_default_company(),
"payroll_period": "_Test Payroll Period",
"currency": erpnext.get_default_currency(),
@@ -112,6 +115,298 @@ class TestEmployeeTaxExemptionDeclaration(unittest.TestCase):
self.assertEqual(declaration.total_exemption_amount, 100000)
def test_india_hra_exemption(self):
# set country
current_country = frappe.flags.country
frappe.flags.country = "India"
setup_hra_exemption_prerequisites("Monthly")
employee = frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name")
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"company": "_Test Company",
"payroll_period": "_Test Payroll Period",
"currency": "INR",
"monthly_house_rent": 50000,
"rented_in_metro_city": 1,
"declarations": [
dict(
exemption_sub_category="_Test Sub Category",
exemption_category="_Test Category",
amount=80000,
),
dict(
exemption_sub_category="_Test1 Sub Category",
exemption_category="_Test Category",
amount=60000,
),
],
}
).insert()
# Monthly HRA received = 3000
# should set HRA exemption as per actual annual HRA because that's the minimum
self.assertEqual(declaration.monthly_hra_exemption, 3000)
self.assertEqual(declaration.annual_hra_exemption, 36000)
# 100000 Standard Exemption + 36000 HRA exemption
self.assertEqual(declaration.total_exemption_amount, 136000)
# reset
frappe.flags.country = current_country
def test_india_hra_exemption_with_daily_payroll_frequency(self):
# set country
current_country = frappe.flags.country
frappe.flags.country = "India"
setup_hra_exemption_prerequisites("Daily")
employee = frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name")
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"company": "_Test Company",
"payroll_period": "_Test Payroll Period",
"currency": "INR",
"monthly_house_rent": 170000,
"rented_in_metro_city": 1,
"declarations": [
dict(
exemption_sub_category="_Test1 Sub Category",
exemption_category="_Test Category",
amount=60000,
),
],
}
).insert()
# Daily HRA received = 3000
# should set HRA exemption as per (rent - 10% of Basic Salary), that's the minimum
self.assertEqual(declaration.monthly_hra_exemption, 17916.67)
self.assertEqual(declaration.annual_hra_exemption, 215000)
# 50000 Standard Exemption + 215000 HRA exemption
self.assertEqual(declaration.total_exemption_amount, 265000)
# reset
frappe.flags.country = current_country
def test_india_hra_exemption_with_weekly_payroll_frequency(self):
# set country
current_country = frappe.flags.country
frappe.flags.country = "India"
setup_hra_exemption_prerequisites("Weekly")
employee = frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name")
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"company": "_Test Company",
"payroll_period": "_Test Payroll Period",
"currency": "INR",
"monthly_house_rent": 170000,
"rented_in_metro_city": 1,
"declarations": [
dict(
exemption_sub_category="_Test1 Sub Category",
exemption_category="_Test Category",
amount=60000,
),
],
}
).insert()
# Weekly HRA received = 3000
# should set HRA exemption as per actual annual HRA because that's the minimum
self.assertEqual(declaration.monthly_hra_exemption, 13000)
self.assertEqual(declaration.annual_hra_exemption, 156000)
# 50000 Standard Exemption + 156000 HRA exemption
self.assertEqual(declaration.total_exemption_amount, 206000)
# reset
frappe.flags.country = current_country
def test_india_hra_exemption_with_fortnightly_payroll_frequency(self):
# set country
current_country = frappe.flags.country
frappe.flags.country = "India"
setup_hra_exemption_prerequisites("Fortnightly")
employee = frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name")
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"company": "_Test Company",
"payroll_period": "_Test Payroll Period",
"currency": "INR",
"monthly_house_rent": 170000,
"rented_in_metro_city": 1,
"declarations": [
dict(
exemption_sub_category="_Test1 Sub Category",
exemption_category="_Test Category",
amount=60000,
),
],
}
).insert()
# Fortnightly HRA received = 3000
# should set HRA exemption as per actual annual HRA because that's the minimum
self.assertEqual(declaration.monthly_hra_exemption, 6500)
self.assertEqual(declaration.annual_hra_exemption, 78000)
# 50000 Standard Exemption + 78000 HRA exemption
self.assertEqual(declaration.total_exemption_amount, 128000)
# reset
frappe.flags.country = current_country
def test_india_hra_exemption_with_bimonthly_payroll_frequency(self):
# set country
current_country = frappe.flags.country
frappe.flags.country = "India"
setup_hra_exemption_prerequisites("Bimonthly")
employee = frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name")
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"company": "_Test Company",
"payroll_period": "_Test Payroll Period",
"currency": "INR",
"monthly_house_rent": 50000,
"rented_in_metro_city": 1,
"declarations": [
dict(
exemption_sub_category="_Test Sub Category",
exemption_category="_Test Category",
amount=80000,
),
dict(
exemption_sub_category="_Test1 Sub Category",
exemption_category="_Test Category",
amount=60000,
),
],
}
).insert()
# Bimonthly HRA received = 3000
# should set HRA exemption as per actual annual HRA because that's the minimum
self.assertEqual(declaration.monthly_hra_exemption, 1500)
self.assertEqual(declaration.annual_hra_exemption, 18000)
# 100000 Standard Exemption + 18000 HRA exemption
self.assertEqual(declaration.total_exemption_amount, 118000)
# reset
frappe.flags.country = current_country
def test_india_hra_exemption_with_multiple_salary_structure_assignments(self):
from erpnext.payroll.doctype.salary_slip.test_salary_slip import create_tax_slab
from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
create_salary_structure_assignment,
make_salary_structure,
)
# set country
current_country = frappe.flags.country
frappe.flags.country = "India"
employee = make_employee("employee@taxexemption2.com", company="_Test Company")
payroll_period = create_payroll_period(name="_Test Payroll Period", company="_Test Company")
create_tax_slab(
payroll_period,
allow_tax_exemption=True,
currency="INR",
effective_date=getdate("2019-04-01"),
company="_Test Company",
)
frappe.db.set_value(
"Company", "_Test Company", {"basic_component": "Basic Salary", "hra_component": "HRA"}
)
# salary structure with base 50000, HRA 3000
make_salary_structure(
"Monthly Structure for HRA Exemption 1",
"Monthly",
employee=employee,
company="_Test Company",
currency="INR",
payroll_period=payroll_period.name,
from_date=payroll_period.start_date,
)
# salary structure with base 70000, HRA = base * 0.2 = 14000
salary_structure = make_salary_structure(
"Monthly Structure for HRA Exemption 2",
"Monthly",
employee=employee,
company="_Test Company",
currency="INR",
payroll_period=payroll_period.name,
from_date=payroll_period.start_date,
dont_submit=True,
)
for component_row in salary_structure.earnings:
if component_row.salary_component == "HRA":
component_row.amount = 0
component_row.amount_based_on_formula = 1
component_row.formula = "base * 0.2"
break
salary_structure.submit()
create_salary_structure_assignment(
employee,
salary_structure.name,
from_date=add_months(payroll_period.start_date, 6),
company="_Test Company",
currency="INR",
payroll_period=payroll_period.name,
base=70000,
allow_duplicate=True,
)
declaration = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Declaration",
"employee": employee,
"company": "_Test Company",
"payroll_period": payroll_period.name,
"currency": "INR",
"monthly_house_rent": 50000,
"rented_in_metro_city": 1,
"declarations": [
dict(
exemption_sub_category="_Test1 Sub Category",
exemption_category="_Test Category",
amount=60000,
),
],
}
).insert()
# Monthly HRA received = 50000 * 6 months + 70000 * 6 months
# should set HRA exemption as per actual annual HRA because that's the minimum
self.assertEqual(declaration.monthly_hra_exemption, 8500)
self.assertEqual(declaration.annual_hra_exemption, 102000)
# 50000 Standard Exemption + 102000 HRA exemption
self.assertEqual(declaration.total_exemption_amount, 152000)
# reset
frappe.flags.country = current_country
def create_payroll_period(**args):
args = frappe._dict(args)
@@ -163,3 +458,33 @@ def create_exemption_category():
"is_active": 1,
}
).insert()
def setup_hra_exemption_prerequisites(frequency, employee=None):
from erpnext.payroll.doctype.salary_slip.test_salary_slip import create_tax_slab
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
payroll_period = create_payroll_period(name="_Test Payroll Period", company="_Test Company")
if not employee:
employee = frappe.get_value("Employee", {"user_id": "employee@taxexemption.com"}, "name")
create_tax_slab(
payroll_period,
allow_tax_exemption=True,
currency="INR",
effective_date=getdate("2019-04-01"),
company="_Test Company",
)
make_salary_structure(
f"{frequency} Structure for HRA Exemption",
frequency,
employee=employee,
company="_Test Company",
currency="INR",
payroll_period=payroll_period,
)
frappe.db.set_value(
"Company", "_Test Company", {"basic_component": "Basic Salary", "hra_component": "HRA"}
)

View File

@@ -31,7 +31,9 @@ class EmployeeTaxExemptionProofSubmission(Document):
self.total_actual_amount += flt(d.amount)
def set_total_exemption_amount(self):
self.exemption_amount = get_total_exemption_amount(self.tax_exemption_proofs)
self.exemption_amount = flt(
get_total_exemption_amount(self.tax_exemption_proofs), self.precision("exemption_amount")
)
def calculate_hra_exemption(self):
self.monthly_hra_exemption, self.monthly_house_rent, self.total_eligible_hra_exemption = 0, 0, 0
@@ -39,6 +41,13 @@ class EmployeeTaxExemptionProofSubmission(Document):
hra_exemption = calculate_hra_exemption_for_period(self)
if hra_exemption:
self.exemption_amount += hra_exemption["total_eligible_hra_exemption"]
self.monthly_hra_exemption = hra_exemption["monthly_exemption"]
self.monthly_house_rent = hra_exemption["monthly_house_rent"]
self.total_eligible_hra_exemption = hra_exemption["total_eligible_hra_exemption"]
self.exemption_amount = flt(self.exemption_amount, self.precision("exemption_amount"))
self.monthly_hra_exemption = flt(
hra_exemption["monthly_exemption"], self.precision("monthly_hra_exemption")
)
self.monthly_house_rent = flt(
hra_exemption["monthly_house_rent"], self.precision("monthly_house_rent")
)
self.total_eligible_hra_exemption = flt(
hra_exemption["total_eligible_hra_exemption"], self.precision("total_eligible_hra_exemption")
)

View File

@@ -4,22 +4,26 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import (
create_exemption_category,
create_payroll_period,
setup_hra_exemption_prerequisites,
)
class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase):
def setup(self):
make_employee("employee@proofsubmission.com")
create_payroll_period()
class TestEmployeeTaxExemptionProofSubmission(FrappeTestCase):
def setUp(self):
make_employee("employee@proofsubmission.com", company="_Test Company")
create_payroll_period(company="_Test Company")
create_exemption_category()
frappe.db.sql("""delete from `tabEmployee Tax Exemption Proof Submission`""")
frappe.db.delete("Employee Tax Exemption Proof Submission")
frappe.db.delete("Salary Structure Assignment")
def test_exemption_amount_lesser_than_category_max(self):
declaration = frappe.get_doc(
proof = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Proof Submission",
"employee": frappe.get_value("Employee", {"user_id": "employee@proofsubmission.com"}, "name"),
@@ -34,8 +38,8 @@ class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase):
],
}
)
self.assertRaises(frappe.ValidationError, declaration.save)
declaration = frappe.get_doc(
self.assertRaises(frappe.ValidationError, proof.save)
proof = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Proof Submission",
"payroll_period": "Test Payroll Period",
@@ -50,11 +54,11 @@ class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase):
],
}
)
self.assertTrue(declaration.save)
self.assertTrue(declaration.submit)
self.assertTrue(proof.save)
self.assertTrue(proof.submit)
def test_duplicate_category_in_proof_submission(self):
declaration = frappe.get_doc(
proof = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Proof Submission",
"employee": frappe.get_value("Employee", {"user_id": "employee@proofsubmission.com"}, "name"),
@@ -74,4 +78,59 @@ class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase):
],
}
)
self.assertRaises(frappe.ValidationError, declaration.save)
self.assertRaises(frappe.ValidationError, proof.save)
def test_india_hra_exemption(self):
# set country
current_country = frappe.flags.country
frappe.flags.country = "India"
employee = frappe.get_value("Employee", {"user_id": "employee@proofsubmission.com"}, "name")
setup_hra_exemption_prerequisites("Monthly", employee)
payroll_period = frappe.db.get_value(
"Payroll Period", "_Test Payroll Period", ["start_date", "end_date"], as_dict=True
)
proof = frappe.get_doc(
{
"doctype": "Employee Tax Exemption Proof Submission",
"employee": employee,
"company": "_Test Company",
"payroll_period": "_Test Payroll Period",
"currency": "INR",
"house_rent_payment_amount": 600000,
"rented_in_metro_city": 1,
"rented_from_date": payroll_period.start_date,
"rented_to_date": payroll_period.end_date,
"tax_exemption_proofs": [
dict(
exemption_sub_category="_Test Sub Category",
exemption_category="_Test Category",
type_of_proof="Test Proof",
amount=100000,
),
dict(
exemption_sub_category="_Test1 Sub Category",
exemption_category="_Test Category",
type_of_proof="Test Proof",
amount=50000,
),
],
}
).insert()
self.assertEqual(proof.monthly_house_rent, 50000)
# Monthly HRA received = 3000
# should set HRA exemption as per actual annual HRA because that's the minimum
self.assertEqual(proof.monthly_hra_exemption, 3000)
self.assertEqual(proof.total_eligible_hra_exemption, 36000)
# total exemptions + house rent payment amount
self.assertEqual(proof.total_actual_amount, 750000)
# 100000 Standard Exemption + 36000 HRA exemption
self.assertEqual(proof.exemption_amount, 136000)
# reset
frappe.flags.country = current_country

View File

@@ -76,9 +76,8 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Draft\nUnpaid\nPaid",
"read_only": 1,
"reqd": 1
"options": "Draft\nUnpaid\nPaid\nSubmitted\nCancelled",
"read_only": 1
},
{
"depends_on": "eval: !doc.pay_via_salary_slip",
@@ -194,7 +193,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-02-02 14:00:45.536152",
"modified": "2022-05-27 13:56:14.349183",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Gratuity",

View File

@@ -0,0 +1,12 @@
frappe.listview_settings["Gratuity"] = {
get_indicator: function(doc) {
let status_color = {
"Draft": "red",
"Submitted": "blue",
"Cancelled": "red",
"Paid": "green",
"Unpaid": "orange",
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
}
};

View File

@@ -4,57 +4,69 @@
import unittest
import frappe
from frappe.utils import add_days, flt, get_datetime, getdate
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, add_months, floor, flt, get_datetime, get_first_day, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
from erpnext.payroll.doctype.gratuity.gratuity import get_last_salary_slip
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
make_deduction_salary_component,
make_earning_salary_component,
make_employee_salary_slip,
make_holiday_list,
)
from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.regional.united_arab_emirates.setup import create_gratuity_rule
test_dependencies = ["Salary Component", "Salary Slip", "Account"]
class TestGratuity(unittest.TestCase):
class TestGratuity(FrappeTestCase):
def setUp(self):
frappe.db.delete("Gratuity")
frappe.db.delete("Salary Slip")
frappe.db.delete("Additional Salary", {"ref_doctype": "Gratuity"})
make_earning_salary_component(
setup=True, test_tax=True, company_list=["_Test Company"], include_flexi_benefits=True
)
make_deduction_salary_component(setup=True, test_tax=True, company_list=["_Test Company"])
make_holiday_list()
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_get_last_salary_slip_should_return_none_for_new_employee(self):
new_employee = make_employee("new_employee@salary.com", company="_Test Company")
salary_slip = get_last_salary_slip(new_employee)
assert salary_slip is None
self.assertIsNone(salary_slip)
def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self):
employee, sal_slip = create_employee_and_get_last_salary_slip()
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_gratuity_based_on_current_slab_via_additional_salary(self):
"""
Range | Fraction
5-0 | 1
"""
doj = add_days(getdate(), -(6 * 365))
relieving_date = getdate()
employee = make_employee(
"test_employee_gratuity@salary.com",
company="_Test Company",
date_of_joining=doj,
relieving_date=relieving_date,
)
sal_slip = create_salary_slip("test_employee_gratuity@salary.com")
rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)")
gratuity = create_gratuity(pay_via_salary_slip=1, employee=employee, rule=rule.name)
# work experience calculation
date_of_joining, relieving_date = frappe.db.get_value(
"Employee", employee, ["date_of_joining", "relieving_date"]
)
employee_total_workings_days = (
get_datetime(relieving_date) - get_datetime(date_of_joining)
).days
employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(doj)).days
experience = floor(employee_total_workings_days / rule.total_working_days_per_year)
self.assertEqual(gratuity.current_work_experience, experience)
experience = employee_total_workings_days / rule.total_working_days_per_year
gratuity.reload()
from math import floor
self.assertEqual(floor(experience), gratuity.current_work_experience)
# amount Calculation
# amount calculation
component_amount = frappe.get_all(
"Salary Detail",
filters={
@@ -64,20 +76,44 @@ class TestGratuity(unittest.TestCase):
"salary_component": "Basic Salary",
},
fields=["amount"],
limit=1,
)
""" 5 - 0 fraction is 1 """
gratuity_amount = component_amount[0].amount * experience
gratuity.reload()
self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
# additional salary creation (Pay via salary slip)
self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name}))
def test_check_gratuity_amount_based_on_all_previous_slabs(self):
employee, sal_slip = create_employee_and_get_last_salary_slip()
# gratuity should be marked "Paid" on the next salary slip submission
salary_slip = make_salary_slip("Test Gratuity", employee=employee)
salary_slip.posting_date = getdate()
salary_slip.insert()
salary_slip.submit()
gratuity.reload()
self.assertEqual(gratuity.status, "Paid")
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
def test_gratuity_based_on_all_previous_slabs_via_payment_entry(self):
"""
Range | Fraction
0-1 | 0
1-5 | 0.7
5-0 | 1
"""
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
doj = add_days(getdate(), -(6 * 365))
relieving_date = getdate()
employee = make_employee(
"test_employee_gratuity@salary.com",
company="_Test Company",
date_of_joining=doj,
relieving_date=relieving_date,
)
sal_slip = create_salary_slip("test_employee_gratuity@salary.com")
rule = get_gratuity_rule("Rule Under Limited Contract (UAE)")
set_mode_of_payment_account()
@@ -86,22 +122,11 @@ class TestGratuity(unittest.TestCase):
)
# work experience calculation
date_of_joining, relieving_date = frappe.db.get_value(
"Employee", employee, ["date_of_joining", "relieving_date"]
)
employee_total_workings_days = (
get_datetime(relieving_date) - get_datetime(date_of_joining)
).days
employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(doj)).days
experience = floor(employee_total_workings_days / rule.total_working_days_per_year)
self.assertEqual(gratuity.current_work_experience, experience)
experience = employee_total_workings_days / rule.total_working_days_per_year
gratuity.reload()
from math import floor
self.assertEqual(floor(experience), gratuity.current_work_experience)
# amount Calculation
# amount calculation
component_amount = frappe.get_all(
"Salary Detail",
filters={
@@ -111,35 +136,22 @@ class TestGratuity(unittest.TestCase):
"salary_component": "Basic Salary",
},
fields=["amount"],
limit=1,
)
""" range | Fraction
0-1 | 0
1-5 | 0.7
5-0 | 1
"""
gratuity_amount = ((0 * 1) + (4 * 0.7) + (1 * 1)) * component_amount[0].amount
gratuity.reload()
self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
self.assertEqual(gratuity.status, "Unpaid")
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
pe = get_payment_entry("Gratuity", gratuity.name)
pe.reference_no = "123467"
pe.reference_date = getdate()
pe.submit()
pay_entry = get_payment_entry("Gratuity", gratuity.name)
pay_entry.reference_no = "123467"
pay_entry.reference_date = getdate()
pay_entry.save()
pay_entry.submit()
gratuity.reload()
self.assertEqual(gratuity.status, "Paid")
self.assertEqual(flt(gratuity.paid_amount, 2), flt(gratuity.amount, 2))
def tearDown(self):
frappe.db.rollback()
def get_gratuity_rule(name):
rule = frappe.db.exists("Gratuity Rule", name)
@@ -149,7 +161,6 @@ def get_gratuity_rule(name):
rule.applicable_earnings_component = []
rule.append("applicable_earnings_component", {"salary_component": "Basic Salary"})
rule.save()
rule.reload()
return rule
@@ -204,23 +215,17 @@ def create_account():
).insert(ignore_permissions=True)
def create_employee_and_get_last_salary_slip():
employee = make_employee("test_employee@salary.com", company="_Test Company")
frappe.db.set_value("Employee", employee, "relieving_date", getdate())
frappe.db.set_value("Employee", employee, "date_of_joining", add_days(getdate(), -(6 * 365)))
def create_salary_slip(employee):
if not frappe.db.exists("Salary Slip", {"employee": employee}):
salary_slip = make_employee_salary_slip("test_employee@salary.com", "Monthly")
posting_date = get_first_day(add_months(getdate(), -1))
salary_slip = make_employee_salary_slip(
employee, "Monthly", "Test Gratuity", posting_date=posting_date
)
salary_slip.start_date = posting_date
salary_slip.end_date = None
salary_slip.submit()
salary_slip = salary_slip.name
else:
salary_slip = get_last_salary_slip(employee)
if not frappe.db.get_value("Employee", "test_employee@salary.com", "holiday_list"):
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
make_holiday_list()
frappe.db.set_value(
"Company", "_Test Company", "default_holiday_list", "Salary Slip Test Holiday List"
)
return employee, salary_slip
return salary_slip

View File

@@ -49,6 +49,7 @@ class PayrollEntry(Document):
def before_submit(self):
self.validate_employee_details()
self.validate_payroll_payable_account()
if self.validate_attendance:
if self.validate_employee_attendance():
frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
@@ -79,6 +80,14 @@ class PayrollEntry(Document):
if len(emp_with_sal_slip):
frappe.throw(_("Salary Slip already exists for {0}").format(comma_and(emp_with_sal_slip)))
def validate_payroll_payable_account(self):
if frappe.db.get_value("Account", self.payroll_payable_account, "account_type"):
frappe.throw(
_(
"Account type cannot be set for payroll payable account {0}, please remove and try again"
).format(frappe.bold(get_link_to_form("Account", self.payroll_payable_account)))
)
def on_cancel(self):
frappe.delete_doc(
"Salary Slip",

View File

@@ -29,6 +29,9 @@ from erpnext.loan_management.doctype.loan_repayment.loan_repayment import (
calculate_amounts,
create_repayment_entry,
)
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
process_loan_interest_accrual_for_term_loans,
)
from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import (
get_benefit_component_amount,
@@ -116,10 +119,10 @@ class SalarySlip(TransactionBase):
self.update_payment_status_for_gratuity()
def update_payment_status_for_gratuity(self):
add_salary = frappe.db.get_all(
additional_salary = frappe.db.get_all(
"Additional Salary",
filters={
"payroll_date": ("BETWEEN", [self.start_date, self.end_date]),
"payroll_date": ("between", [self.start_date, self.end_date]),
"employee": self.employee,
"ref_doctype": "Gratuity",
"docstatus": 1,
@@ -128,10 +131,10 @@ class SalarySlip(TransactionBase):
limit=1,
)
if len(add_salary):
if additional_salary:
status = "Paid" if self.docstatus == 1 else "Unpaid"
if add_salary[0].name in [data.additional_salary for data in self.earnings]:
frappe.db.set_value("Gratuity", add_salary.ref_docname, "status", status)
if additional_salary[0].name in [entry.additional_salary for entry in self.earnings]:
frappe.db.set_value("Gratuity", additional_salary[0].ref_docname, "status", status)
def on_cancel(self):
self.set_status()
@@ -1364,9 +1367,9 @@ class SalarySlip(TransactionBase):
self.total_loan_repayment += payment.total_payment
def get_loan_details(self):
return frappe.get_all(
loan_details = frappe.get_all(
"Loan",
fields=["name", "interest_income_account", "loan_account", "loan_type"],
fields=["name", "interest_income_account", "loan_account", "loan_type", "is_term_loan"],
filters={
"applicant": self.employee,
"docstatus": 1,
@@ -1375,6 +1378,15 @@ class SalarySlip(TransactionBase):
},
)
if loan_details:
for loan in loan_details:
if loan.is_term_loan:
process_loan_interest_accrual_for_term_loans(
posting_date=self.posting_date, loan_type=loan.loan_type, loan=loan.name
)
return loan_details
def make_loan_repayment_entry(self):
payroll_payable_account = get_payroll_payable_account(self.company, self.payroll_entry)
for loan in self.loans:

View File

@@ -997,7 +997,7 @@ class TestSalarySlip(unittest.TestCase):
return [no_of_days_in_month[1], no_of_holidays_in_month]
def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
def make_employee_salary_slip(user, payroll_frequency, salary_structure=None, posting_date=None):
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
if not salary_structure:
@@ -1008,7 +1008,11 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
)
salary_structure_doc = make_salary_structure(
salary_structure, payroll_frequency, employee=employee.name, company=employee.company
salary_structure,
payroll_frequency,
employee=employee.name,
company=employee.company,
from_date=posting_date,
)
salary_slip_name = frappe.db.get_value(
"Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}
@@ -1018,7 +1022,7 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None):
salary_slip = make_salary_slip(salary_structure_doc.name, employee=employee.name)
salary_slip.employee_name = employee.employee_name
salary_slip.payroll_frequency = payroll_frequency
salary_slip.posting_date = nowdate()
salary_slip.posting_date = posting_date or nowdate()
salary_slip.insert()
else:
salary_slip = frappe.get_doc("Salary Slip", salary_slip_name)

View File

@@ -253,6 +253,7 @@ def make_salary_slip(
source_name,
target_doc=None,
employee=None,
posting_date=None,
as_print=False,
print_format=None,
for_preview=0,
@@ -269,6 +270,9 @@ def make_salary_slip(
target.designation = employee_details.designation
target.department = employee_details.department
if posting_date:
target.posting_date = posting_date
target.run_method("process_salary_structure", for_preview=for_preview)
doc = get_mapped_doc(

View File

@@ -227,9 +227,12 @@ def create_salary_structure_assignment(
company=None,
currency=erpnext.get_default_currency(),
payroll_period=None,
base=None,
allow_duplicate=False,
):
if frappe.db.exists("Salary Structure Assignment", {"employee": employee}):
if not allow_duplicate and frappe.db.exists(
"Salary Structure Assignment", {"employee": employee}
):
frappe.db.sql("""delete from `tabSalary Structure Assignment` where employee=%s""", (employee))
if not payroll_period:
@@ -242,7 +245,7 @@ def create_salary_structure_assignment(
salary_structure_assignment = frappe.new_doc("Salary Structure Assignment")
salary_structure_assignment.employee = employee
salary_structure_assignment.base = 50000
salary_structure_assignment.base = base or 50000
salary_structure_assignment.variable = 5000
if not from_date: