Compare commits
20 Commits
deepeshgar
...
version-13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5160dc83d | ||
|
|
ab82e30fac | ||
|
|
32f3365ac7 | ||
|
|
80012b7339 | ||
|
|
b049b52294 | ||
|
|
5d1c35634c | ||
|
|
9900274b27 | ||
|
|
14955c70d4 | ||
|
|
8c55e35d20 | ||
|
|
e6e9f1dc26 | ||
|
|
f0877ffa47 | ||
|
|
e291b5db3d | ||
|
|
b0f7de1a0f | ||
|
|
8dbb200fe3 | ||
|
|
7df8425756 | ||
|
|
3863c4e7fb | ||
|
|
10f02e60ce | ||
|
|
48eaa51c4a | ||
|
|
fee4eae96c | ||
|
|
ee7c9add39 |
@@ -175,52 +175,53 @@ class PaymentEntry(AccountsController):
|
|||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
|
|
||||||
def validate_allocated_amount_with_latest_data(self):
|
def validate_allocated_amount_with_latest_data(self):
|
||||||
latest_references = get_outstanding_reference_documents(
|
if self.references:
|
||||||
{
|
latest_references = get_outstanding_reference_documents(
|
||||||
"posting_date": self.posting_date,
|
{
|
||||||
"company": self.company,
|
"posting_date": self.posting_date,
|
||||||
"party_type": self.party_type,
|
"company": self.company,
|
||||||
"payment_type": self.payment_type,
|
"party_type": self.party_type,
|
||||||
"party": self.party,
|
"payment_type": self.payment_type,
|
||||||
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
|
"party": self.party,
|
||||||
"get_outstanding_invoices": True,
|
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
|
||||||
"get_orders_to_be_billed": True,
|
"get_outstanding_invoices": True,
|
||||||
}
|
"get_orders_to_be_billed": True,
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Group latest_references by (voucher_type, voucher_no)
|
# Group latest_references by (voucher_type, voucher_no)
|
||||||
latest_lookup = {}
|
latest_lookup = {}
|
||||||
for d in latest_references:
|
for d in latest_references:
|
||||||
d = frappe._dict(d)
|
d = frappe._dict(d)
|
||||||
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
||||||
|
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
||||||
|
|
||||||
# The reference has already been fully paid
|
# The reference has already been fully paid
|
||||||
if not latest:
|
if not latest:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
||||||
)
|
)
|
||||||
# The reference has already been partly paid
|
# The reference has already been partly paid
|
||||||
elif latest.outstanding_amount < latest.invoice_amount and flt(
|
elif latest.outstanding_amount < latest.invoice_amount and flt(
|
||||||
d.outstanding_amount, d.precision("outstanding_amount")
|
d.outstanding_amount, d.precision("outstanding_amount")
|
||||||
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
|
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
|
||||||
|
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
||||||
).format(_(d.reference_doctype), d.reference_name)
|
).format(_(d.reference_doctype), d.reference_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||||
|
|
||||||
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
|
|
||||||
# Check for negative outstanding invoices as well
|
# Check for negative outstanding invoices as well
|
||||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
frappe.throw(fail_message.format(d.idx))
|
||||||
|
|
||||||
def delink_advance_entry_references(self):
|
def delink_advance_entry_references(self):
|
||||||
for reference in self.references:
|
for reference in self.references:
|
||||||
|
|||||||
@@ -704,7 +704,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
|||||||
reserved_qty = (
|
reserved_qty = (
|
||||||
frappe.qb.from_(p_inv)
|
frappe.qb.from_(p_inv)
|
||||||
.from_(p_item)
|
.from_(p_item)
|
||||||
.select(Sum(p_item.qty).as_("qty"))
|
.select(Sum(p_item.stock_qty).as_("stock_qty"))
|
||||||
.where(
|
.where(
|
||||||
(p_inv.name == p_item.parent)
|
(p_inv.name == p_item.parent)
|
||||||
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
||||||
@@ -715,7 +715,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
|||||||
)
|
)
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
|
|
||||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
return flt(reserved_qty[0].stock_qty) if reserved_qty else 0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -713,25 +713,31 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
self.assertEqual(details.leave_balance, 30)
|
self.assertEqual(details.leave_balance, 30)
|
||||||
|
|
||||||
def test_earned_leaves_creation(self):
|
def test_earned_leaves_creation(self):
|
||||||
from erpnext.hr.utils import allocate_earned_leaves
|
from erpnext.hr.doctype.leave_policy_assignment.test_leave_policy_assignment import (
|
||||||
|
allocate_earned_leaves_for_months,
|
||||||
|
)
|
||||||
|
|
||||||
leave_period = get_leave_period()
|
year_start = get_year_start(getdate())
|
||||||
|
year_end = get_year_ending(getdate())
|
||||||
|
frappe.flags.current_date = year_start
|
||||||
|
|
||||||
|
leave_period = get_leave_period(year_start, year_end)
|
||||||
employee = get_employee()
|
employee = get_employee()
|
||||||
leave_type = "Test Earned Leave Type"
|
leave_type = "Test Earned Leave Type"
|
||||||
|
|
||||||
make_policy_assignment(employee, leave_type, leave_period)
|
make_policy_assignment(employee, leave_type, leave_period)
|
||||||
|
|
||||||
for i in range(0, 14):
|
# leaves for 6 months = 3, but max leaves restricts allocation to 2
|
||||||
allocate_earned_leaves()
|
frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 2)
|
||||||
|
allocate_earned_leaves_for_months(6)
|
||||||
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 6)
|
self.assertEqual(get_leave_balance_on(employee.name, leave_type, frappe.flags.current_date), 2)
|
||||||
|
|
||||||
# validate earned leaves creation without maximum leaves
|
# validate earned leaves creation without maximum leaves
|
||||||
frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 0)
|
frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 0)
|
||||||
|
allocate_earned_leaves_for_months(5)
|
||||||
|
self.assertEqual(get_leave_balance_on(employee.name, leave_type, frappe.flags.current_date), 4.5)
|
||||||
|
|
||||||
for i in range(0, 6):
|
frappe.flags.current_date = None
|
||||||
allocate_earned_leaves()
|
|
||||||
|
|
||||||
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 9)
|
|
||||||
|
|
||||||
# test to not consider current leave in leave balance while submitting
|
# test to not consider current leave in leave balance while submitting
|
||||||
def test_current_leave_on_submit(self):
|
def test_current_leave_on_submit(self):
|
||||||
@@ -1254,7 +1260,7 @@ def set_leave_approver():
|
|||||||
dept_doc.save(ignore_permissions=True)
|
dept_doc.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
|
||||||
def get_leave_period():
|
def get_leave_period(from_date=None, to_date=None):
|
||||||
leave_period_name = frappe.db.exists({"doctype": "Leave Period", "company": "_Test Company"})
|
leave_period_name = frappe.db.exists({"doctype": "Leave Period", "company": "_Test Company"})
|
||||||
if leave_period_name:
|
if leave_period_name:
|
||||||
return frappe.get_doc("Leave Period", leave_period_name[0][0])
|
return frappe.get_doc("Leave Period", leave_period_name[0][0])
|
||||||
@@ -1263,8 +1269,8 @@ def get_leave_period():
|
|||||||
dict(
|
dict(
|
||||||
name="Test Leave Period",
|
name="Test Leave Period",
|
||||||
doctype="Leave Period",
|
doctype="Leave Period",
|
||||||
from_date=add_months(nowdate(), -6),
|
from_date=from_date or add_months(nowdate(), -6),
|
||||||
to_date=add_months(nowdate(), 6),
|
to_date=to_date or add_months(nowdate(), 6),
|
||||||
company="_Test Company",
|
company="_Test Company",
|
||||||
is_active=1,
|
is_active=1,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class LeavePolicyAssignment(Document):
|
|||||||
return leave_allocations
|
return leave_allocations
|
||||||
|
|
||||||
def create_leave_allocation(
|
def create_leave_allocation(
|
||||||
self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining
|
self, leave_type, annual_allocation, leave_type_details, date_of_joining
|
||||||
):
|
):
|
||||||
# Creates leave allocation for the given employee in the provided leave period
|
# Creates leave allocation for the given employee in the provided leave period
|
||||||
carry_forward = self.carry_forward
|
carry_forward = self.carry_forward
|
||||||
@@ -108,7 +108,7 @@ class LeavePolicyAssignment(Document):
|
|||||||
carry_forward = 0
|
carry_forward = 0
|
||||||
|
|
||||||
new_leaves_allocated = self.get_new_leaves(
|
new_leaves_allocated = self.get_new_leaves(
|
||||||
leave_type, new_leaves_allocated, leave_type_details, date_of_joining
|
leave_type, annual_allocation, leave_type_details, date_of_joining
|
||||||
)
|
)
|
||||||
|
|
||||||
allocation = frappe.get_doc(
|
allocation = frappe.get_doc(
|
||||||
@@ -129,7 +129,7 @@ class LeavePolicyAssignment(Document):
|
|||||||
allocation.submit()
|
allocation.submit()
|
||||||
return allocation.name, new_leaves_allocated
|
return allocation.name, new_leaves_allocated
|
||||||
|
|
||||||
def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
|
def get_new_leaves(self, leave_type, annual_allocation, leave_type_details, date_of_joining):
|
||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
|
|
||||||
precision = get_field_precision(
|
precision = get_field_precision(
|
||||||
@@ -146,20 +146,27 @@ class LeavePolicyAssignment(Document):
|
|||||||
else:
|
else:
|
||||||
# get leaves for past months if assignment is based on Leave Period / Joining Date
|
# get leaves for past months if assignment is based on Leave Period / Joining Date
|
||||||
new_leaves_allocated = self.get_leaves_for_passed_months(
|
new_leaves_allocated = self.get_leaves_for_passed_months(
|
||||||
leave_type, new_leaves_allocated, leave_type_details, date_of_joining
|
leave_type, annual_allocation, leave_type_details, date_of_joining
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
|
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
|
||||||
elif getdate(date_of_joining) > getdate(self.effective_from):
|
else:
|
||||||
remaining_period = (date_diff(self.effective_to, date_of_joining) + 1) / (
|
if getdate(date_of_joining) > getdate(self.effective_from):
|
||||||
date_diff(self.effective_to, self.effective_from) + 1
|
remaining_period = (date_diff(self.effective_to, date_of_joining) + 1) / (
|
||||||
)
|
date_diff(self.effective_to, self.effective_from) + 1
|
||||||
new_leaves_allocated = ceil(new_leaves_allocated * remaining_period)
|
)
|
||||||
|
new_leaves_allocated = ceil(annual_allocation * remaining_period)
|
||||||
|
else:
|
||||||
|
new_leaves_allocated = annual_allocation
|
||||||
|
|
||||||
|
# leave allocation should not exceed annual allocation as per policy assignment
|
||||||
|
if new_leaves_allocated > annual_allocation:
|
||||||
|
new_leaves_allocated = annual_allocation
|
||||||
|
|
||||||
return flt(new_leaves_allocated, precision)
|
return flt(new_leaves_allocated, precision)
|
||||||
|
|
||||||
def get_leaves_for_passed_months(
|
def get_leaves_for_passed_months(
|
||||||
self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining
|
self, leave_type, annual_allocation, leave_type_details, date_of_joining
|
||||||
):
|
):
|
||||||
from erpnext.hr.utils import get_monthly_earned_leave
|
from erpnext.hr.utils import get_monthly_earned_leave
|
||||||
|
|
||||||
@@ -184,7 +191,7 @@ class LeavePolicyAssignment(Document):
|
|||||||
|
|
||||||
if months_passed > 0:
|
if months_passed > 0:
|
||||||
monthly_earned_leave = get_monthly_earned_leave(
|
monthly_earned_leave = get_monthly_earned_leave(
|
||||||
new_leaves_allocated,
|
annual_allocation,
|
||||||
leave_type_details.get(leave_type).earned_leave_frequency,
|
leave_type_details.get(leave_type).earned_leave_frequency,
|
||||||
leave_type_details.get(leave_type).rounding,
|
leave_type_details.get(leave_type).rounding,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, add_months, get_first_day, get_last_day, getdate
|
from frappe.utils import add_days, add_months, get_first_day, get_last_day, get_year_start, getdate
|
||||||
|
|
||||||
|
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
|
||||||
|
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
|
||||||
from erpnext.hr.doctype.leave_application.test_leave_application import (
|
from erpnext.hr.doctype.leave_application.test_leave_application import (
|
||||||
get_employee,
|
get_employee,
|
||||||
get_leave_period,
|
get_leave_period,
|
||||||
@@ -15,6 +17,7 @@ from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_polic
|
|||||||
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
|
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
|
||||||
create_assignment_for_multiple_employees,
|
create_assignment_for_multiple_employees,
|
||||||
)
|
)
|
||||||
|
from erpnext.hr.utils import allocate_earned_leaves
|
||||||
|
|
||||||
test_dependencies = ["Employee"]
|
test_dependencies = ["Employee"]
|
||||||
|
|
||||||
@@ -34,6 +37,8 @@ class TestLeavePolicyAssignment(FrappeTestCase):
|
|||||||
self.original_doj = employee.date_of_joining
|
self.original_doj = employee.date_of_joining
|
||||||
self.employee = employee
|
self.employee = employee
|
||||||
|
|
||||||
|
self.leave_type = "Test Earned Leave"
|
||||||
|
|
||||||
def test_grant_leaves(self):
|
def test_grant_leaves(self):
|
||||||
leave_period = get_leave_period()
|
leave_period = get_leave_period()
|
||||||
# allocation = 10
|
# allocation = 10
|
||||||
@@ -326,6 +331,90 @@ class TestLeavePolicyAssignment(FrappeTestCase):
|
|||||||
self.assertEqual(effective_from, self.employee.date_of_joining)
|
self.assertEqual(effective_from, self.employee.date_of_joining)
|
||||||
self.assertEqual(leaves_allocated, 3)
|
self.assertEqual(leaves_allocated, 3)
|
||||||
|
|
||||||
|
def test_overallocation(self):
|
||||||
|
"""Tests if earned leave allocation does not exceed annual allocation"""
|
||||||
|
frappe.flags.current_date = get_year_start(getdate())
|
||||||
|
make_policy_assignment(
|
||||||
|
self.employee,
|
||||||
|
annual_allocation=22,
|
||||||
|
allocate_on_day="First Day",
|
||||||
|
start_date=frappe.flags.current_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
# leaves for 12 months = 22
|
||||||
|
# With rounding, 22 leaves would be allocated in 11 months only
|
||||||
|
frappe.db.set_value("Leave Type", self.leave_type, "rounding", 1.0)
|
||||||
|
allocate_earned_leaves_for_months(11)
|
||||||
|
self.assertEqual(
|
||||||
|
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 22
|
||||||
|
)
|
||||||
|
|
||||||
|
# should not allocate more leaves than annual allocation
|
||||||
|
allocate_earned_leaves_for_months(1)
|
||||||
|
self.assertEqual(
|
||||||
|
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 22
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_over_allocation_during_assignment_creation(self):
|
||||||
|
"""Tests backdated earned leave allocation does not exceed annual allocation"""
|
||||||
|
start_date = get_first_day(add_months(getdate(), -12))
|
||||||
|
|
||||||
|
# joining date set to 1Y ago
|
||||||
|
self.employee.date_of_joining = start_date
|
||||||
|
self.employee.save()
|
||||||
|
|
||||||
|
# create backdated assignment for last year
|
||||||
|
frappe.flags.current_date = get_first_day(getdate())
|
||||||
|
|
||||||
|
leave_policy_assignments = make_policy_assignment(
|
||||||
|
self.employee, start_date=start_date, allocate_on_day="Date of Joining"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 13 months have passed but annual allocation = 12
|
||||||
|
# check annual allocation is not exceeded
|
||||||
|
leaves_allocated = get_allocated_leaves(leave_policy_assignments[0])
|
||||||
|
self.assertEqual(leaves_allocated, 12)
|
||||||
|
|
||||||
|
def test_overallocation_with_carry_forwarding(self):
|
||||||
|
"""Tests earned leave allocation with cf leaves does not exceed annual allocation"""
|
||||||
|
year_start = get_year_start(getdate())
|
||||||
|
|
||||||
|
# initial leave allocation = 5
|
||||||
|
leave_allocation = create_leave_allocation(
|
||||||
|
employee=self.employee.name,
|
||||||
|
employee_name=self.employee.employee_name,
|
||||||
|
leave_type=self.leave_type,
|
||||||
|
from_date=get_first_day(add_months(year_start, -1)),
|
||||||
|
to_date=get_last_day(add_months(year_start, -1)),
|
||||||
|
new_leaves_allocated=5,
|
||||||
|
carry_forward=0,
|
||||||
|
)
|
||||||
|
leave_allocation.submit()
|
||||||
|
|
||||||
|
frappe.flags.current_date = year_start
|
||||||
|
# carry forwarded leaves = 5
|
||||||
|
make_policy_assignment(
|
||||||
|
self.employee,
|
||||||
|
annual_allocation=22,
|
||||||
|
allocate_on_day="First Day",
|
||||||
|
start_date=year_start,
|
||||||
|
carry_forward=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_value("Leave Type", self.leave_type, "rounding", 1.0)
|
||||||
|
allocate_earned_leaves_for_months(11)
|
||||||
|
|
||||||
|
# 5 carry forwarded leaves + 22 EL allocated = 27 leaves
|
||||||
|
self.assertEqual(
|
||||||
|
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 27
|
||||||
|
)
|
||||||
|
|
||||||
|
# should not allocate more leaves than annual allocation (22 excluding 5 cf leaves)
|
||||||
|
allocate_earned_leaves_for_months(1)
|
||||||
|
self.assertEqual(
|
||||||
|
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 27
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj)
|
frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj)
|
||||||
frappe.flags.current_date = None
|
frappe.flags.current_date = None
|
||||||
@@ -376,3 +465,51 @@ def setup_leave_period_and_policy(start_date, based_on_doj=False):
|
|||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
return leave_period, leave_policy
|
return leave_period, leave_policy
|
||||||
|
|
||||||
|
|
||||||
|
def make_policy_assignment(
|
||||||
|
employee,
|
||||||
|
allocate_on_day="Last Day",
|
||||||
|
earned_leave_frequency="Monthly",
|
||||||
|
start_date=None,
|
||||||
|
annual_allocation=12,
|
||||||
|
carry_forward=0,
|
||||||
|
assignment_based_on="Leave Period",
|
||||||
|
):
|
||||||
|
leave_type = create_earned_leave_type("Test Earned Leave", allocate_on_day)
|
||||||
|
leave_period = create_leave_period("Test Earned Leave Period", start_date=start_date)
|
||||||
|
leave_policy = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Leave Policy",
|
||||||
|
"title": "Test Earned Leave Policy",
|
||||||
|
"leave_policy_details": [
|
||||||
|
{"leave_type": leave_type.name, "annual_allocation": annual_allocation}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"assignment_based_on": assignment_based_on,
|
||||||
|
"leave_policy": leave_policy.name,
|
||||||
|
"leave_period": leave_period.name,
|
||||||
|
"carry_forward": carry_forward,
|
||||||
|
}
|
||||||
|
|
||||||
|
leave_policy_assignments = create_assignment_for_multiple_employees(
|
||||||
|
[employee.name], frappe._dict(data)
|
||||||
|
)
|
||||||
|
return leave_policy_assignments
|
||||||
|
|
||||||
|
|
||||||
|
def get_allocated_leaves(assignment):
|
||||||
|
return frappe.db.get_value(
|
||||||
|
"Leave Allocation",
|
||||||
|
{"leave_policy_assignment": assignment},
|
||||||
|
"total_leaves_allocated",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def allocate_earned_leaves_for_months(months):
|
||||||
|
for i in range(0, months):
|
||||||
|
frappe.flags.current_date = add_months(frappe.flags.current_date, 1)
|
||||||
|
allocate_earned_leaves()
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ def generate_leave_encashment():
|
|||||||
def allocate_earned_leaves():
|
def allocate_earned_leaves():
|
||||||
"""Allocate earned leaves to Employees"""
|
"""Allocate earned leaves to Employees"""
|
||||||
e_leave_types = get_earned_leaves()
|
e_leave_types = get_earned_leaves()
|
||||||
today = getdate()
|
today = frappe.flags.current_date or getdate()
|
||||||
|
|
||||||
for e_leave_type in e_leave_types:
|
for e_leave_type in e_leave_types:
|
||||||
|
|
||||||
@@ -496,18 +496,28 @@ def allocate_earned_leaves():
|
|||||||
|
|
||||||
|
|
||||||
def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
|
def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
|
||||||
|
allocation = frappe.get_doc("Leave Allocation", allocation.name)
|
||||||
|
annual_allocation = flt(annual_allocation, allocation.precision("total_leaves_allocated"))
|
||||||
|
|
||||||
earned_leaves = get_monthly_earned_leave(
|
earned_leaves = get_monthly_earned_leave(
|
||||||
annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding
|
annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding
|
||||||
)
|
)
|
||||||
|
|
||||||
allocation = frappe.get_doc("Leave Allocation", allocation.name)
|
|
||||||
new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
|
new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
|
||||||
|
new_allocation_without_cf = flt(
|
||||||
|
flt(allocation.get_existing_leave_count()) + flt(earned_leaves),
|
||||||
|
allocation.precision("total_leaves_allocated"),
|
||||||
|
)
|
||||||
|
|
||||||
if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
|
if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
|
||||||
new_allocation = e_leave_type.max_leaves_allowed
|
new_allocation = e_leave_type.max_leaves_allowed
|
||||||
|
|
||||||
if new_allocation != allocation.total_leaves_allocated:
|
if (
|
||||||
today_date = today()
|
new_allocation != allocation.total_leaves_allocated
|
||||||
|
# annual allocation as per policy should not be exceeded
|
||||||
|
and new_allocation_without_cf <= annual_allocation
|
||||||
|
):
|
||||||
|
today_date = frappe.flags.current_date or getdate()
|
||||||
|
|
||||||
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
|
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
|
||||||
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
|
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _, qb
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
|
||||||
from erpnext import get_default_company
|
from erpnext import get_default_company
|
||||||
from erpnext.accounts.party import get_party_details
|
from erpnext.accounts.party import get_party_details
|
||||||
from erpnext.stock.get_item_details import get_price_list_rate_for
|
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@@ -50,6 +50,42 @@ def get_columns(filters=None):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_item_prices(
|
||||||
|
customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None
|
||||||
|
):
|
||||||
|
price_list_map = frappe._dict()
|
||||||
|
ip = qb.DocType("Item Price")
|
||||||
|
and_conditions = []
|
||||||
|
or_conditions = []
|
||||||
|
if items:
|
||||||
|
and_conditions.append(ip.item_code.isin([x.item_code for x in items]))
|
||||||
|
and_conditions.append(ip.selling == True)
|
||||||
|
|
||||||
|
or_conditions.append(ip.customer == None)
|
||||||
|
or_conditions.append(ip.price_list == None)
|
||||||
|
|
||||||
|
if customer:
|
||||||
|
or_conditions.append(ip.customer == customer)
|
||||||
|
|
||||||
|
if price_list:
|
||||||
|
or_conditions.append(ip.price_list == price_list)
|
||||||
|
|
||||||
|
if selling_price_list:
|
||||||
|
or_conditions.append(ip.price_list == selling_price_list)
|
||||||
|
|
||||||
|
res = (
|
||||||
|
qb.from_(ip)
|
||||||
|
.select(ip.item_code, ip.price_list, ip.price_list_rate)
|
||||||
|
.where(Criterion.all(and_conditions))
|
||||||
|
.where(Criterion.any(or_conditions))
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
for x in res:
|
||||||
|
price_list_map.update({(x.item_code, x.price_list): x.price_list_rate})
|
||||||
|
|
||||||
|
return price_list_map
|
||||||
|
|
||||||
|
|
||||||
def get_data(filters=None):
|
def get_data(filters=None):
|
||||||
data = []
|
data = []
|
||||||
customer_details = get_customer_details(filters)
|
customer_details = get_customer_details(filters)
|
||||||
@@ -59,9 +95,17 @@ def get_data(filters=None):
|
|||||||
"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
|
"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
|
||||||
)
|
)
|
||||||
item_stock_map = {item.item_code: item.available for item in item_stock_map}
|
item_stock_map = {item.item_code: item.available for item in item_stock_map}
|
||||||
|
price_list_map = fetch_item_prices(
|
||||||
|
customer_details.customer,
|
||||||
|
customer_details.price_list,
|
||||||
|
customer_details.selling_price_list,
|
||||||
|
items,
|
||||||
|
)
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
|
price_list_rate = price_list_map.get(
|
||||||
|
(item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0
|
||||||
|
)
|
||||||
available_stock = item_stock_map.get(item.item_code)
|
available_stock = item_stock_map.get(item.item_code)
|
||||||
|
|
||||||
data.append(
|
data.append(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint
|
|
||||||
|
|
||||||
|
|
||||||
def get_leaderboards():
|
def get_leaderboards():
|
||||||
@@ -54,12 +53,13 @@ def get_leaderboards():
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_customers(date_range, company, field, limit=None):
|
def get_all_customers(date_range, company, field, limit=None):
|
||||||
|
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
if field == "outstanding_amount":
|
if field == "outstanding_amount":
|
||||||
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
if from_date and to_date:
|
||||||
if date_range:
|
filters.append(["posting_date", "between", [from_date, to_date]])
|
||||||
date_range = frappe.parse_json(date_range)
|
|
||||||
filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]])
|
return frappe.get_list(
|
||||||
return frappe.db.get_all(
|
|
||||||
"Sales Invoice",
|
"Sales Invoice",
|
||||||
fields=["customer as name", "sum(outstanding_amount) as value"],
|
fields=["customer as name", "sum(outstanding_amount) as value"],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
@@ -69,26 +69,20 @@ def get_all_customers(date_range, company, field, limit=None):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if field == "total_sales_amount":
|
if field == "total_sales_amount":
|
||||||
select_field = "sum(so_item.base_net_amount)"
|
select_field = "base_net_total"
|
||||||
elif field == "total_qty_sold":
|
elif field == "total_qty_sold":
|
||||||
select_field = "sum(so_item.stock_qty)"
|
select_field = "total_qty"
|
||||||
|
|
||||||
date_condition = get_date_condition(date_range, "so.transaction_date")
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.get_list(
|
||||||
"""
|
"Sales Order",
|
||||||
select so.customer as name, {0} as value
|
fields=["customer as name", f"sum({select_field}) as value"],
|
||||||
FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item
|
filters=filters,
|
||||||
ON so.name = so_item.parent
|
group_by="customer",
|
||||||
where so.docstatus = 1 {1} and so.company = %s
|
order_by="value desc",
|
||||||
group by so.customer
|
limit=limit,
|
||||||
order by value DESC
|
|
||||||
limit %s
|
|
||||||
""".format(
|
|
||||||
select_field, date_condition
|
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -96,55 +90,58 @@ def get_all_customers(date_range, company, field, limit=None):
|
|||||||
def get_all_items(date_range, company, field, limit=None):
|
def get_all_items(date_range, company, field, limit=None):
|
||||||
if field in ("available_stock_qty", "available_stock_value"):
|
if field in ("available_stock_qty", "available_stock_value"):
|
||||||
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
|
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
|
||||||
return frappe.db.get_all(
|
results = frappe.db.get_all(
|
||||||
"Bin",
|
"Bin",
|
||||||
fields=["item_code as name", "{0} as value".format(select_field)],
|
fields=["item_code as name", "{0} as value".format(select_field)],
|
||||||
group_by="item_code",
|
group_by="item_code",
|
||||||
order_by="value desc",
|
order_by="value desc",
|
||||||
limit=limit,
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name"))
|
||||||
|
return [item for item in results if item["name"] in readable_active_items]
|
||||||
else:
|
else:
|
||||||
if field == "total_sales_amount":
|
if field == "total_sales_amount":
|
||||||
select_field = "sum(order_item.base_net_amount)"
|
select_field = "base_net_amount"
|
||||||
select_doctype = "Sales Order"
|
select_doctype = "Sales Order"
|
||||||
elif field == "total_purchase_amount":
|
elif field == "total_purchase_amount":
|
||||||
select_field = "sum(order_item.base_net_amount)"
|
select_field = "base_net_amount"
|
||||||
select_doctype = "Purchase Order"
|
select_doctype = "Purchase Order"
|
||||||
elif field == "total_qty_sold":
|
elif field == "total_qty_sold":
|
||||||
select_field = "sum(order_item.stock_qty)"
|
select_field = "stock_qty"
|
||||||
select_doctype = "Sales Order"
|
select_doctype = "Sales Order"
|
||||||
elif field == "total_qty_purchased":
|
elif field == "total_qty_purchased":
|
||||||
select_field = "sum(order_item.stock_qty)"
|
select_field = "stock_qty"
|
||||||
select_doctype = "Purchase Order"
|
select_doctype = "Purchase Order"
|
||||||
|
|
||||||
date_condition = get_date_condition(date_range, "sales_order.transaction_date")
|
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
child_doctype = f"{select_doctype} Item"
|
||||||
"""
|
return frappe.get_list(
|
||||||
select order_item.item_code as name, {0} as value
|
select_doctype,
|
||||||
from `tab{1}` sales_order join `tab{1} Item` as order_item
|
fields=[
|
||||||
on sales_order.name = order_item.parent
|
f"`tab{child_doctype}`.item_code as name",
|
||||||
where sales_order.docstatus = 1
|
f"sum(`tab{child_doctype}`.{select_field}) as value",
|
||||||
and sales_order.company = %s {2}
|
],
|
||||||
group by order_item.item_code
|
filters=filters,
|
||||||
order by value desc
|
order_by="value desc",
|
||||||
limit %s
|
group_by=f"`tab{child_doctype}`.item_code",
|
||||||
""".format(
|
limit=limit,
|
||||||
select_field, select_doctype, date_condition
|
)
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_suppliers(date_range, company, field, limit=None):
|
def get_all_suppliers(date_range, company, field, limit=None):
|
||||||
|
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
|
|
||||||
if field == "outstanding_amount":
|
if field == "outstanding_amount":
|
||||||
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
if from_date and to_date:
|
||||||
if date_range:
|
filters.append(["posting_date", "between", [from_date, to_date]])
|
||||||
date_range = frappe.parse_json(date_range)
|
|
||||||
filters.append(["posting_date", "between", [date_range[0], date_range[1]]])
|
return frappe.get_list(
|
||||||
return frappe.db.get_all(
|
|
||||||
"Purchase Invoice",
|
"Purchase Invoice",
|
||||||
fields=["supplier as name", "sum(outstanding_amount) as value"],
|
fields=["supplier as name", "sum(outstanding_amount) as value"],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
@@ -154,48 +151,40 @@ def get_all_suppliers(date_range, company, field, limit=None):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if field == "total_purchase_amount":
|
if field == "total_purchase_amount":
|
||||||
select_field = "sum(purchase_order_item.base_net_amount)"
|
select_field = "base_net_total"
|
||||||
elif field == "total_qty_purchased":
|
elif field == "total_qty_purchased":
|
||||||
select_field = "sum(purchase_order_item.stock_qty)"
|
select_field = "total_qty"
|
||||||
|
|
||||||
date_condition = get_date_condition(date_range, "purchase_order.modified")
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.get_list(
|
||||||
"""
|
"Purchase Order",
|
||||||
select purchase_order.supplier as name, {0} as value
|
fields=["supplier as name", f"sum({select_field}) as value"],
|
||||||
FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item`
|
filters=filters,
|
||||||
as purchase_order_item ON purchase_order.name = purchase_order_item.parent
|
group_by="supplier",
|
||||||
where
|
order_by="value desc",
|
||||||
purchase_order.docstatus = 1
|
limit=limit,
|
||||||
{1}
|
)
|
||||||
and purchase_order.company = %s
|
|
||||||
group by purchase_order.supplier
|
|
||||||
order by value DESC
|
|
||||||
limit %s""".format(
|
|
||||||
select_field, date_condition
|
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_sales_partner(date_range, company, field, limit=None):
|
def get_all_sales_partner(date_range, company, field, limit=None):
|
||||||
if field == "total_sales_amount":
|
if field == "total_sales_amount":
|
||||||
select_field = "sum(`base_net_total`)"
|
select_field = "base_net_total"
|
||||||
elif field == "total_commission":
|
elif field == "total_commission":
|
||||||
select_field = "sum(`total_commission`)"
|
select_field = "total_commission"
|
||||||
|
|
||||||
filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company}
|
filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]]
|
||||||
if date_range:
|
from_date, to_date = parse_date_range(date_range)
|
||||||
date_range = frappe.parse_json(date_range)
|
if from_date and to_date:
|
||||||
filters["transaction_date"] = ["between", [date_range[0], date_range[1]]]
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.get_list(
|
return frappe.get_list(
|
||||||
"Sales Order",
|
"Sales Order",
|
||||||
fields=[
|
fields=[
|
||||||
"`sales_partner` as name",
|
"sales_partner as name",
|
||||||
"{} as value".format(select_field),
|
f"sum({select_field}) as value",
|
||||||
],
|
],
|
||||||
filters=filters,
|
filters=filters,
|
||||||
group_by="sales_partner",
|
group_by="sales_partner",
|
||||||
@@ -206,24 +195,25 @@ def get_all_sales_partner(date_range, company, field, limit=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_all_sales_person(date_range, company, field=None, limit=0):
|
def get_all_sales_person(date_range, company, field=None, limit=0):
|
||||||
date_condition = get_date_condition(date_range, "sales_order.transaction_date")
|
filters = [
|
||||||
|
["docstatus", "=", "1"],
|
||||||
|
["company", "=", company],
|
||||||
|
["Sales Team", "sales_person", "is", "set"],
|
||||||
|
]
|
||||||
|
from_date, to_date = parse_date_range(date_range)
|
||||||
|
if from_date and to_date:
|
||||||
|
filters.append(["transaction_date", "between", [from_date, to_date]])
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.get_list(
|
||||||
"""
|
"Sales Order",
|
||||||
select sales_team.sales_person as name, sum(sales_order.base_net_total) as value
|
fields=[
|
||||||
from `tabSales Order` as sales_order join `tabSales Team` as sales_team
|
"`tabSales Team`.sales_person as name",
|
||||||
on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order'
|
"sum(`tabSales Team`.allocated_amount) as value",
|
||||||
where sales_order.docstatus = 1
|
],
|
||||||
and sales_order.company = %s
|
filters=filters,
|
||||||
{date_condition}
|
group_by="`tabSales Team`.sales_person",
|
||||||
group by sales_team.sales_person
|
order_by="value desc",
|
||||||
order by value DESC
|
limit=limit,
|
||||||
limit %s
|
|
||||||
""".format(
|
|
||||||
date_condition=date_condition
|
|
||||||
),
|
|
||||||
(company, cint(limit)),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -236,3 +226,11 @@ def get_date_condition(date_range, field):
|
|||||||
field, frappe.db.escape(from_date), frappe.db.escape(to_date)
|
field, frappe.db.escape(from_date), frappe.db.escape(to_date)
|
||||||
)
|
)
|
||||||
return date_condition
|
return date_condition
|
||||||
|
|
||||||
|
|
||||||
|
def parse_date_range(date_range):
|
||||||
|
if date_range:
|
||||||
|
date_range = frappe.parse_json(date_range)
|
||||||
|
return date_range[0], date_range[1]
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|||||||
Reference in New Issue
Block a user