Merge pull request #26712 from ankush/against_acc_pr
fix: empty "against account" in Purchase Receipt GLE
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index cc98f45..bd62227 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -8,11 +8,12 @@
jobs:
main:
runs-on: ubuntu-latest
+ timeout-minutes: 60
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
- repository: "ankush/backport"
+ repository: "frappe/backport"
path: ./actions
ref: develop
- name: Install Actions
diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml
index cdf676d..db46c56 100644
--- a/.github/workflows/docs-checker.yml
+++ b/.github/workflows/docs-checker.yml
@@ -6,6 +6,7 @@
jobs:
build:
runs-on: ubuntu-latest
+ timeout-minutes: 10
steps:
- name: 'Setup Environment'
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index b96a3d6..dc72987 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -5,6 +5,7 @@
jobs:
test:
runs-on: ubuntu-18.04
+ timeout-minutes: 60
name: Patch Test
diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml
index 69afa15..606002e 100644
--- a/.github/workflows/server-tests.yml
+++ b/.github/workflows/server-tests.yml
@@ -9,6 +9,7 @@
jobs:
test:
runs-on: ubuntu-18.04
+ timeout-minutes: 60
strategy:
fail-fast: false
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 412a05b..9e29b6f 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -7,6 +7,7 @@
jobs:
test:
runs-on: ubuntu-18.04
+ timeout-minutes: 60
strategy:
fail-fast: false
diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py
new file mode 100644
index 0000000..1898222
--- /dev/null
+++ b/erpnext/controllers/employee_boarding_controller.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe import _
+from frappe.desk.form import assign_to
+from frappe.model.document import Document
+from frappe.utils import flt, unique
+
+class EmployeeBoardingController(Document):
+ '''
+ Create the project and the task for the boarding process
+ Assign to the concerned person and roles as per the onboarding/separation template
+ '''
+ def validate(self):
+ # remove the task if linked before submitting the form
+ if self.amended_from:
+ for activity in self.activities:
+ activity.task = ''
+
+ def on_submit(self):
+ # create the project for the given employee onboarding
+ project_name = _(self.doctype) + ' : '
+ if self.doctype == 'Employee Onboarding':
+ project_name += self.job_applicant
+ else:
+ project_name += self.employee
+
+ project = frappe.get_doc({
+ 'doctype': 'Project',
+ 'project_name': project_name,
+ 'expected_start_date': self.date_of_joining if self.doctype == 'Employee Onboarding' else self.resignation_letter_date,
+ 'department': self.department,
+ 'company': self.company
+ }).insert(ignore_permissions=True, ignore_mandatory=True)
+
+ self.db_set('project', project.name)
+ self.db_set('boarding_status', 'Pending')
+ self.reload()
+ self.create_task_and_notify_user()
+
+ def create_task_and_notify_user(self):
+ # create the task for the given project and assign to the concerned person
+ for activity in self.activities:
+ if activity.task:
+ continue
+
+ task = frappe.get_doc({
+ 'doctype': 'Task',
+ 'project': self.project,
+ 'subject': activity.activity_name + ' : ' + self.employee_name,
+ 'description': activity.description,
+ 'department': self.department,
+ 'company': self.company,
+ 'task_weight': activity.task_weight
+ }).insert(ignore_permissions=True)
+ activity.db_set('task', task.name)
+
+ users = [activity.user] if activity.user else []
+ if activity.role:
+ user_list = frappe.db.sql_list('''
+ SELECT
+ DISTINCT(has_role.parent)
+ FROM
+ `tabHas Role` has_role
+ LEFT JOIN `tabUser` user
+ ON has_role.parent = user.name
+ WHERE
+ has_role.parenttype = 'User'
+ AND user.enabled = 1
+ AND has_role.role = %s
+ ''', activity.role)
+ users = unique(users + user_list)
+
+ if 'Administrator' in users:
+ users.remove('Administrator')
+
+ # assign the task the users
+ if users:
+ self.assign_task_to_users(task, users)
+
+ def assign_task_to_users(self, task, users):
+ for user in users:
+ args = {
+ 'assign_to': [user],
+ 'doctype': task.doctype,
+ 'name': task.name,
+ 'description': task.description or task.subject,
+ 'notify': self.notify_users_by_email
+ }
+ assign_to.add(args)
+
+ def on_cancel(self):
+ # delete task project
+ for task in frappe.get_all('Task', filters={'project': self.project}):
+ frappe.delete_doc('Task', task.name, force=1)
+ frappe.delete_doc('Project', self.project, force=1)
+ self.db_set('project', '')
+ for activity in self.activities:
+ activity.db_set('task', '')
+
+
+@frappe.whitelist()
+def get_onboarding_details(parent, parenttype):
+ return frappe.get_all('Employee Boarding Activity',
+ fields=['activity_name', 'role', 'user', 'required_for_employee_creation', 'description', 'task_weight'],
+ filters={'parent': parent, 'parenttype': parenttype},
+ order_by= 'idx')
+
+
+def update_employee_boarding_status(project):
+ employee_onboarding = frappe.db.exists('Employee Onboarding', {'project': project.name})
+ employee_separation = frappe.db.exists('Employee Separation', {'project': project.name})
+
+ if not (employee_onboarding or employee_separation):
+ return
+
+ status = 'Pending'
+ if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0:
+ status = 'In Process'
+ elif flt(project.percent_complete) == 100.0:
+ status = 'Completed'
+
+ if employee_onboarding:
+ frappe.db.set_value('Employee Onboarding', employee_onboarding, 'boarding_status', status)
+ elif employee_separation:
+ frappe.db.set_value('Employee Separation', employee_separation, 'boarding_status', status)
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index cb72f6b..08e0b24 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -22,7 +22,6 @@
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry')
- self.set_status()
def set_status(self):
if self.docstatus == 0:
@@ -183,9 +182,9 @@
bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
if not bank_cash_account:
frappe.throw(_("Please set a Default Cash Account in Company defaults"))
-
+
advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
-
+
je = frappe.new_doc('Journal Entry')
je.posting_date = nowdate()
je.voucher_type = get_voucher_type(mode_of_payment)
@@ -229,4 +228,4 @@
if mode_of_payment_type == "Bank":
voucher_type = "Bank Entry"
- return voucher_type
\ No newline at end of file
+ return voucher_type
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
index d6047e1..5d1a024 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
@@ -50,28 +50,13 @@
}, __('Create'));
frm.page.set_inner_btn_group_as_primary(__('Create'));
}
- if (frm.doc.docstatus === 1 && frm.doc.project) {
- frappe.call({
- method: "erpnext.hr.utils.get_boarding_status",
- args: {
- "project": frm.doc.project
- },
- callback: function(r) {
- if (r.message) {
- frm.set_value('boarding_status', r.message);
- }
- refresh_field("boarding_status");
- }
- });
- }
-
},
employee_onboarding_template: function(frm) {
frm.set_value("activities" ,"");
if (frm.doc.employee_onboarding_template) {
frappe.call({
- method: "erpnext.hr.utils.get_onboarding_details",
+ method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
args: {
"parent": frm.doc.employee_onboarding_template,
"parenttype": "Employee Onboarding Template"
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
index 783c757..673e228 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
@@ -30,18 +30,14 @@
"fieldtype": "Link",
"label": "Job Applicant",
"options": "Job Applicant",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "job_offer",
"fieldtype": "Link",
"label": "Job Offer",
"options": "Job Offer",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fetch_from": "job_applicant.applicant_name",
@@ -49,116 +45,90 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Employee Name",
- "reqd": 1,
- "show_days": 1,
- "show_seconds": 1
+ "reqd": 1
},
{
"fieldname": "employee",
"fieldtype": "Link",
"label": "Employee",
"options": "Employee",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "date_of_joining",
"fieldtype": "Date",
"in_list_view": 1,
- "label": "Date of Joining",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Date of Joining"
},
{
"allow_on_submit": 1,
+ "default": "Pending",
"fieldname": "boarding_status",
"fieldtype": "Select",
"label": "Status",
- "options": "\nPending\nIn Process\nCompleted",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Pending\nIn Process\nCompleted",
+ "read_only": 1
},
{
"allow_on_submit": 1,
"default": "0",
"fieldname": "notify_users_by_email",
"fieldtype": "Check",
- "label": "Notify users by email",
- "show_days": 1,
- "show_seconds": 1
+ "label": "Notify users by email"
},
{
"fieldname": "column_break_7",
- "fieldtype": "Column Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Column Break"
},
{
"fieldname": "employee_onboarding_template",
"fieldtype": "Link",
"label": "Employee Onboarding Template",
- "options": "Employee Onboarding Template",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Employee Onboarding Template"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
- "options": "Company",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Company"
},
{
"fieldname": "department",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Department",
- "options": "Department",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Department"
},
{
"fieldname": "designation",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Designation",
- "options": "Designation",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Designation"
},
{
"fieldname": "employee_grade",
"fieldtype": "Link",
"label": "Employee Grade",
- "options": "Employee Grade",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Employee Grade"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project",
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
},
{
"fieldname": "table_for_activity",
- "fieldtype": "Section Break",
- "show_days": 1,
- "show_seconds": 1
+ "fieldtype": "Section Break"
},
{
"allow_on_submit": 1,
"fieldname": "activities",
"fieldtype": "Table",
"label": "Activities",
- "options": "Employee Boarding Activity",
- "show_days": 1,
- "show_seconds": 1
+ "options": "Employee Boarding Activity"
},
{
"fieldname": "amended_from",
@@ -167,14 +137,12 @@
"no_copy": 1,
"options": "Employee Onboarding",
"print_hide": 1,
- "read_only": 1,
- "show_days": 1,
- "show_seconds": 1
+ "read_only": 1
}
],
"is_submittable": 1,
"links": [],
- "modified": "2020-06-25 15:22:24.923835",
+ "modified": "2021-06-03 18:01:51.097927",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Onboarding",
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index 6cc2bf5..55fe317 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from erpnext.hr.utils import EmployeeBoardingController
+from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
from frappe.model.mapper import get_mapped_doc
class IncompleteTaskError(frappe.ValidationError): pass
@@ -16,9 +16,9 @@
self.validate_duplicate_employee_onboarding()
def validate_duplicate_employee_onboarding(self):
- emp_onboarding = frappe.db.exists("Employee Onboarding",{"job_applicant": self.job_applicant})
+ emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant})
if emp_onboarding and emp_onboarding != self.name:
- frappe.throw(_("Employee Onboarding: {0} is already for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
+ frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
def validate_employee_creation(self):
if self.docstatus != 1:
@@ -30,7 +30,7 @@
else:
task_status = frappe.db.get_value("Task", activity.task, "status")
if task_status not in ["Completed", "Cancelled"]:
- frappe.throw(_("All the mandatory Task for employee creation hasn't been done yet."), IncompleteTaskError)
+ frappe.throw(_("All the mandatory tasks for employee creation are not completed yet."), IncompleteTaskError)
def on_submit(self):
super(EmployeeOnboarding, self).on_submit()
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 336e13c..5f7756b 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -11,39 +11,26 @@
from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
class TestEmployeeOnboarding(unittest.TestCase):
- def test_employee_onboarding_incomplete_task(self):
+ def setUp(self):
if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
- _set_up()
- applicant = get_job_applicant()
- job_offer = create_job_offer(job_applicant=applicant.name)
- job_offer.submit()
+ project = "Employee Onboarding : Test Researcher - test@researcher.com"
+ frappe.db.sql("delete from tabProject where name=%s", project)
+ frappe.db.sql("delete from tabTask where project=%s", project)
- onboarding = frappe.new_doc('Employee Onboarding')
- onboarding.job_applicant = applicant.name
- onboarding.job_offer = job_offer.name
- onboarding.company = '_Test Company'
- onboarding.designation = 'Researcher'
- onboarding.append('activities', {
- 'activity_name': 'Assign ID Card',
- 'role': 'HR User',
- 'required_for_employee_creation': 1
- })
- onboarding.append('activities', {
- 'activity_name': 'Assign a laptop',
- 'role': 'HR User'
- })
- onboarding.status = 'Pending'
- onboarding.insert()
- onboarding.submit()
+ def test_employee_onboarding_incomplete_task(self):
+ onboarding = create_employee_onboarding()
- project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
+ project_name = frappe.db.get_value('Project', onboarding.project, 'project_name')
self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
# don't allow making employee if onboarding is not complete
self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
+ # boarding status
+ self.assertEqual(onboarding.boarding_status, 'Pending')
+
# complete the task
project = frappe.get_doc('Project', onboarding.project)
for task in frappe.get_all('Task', dict(project=project.name)):
@@ -51,6 +38,10 @@
task.status = 'Completed'
task.save()
+ # boarding status
+ onboarding.reload()
+ self.assertEqual(onboarding.boarding_status, 'Completed')
+
# make employee
onboarding.reload()
employee = make_employee(onboarding.name)
@@ -61,6 +52,13 @@
employee.insert()
self.assertEqual(employee.employee_name, 'Test Researcher')
+ def tearDown(self):
+ for entry in frappe.get_all('Employee Onboarding'):
+ doc = frappe.get_doc('Employee Onboarding', entry.name)
+ doc.cancel()
+ doc.delete()
+
+
def get_job_applicant():
if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'):
return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com')
@@ -72,10 +70,35 @@
applicant.insert()
return applicant
-def _set_up():
- for doctype in ["Employee Onboarding"]:
- frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
+def get_job_offer(applicant_name):
+ job_offer = frappe.db.exists('Job Offer', {'job_applicant': applicant_name})
+ if job_offer:
+ return frappe.get_doc('Job Offer', job_offer)
- project = "Employee Onboarding : Test Researcher - test@researcher.com"
- frappe.db.sql("delete from tabProject where name=%s", project)
- frappe.db.sql("delete from tabTask where project=%s", project)
+ job_offer = create_job_offer(job_applicant=applicant_name)
+ job_offer.submit()
+ return job_offer
+
+def create_employee_onboarding():
+ applicant = get_job_applicant()
+ job_offer = get_job_offer(applicant.name)
+
+ onboarding = frappe.new_doc('Employee Onboarding')
+ onboarding.job_applicant = applicant.name
+ onboarding.job_offer = job_offer.name
+ onboarding.company = '_Test Company'
+ onboarding.designation = 'Researcher'
+ onboarding.append('activities', {
+ 'activity_name': 'Assign ID Card',
+ 'role': 'HR User',
+ 'required_for_employee_creation': 1
+ })
+ onboarding.append('activities', {
+ 'activity_name': 'Assign a laptop',
+ 'role': 'HR User'
+ })
+ onboarding.status = 'Pending'
+ onboarding.insert()
+ onboarding.submit()
+
+ return onboarding
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.js b/erpnext/hr/doctype/employee_separation/employee_separation.js
index 9a75c16..d9011b2 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.js
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.js
@@ -23,27 +23,13 @@
frappe.set_route('List', 'Task', {project: frm.doc.project});
},__("View"));
}
- if (frm.doc.docstatus === 1 && frm.doc.project) {
- frappe.call({
- method: "erpnext.hr.utils.get_boarding_status",
- args: {
- "project": frm.doc.project
- },
- callback: function(r) {
- if (r.message) {
- frm.set_value('boarding_status', r.message);
- }
- refresh_field("boarding_status");
- }
- });
- }
},
employee_separation_template: function(frm) {
frm.set_value("activities" ,"");
if (frm.doc.employee_separation_template) {
frappe.call({
- method: "erpnext.hr.utils.get_onboarding_details",
+ method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
args: {
"parent": frm.doc.employee_separation_template,
"parenttype": "Employee Separation Template"
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json
index 7af20988..c10da5c 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.json
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.json
@@ -50,11 +50,12 @@
},
{
"allow_on_submit": 1,
+ "default": "Pending",
"fieldname": "boarding_status",
"fieldtype": "Select",
"label": "Status",
- "options": "\nPending\nIn Process\nCompleted",
- "reqd": 1
+ "options": "Pending\nIn Process\nCompleted",
+ "read_only": 1
},
{
"allow_on_submit": 1,
@@ -147,7 +148,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2021-04-28 15:58:36.020196",
+ "modified": "2021-06-03 18:02:54.007313",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Separation",
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.py b/erpnext/hr/doctype/employee_separation/employee_separation.py
index b646681..8afee25 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.py
@@ -3,7 +3,7 @@
# For license information, please see license.txt
from __future__ import unicode_literals
-from erpnext.hr.utils import EmployeeBoardingController
+from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
class EmployeeSeparation(EmployeeBoardingController):
def validate(self):
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index 713fcf5..f787d9c 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -6,21 +6,43 @@
import frappe
import unittest
-test_dependencies = ["Employee Onboarding"]
+test_dependencies = ['Employee Onboarding']
class TestEmployeeSeparation(unittest.TestCase):
def test_employee_separation(self):
- employee = frappe.db.get_value("Employee", {"status": "Active"})
- separation = frappe.new_doc('Employee Separation')
- separation.employee = employee
- separation.company = '_Test Company'
- separation.append('activities', {
- 'activity_name': 'Deactivate Employee',
- 'role': 'HR User'
- })
- separation.boarding_status = 'Pending'
- separation.insert()
- separation.submit()
+ separation = create_employee_separation()
+
self.assertEqual(separation.docstatus, 1)
+ self.assertEqual(separation.boarding_status, 'Pending')
+
+ project = frappe.get_doc('Project', separation.project)
+ project.percent_complete_method = 'Manual'
+ project.status = 'Completed'
+ project.save()
+
+ separation.reload()
+ self.assertEqual(separation.boarding_status, 'Completed')
+
separation.cancel()
- self.assertEqual(separation.project, "")
\ No newline at end of file
+ self.assertEqual(separation.project, '')
+
+ def tearDown(self):
+ for entry in frappe.get_all('Employee Separation'):
+ doc = frappe.get_doc('Employee Separation', entry.name)
+ if doc.docstatus == 1:
+ doc.cancel()
+ doc.delete()
+
+def create_employee_separation():
+ employee = frappe.db.get_value('Employee', {'status': 'Active'})
+ separation = frappe.new_doc('Employee Separation')
+ separation.employee = employee
+ separation.company = '_Test Company'
+ separation.append('activities', {
+ 'activity_name': 'Deactivate Employee',
+ 'role': 'HR User'
+ })
+ separation.boarding_status = 'Pending'
+ separation.insert()
+ separation.submit()
+ return separation
\ No newline at end of file
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 5010fc3..8cef143 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -35,8 +35,8 @@
if self.task and not self.project:
self.project = frappe.db.get_value("Task", self.task, "project")
- def set_status(self):
- self.status = {
+ def set_status(self, update=False):
+ status = {
"0": "Draft",
"1": "Submitted",
"2": "Cancelled"
@@ -44,14 +44,18 @@
paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
precision = self.precision("grand_total")
- if (self.is_paid or (flt(self.total_sanctioned_amount) > 0
- and flt(self.grand_total, precision) == flt(paid_amount, precision))) \
- and self.docstatus == 1 and self.approval_status == 'Approved':
- self.status = "Paid"
+ if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1
+ and flt(self.grand_total, precision) == flt(paid_amount, precision))) and self.approval_status == 'Approved':
+ status = "Paid"
elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
- self.status = "Unpaid"
+ status = "Unpaid"
elif self.docstatus == 1 and self.approval_status == 'Rejected':
- self.status = 'Rejected'
+ status = 'Rejected'
+
+ if update:
+ self.db_set("status", status)
+ else:
+ self.status = status
def on_update(self):
share_doc_with_approver(self, self.expense_approver)
@@ -74,7 +78,7 @@
if self.is_paid:
update_reimbursed_amount(self)
- self.set_status()
+ self.set_status(update=True)
self.update_claimed_amount_in_employee_advance()
def on_cancel(self):
@@ -86,7 +90,6 @@
if self.is_paid:
update_reimbursed_amount(self)
- self.set_status()
self.update_claimed_amount_in_employee_advance()
def update_claimed_amount_in_employee_advance(self):
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index ebb1734..3cc1a01 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -13,118 +13,6 @@
class DuplicateDeclarationError(frappe.ValidationError): pass
-
-class EmployeeBoardingController(Document):
- '''
- Create the project and the task for the boarding process
- Assign to the concerned person and roles as per the onboarding/separation template
- '''
- def validate(self):
- # remove the task if linked before submitting the form
- if self.amended_from:
- for activity in self.activities:
- activity.task = ''
-
- def on_submit(self):
- # create the project for the given employee onboarding
- project_name = _(self.doctype) + " : "
- if self.doctype == "Employee Onboarding":
- project_name += self.job_applicant
- else:
- project_name += self.employee
-
- project = frappe.get_doc({
- "doctype": "Project",
- "project_name": project_name,
- "expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
- "department": self.department,
- "company": self.company
- }).insert(ignore_permissions=True, ignore_mandatory=True)
-
- self.db_set("project", project.name)
- self.db_set("boarding_status", "Pending")
- self.reload()
- self.create_task_and_notify_user()
-
- def create_task_and_notify_user(self):
- # create the task for the given project and assign to the concerned person
- for activity in self.activities:
- if activity.task:
- continue
-
- task = frappe.get_doc({
- "doctype": "Task",
- "project": self.project,
- "subject": activity.activity_name + " : " + self.employee_name,
- "description": activity.description,
- "department": self.department,
- "company": self.company,
- "task_weight": activity.task_weight
- }).insert(ignore_permissions=True)
- activity.db_set("task", task.name)
-
- users = [activity.user] if activity.user else []
- if activity.role:
- user_list = frappe.db.sql_list('''
- SELECT
- DISTINCT(has_role.parent)
- FROM
- `tabHas Role` has_role
- LEFT JOIN `tabUser` user
- ON has_role.parent = user.name
- WHERE
- has_role.parenttype = 'User'
- AND user.enabled = 1
- AND has_role.role = %s
- ''', activity.role)
- users = unique(users + user_list)
-
- if "Administrator" in users:
- users.remove("Administrator")
-
- # assign the task the users
- if users:
- self.assign_task_to_users(task, users)
-
- def assign_task_to_users(self, task, users):
- for user in users:
- args = {
- 'assign_to': [user],
- 'doctype': task.doctype,
- 'name': task.name,
- 'description': task.description or task.subject,
- 'notify': self.notify_users_by_email
- }
- assign_to.add(args)
-
- def on_cancel(self):
- # delete task project
- for task in frappe.get_all("Task", filters={"project": self.project}):
- frappe.delete_doc("Task", task.name, force=1)
- frappe.delete_doc("Project", self.project, force=1)
- self.db_set('project', '')
- for activity in self.activities:
- activity.db_set("task", "")
-
-
-@frappe.whitelist()
-def get_onboarding_details(parent, parenttype):
- return frappe.get_all("Employee Boarding Activity",
- fields=["activity_name", "role", "user", "required_for_employee_creation", "description", "task_weight"],
- filters={"parent": parent, "parenttype": parenttype},
- order_by= "idx")
-
-@frappe.whitelist()
-def get_boarding_status(project):
- status = 'Pending'
- if project:
- doc = frappe.get_doc('Project', project)
- if flt(doc.percent_complete) > 0.0 and flt(doc.percent_complete) < 100.0:
- status = 'In Process'
- elif flt(doc.percent_complete) == 100.0:
- status = 'Completed'
- return status
-
def set_employee_name(doc):
if doc.employee and not doc.employee_name:
doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index c8fbe0b..1e4b2b0 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -14,6 +14,7 @@
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
from frappe.model.document import Document
from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list
+from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status
class Project(Document):
def get_feed(self):
@@ -37,6 +38,7 @@
self.send_welcome_email()
self.update_costing()
self.update_percent_complete()
+ update_employee_boarding_status(self)
def copy_from_template(self):
'''
@@ -132,6 +134,7 @@
def update_project(self):
'''Called externally by Task'''
self.update_percent_complete()
+ update_employee_boarding_status(self)
self.update_costing()
self.db_update()
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 3ad9909..026b85e 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -162,8 +162,15 @@
from `tabStock Entry Detail` where material_request = %s
and material_request_item = %s and docstatus = 1""",
(self.name, d.name))[0][0])
+ mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
- if d.ordered_qty and d.ordered_qty > d.stock_qty:
+ if mr_qty_allowance:
+ allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
+ if d.ordered_qty and d.ordered_qty > allowed_qty:
+ frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
+ cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
+
+ elif d.ordered_qty and d.ordered_qty > d.stock_qty:
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 72a3a5e..b4776ba 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -329,6 +329,58 @@
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
+ def test_over_transfer_qty_allowance(self):
+ mr = frappe.new_doc('Material Request')
+ mr.company = "_Test Company"
+ mr.scheduled_date = today()
+ mr.append('items',{
+ "item_code": "_Test FG Item",
+ "item_name": "_Test FG Item",
+ "qty": 10,
+ "schedule_date": today(),
+ "uom": "_Test UOM 1",
+ "warehouse": "_Test Warehouse - _TC"
+ })
+
+ mr.material_request_type = "Material Transfer"
+ mr.insert()
+ mr.submit()
+
+ frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
+
+ # map a stock entry
+
+ se_doc = make_stock_entry(mr.name)
+ se_doc.update({
+ "posting_date": today(),
+ "posting_time": "00:00",
+ })
+ se_doc.get("items")[0].update({
+ "qty": 13,
+ "transfer_qty": 12.0,
+ "s_warehouse": "_Test Warehouse - _TC",
+ "t_warehouse": "_Test Warehouse 1 - _TC",
+ "basic_rate": 1.0
+ })
+
+ # make available the qty in _Test Warehouse 1 before transfer
+ sr = frappe.new_doc("Stock Reconciliation")
+ sr.company = "_Test Company"
+ sr.purpose = "Opening Stock"
+ sr.append('items', {
+ "item_code": "_Test FG Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 20,
+ "valuation_rate": 0.01
+ })
+ sr.insert()
+ sr.submit()
+ se = frappe.copy_doc(se_doc)
+ se.insert()
+ self.assertRaises(frappe.ValidationError)
+ se.items[0].qty = 12
+ se.submit()
+
def test_completed_qty_for_over_transfer(self):
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 2a9dcfb..f75cb56 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -18,6 +18,7 @@
"section_break_9",
"over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
+ "mr_qty_allowance",
"column_break_12",
"auto_insert_price_list_rate_if_missing",
"allow_negative_stock",
@@ -283,6 +284,12 @@
"fieldtype": "Select",
"label": "Action If Quality Inspection Is Rejected",
"options": "Stop\nWarn"
+ },
+ {
+ "description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.",
+ "fieldname": "mr_qty_allowance",
+ "fieldtype": "Float",
+ "label": "Over Transfer Allowance"
}
],
"icon": "icon-cog",
@@ -290,7 +297,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-07-10 16:17:42.159829",
+ "modified": "2021-06-28 17:02:26.683002",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
@@ -310,4 +317,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
-}
\ No newline at end of file
+}