Merge pull request #31104 from ruchamahabal/emp-adv-status-v13

feat(Employee Advance): add 'Returned' and 'Partly Claimed and Returned'
This commit is contained in:
Rucha Mahabal
2022-05-25 11:09:52 +05:30
committed by GitHub
9 changed files with 216 additions and 32 deletions

View File

@@ -2,7 +2,7 @@
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2017-10-09 14:26:29.612365",
"creation": "2022-01-17 18:36:51.450395",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@@ -121,7 +121,7 @@
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled",
"options": "Draft\nPaid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled",
"read_only": 1
},
{
@@ -200,7 +200,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2021-09-11 18:38:38.617478",
"modified": "2022-05-23 19:33:52.345823",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
@@ -236,5 +236,6 @@
"search_fields": "employee,employee_name",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "employee_name",
"track_changes": 1
}

View File

@@ -29,19 +29,43 @@ class EmployeeAdvance(Document):
def on_cancel(self):
self.ignore_linked_doctypes = "GL Entry"
self.set_status(update=True)
def set_status(self, update=False):
precision = self.precision("paid_amount")
total_amount = flt(flt(self.claimed_amount) + flt(self.return_amount), precision)
status = None
def set_status(self):
if self.docstatus == 0:
self.status = "Draft"
if self.docstatus == 1:
if self.claimed_amount and flt(self.claimed_amount) == flt(self.paid_amount):
self.status = "Claimed"
elif self.paid_amount and self.advance_amount == flt(self.paid_amount):
self.status = "Paid"
status = "Draft"
elif self.docstatus == 1:
if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(
self.paid_amount, precision
):
status = "Claimed"
elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(
self.paid_amount, precision
):
status = "Returned"
elif (
flt(self.claimed_amount) > 0
and (flt(self.return_amount) > 0)
and total_amount == flt(self.paid_amount, precision)
):
status = "Partly Claimed and Returned"
elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(
self.paid_amount, precision
):
status = "Paid"
else:
self.status = "Unpaid"
status = "Unpaid"
elif self.docstatus == 2:
self.status = "Cancelled"
status = "Cancelled"
if update:
self.db_set("status", status)
else:
self.status = status
def set_total_advance_paid(self):
gle = frappe.qb.DocType("GL Entry")
@@ -89,8 +113,7 @@ class EmployeeAdvance(Document):
self.db_set("paid_amount", paid_amount)
self.db_set("return_amount", return_amount)
self.set_status()
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
self.set_status(update=True)
def update_claimed_amount(self):
claimed_amount = (
@@ -112,8 +135,7 @@ class EmployeeAdvance(Document):
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload()
self.set_status()
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
self.set_status(update=True)
@frappe.whitelist()
@@ -265,6 +287,7 @@ def make_return_entry(
"party_type": "Employee",
"party": employee,
"is_advance": "Yes",
"cost_center": erpnext.get_default_cost_center(company),
},
)
@@ -282,6 +305,7 @@ def make_return_entry(
"account_currency": bank_cash_account.account_currency,
"account_type": bank_cash_account.account_type,
"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
"cost_center": erpnext.get_default_cost_center(company),
},
)

View File

@@ -0,0 +1,15 @@
frappe.listview_settings["Employee Advance"] = {
get_indicator: function(doc) {
let status_color = {
"Draft": "red",
"Submitted": "blue",
"Cancelled": "red",
"Paid": "green",
"Unpaid": "orange",
"Claimed": "blue",
"Returned": "gray",
"Partly Claimed and Returned": "yellow"
};
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
}
};

View File

