Merge branch 'develop' into fixed-incorrect-batch-selected-in-purchase-rceipt-develop
This commit is contained in:
commit
6a98d39201
@ -61,7 +61,6 @@ class TestBankTransaction(unittest.TestCase):
|
||||
def test_debit_credit_output(self):
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
|
||||
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
|
||||
print(linked_payments)
|
||||
self.assertTrue(linked_payments[0][3])
|
||||
|
||||
# Check error if already reconciled
|
||||
|
@ -5,7 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import date_diff, add_days, getdate, cint
|
||||
from frappe.utils import date_diff, add_days, getdate, cint, format_date
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
|
||||
get_holidays_for_employee, create_additional_leave_ledger_entry
|
||||
@ -40,7 +40,12 @@ class CompensatoryLeaveRequest(Document):
|
||||
def validate_holidays(self):
|
||||
holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date)
|
||||
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
|
||||
frappe.throw(_("Compensatory leave request days not in valid holidays"))
|
||||
if date_diff(self.work_end_date, self.work_from_date):
|
||||
msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
|
||||
else:
|
||||
msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
|
||||
|
||||
frappe.throw(msg)
|
||||
|
||||
def on_submit(self):
|
||||
company = frappe.db.get_value("Employee", self.employee, "company")
|
||||
@ -63,7 +68,7 @@ class CompensatoryLeaveRequest(Document):
|
||||
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
|
||||
self.leave_allocation=leave_allocation.name
|
||||
else:
|
||||
frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date))
|
||||
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
|
||||
|
||||
def on_cancel(self):
|
||||
if self.leave_allocation:
|
||||
|
@ -80,6 +80,7 @@ class Employee(NestedSet):
|
||||
self.update_user()
|
||||
self.update_user_permissions()
|
||||
self.reset_employee_emails_cache()
|
||||
self.update_approver_role()
|
||||
|
||||
def update_user_permissions(self):
|
||||
if not self.create_user_permission: return
|
||||
@ -145,6 +146,17 @@ class Employee(NestedSet):
|
||||
|
||||
user.save()
|
||||
|
||||
def update_approver_role(self):
|
||||
if self.leave_approver:
|
||||
user = frappe.get_doc("User", self.leave_approver)
|
||||
user.flags.ignore_permissions = True
|
||||
user.add_roles("Leave Approver")
|
||||
|
||||
if self.expense_approver:
|
||||
user = frappe.get_doc("User", self.expense_approver)
|
||||
user.flags.ignore_permissions = True
|
||||
user.add_roles("Expense Approver")
|
||||
|
||||
def validate_date(self):
|
||||
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
|
||||
throw(_("Date of Birth cannot be greater than today."))
|
||||
@ -503,7 +515,7 @@ def has_user_permission_for_employee(user_name, employee_name):
|
||||
})
|
||||
|
||||
def has_upload_permission(doc, ptype='read', user=None):
|
||||
if not user:
|
||||
if not user:
|
||||
user = frappe.session.user
|
||||
if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
|
||||
return True
|
||||
|
@ -6,7 +6,7 @@ import frappe, erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import get_fullname, flt, cstr, get_link_to_form
|
||||
from frappe.model.document import Document
|
||||
from erpnext.hr.utils import set_employee_name
|
||||
from erpnext.hr.utils import set_employee_name, share_doc_with_approver
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
@ -53,6 +53,9 @@ class ExpenseClaim(AccountsController):
|
||||
elif self.docstatus == 1 and self.approval_status == 'Rejected':
|
||||
self.status = 'Rejected'
|
||||
|
||||
def on_update(self):
|
||||
share_doc_with_approver(self, self.expense_approver)
|
||||
|
||||
def set_payable_account(self):
|
||||
if not self.payable_account and not self.is_paid:
|
||||
self.payable_account = frappe.get_cached_value('Company', self.company, 'default_expense_claim_payable_account')
|
||||
|
@ -95,12 +95,12 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
def test_rejected_expense_claim(self):
|
||||
payable_account = get_payable_account(company_name)
|
||||
expense_claim = frappe.get_doc({
|
||||
"doctype": "Expense Claim",
|
||||
"employee": "_T-Employee-00001",
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Rejected",
|
||||
"expenses":
|
||||
[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
|
||||
"doctype": "Expense Claim",
|
||||
"employee": "_T-Employee-00001",
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Rejected",
|
||||
"expenses":
|
||||
[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
|
||||
})
|
||||
expense_claim.submit()
|
||||
|
||||
@ -110,6 +110,34 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
|
||||
self.assertEquals(len(gl_entry), 0)
|
||||
|
||||
def test_expense_approver_perms(self):
|
||||
user = "test_approver_perm_emp@example.com"
|
||||
make_employee(user, "_Test Company")
|
||||
|
||||
# check doc shared
|
||||
payable_account = get_payable_account("_Test Company")
|
||||
expense_claim = make_expense_claim(payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
|
||||
expense_claim.expense_approver = user
|
||||
expense_claim.save()
|
||||
self.assertTrue(expense_claim.name in frappe.share.get_shared("Expense Claim", user))
|
||||
|
||||
# check shared doc revoked
|
||||
expense_claim.reload()
|
||||
expense_claim.expense_approver = "test@example.com"
|
||||
expense_claim.save()
|
||||
self.assertTrue(expense_claim.name not in frappe.share.get_shared("Expense Claim", user))
|
||||
|
||||
expense_claim.reload()
|
||||
expense_claim.expense_approver = user
|
||||
expense_claim.save()
|
||||
|
||||
frappe.set_user(user)
|
||||
expense_claim.reload()
|
||||
expense_claim.status = "Approved"
|
||||
expense_claim.submit()
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
|
||||
def get_payable_account(company):
|
||||
return frappe.get_cached_value('Company', company, 'default_payable_account')
|
||||
|
||||
@ -133,21 +161,21 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco
|
||||
|
||||
currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
|
||||
expense_claim = {
|
||||
"doctype": "Expense Claim",
|
||||
"employee": employee,
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Approved",
|
||||
"company": company,
|
||||
'currency': currency,
|
||||
"expenses": [{
|
||||
"doctype": "Expense Claim",
|
||||
"employee": employee,
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Approved",
|
||||
"company": company,
|
||||
"currency": currency,
|
||||
"expenses": [{
|
||||
"expense_type": "Travel",
|
||||
"default_account": account,
|
||||
"currency": currency,
|
||||
"amount": amount,
|
||||
"sanctioned_amount": sanctioned_amount,
|
||||
"cost_center": cost_center
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
if taxes:
|
||||
expense_claim.update(taxes)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
|
||||
comma_or, get_fullname, add_days, nowdate, get_datetime_str
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period
|
||||
from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
|
||||
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
|
||||
@ -43,6 +43,8 @@ class LeaveApplication(Document):
|
||||
if frappe.db.get_single_value("HR Settings", "send_leave_notification"):
|
||||
self.notify_leave_approver()
|
||||
|
||||
share_doc_with_approver(self, self.leave_approver)
|
||||
|
||||
def on_submit(self):
|
||||
if self.status == "Open":
|
||||
frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted"))
|
||||
@ -417,6 +419,7 @@ class LeaveApplication(Document):
|
||||
))
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
|
||||
def get_allocation_expiry(employee, leave_type, to_date, from_date):
|
||||
''' Returns expiry of carry forward allocation in leave ledger entry '''
|
||||
expiry = frappe.get_all("Leave Ledger Entry",
|
||||
|
@ -11,6 +11,7 @@ from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months
|
||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
|
||||
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
|
||||
test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
|
||||
|
||||
@ -567,6 +568,48 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
|
||||
|
||||
def test_leave_approver_perms(self):
|
||||
employee = get_employee()
|
||||
user = "test_approver_perm_emp@example.com"
|
||||
make_employee(user, "_Test Company")
|
||||
|
||||
# set approver for employee
|
||||
employee.reload()
|
||||
employee.leave_approver = user
|
||||
employee.save()
|
||||
self.assertTrue("Leave Approver" in frappe.get_roles(user))
|
||||
|
||||
make_allocation_record(employee.name)
|
||||
|
||||
application = self.get_application(_test_records[0])
|
||||
application.from_date = '2018-01-01'
|
||||
application.to_date = '2018-01-03'
|
||||
application.leave_approver = user
|
||||
application.insert()
|
||||
self.assertTrue(application.name in frappe.share.get_shared("Leave Application", user))
|
||||
|
||||
# check shared doc revoked
|
||||
application.reload()
|
||||
application.leave_approver = "test@example.com"
|
||||
application.save()
|
||||
self.assertTrue(application.name not in frappe.share.get_shared("Leave Application", user))
|
||||
|
||||
application.reload()
|
||||
application.leave_approver = user
|
||||
application.save()
|
||||
|
||||
frappe.set_user(user)
|
||||
application.reload()
|
||||
application.status = "Approved"
|
||||
application.submit()
|
||||
|
||||
# unset leave approver
|
||||
frappe.set_user("Administrator")
|
||||
employee.reload()
|
||||
employee.leave_approver = ""
|
||||
employee.save()
|
||||
|
||||
|
||||
def create_carry_forwarded_allocation(employee, leave_type):
|
||||
# initial leave allocation
|
||||
leave_allocation = create_leave_allocation(
|
||||
|
@ -34,8 +34,8 @@ def validate_leave_allocation_against_leave_application(ledger):
|
||||
""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
|
||||
|
||||
if leave_application_records:
|
||||
frappe.throw(_("Leave allocation %s is linked with leave application %s"
|
||||
% (ledger.transaction_name, ', '.join(leave_application_records))))
|
||||
frappe.throw(_("Leave allocation {0} is linked with the Leave Application {1}").format(
|
||||
ledger.transaction_name, ', '.join(leave_application_records)))
|
||||
|
||||
def create_leave_ledger_entry(ref_doc, args, submit=True):
|
||||
ledger = frappe._dict(
|
||||
@ -52,7 +52,9 @@ def create_leave_ledger_entry(ref_doc, args, submit=True):
|
||||
ledger.update(args)
|
||||
|
||||
if submit:
|
||||
frappe.get_doc(ledger).submit()
|
||||
doc = frappe.get_doc(ledger)
|
||||
doc.flags.ignore_permissions = 1
|
||||
doc.submit()
|
||||
else:
|
||||
delete_ledger_entry(ledger)
|
||||
|
||||
|
@ -7,6 +7,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import formatdate, getdate
|
||||
from erpnext.hr.utils import share_doc_with_approver
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
|
||||
@ -17,6 +18,9 @@ class ShiftRequest(Document):
|
||||
self.validate_approver()
|
||||
self.validate_default_shift()
|
||||
|
||||
def on_update(self):
|
||||
share_doc_with_approver(self, self.approver)
|
||||
|
||||
def on_submit(self):
|
||||
if self.status not in ["Approved", "Rejected"]:
|
||||
frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted"))
|
||||
@ -29,6 +33,7 @@ class ShiftRequest(Document):
|
||||
if self.to_date:
|
||||
assignment_doc.end_date = self.to_date
|
||||
assignment_doc.shift_request = self.name
|
||||
assignment_doc.flags.ignore_permissions = 1
|
||||
assignment_doc.insert()
|
||||
assignment_doc.submit()
|
||||
|
||||
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate, add_days
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
|
||||
test_dependencies = ["Shift Type"]
|
||||
|
||||
@ -19,19 +20,8 @@ class TestShiftRequest(unittest.TestCase):
|
||||
set_shift_approver(department)
|
||||
approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
|
||||
|
||||
shift_request = frappe.get_doc({
|
||||
"doctype": "Shift Request",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"employee_name": "_Test Employee",
|
||||
"from_date": nowdate(),
|
||||
"to_date": add_days(nowdate(), 10),
|
||||
"approver": approver,
|
||||
"status": "Approved"
|
||||
})
|
||||
shift_request.insert()
|
||||
shift_request.submit()
|
||||
shift_request = make_shift_request(approver)
|
||||
|
||||
shift_assignments = frappe.db.sql('''
|
||||
SELECT shift_request, employee
|
||||
FROM `tabShift Assignment`
|
||||
@ -44,8 +34,65 @@ class TestShiftRequest(unittest.TestCase):
|
||||
shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')})
|
||||
self.assertEqual(shift_assignment_doc.docstatus, 2)
|
||||
|
||||
def test_shift_request_approver_perms(self):
|
||||
employee = frappe.get_doc("Employee", "_T-Employee-00001")
|
||||
user = "test_approver_perm_emp@example.com"
|
||||
make_employee(user, "_Test Company")
|
||||
|
||||
# set approver for employee
|
||||
employee.reload()
|
||||
employee.shift_request_approver = user
|
||||
employee.save()
|
||||
|
||||
shift_request = make_shift_request(user, do_not_submit=True)
|
||||
self.assertTrue(shift_request.name in frappe.share.get_shared("Shift Request", user))
|
||||
|
||||
# check shared doc revoked
|
||||
shift_request.reload()
|
||||
department = frappe.get_value("Employee", "_T-Employee-00001", "department")
|
||||
set_shift_approver(department)
|
||||
department_approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
|
||||
shift_request.approver = department_approver
|
||||
shift_request.save()
|
||||
self.assertTrue(shift_request.name not in frappe.share.get_shared("Shift Request", user))
|
||||
|
||||
shift_request.reload()
|
||||
shift_request.approver = user
|
||||
shift_request.save()
|
||||
|
||||
frappe.set_user(user)
|
||||
shift_request.reload()
|
||||
shift_request.status = "Approved"
|
||||
shift_request.submit()
|
||||
|
||||
# unset approver
|
||||
frappe.set_user("Administrator")
|
||||
employee.reload()
|
||||
employee.shift_request_approver = ""
|
||||
employee.save()
|
||||
|
||||
|
||||
def set_shift_approver(department):
|
||||
department_doc = frappe.get_doc("Department", department)
|
||||
department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
|
||||
department_doc.save()
|
||||
department_doc.reload()
|
||||
|
||||
def make_shift_request(approver, do_not_submit=0):
|
||||
shift_request = frappe.get_doc({
|
||||
"doctype": "Shift Request",
|
||||
"shift_type": "Day Shift",
|
||||
"company": "_Test Company",
|
||||
"employee": "_T-Employee-00001",
|
||||
"employee_name": "_Test Employee",
|
||||
"from_date": nowdate(),
|
||||
"to_date": add_days(nowdate(), 10),
|
||||
"approver": approver,
|
||||
"status": "Approved"
|
||||
}).insert()
|
||||
|
||||
if do_not_submit:
|
||||
return shift_request
|
||||
|
||||
shift_request.submit()
|
||||
return shift_request
|
@ -504,3 +504,25 @@ def grant_leaves_automatically():
|
||||
lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
|
||||
for assignment in lpa:
|
||||
frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
|
||||
|
||||
def share_doc_with_approver(doc, user):
|
||||
# if approver does not have permissions, share
|
||||
if not frappe.has_permission(doc=doc, ptype="submit", user=user):
|
||||
frappe.share.add(doc.doctype, doc.name, user, submit=1,
|
||||
flags={"ignore_share_permission": True})
|
||||
|
||||
frappe.msgprint(_("Shared with the user {0} with {1} access").format(
|
||||
user, frappe.bold("submit"), alert=True))
|
||||
|
||||
# remove shared doc if approver changes
|
||||
doc_before_save = doc.get_doc_before_save()
|
||||
if doc_before_save:
|
||||
approvers = {
|
||||
"Leave Application": "leave_approver",
|
||||
"Expense Claim": "expense_approver",
|
||||
"Shift Request": "approver"
|
||||
}
|
||||
|
||||
approver = approvers.get(doc.doctype)
|
||||
if doc_before_save.get(approver) != doc.get(approver):
|
||||
frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
|
||||
|
@ -21,6 +21,7 @@
|
||||
"interest_payable",
|
||||
"payable_amount",
|
||||
"column_break_9",
|
||||
"shortfall_amount",
|
||||
"payable_principal_amount",
|
||||
"penalty_amount",
|
||||
"amount_paid",
|
||||
@ -31,6 +32,7 @@
|
||||
"column_break_21",
|
||||
"reference_date",
|
||||
"principal_amount_paid",
|
||||
"total_penalty_paid",
|
||||
"total_interest_paid",
|
||||
"repayment_details",
|
||||
"amended_from"
|
||||
@ -226,12 +228,25 @@
|
||||
"fieldtype": "Percent",
|
||||
"label": "Rate Of Interest",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shortfall_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Shortfall Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_penalty_paid",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Penalty Paid",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-05 10:06:58.792841",
|
||||
"modified": "2021-04-05 13:45:19.137896",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Repayment",
|
||||
|
@ -21,6 +21,7 @@ class LoanRepayment(AccountsController):
|
||||
def validate(self):
|
||||
amounts = calculate_amounts(self.against_loan, self.posting_date)
|
||||
self.set_missing_values(amounts)
|
||||
self.check_future_entries()
|
||||
self.validate_amount()
|
||||
self.allocate_amounts(amounts)
|
||||
|
||||
@ -60,16 +61,29 @@ class LoanRepayment(AccountsController):
|
||||
if not self.payable_amount:
|
||||
self.payable_amount = flt(amounts['payable_amount'], precision)
|
||||
|
||||
shortfall_amount = flt(frappe.db.get_value('Loan Security Shortfall', {'loan': self.against_loan, 'status': 'Pending'},
|
||||
'shortfall_amount'))
|
||||
|
||||
if shortfall_amount:
|
||||
self.shortfall_amount = shortfall_amount
|
||||
|
||||
if amounts.get('due_date'):
|
||||
self.due_date = amounts.get('due_date')
|
||||
|
||||
def check_future_entries(self):
|
||||
future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date),
|
||||
"docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
|
||||
|
||||
if future_repayment_date:
|
||||
frappe.throw("Repayment already made till date {0}".format(getdate(future_repayment_date)))
|
||||
|
||||
def validate_amount(self):
|
||||
precision = cint(frappe.db.get_default("currency_precision")) or 2
|
||||
|
||||
if not self.amount_paid:
|
||||
frappe.throw(_("Amount paid cannot be zero"))
|
||||
|
||||
if self.amount_paid < self.penalty_amount:
|
||||
if not self.shortfall_amount and self.amount_paid < self.penalty_amount:
|
||||
msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
|
||||
frappe.throw(msg)
|
||||
|
||||
@ -148,11 +162,28 @@ class LoanRepayment(AccountsController):
|
||||
def allocate_amounts(self, repayment_details):
|
||||
self.set('repayment_details', [])
|
||||
self.principal_amount_paid = 0
|
||||
total_interest_paid = 0
|
||||
interest_paid = self.amount_paid - self.penalty_amount
|
||||
self.total_penalty_paid = 0
|
||||
interest_paid = self.amount_paid
|
||||
|
||||
if self.amount_paid - self.penalty_amount > 0:
|
||||
interest_paid = self.amount_paid - self.penalty_amount
|
||||
if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
|
||||
self.principal_amount_paid = self.shortfall_amount
|
||||
elif self.shortfall_amount:
|
||||
self.principal_amount_paid = self.amount_paid
|
||||
|
||||
interest_paid -= self.principal_amount_paid
|
||||
|
||||
if interest_paid > 0:
|
||||
if self.penalty_amount and interest_paid > self.penalty_amount:
|
||||
self.total_penalty_paid = self.penalty_amount
|
||||
elif self.penalty_amount:
|
||||
self.total_penalty_paid = interest_paid
|
||||
|
||||
interest_paid -= self.total_penalty_paid
|
||||
|
||||
total_interest_paid = 0
|
||||
# interest_paid = self.amount_paid - self.principal_amount_paid - self.penalty_amount
|
||||
|
||||
if interest_paid > 0:
|
||||
for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])):
|
||||
if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
|
||||
interest_amount = amounts['interest_amount']
|
||||
@ -177,7 +208,7 @@ class LoanRepayment(AccountsController):
|
||||
'paid_principal_amount': paid_principal
|
||||
})
|
||||
|
||||
if repayment_details['unaccrued_interest'] and interest_paid:
|
||||
if repayment_details['unaccrued_interest'] and interest_paid > 0:
|
||||
# no of days for which to accrue interest
|
||||
# Interest can only be accrued for an entire day and not partial
|
||||
if interest_paid > repayment_details['unaccrued_interest']:
|
||||
@ -193,20 +224,20 @@ class LoanRepayment(AccountsController):
|
||||
interest_paid -= no_of_days * per_day_interest
|
||||
|
||||
self.total_interest_paid = total_interest_paid
|
||||
if interest_paid:
|
||||
if interest_paid > 0:
|
||||
self.principal_amount_paid += interest_paid
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
gle_map = []
|
||||
loan_details = frappe.get_doc("Loan", self.against_loan)
|
||||
|
||||
if self.penalty_amount:
|
||||
if self.total_penalty_paid:
|
||||
gle_map.append(
|
||||
self.get_gl_dict({
|
||||
"account": loan_details.loan_account,
|
||||
"against": loan_details.payment_account,
|
||||
"debit": self.penalty_amount,
|
||||
"debit_in_account_currency": self.penalty_amount,
|
||||
"debit": self.total_penalty_paid,
|
||||
"debit_in_account_currency": self.total_penalty_paid,
|
||||
"against_voucher_type": "Loan",
|
||||
"against_voucher": self.against_loan,
|
||||
"remarks": _("Penalty against loan:") + self.against_loan,
|
||||
@ -221,8 +252,8 @@ class LoanRepayment(AccountsController):
|
||||
self.get_gl_dict({
|
||||
"account": loan_details.penalty_income_account,
|
||||
"against": loan_details.payment_account,
|
||||
"credit": self.penalty_amount,
|
||||
"credit_in_account_currency": self.penalty_amount,
|
||||
"credit": self.total_penalty_paid,
|
||||
"credit_in_account_currency": self.total_penalty_paid,
|
||||
"against_voucher_type": "Loan",
|
||||
"against_voucher": self.against_loan,
|
||||
"remarks": _("Penalty against loan:") + self.against_loan,
|
||||
@ -284,7 +315,9 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
|
||||
|
||||
return lr
|
||||
|
||||
def get_accrued_interest_entries(against_loan):
|
||||
def get_accrued_interest_entries(against_loan, posting_date=None):
|
||||
if not posting_date:
|
||||
posting_date = getdate()
|
||||
|
||||
unpaid_accrued_entries = frappe.db.sql(
|
||||
"""
|
||||
@ -295,12 +328,13 @@ def get_accrued_interest_entries(against_loan):
|
||||
`tabLoan Interest Accrual`
|
||||
WHERE
|
||||
loan = %s
|
||||
AND posting_date <= %s
|
||||
AND (interest_amount - paid_interest_amount > 0 OR
|
||||
payable_principal_amount - paid_principal_amount > 0)
|
||||
AND
|
||||
docstatus = 1
|
||||
ORDER BY posting_date
|
||||
""", (against_loan), as_dict=1)
|
||||
""", (against_loan, posting_date), as_dict=1)
|
||||
|
||||
return unpaid_accrued_entries
|
||||
|
||||
@ -312,7 +346,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
||||
|
||||
against_loan_doc = frappe.get_doc("Loan", against_loan)
|
||||
loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
|
||||
accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name)
|
||||
accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name, posting_date)
|
||||
|
||||
pending_accrual_entries = {}
|
||||
|
||||
|
@ -12,7 +12,7 @@ from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpled
|
||||
class LoanSecurityShortfall(Document):
|
||||
pass
|
||||
|
||||
def update_shortfall_status(loan, security_value):
|
||||
def update_shortfall_status(loan, security_value, on_cancel=0):
|
||||
loan_security_shortfall = frappe.db.get_value("Loan Security Shortfall",
|
||||
{"loan": loan, "status": "Pending"}, ['name', 'shortfall_amount'], as_dict=1)
|
||||
|
||||
|
@ -70,7 +70,9 @@
|
||||
{
|
||||
"fieldname": "loan_repayment_entry",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Loan Repayment Entry",
|
||||
"no_copy": 1,
|
||||
"options": "Loan Repayment",
|
||||
"read_only": 1
|
||||
},
|
||||
@ -83,9 +85,10 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-16 13:17:04.798335",
|
||||
"modified": "2021-03-14 20:47:11.725818",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Salary Slip Loan",
|
||||
|
@ -47,6 +47,8 @@ class JobCard(Document):
|
||||
if d.completed_qty:
|
||||
self.total_completed_qty += d.completed_qty
|
||||
|
||||
self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
|
||||
|
||||
def get_overlap_for(self, args, check_next_available_slot=False):
|
||||
production_capacity = 1
|
||||
|
||||
|
@ -6,6 +6,8 @@ def execute():
|
||||
if "Healthcare" not in frappe.get_active_domains():
|
||||
return
|
||||
|
||||
frappe.reload_doc("healthcare", "doctype", "Therapy Session")
|
||||
frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order")
|
||||
frappe.reload_doc("healthcare", "doctype", "Patient History Settings")
|
||||
frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type")
|
||||
frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type")
|
||||
|
@ -133,45 +133,59 @@ frappe.ui.form.on('Payroll Entry', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('employee', 'employees', () => {
|
||||
if (!frm.doc.company) {
|
||||
frappe.msgprint(__("Please set a Company"));
|
||||
return [];
|
||||
}
|
||||
return {
|
||||
query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query",
|
||||
filters: frm.events.get_employee_filters(frm)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_filters: function (frm) {
|
||||
let filters = {};
|
||||
filters['company'] = frm.doc.company;
|
||||
filters['start_date'] = frm.doc.start_date;
|
||||
filters['end_date'] = frm.doc.end_date;
|
||||
|
||||
if (frm.doc.department) {
|
||||
filters['department'] = frm.doc.department;
|
||||
}
|
||||
if (frm.doc.branch) {
|
||||
filters['branch'] = frm.doc.branch;
|
||||
}
|
||||
if (frm.doc.designation) {
|
||||
filters['designation'] = frm.doc.designation;
|
||||
}
|
||||
if (frm.doc.employees) {
|
||||
filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee);
|
||||
}
|
||||
return filters;
|
||||
},
|
||||
|
||||
payroll_frequency: function (frm) {
|
||||
frm.trigger("set_start_end_dates").then( ()=> {
|
||||
frm.events.clear_employee_table(frm);
|
||||
frm.events.get_employee_with_salary_slip_and_set_query(frm);
|
||||
});
|
||||
},
|
||||
|
||||
employee_filters: function (frm, emp_list) {
|
||||
frm.set_query('employee', 'employees', () => {
|
||||
return {
|
||||
filters: {
|
||||
name: ["not in", emp_list]
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_employee_with_salary_slip_and_set_query: function (frm) {
|
||||
frappe.db.get_list('Salary Slip', {
|
||||
filters: {
|
||||
start_date: frm.doc.start_date,
|
||||
end_date: frm.doc.end_date,
|
||||
docstatus: 1,
|
||||
},
|
||||
fields: ['employee']
|
||||
}).then((emp) => {
|
||||
var emp_list = [];
|
||||
emp.forEach((employee_data) => {
|
||||
emp_list.push(Object.values(employee_data)[0]);
|
||||
});
|
||||
frm.events.employee_filters(frm, emp_list);
|
||||
});
|
||||
},
|
||||
|
||||
company: function (frm) {
|
||||
frm.events.clear_employee_table(frm);
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
frm.trigger("set_payable_account_and_currency");
|
||||
},
|
||||
|
||||
set_payable_account_and_currency: function (frm) {
|
||||
frappe.db.get_value("Company", {"name": frm.doc.company}, "default_currency", (r) => {
|
||||
frm.set_value('currency', r.default_currency);
|
||||
});
|
||||
frappe.db.get_value("Company", {"name": frm.doc.company}, "default_payroll_payable_account", (r) => {
|
||||
frm.set_value('payroll_payable_account', r.default_payroll_payable_account);
|
||||
});
|
||||
},
|
||||
|
||||
currency: function (frm) {
|
||||
@ -345,11 +359,3 @@ let render_employee_attendance = function (frm, data) {
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
frappe.ui.form.on('Payroll Employee Detail', {
|
||||
employee: function(frm) {
|
||||
if (!frm.doc.payroll_frequency) {
|
||||
frappe.throw(__("Please set a Payroll Frequency"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -10,16 +10,17 @@ from frappe.utils import cint, flt, add_days, getdate, add_to_date, DATE_FORMAT,
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
|
||||
from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||
|
||||
class PayrollEntry(Document):
|
||||
def onload(self):
|
||||
if not self.docstatus==1 or self.salary_slips_submitted:
|
||||
return
|
||||
return
|
||||
|
||||
# check if salary slips were manually submitted
|
||||
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
|
||||
if cint(entries) == len(self.employees):
|
||||
self.set_onload("submitted_ss", True)
|
||||
self.set_onload("submitted_ss", True)
|
||||
|
||||
def validate(self):
|
||||
self.number_of_employees = len(self.employees)
|
||||
@ -59,16 +60,16 @@ class PayrollEntry(Document):
|
||||
condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
|
||||
|
||||
sal_struct = frappe.db.sql_list("""
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
select
|
||||
name from `tabSalary Structure`
|
||||
where
|
||||
docstatus = 1 and
|
||||
is_active = 'Yes'
|
||||
and company = %(company)s
|
||||
and currency = %(currency)s and
|
||||
ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
|
||||
{condition}""".format(condition=condition),
|
||||
{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
|
||||
|
||||
if sal_struct:
|
||||
cond += "and t2.salary_structure IN %(sal_struct)s "
|
||||
@ -176,15 +177,15 @@ class PayrollEntry(Document):
|
||||
"""
|
||||
Returns list of salary slips based on selected criteria
|
||||
"""
|
||||
cond = self.get_filter_condition()
|
||||
|
||||
ss_list = frappe.db.sql("""
|
||||
select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
|
||||
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
|
||||
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
|
||||
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
|
||||
where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s
|
||||
and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s
|
||||
""", (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict)
|
||||
return ss_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_salary_slips(self):
|
||||
self.check_permission('write')
|
||||
ss_list = self.get_sal_slip_list(ss_status=0)
|
||||
@ -270,26 +271,26 @@ class PayrollEntry(Document):
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount += flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"debit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"project": self.project
|
||||
})
|
||||
"account": acc_cc[0],
|
||||
"debit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"party_type": '',
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"project": self.project
|
||||
})
|
||||
|
||||
# Deductions
|
||||
for acc_cc, amount in deductions.items():
|
||||
exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
|
||||
payable_amount -= flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc_cc[0],
|
||||
"credit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"party_type": '',
|
||||
"project": self.project
|
||||
})
|
||||
"account": acc_cc[0],
|
||||
"credit_in_account_currency": flt(amt, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"cost_center": acc_cc[1] or self.cost_center,
|
||||
"party_type": '',
|
||||
"project": self.project
|
||||
})
|
||||
|
||||
# Payable amount
|
||||
exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
|
||||
@ -335,10 +336,9 @@ class PayrollEntry(Document):
|
||||
def make_payment_entry(self):
|
||||
self.check_permission('write')
|
||||
|
||||
cond = self.get_filter_condition()
|
||||
salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1
|
||||
where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
|
||||
""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True)
|
||||
where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s
|
||||
""", (self.start_date, self.end_date, self.name), as_list = True)
|
||||
|
||||
if salary_slip_name_list and len(salary_slip_name_list) > 0:
|
||||
salary_slip_total = 0
|
||||
@ -370,20 +370,20 @@ class PayrollEntry(Document):
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
})
|
||||
"account": self.payment_account,
|
||||
"bank_account": self.bank_account,
|
||||
"credit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
})
|
||||
|
||||
exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
|
||||
accounts.append({
|
||||
"account": payroll_payable_account,
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
"account": payroll_payable_account,
|
||||
"debit_in_account_currency": flt(amount, precision),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"reference_type": self.doctype,
|
||||
"reference_name": self.name
|
||||
})
|
||||
|
||||
if len(currencies) > 1:
|
||||
multi_currency = 1
|
||||
@ -409,6 +409,7 @@ class PayrollEntry(Document):
|
||||
self.update(get_start_end_dates(self.payroll_frequency,
|
||||
self.start_date or self.posting_date, self.company))
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_employee_attendance(self):
|
||||
employees_to_mark_attendance = []
|
||||
days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
|
||||
@ -424,7 +425,7 @@ class PayrollEntry(Document):
|
||||
employees_to_mark_attendance.append({
|
||||
"employee": employee_detail.employee,
|
||||
"employee_name": employee_detail.employee_name
|
||||
})
|
||||
})
|
||||
return employees_to_mark_attendance
|
||||
|
||||
def get_count_holidays_of_employee(self, employee, start_date):
|
||||
@ -441,11 +442,11 @@ class PayrollEntry(Document):
|
||||
def get_count_employee_attendance(self, employee, start_date):
|
||||
marked_days = 0
|
||||
attendances = frappe.get_all("Attendance",
|
||||
fields = ["count(*)"],
|
||||
filters = {
|
||||
"employee": employee,
|
||||
"attendance_date": ('between', [start_date, self.end_date])
|
||||
}, as_list=1)
|
||||
fields = ["count(*)"],
|
||||
filters = {
|
||||
"employee": employee,
|
||||
"attendance_date": ('between', [start_date, self.end_date])
|
||||
}, as_list=1)
|
||||
if attendances and attendances[0][0]:
|
||||
marked_days = attendances[0][0]
|
||||
return marked_days
|
||||
@ -553,6 +554,7 @@ def payroll_entry_has_bank_entries(name):
|
||||
def create_salary_slips_for_employees(employees, args, publish_progress=True):
|
||||
salary_slips_exists_for = get_existing_salary_slips(employees, args)
|
||||
count=0
|
||||
salary_slips_not_created = []
|
||||
for emp in employees:
|
||||
if emp not in salary_slips_exists_for:
|
||||
args.update({
|
||||
@ -566,33 +568,24 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
|
||||
frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
|
||||
title = _("Creating Salary Slips..."))
|
||||
else:
|
||||
salary_slip_name = frappe.db.sql(
|
||||
'''SELECT
|
||||
name
|
||||
FROM `tabSalary Slip`
|
||||
WHERE company=%s
|
||||
AND start_date >= %s
|
||||
AND end_date <= %s
|
||||
AND employee = %s
|
||||
''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
|
||||
|
||||
salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
|
||||
salary_slip_doc.exchange_rate = args.exchange_rate
|
||||
salary_slip_doc.set_totals()
|
||||
salary_slip_doc.db_update()
|
||||
salary_slips_not_created.append(emp)
|
||||
|
||||
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
|
||||
payroll_entry.db_set("salary_slips_created", 1)
|
||||
payroll_entry.notify_update()
|
||||
|
||||
if salary_slips_not_created:
|
||||
frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.")
|
||||
.format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange")
|
||||
|
||||
def get_existing_salary_slips(employees, args):
|
||||
return frappe.db.sql_list("""
|
||||
select distinct employee from `tabSalary Slip`
|
||||
where docstatus!= 2 and company = %s
|
||||
where docstatus!= 2 and company = %s and payroll_entry = %s
|
||||
and start_date >= %s and end_date <= %s
|
||||
and employee in (%s)
|
||||
""" % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))),
|
||||
[args.company, args.start_date, args.end_date] + employees)
|
||||
""" % ('%s', '%s', '%s', '%s', ', '.join(['%s']*len(employees))),
|
||||
[args.company, args.payroll_entry, args.start_date, args.end_date] + employees)
|
||||
|
||||
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
|
||||
submitted_ss = []
|
||||
@ -644,3 +637,61 @@ def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filte
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'start': start, 'page_len': page_len
|
||||
})
|
||||
|
||||
def get_employee_with_existing_salary_slip(start_date, end_date, company):
|
||||
return frappe.db.sql_list("""
|
||||
select employee from `tabSalary Slip`
|
||||
where
|
||||
(start_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
end_date between %(start_date)s and %(end_date)s
|
||||
or
|
||||
%(start_date)s between start_date and end_date)
|
||||
and company = %(company)s
|
||||
and docstatus = 1
|
||||
""", {'start_date': start_date, 'end_date': end_date, 'company': company})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
filters = frappe._dict(filters)
|
||||
conditions = []
|
||||
exclude_employees = []
|
||||
emp_cond = ''
|
||||
if filters.start_date and filters.end_date:
|
||||
employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company)
|
||||
emp = filters.get('employees')
|
||||
filters.pop('start_date')
|
||||
filters.pop('end_date')
|
||||
if filters.employees is not None:
|
||||
filters.pop('employees')
|
||||
if employee_list:
|
||||
exclude_employees.extend(employee_list)
|
||||
if emp:
|
||||
exclude_employees.extend(emp)
|
||||
if exclude_employees:
|
||||
emp_cond += 'and employee not in %(exclude_employees)s'
|
||||
|
||||
return frappe.db.sql("""select name, employee_name from `tabEmployee`
|
||||
where status = 'Active'
|
||||
and docstatus < 2
|
||||
and ({key} like %(txt)s
|
||||
or employee_name like %(txt)s)
|
||||
{emp_cond}
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
|
||||
idx desc,
|
||||
name, employee_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'key': searchfield,
|
||||
'fcond': get_filters_cond(doctype, filters, conditions),
|
||||
'mcond': get_match_cond(doctype),
|
||||
'emp_cond': emp_cond
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len,
|
||||
'exclude_employees': exclude_employees})
|
||||
|
@ -51,21 +51,22 @@ class TestPayrollEntry(unittest.TestCase):
|
||||
|
||||
company_doc = frappe.get_doc('Company', company)
|
||||
salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company=company)
|
||||
create_salary_structure_assignment(employee, salary_structure.name, company=company, currency='USD')
|
||||
frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
|
||||
salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
|
||||
dates = get_start_end_dates('Monthly', nowdate())
|
||||
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
|
||||
payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
|
||||
payroll_entry.make_payment_entry()
|
||||
|
||||
salary_slip.load_from_db()
|
||||
|
||||
payroll_je = salary_slip.journal_entry
|
||||
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
|
||||
if payroll_je:
|
||||
payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
|
||||
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
|
||||
self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
|
||||
|
||||
payment_entry = frappe.db.sql('''
|
||||
Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
|
||||
|
@ -39,7 +39,8 @@ frappe.ui.form.on("Salary Slip", {
|
||||
|
||||
frm.set_query("employee", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
query: "erpnext.controllers.queries.employee_query",
|
||||
filters: frm.doc.company
|
||||
};
|
||||
});
|
||||
},
|
||||
@ -93,28 +94,31 @@ frappe.ui.form.on("Salary Slip", {
|
||||
},
|
||||
|
||||
set_exchange_rate: function(frm, company_currency) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.hide_loan_section(frm);
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property("exchange_rate", "hidden", 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property("exchange_rate", "hidden", 1);
|
||||
frm.set_df_property("exchange_rate", "description", "");
|
||||
}
|
||||
if (frm.doc.docstatus === 0) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
if (from_currency != company_currency) {
|
||||
frm.events.hide_loan_section(frm);
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
to_currency: company_currency,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -124,9 +124,12 @@ class SalarySlip(TransactionBase):
|
||||
|
||||
def check_existing(self):
|
||||
if not self.salary_slip_based_on_timesheet:
|
||||
cond = ""
|
||||
if self.payroll_entry:
|
||||
cond += "and payroll_entry = '{0}'".format(self.payroll_entry)
|
||||
ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
|
||||
where start_date = %s and end_date = %s and docstatus != 2
|
||||
and employee = %s and name != %s""",
|
||||
and employee = %s and name != %s {0}""".format(cond),
|
||||
(self.start_date, self.end_date, self.employee, self.name))
|
||||
if ret_exist:
|
||||
self.employee = ''
|
||||
@ -1053,7 +1056,7 @@ class SalarySlip(TransactionBase):
|
||||
repayment_entry.save()
|
||||
repayment_entry.submit()
|
||||
|
||||
loan.loan_repayment_entry = repayment_entry.name
|
||||
frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
|
||||
|
||||
def cancel_loan_repayment_entry(self):
|
||||
for loan in self.loans:
|
||||
|
@ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', {
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "UOM Conversion Factor",
|
||||
filters: { "category": "Length" },
|
||||
filters: { "category": __("Length") },
|
||||
fields: ["to_uom"],
|
||||
limit_page_length: 500
|
||||
},
|
||||
|
@ -16,6 +16,11 @@ class TestShoppingCart(unittest.TestCase):
|
||||
Note:
|
||||
Shopping Cart == Quotation
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
create_test_contact_and_address()
|
||||
@ -51,8 +56,8 @@ class TestShoppingCart(unittest.TestCase):
|
||||
def test_add_to_cart(self):
|
||||
self.login_as_customer()
|
||||
|
||||
# remove from cart
|
||||
self.remove_all_items_from_cart()
|
||||
# clear existing quotations
|
||||
self.clear_existing_quotations()
|
||||
|
||||
# add first item
|
||||
update_cart("_Test Item", 1)
|
||||
@ -100,6 +105,7 @@ class TestShoppingCart(unittest.TestCase):
|
||||
self.assertEqual(len(quotation.get("items")), 1)
|
||||
|
||||
def test_tax_rule(self):
|
||||
self.create_tax_rule()
|
||||
self.login_as_customer()
|
||||
quotation = self.create_quotation()
|
||||
|
||||
@ -115,6 +121,13 @@ class TestShoppingCart(unittest.TestCase):
|
||||
|
||||
self.remove_test_quotation(quotation)
|
||||
|
||||
def create_tax_rule(self):
|
||||
tax_rule = frappe.get_test_records("Tax Rule")[0]
|
||||
try:
|
||||
frappe.get_doc(tax_rule).insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
def create_quotation(self):
|
||||
quotation = frappe.new_doc("Quotation")
|
||||
|
||||
@ -195,10 +208,15 @@ class TestShoppingCart(unittest.TestCase):
|
||||
"_Test Contact For _Test Customer")
|
||||
frappe.set_user("test_contact_customer@example.com")
|
||||
|
||||
def remove_all_items_from_cart(self):
|
||||
quotation = _get_cart_quotation()
|
||||
quotation.flags.ignore_permissions=True
|
||||
quotation.delete()
|
||||
def clear_existing_quotations(self):
|
||||
quotations = frappe.get_all("Quotation", filters={
|
||||
"party_name": get_party().name,
|
||||
"order_type": "Shopping Cart",
|
||||
"docstatus": 0
|
||||
}, order_by="modified desc", pluck="name")
|
||||
|
||||
for quotation in quotations:
|
||||
frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True)
|
||||
|
||||
def create_user_if_not_exists(self, email, first_name = None):
|
||||
if frappe.db.exists("User", email):
|
||||
|
@ -22,7 +22,7 @@ class TestPickList(unittest.TestCase):
|
||||
'purpose': 'Opening Stock',
|
||||
'expense_account': 'Temporary Opening - _TC',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'item_code': '_Test Item',
|
||||
'warehouse': '_Test Warehouse - _TC',
|
||||
'valuation_rate': 100,
|
||||
'qty': 5
|
||||
@ -37,7 +37,7 @@ class TestPickList(unittest.TestCase):
|
||||
'customer': '_Test Customer',
|
||||
'items_based_on': 'Sales Order',
|
||||
'locations': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'item_code': '_Test Item',
|
||||
'qty': 5,
|
||||
'stock_qty': 5,
|
||||
'conversion_factor': 1,
|
||||
@ -47,7 +47,7 @@ class TestPickList(unittest.TestCase):
|
||||
})
|
||||
pick_list.set_item_locations()
|
||||
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
|
||||
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
|
||||
self.assertEqual(pick_list.locations[0].qty, 5)
|
||||
|
||||
@ -237,7 +237,7 @@ class TestPickList(unittest.TestCase):
|
||||
'purpose': 'Opening Stock',
|
||||
'expense_account': 'Temporary Opening - _TC',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'item_code': '_Test Item',
|
||||
'warehouse': '_Test Warehouse - _TC',
|
||||
'valuation_rate': 100,
|
||||
'qty': 10
|
||||
@ -251,7 +251,7 @@ class TestPickList(unittest.TestCase):
|
||||
'customer': '_Test Customer',
|
||||
'company': '_Test Company',
|
||||
'items': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'item_code': '_Test Item',
|
||||
'qty': 10,
|
||||
'delivery_date': frappe.utils.today()
|
||||
}],
|
||||
@ -264,14 +264,14 @@ class TestPickList(unittest.TestCase):
|
||||
'customer': '_Test Customer',
|
||||
'items_based_on': 'Sales Order',
|
||||
'locations': [{
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'item_code': '_Test Item',
|
||||
'qty': 5,
|
||||
'stock_qty': 5,
|
||||
'conversion_factor': 1,
|
||||
'sales_order': '_T-Sales Order-1',
|
||||
'sales_order_item': '_T-Sales Order-1_item',
|
||||
}, {
|
||||
'item_code': '_Test Item Home Desktop 100',
|
||||
'item_code': '_Test Item',
|
||||
'qty': 5,
|
||||
'stock_qty': 5,
|
||||
'conversion_factor': 1,
|
||||
@ -281,12 +281,12 @@ class TestPickList(unittest.TestCase):
|
||||
})
|
||||
pick_list.set_item_locations()
|
||||
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
|
||||
self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
|
||||
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
|
||||
self.assertEqual(pick_list.locations[0].qty, 5)
|
||||
self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item')
|
||||
|
||||
self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100')
|
||||
self.assertEqual(pick_list.locations[1].item_code, '_Test Item')
|
||||
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC')
|
||||
self.assertEqual(pick_list.locations[1].qty, 5)
|
||||
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
|
||||
@ -302,4 +302,4 @@ class TestPickList(unittest.TestCase):
|
||||
# pass
|
||||
|
||||
# def test_pick_list_from_material_request(self):
|
||||
# pass
|
||||
# pass
|
||||
|
Loading…
x
Reference in New Issue
Block a user