Merge pull request #26735 from rohitwaghchaure/fixed-cogs-in-purchase-receipt
fix: COGS account in purchase receipt
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index afa0be9..4493a3f 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -34,11 +34,14 @@
}
}}, ignore_permissions=True)
student.save()
+
+ student_applicant = frappe.db.get_value("Student Applicant", source_name,
+ ["student_category", "program"], as_dict=True)
program_enrollment = frappe.new_doc("Program Enrollment")
program_enrollment.student = student.name
- program_enrollment.student_category = student.student_category
+ program_enrollment.student_category = student_applicant.student_category
program_enrollment.student_name = student.title
- program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
+ program_enrollment.program = student_applicant.program
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
return program_enrollment
diff --git a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
index 9be292b..1d74973 100644
--- a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
+++ b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
@@ -1,195 +1,68 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-06-10 03:29:02.539914",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-06-10 03:29:02.539914",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "student_applicant",
+ "student",
+ "student_name",
+ "column_break_3",
+ "student_batch_name",
+ "student_category"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "student_applicant",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student Applicant",
- "length": 0,
- "no_copy": 0,
- "options": "Student Applicant",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student_applicant",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student Applicant",
+ "options": "Student Applicant"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "student",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student",
- "length": 0,
- "no_copy": 0,
- "options": "Student",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student",
+ "options": "Student"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "student_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Student Name",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "student_batch_name",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Student Batch Name",
- "length": 0,
- "no_copy": 0,
- "options": "Student Batch Name",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "student_batch_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Student Batch Name",
+ "options": "Student Batch Name"
+ },
+ {
+ "fieldname": "student_category",
+ "fieldtype": "Link",
+ "label": "Student Category",
+ "options": "Student Category",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-01-02 12:03:53.890741",
- "modified_by": "Administrator",
- "module": "Education",
- "name": "Program Enrollment Tool Student",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "restrict_to_domain": "Education",
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-29 18:19:54.471594",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Program Enrollment Tool Student",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC"
}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py
index f760187..c2ed457 100644
--- a/erpnext/hr/doctype/appraisal/appraisal.py
+++ b/erpnext/hr/doctype/appraisal/appraisal.py
@@ -9,7 +9,7 @@
from frappe import _
from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, validate_active_employee
class Appraisal(Document):
def validate(self):
@@ -19,6 +19,7 @@
if not self.goals:
frappe.throw(_("Goals cannot be empty"))
+ validate_active_employee(self.employee)
set_employee_name(self)
self.validate_dates()
self.validate_existing_appraisal()
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 3412675..f79f0fe 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -8,11 +8,13 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, formatdate
+from erpnext.hr.utils import validate_active_employee
class Attendance(Document):
def validate(self):
from erpnext.controllers.status_updater import validate_status
validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
+ validate_active_employee(self.employee)
self.validate_attendance_date()
self.validate_duplicate_record()
self.validate_employee_status()
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py
index 090d532..7f88fed 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request.py
@@ -8,10 +8,11 @@
from frappe.model.document import Document
from frappe.utils import date_diff, add_days, getdate
from erpnext.hr.doctype.employee.employee import is_holiday
-from erpnext.hr.utils import validate_dates
+from erpnext.hr.utils import validate_dates, validate_active_employee
class AttendanceRequest(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_dates(self, self.from_date, self.to_date)
if self.half_day:
if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index a6fe429..0d7fded 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -7,12 +7,13 @@
from frappe import _
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, \
+from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \
get_holidays_for_employee, create_additional_leave_ledger_entry
class CompensatoryLeaveRequest(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_dates(self, self.work_from_date, self.work_end_date)
if self.half_day:
if not self.half_day_date:
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index fa017d9..5ca4756 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -13,8 +13,10 @@
from erpnext.utilities.transaction_base import delete_events
from frappe.utils.nestedset import NestedSet
-class EmployeeUserDisabledError(frappe.ValidationError): pass
-class EmployeeLeftValidationError(frappe.ValidationError): pass
+class EmployeeUserDisabledError(frappe.ValidationError):
+ pass
+class InactiveEmployeeStatusError(frappe.ValidationError):
+ pass
class Employee(NestedSet):
nsm_parent_field = 'reports_to'
@@ -196,7 +198,7 @@
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
message += "</li></ul><br>"
message += _("Please make sure the employees above report to another Active employee.")
- throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
+ throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee"))
if not self.relieving_date:
throw(_("Please enter relieving date."))
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 7d652a7..8fc7cf1 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -7,7 +7,7 @@
import erpnext
import unittest
import frappe.utils
-from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError
+from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
test_records = frappe.get_test_records('Employee')
@@ -45,10 +45,33 @@
employee2_doc.save()
employee1_doc.reload()
employee1_doc.status = 'Left'
- self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
+ self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
+
+ def test_employee_status_inactive(self):
+ from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+ from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
+ from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
+
+ employee = make_employee("test_employee_status@company.com")
+ employee_doc = frappe.get_doc("Employee", employee)
+ employee_doc.status = "Inactive"
+ employee_doc.save()
+ employee_doc.reload()
+
+ make_holiday_list()
+ frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
+
+ frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
+ salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
+ employee=employee_doc.name, company=employee_doc.company)
+ salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
+
+ self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
+
+ def tearDown(self):
+ frappe.db.rollback()
def make_employee(user, company=None, **kwargs):
- ""
if not frappe.db.get_value("User", user):
frappe.get_doc({
"doctype": "User",
@@ -80,4 +103,5 @@
employee.insert()
return employee.name
else:
+ frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
return frappe.get_value("Employee", {"employee_name":user}, "name")
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index 08e0b24..cbb3cc8 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -8,6 +8,7 @@
from frappe.model.document import Document
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
+from erpnext.hr.utils import validate_active_employee
class EmployeeAdvanceOverPayment(frappe.ValidationError):
pass
@@ -18,6 +19,7 @@
'make_payment_via_journal_entry')
def validate(self):
+ validate_active_employee(self.employee)
self.set_status()
def on_cancel(self):
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index 15fbd4e..60ea0f9 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -9,9 +9,11 @@
from frappe import _
from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift
+from erpnext.hr.utils import validate_active_employee
class EmployeeCheckin(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_duplicate_log()
self.fetch_shift()
@@ -122,7 +124,7 @@
def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
Zero is returned for all invalid cases.
-
+
:param logs: The List of 'Employee Checkin'.
:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
index 83fb235..a3a6183 100644
--- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
@@ -7,12 +7,11 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate
-from erpnext.hr.utils import update_employee
+from erpnext.hr.utils import update_employee, validate_active_employee
class EmployeePromotion(Document):
def validate(self):
- if frappe.get_value("Employee", self.employee, "status") != "Active":
- frappe.throw(_("Cannot promote Employee with status Left or Inactive"))
+ validate_active_employee(self.employee)
def before_submit(self):
if getdate(self.promotion_date) > getdate():
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
index 45d6872..0493306 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -7,9 +7,11 @@
from frappe import _
from frappe.utils import get_link_to_form
from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
class EmployeeReferral(Document):
def validate(self):
+ validate_active_employee(self.referrer)
self.set_full_name()
self.set_referral_bonus_payment_status()
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
index 6eec9fa..c200774 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
@@ -10,10 +10,6 @@
from erpnext.hr.utils import update_employee
class EmployeeTransfer(Document):
- def validate(self):
- if frappe.get_value("Employee", self.employee, "status") != "Active":
- frappe.throw(_("Cannot transfer Employee with status Left or Inactive"))
-
def before_submit(self):
if getdate(self.transfer_date) > getdate():
frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 8cef143..95e2806 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -6,7 +6,7 @@
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, share_doc_with_approver
+from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee
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
@@ -23,6 +23,7 @@
'make_payment_via_journal_entry')
def validate(self):
+ validate_active_employee(self.employee)
self.validate_advances()
self.validate_sanctioned_amount()
self.calculate_total_amount()
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index cee6f37..93fb19f 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
-from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
+from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee
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
@@ -22,6 +22,7 @@
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
def validate(self):
+ validate_active_employee(self.employee)
set_employee_name(self)
self.validate_dates()
self.validate_balance_leaves()
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index e041b7f..912bd8a 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -7,7 +7,7 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import getdate, nowdate, flt
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, validate_active_employee
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
@@ -15,6 +15,7 @@
class LeaveEncashment(Document):
def validate(self):
set_employee_name(self)
+ validate_active_employee(self.employee)
self.get_leave_details_for_encashment()
self.validate_salary_structure()
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index ab65260..89ae4d5 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -9,10 +9,12 @@
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
+from erpnext.hr.utils import validate_active_employee
from datetime import timedelta, datetime
class ShiftAssignment(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_overlapping_dates()
if self.end_date and self.end_date <= self.start_date:
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index 177c45e..6461f07 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -7,12 +7,13 @@
from frappe import _
from frappe.model.document import Document
from frappe.utils import formatdate, getdate
-from erpnext.hr.utils import share_doc_with_approver
+from erpnext.hr.utils import share_doc_with_approver, validate_active_employee
class OverlapError(frappe.ValidationError): pass
class ShiftRequest(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_dates()
self.validate_shift_request_overlap_dates()
self.validate_approver()
diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py
index 01d3f34..60834d3 100644
--- a/erpnext/hr/doctype/travel_request/travel_request.py
+++ b/erpnext/hr/doctype/travel_request/travel_request.py
@@ -5,6 +5,8 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
class TravelRequest(Document):
- pass
+ def validate(self):
+ validate_active_employee(self.employee)
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 3cc1a01..992b18d 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -3,13 +3,12 @@
import erpnext
import frappe
-from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError
from frappe import _
from frappe.desk.form import assign_to
from frappe.model.document import Document
from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate,
- get_datetime, getdate, nowdate, today, unique)
-
+ get_datetime, getdate, nowdate, today, unique, get_link_to_form)
class DuplicateDeclarationError(frappe.ValidationError): pass
@@ -410,3 +409,8 @@
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))
+
+def validate_active_employee(employee):
+ if frappe.db.get_value("Employee", employee, "status") == "Inactive":
+ frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format(
+ get_link_to_form("Employee", employee)), InactiveEmployeeStatusError)
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index ebeddf9..381f399 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -7,6 +7,7 @@
from frappe.model.document import Document
from frappe import _, bold
from frappe.utils import getdate, date_diff, comma_and, formatdate
+from erpnext.hr.utils import validate_active_employee
class AdditionalSalary(Document):
def on_submit(self):
@@ -19,6 +20,7 @@
self.update_employee_referral(cancel=True)
def validate(self):
+ validate_active_employee(self.employee)
self.validate_dates()
self.validate_salary_structure()
self.validate_recurring_additional_salary_overlap()
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
index 27df30a..5ebe514 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
@@ -9,10 +9,11 @@
from frappe.model.document import Document
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
-from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount
+from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee
class EmployeeBenefitApplication(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_duplicate_on_payroll_period()
if not self.max_benefits:
self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period)
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
index d9937a7..c6713f3 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
@@ -8,12 +8,13 @@
from frappe.utils import flt
from frappe.model.document import Document
from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
-from erpnext.hr.utils import get_previous_claimed_amount
+from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee
from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period
from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
class EmployeeBenefitClaim(Document):
def validate(self):
+ validate_active_employee(self.employee)
max_benefits = get_max_benefits(self.employee, self.claim_date)
if not max_benefits or max_benefits <= 0:
frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee))
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
index ead3db1..6b918ba 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
@@ -6,9 +6,11 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
class EmployeeIncentive(Document):
def validate(self):
+ validate_active_employee(self.employee)
self.validate_salary_structure()
def validate_salary_structure(self):
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
index fb71a28..e11d60a 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
@@ -8,11 +8,12 @@
from frappe import _
from frappe.utils import flt
from frappe.model.mapper import get_mapped_doc
-from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
+from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionDeclaration(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_tax_declaration(self.declarations)
validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
self.set_total_declared_amount()
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
index 5bc33a6..8131ae0 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
@@ -7,11 +7,12 @@
from frappe.model.document import Document
from frappe import _
from frappe.utils import flt
-from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
+from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
class EmployeeTaxExemptionProofSubmission(Document):
def validate(self):
+ validate_active_employee(self.employee)
validate_tax_declaration(self.tax_exemption_proofs)
self.set_total_actual_amount()
self.set_total_exemption_amount()
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
index 049ea26..055bea7 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
@@ -7,11 +7,10 @@
from frappe.model.document import Document
from frappe import _
from frappe.utils import getdate
-
+from erpnext.hr.utils import validate_active_employee
class RetentionBonus(Document):
def validate(self):
- if frappe.get_value('Employee', self.employee, 'status') != 'Active':
- frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees'))
+ validate_active_employee(self.employee)
if getdate(self.bonus_payment_date) < getdate():
frappe.throw(_('Bonus Payment Date cannot be a past date'))
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index f82b0d5..c321f6f 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -19,6 +19,7 @@
from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
from erpnext.accounts.utils import get_fiscal_year
+from erpnext.hr.utils import validate_active_employee
from six import iteritems
class SalarySlip(TransactionBase):
@@ -39,6 +40,7 @@
def validate(self):
self.status = self.get_status()
+ validate_active_employee(self.employee)
self.validate_dates()
self.check_existing()
if not self.salary_slip_based_on_timesheet:
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index c8bd80f..ae38d4c 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -15,12 +15,15 @@
WorkstationHolidayError)
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
from erpnext.setup.utils import get_exchange_rate
+from erpnext.hr.utils import validate_active_employee
class OverlapError(frappe.ValidationError): pass
class OverWorkLoggedError(frappe.ValidationError): pass
class Timesheet(Document):
def validate(self):
+ if self.employee:
+ validate_active_employee(self.employee)
self.set_employee_name()
self.set_status()
self.validate_dates()
diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
index c5c01c5..4ff2dd7 100644
--- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
+++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
@@ -62,12 +62,12 @@
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
self.assertEqual(exchange_rate, 62.9)
-
- # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
+
+ # Exchange rate as on 15th Dec, 2015
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling")
self.assertFalse(exchange_rate == 60)
- self.assertEqual(flt(exchange_rate, 3), 66.894)
+ self.assertEqual(flt(exchange_rate, 3), 66.999)
def test_exchange_rate_strict(self):
# strict currency settings
@@ -77,28 +77,17 @@
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying")
self.assertEqual(exchange_rate, 60.0)
- # Will fetch from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
- self.assertEqual(flt(exchange_rate, 3), 67.79)
+ self.assertEqual(flt(exchange_rate, 3), 67.235)
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
self.assertEqual(exchange_rate, 62.9)
- # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
+ # Exchange rate as on 15th Dec, 2015
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying")
- self.assertEqual(flt(exchange_rate, 3), 66.894)
-
- exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling")
- self.assertEqual(exchange_rate, 65.1)
-
- # NGN is not available on fixer.io so these should return 0
- exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling")
- self.assertEqual(exchange_rate, 0)
-
- exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling")
- self.assertEqual(exchange_rate, 0)
+ self.assertEqual(flt(exchange_rate, 3), 66.999)
def test_exchange_rate_strict_switched(self):
# Start with allow_stale is True
@@ -111,4 +100,4 @@
# Will fetch from fixer.io
self.clear_cache()
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
- self.assertEqual(flt(exchange_rate, 3), 67.79)
+ self.assertEqual(flt(exchange_rate, 3), 67.235)
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index d5dbd4c..e49259e 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -93,21 +93,21 @@
try:
cache = frappe.cache()
- key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency)
+ key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)
value = cache.get(key)
if not value:
import requests
- api_url = "https://frankfurter.app/{0}".format(transaction_date)
+ api_url = "https://api.exchangerate.host/convert"
response = requests.get(api_url, params={
- "base": from_currency,
- "symbols": to_currency
+ "date": transaction_date,
+ "from": from_currency,
+ "to": to_currency
})
# expire in 6 hours
response.raise_for_status()
- value = response.json()["rates"][to_currency]
-
- cache.set_value(key, value, expires_in_sec=6 * 60 * 60)
+ value = response.json()["result"]
+ cache.setex(name=key, time=21600, value=flt(value))
return flt(value)
except:
frappe.log_error(title="Get Exchange Rate")
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 4540954..84f65a0 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -56,25 +56,40 @@
},
get_items: function(frm) {
- let fields = [{
- label: 'Warehouse', fieldname: 'warehouse', fieldtype: 'Link', options: 'Warehouse', reqd: 1,
- "get_query": function() {
- return {
- "filters": {
- "company": frm.doc.company,
- }
- };
+ let fields = [
+ {
+ label: 'Warehouse',
+ fieldname: 'warehouse',
+ fieldtype: 'Link',
+ options: 'Warehouse',
+ reqd: 1,
+ "get_query": function() {
+ return {
+ "filters": {
+ "company": frm.doc.company,
+ }
+ };
+ }
+ },
+ {
+ label: "Item Code",
+ fieldname: "item_code",
+ fieldtype: "Link",
+ options: "Item",
+ "get_query": function() {
+ return {
+ "filters": {
+ "disabled": 0,
+ }
+ };
+ }
+ },
+ {
+ label: __("Ignore Empty Stock"),
+ fieldname: "ignore_empty_stock",
+ fieldtype: "Check"
}
- }, {
- label: "Item Code", fieldname: "item_code", fieldtype: "Link", options: "Item",
- "get_query": function() {
- return {
- "filters": {
- "disabled": 0,
- }
- };
- }
- }];
+ ];
frappe.prompt(fields, function(data) {
frappe.call({
@@ -84,22 +99,21 @@
posting_date: frm.doc.posting_date,
posting_time: frm.doc.posting_time,
company: frm.doc.company,
- item_code: data.item_code
+ item_code: data.item_code,
+ ignore_empty_stock: data.ignore_empty_stock
},
callback: function(r) {
+ if (r.exc || !r.message || !r.message.length) return;
+
frm.clear_table("items");
- for (var i=0; i<r.message.length; i++) {
- var d = frm.add_child("items");
- $.extend(d, r.message[i]);
- if (!d.qty) {
- d.qty = 0;
- }
+ r.message.forEach((row) => {
+ let item = frm.add_child("items");
+ $.extend(item, row);
- if (!d.valuation_rate) {
- d.valuation_rate = 0;
- }
- }
+ item.qty = item.qty || 0;
+ item.valuation_rate = item.valuation_rate || 0;
+ });
frm.refresh_field("items");
}
});
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 9875491..0bae7cf 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -483,7 +483,8 @@
self._cancel()
@frappe.whitelist()
-def get_items(warehouse, posting_date, posting_time, company, item_code=None):
+def get_items(warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False):
+ ignore_empty_stock = cint(ignore_empty_stock)
items = [frappe._dict({
'item_code': item_code,
'warehouse': warehouse
@@ -497,18 +498,24 @@
for d in items:
if d.item_code in itemwise_batch_data:
- stock_bal = get_stock_balance(d.item_code, d.warehouse,
- posting_date, posting_time, with_valuation_rate=True)
+ valuation_rate = get_stock_balance(d.item_code, d.warehouse,
+ posting_date, posting_time, with_valuation_rate=True)[1]
for row in itemwise_batch_data.get(d.item_code):
- args = get_item_data(row, row.qty, stock_bal[1])
+ if ignore_empty_stock and not row.qty:
+ continue
+
+ args = get_item_data(row, row.qty, valuation_rate)
res.append(args)
else:
stock_bal = get_stock_balance(d.item_code, d.warehouse, posting_date, posting_time,
with_valuation_rate=True , with_serial_no=cint(d.has_serial_no))
+ qty, valuation_rate, serial_no = stock_bal[0], stock_bal[1], stock_bal[2] if cint(d.has_serial_no) else ''
- args = get_item_data(d, stock_bal[0], stock_bal[1],
- stock_bal[2] if cint(d.has_serial_no) else '')
+ if ignore_empty_stock and not stock_bal[0]:
+ continue
+
+ args = get_item_data(d, qty, valuation_rate, serial_no)
res.append(args)
@@ -516,24 +523,44 @@
def get_items_for_stock_reco(warehouse, company):
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
- items = frappe.db.sql("""
- select i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
- from tabBin bin, tabItem i
- where i.name=bin.item_code and IFNULL(i.disabled, 0) = 0 and i.is_stock_item = 1
- and i.has_variants = 0 and exists(
- select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse
- )
- """, (lft, rgt), as_dict=1)
+ items = frappe.db.sql(f"""
+ select
+ i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
+ from
+ tabBin bin, tabItem i
+ where
+ i.name = bin.item_code
+ and IFNULL(i.disabled, 0) = 0
+ and i.is_stock_item = 1
+ and i.has_variants = 0
+ and exists(
+ select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse
+ )
+ """, as_dict=1)
items += frappe.db.sql("""
- select i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
- from tabItem i, `tabItem Default` id
- where i.name = id.parent
- and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse)
- and i.is_stock_item = 1 and i.has_variants = 0 and IFNULL(i.disabled, 0) = 0 and id.company=%s
+ select
+ i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
+ from
+ tabItem i, `tabItem Default` id
+ where
+ i.name = id.parent
+ and exists(
+ select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse
+ )
+ and i.is_stock_item = 1
+ and i.has_variants = 0
+ and IFNULL(i.disabled, 0) = 0
+ and id.company = %s
group by i.name
""", (lft, rgt, company), as_dict=1)
+ # remove duplicates
+ # check if item-warehouse key extracted from each entry exists in set iw_keys
+ # and update iw_keys
+ iw_keys = set()
+ items = [item for item in items if [(item.item_code, item.warehouse) not in iw_keys, iw_keys.add((item.item_code, item.warehouse))][0]]
+
return items
def get_item_data(row, qty, valuation_rate, serial_no=None):