@@ -12,13 +12,21 @@ from erpnext.hr.doctype.employee_advance.employee_advance import (
EmployeeAdvanceOverPayment,
create_return_through_additional_salary,
make_bank_entry,
make_return_entry,
)
from erpnext.hr.doctype.expense_claim.expense_claim import get_advances
from erpnext.hr.doctype.expense_claim.test_expense_claim import (
get_payable_account,
make_expense_claim,
)
from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
class TestEmployeeAdvance(unittest.TestCase):
def setUp(self):
frappe.db.delete("Employee Advance")
def test_paid_amount_and_status(self):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name)
@@ -53,9 +61,108 @@ class TestEmployeeAdvance(unittest.TestCase):
self.assertEqual(advance.paid_amount, 0)
self.assertEqual(advance.status, "Unpaid")
advance.cancel()
advance.reload()
self.assertEqual(advance.status, "Cancelled")
def test_claimed_status(self):
# CLAIMED Status check, full amount claimed
payable_account = get_payable_account("_Test Company")
claim = make_expense_claim(
payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
)
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
pe.submit()
claim = get_advances_for_claim(claim, advance.name)
claim.save()
claim.submit()
advance.reload()
self.assertEqual(advance.claimed_amount, 1000)
self.assertEqual(advance.status, "Claimed")
# advance should not be shown in claims
advances = get_advances(claim.employee)
advances = [entry.name for entry in advances]
self.assertTrue(advance.name not in advances)
# cancel claim; status should be Paid
claim.cancel()
advance.reload()
self.assertEqual(advance.claimed_amount, 0)
self.assertEqual(advance.status, "Paid")
def test_partly_claimed_and_returned_status(self):
payable_account = get_payable_account("_Test Company")
claim = make_expense_claim(
payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
)
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
pe.submit()
# PARTLY CLAIMED AND RETURNED status check
# 500 Claimed, 500 Returned
claim = make_expense_claim(
payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True
)
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
pe.submit()
claim = get_advances_for_claim(claim, advance.name, amount=500)
claim.save()
claim.submit()
advance.reload()
self.assertEqual(advance.claimed_amount, 500)
self.assertEqual(advance.status, "Paid")
entry = make_return_entry(
employee=advance.employee,
company=advance.company,
employee_advance_name=advance.name,
return_amount=flt(advance.paid_amount - advance.claimed_amount),
advance_account=advance.advance_account,
mode_of_payment=advance.mode_of_payment,
currency=advance.currency,
exchange_rate=advance.exchange_rate,
)
entry = frappe.get_doc(entry)
entry.insert()
entry.submit()
advance.reload()
self.assertEqual(advance.return_amount, 500)
self.assertEqual(advance.status, "Partly Claimed and Returned")
# advance should not be shown in claims
advances = get_advances(claim.employee)
advances = [entry.name for entry in advances]
self.assertTrue(advance.name not in advances)
# Cancel return entry; status should change to PAID
entry.cancel()
advance.reload()
self.assertEqual(advance.return_amount, 0)
self.assertEqual(advance.status, "Paid")
# advance should be shown in claims
advances = get_advances(claim.employee)
advances = [entry.name for entry in advances]
self.assertTrue(advance.name in advances)
def test_repay_unclaimed_amount_from_salary(self):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
pe = make_payment_entry(advance)
pe.submit()
args = {"type": "Deduction"}
create_salary_component("Advance Salary - Deduction", **args)
@@ -85,11 +192,13 @@ class TestEmployeeAdvance(unittest.TestCase):
advance.reload()
self.assertEqual(advance.return_amount, 1000)
self.assertEqual(advance.status, "Returned")
# update advance return amount on additional salary cancellation
additional_salary.cancel()
advance.reload()
self.assertEqual(advance.return_amount, 700)
self.assertEqual(advance.status, "Paid")
def tearDown(self):
frappe.db.rollback()

View File

@@ -171,7 +171,7 @@ frappe.ui.form.on("Expense Claim", {
['docstatus', '=', 1],
['employee', '=', frm.doc.employee],
['paid_amount', '>', 0],
['status', '!=', 'Claimed']
['status', 'not in', ['Claimed', 'Returned', 'Partly Claimed and Returned']]
]
};
});

View File

@@ -414,25 +414,27 @@ def get_expense_claim_account(expense_claim_type, company):
@frappe.whitelist()
def get_advances(employee, advance_id=None):
advance = frappe.qb.DocType("Employee Advance")
query = frappe.qb.from_(advance).select(
advance.name,
advance.posting_date,
advance.paid_amount,
advance.claimed_amount,
advance.advance_account,
)
if not advance_id:
condition = "docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount + return_amount".format(
frappe.db.escape(employee)
query = query.where(
(advance.docstatus == 1)
& (advance.employee == employee)
& (advance.paid_amount > 0)
& (advance.status.notin(["Claimed", "Returned", "Partly Claimed and Returned"]))
)
else:
condition = "name={0}".format(frappe.db.escape(advance_id))
query = query.where(advance.name == advance_id)
return frappe.db.sql(
"""
select
name, posting_date, paid_amount, claimed_amount, advance_account
from
`tabEmployee Advance`
where {0}
""".format(
condition
),
as_dict=1,
)
return query.run(as_dict=True)
@frappe.whitelist()

View File

@@ -366,3 +366,5 @@ erpnext.patches.v13_0.education_deprecation_warning
erpnext.patches.v13_0.requeue_recoverable_reposts
erpnext.patches.v13_0.create_accounting_dimensions_in_orders
erpnext.patches.v13_0.set_per_billed_in_return_delivery_note
erpnext.patches.v13_0.update_employee_advance_status

View File

@@ -0,0 +1,29 @@
import frappe
def execute():
frappe.reload_doc("hr", "doctype", "employee_advance")
advance = frappe.qb.DocType("Employee Advance")
(
frappe.qb.update(advance)
.set(advance.status, "Returned")
.where(
(advance.docstatus == 1)
& ((advance.return_amount) & (advance.paid_amount == advance.return_amount))
& (advance.status == "Paid")
)
).run()
(
frappe.qb.update(advance)
.set(advance.status, "Partly Claimed and Returned")
.where(
(advance.docstatus == 1)
& (
(advance.claimed_amount & advance.return_amount)
& (advance.paid_amount == (advance.return_amount + advance.claimed_amount))
)
& (advance.status == "Paid")
)
).run()

View File

@@ -124,6 +124,8 @@ class AdditionalSalary(Document):
return_amount += self.amount
frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount)
advance = frappe.get_doc("Employee Advance", self.ref_docname)
advance.set_status(update=True)
def update_employee_referral(self, cancel=False):
if self.ref_doctype == "Employee Referral":