From c839177f8e0fe6750bbf9c37243d2b4d32f01158 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 22 Feb 2021 21:04:51 +0530 Subject: [PATCH 01/31] fix: Issue on posting inter-warehouse transfer invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9599d4ed0ca..e591ba6bcb4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1030,7 +1030,8 @@ class SalesInvoice(SellingController): ) def make_gle_for_rounding_adjustment(self, gl_entries): - if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment: + if flt(self.rounding_adjustment, self.precision("rounding_adjustment")) and self.base_rounding_adjustment \ + and not self.is_internal_transfer(): round_off_account, round_off_cost_center = \ get_round_off_account_and_cost_center(self.company) From e11ce57f3c4e789552bbbdd051649a59d2ee897e Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 24 Feb 2021 21:45:29 +0100 Subject: [PATCH 02/31] fix: add item taxes at the same times as sales and purchase taxes --- erpnext/setup/doctype/company/company.js | 2 +- .../setup_wizard/operations/taxes_setup.py | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 36033d9daee..c041d269a76 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -140,7 +140,7 @@ frappe.ui.form.on("Company", { doc: frm.doc, freeze: true, callback: function() { - frappe.msgprint(__("Default tax templates for sales and purchase are created.")); + frappe.msgprint(__("Default tax templates for sales, purchase and items are created.")); } }) }, diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index e66fa76f93a..c3c1593c046 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -29,6 +29,7 @@ def make_tax_account_and_template(company, account_name, tax_rate, template_name try: if accounts: make_sales_and_purchase_tax_templates(accounts, template_name) + make_item_tax_templates(accounts, template_name) except frappe.NameError: if frappe.message_log: frappe.message_log.pop() except RootNotEditable: @@ -84,6 +85,27 @@ def make_sales_and_purchase_tax_templates(accounts, template_name=None): doc = frappe.get_doc(purchase_tax_template) doc.insert(ignore_permissions=True) +def make_item_tax_templates(accounts, template_name=None): + if not template_name: + template_name = accounts[0].name + + item_tax_template = { + "doctype": "Item Tax Template", + "title": template_name, + "company": accounts[0].company, + 'taxes': [] + } + + + for account in accounts: + item_tax_template['taxes'].append({ + "tax_type": account.name, + "tax_rate": account.tax_rate + }) + + # Items + frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True) + def get_tax_account_group(company): tax_group = frappe.db.get_value("Account", {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) From b990e71b4c6d05fe3d7e5464d75c5cb03660f093 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 25 Feb 2021 14:01:22 +0530 Subject: [PATCH 03/31] fix: Add search field in project query --- erpnext/projects/doctype/task/task.py | 479 +++++++++++++------------- 1 file changed, 243 insertions(+), 236 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index a2095c95d51..16368aeddb9 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -17,312 +17,319 @@ class CircularReferenceError(frappe.ValidationError): pass class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass class Task(NestedSet): - nsm_parent_field = 'parent_task' + nsm_parent_field = 'parent_task' - def get_feed(self): - return '{0}: {1}'.format(_(self.status), self.subject) + def get_feed(self): + return '{0}: {1}'.format(_(self.status), self.subject) - def get_customer_details(self): - cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) - if cust: - ret = {'customer_name': cust and cust[0][0] or ''} - return ret + def get_customer_details(self): + cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) + if cust: + ret = {'customer_name': cust and cust[0][0] or ''} + return ret - def validate(self): - self.validate_dates() - self.validate_parent_project_dates() - self.validate_progress() - self.validate_status() - self.update_depends_on() - self.validate_dependencies_for_template_task() + def validate(self): + self.validate_dates() + self.validate_parent_project_dates() + self.validate_progress() + self.validate_status() + self.update_depends_on() + self.validate_dependencies_for_template_task() - def validate_dates(self): - if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): - frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ - frappe.bold("Expected End Date"))) + def validate_dates(self): + if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ + frappe.bold("Expected End Date"))) - if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): - frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ - frappe.bold("Actual End Date"))) + if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ + frappe.bold("Actual End Date"))) - def validate_parent_project_dates(self): - if not self.project or frappe.flags.in_test: - return + def validate_parent_project_dates(self): + if not self.project or frappe.flags.in_test: + return - expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") + expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") - if expected_end_date: - validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") - validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") + if expected_end_date: + validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") + validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") - def validate_status(self): - if self.is_template and self.status != "Template": - self.status = "Template" - if self.status!=self.get_db_value("status") and self.status == "Completed": - for d in self.depends_on: - if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): - frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task))) + def validate_status(self): + if self.is_template and self.status != "Template": + self.status = "Template" + if self.status!=self.get_db_value("status") and self.status == "Completed": + for d in self.depends_on: + if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): + frappe.throw(_("Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled.").format(frappe.bold(self.name), frappe.bold(d.task))) - close_all_assignments(self.doctype, self.name) + close_all_assignments(self.doctype, self.name) - def validate_progress(self): - if flt(self.progress or 0) > 100: - frappe.throw(_("Progress % for a task cannot be more than 100.")) + def validate_progress(self): + if flt(self.progress or 0) > 100: + frappe.throw(_("Progress % for a task cannot be more than 100.")) - if flt(self.progress) == 100: - self.status = 'Completed' + if flt(self.progress) == 100: + self.status = 'Completed' - if self.status == 'Completed': - self.progress = 100 + if self.status == 'Completed': + self.progress = 100 - def validate_dependencies_for_template_task(self): - if self.is_template: - self.validate_parent_template_task() - self.validate_depends_on_tasks() - - def validate_parent_template_task(self): - if self.parent_task: - if not frappe.db.get_value("Task", self.parent_task, "is_template"): - parent_task_format = """{0}""".format(self.parent_task) - frappe.throw(_("Parent Task {0} is not a Template Task").format(parent_task_format)) - - def validate_depends_on_tasks(self): - if self.depends_on: - for task in self.depends_on: - if not frappe.db.get_value("Task", task.task, "is_template"): - dependent_task_format = """{0}""".format(task.task) - frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) + def validate_dependencies_for_template_task(self): + if self.is_template: + self.validate_parent_template_task() + self.validate_depends_on_tasks() - def update_depends_on(self): - depends_on_tasks = self.depends_on_tasks or "" - for d in self.depends_on: - if d.task and d.task not in depends_on_tasks: - depends_on_tasks += d.task + "," - self.depends_on_tasks = depends_on_tasks + def validate_parent_template_task(self): + if self.parent_task: + if not frappe.db.get_value("Task", self.parent_task, "is_template"): + parent_task_format = """{0}""".format(self.parent_task) + frappe.throw(_("Parent Task {0} is not a Template Task").format(parent_task_format)) - def update_nsm_model(self): - frappe.utils.nestedset.update_nsm(self) + def validate_depends_on_tasks(self): + if self.depends_on: + for task in self.depends_on: + if not frappe.db.get_value("Task", task.task, "is_template"): + dependent_task_format = """{0}""".format(task.task) + frappe.throw(_("Dependent Task {0} is not a Template Task").format(dependent_task_format)) - def on_update(self): - self.update_nsm_model() - self.check_recursion() - self.reschedule_dependent_tasks() - self.update_project() - self.unassign_todo() - self.populate_depends_on() + def update_depends_on(self): + depends_on_tasks = self.depends_on_tasks or "" + for d in self.depends_on: + if d.task and d.task not in depends_on_tasks: + depends_on_tasks += d.task + "," + self.depends_on_tasks = depends_on_tasks - def unassign_todo(self): - if self.status == "Completed": - close_all_assignments(self.doctype, self.name) - if self.status == "Cancelled": - clear(self.doctype, self.name) + def update_nsm_model(self): + frappe.utils.nestedset.update_nsm(self) - def update_total_expense_claim(self): - self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` - where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0] + def on_update(self): + self.update_nsm_model() + self.check_recursion() + self.reschedule_dependent_tasks() + self.update_project() + self.unassign_todo() + self.populate_depends_on() - def update_time_and_costing(self): - tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date, - sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount, - sum(hours) as time from `tabTimesheet Detail` where task = %s and docstatus=1""" - ,self.name, as_dict=1)[0] - if self.status == "Open": - self.status = "Working" - self.total_costing_amount= tl.total_costing_amount - self.total_billing_amount= tl.total_billing_amount - self.actual_time= tl.time - self.act_start_date= tl.start_date - self.act_end_date= tl.end_date + def unassign_todo(self): + if self.status == "Completed": + close_all_assignments(self.doctype, self.name) + if self.status == "Cancelled": + clear(self.doctype, self.name) - def update_project(self): - if self.project and not self.flags.from_project: - frappe.get_cached_doc("Project", self.project).update_project() + def update_total_expense_claim(self): + self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` + where project = %s and task = %s and docstatus=1""",(self.project, self.name))[0][0] - def check_recursion(self): - if self.flags.ignore_recursion_check: return - check_list = [['task', 'parent'], ['parent', 'task']] - for d in check_list: - task_list, count = [self.name], 0 - while (len(task_list) > count ): - tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " % - (d[0], d[1], '%s'), cstr(task_list[count])) - count = count + 1 - for b in tasks: - if b[0] == self.name: - frappe.throw(_("Circular Reference Error"), CircularReferenceError) - if b[0]: - task_list.append(b[0]) + def update_time_and_costing(self): + tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date, + sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount, + sum(hours) as time from `tabTimesheet Detail` where task = %s and docstatus=1""" + ,self.name, as_dict=1)[0] + if self.status == "Open": + self.status = "Working" + self.total_costing_amount= tl.total_costing_amount + self.total_billing_amount= tl.total_billing_amount + self.actual_time= tl.time + self.act_start_date= tl.start_date + self.act_end_date= tl.end_date - if count == 15: - break + def update_project(self): + if self.project and not self.flags.from_project: + frappe.get_cached_doc("Project", self.project).update_project() - def reschedule_dependent_tasks(self): - end_date = self.exp_end_date or self.act_end_date - if end_date: - for task_name in frappe.db.sql(""" - select name from `tabTask` as parent - where parent.project = %(project)s - and parent.name in ( - select parent from `tabTask Depends On` as child - where child.task = %(task)s and child.project = %(project)s) - """, {'project': self.project, 'task':self.name }, as_dict=1): - task = frappe.get_doc("Task", task_name.name) - if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open": - task_duration = date_diff(task.exp_end_date, task.exp_start_date) - task.exp_start_date = add_days(end_date, 1) - task.exp_end_date = add_days(task.exp_start_date, task_duration) - task.flags.ignore_recursion_check = True - task.save() + def check_recursion(self): + if self.flags.ignore_recursion_check: return + check_list = [['task', 'parent'], ['parent', 'task']] + for d in check_list: + task_list, count = [self.name], 0 + while (len(task_list) > count ): + tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " % + (d[0], d[1], '%s'), cstr(task_list[count])) + count = count + 1 + for b in tasks: + if b[0] == self.name: + frappe.throw(_("Circular Reference Error"), CircularReferenceError) + if b[0]: + task_list.append(b[0]) - def has_webform_permission(self): - project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user") - if project_user: - return True + if count == 15: + break - def populate_depends_on(self): - if self.parent_task: - parent = frappe.get_doc('Task', self.parent_task) - if self.name not in [row.task for row in parent.depends_on]: - parent.append("depends_on", { - "doctype": "Task Depends On", - "task": self.name, - "subject": self.subject - }) - parent.save() + def reschedule_dependent_tasks(self): + end_date = self.exp_end_date or self.act_end_date + if end_date: + for task_name in frappe.db.sql(""" + select name from `tabTask` as parent + where parent.project = %(project)s + and parent.name in ( + select parent from `tabTask Depends On` as child + where child.task = %(task)s and child.project = %(project)s) + """, {'project': self.project, 'task':self.name }, as_dict=1): + task = frappe.get_doc("Task", task_name.name) + if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open": + task_duration = date_diff(task.exp_end_date, task.exp_start_date) + task.exp_start_date = add_days(end_date, 1) + task.exp_end_date = add_days(task.exp_start_date, task_duration) + task.flags.ignore_recursion_check = True + task.save() - def on_trash(self): - if check_if_child_exists(self.name): - throw(_("Child Task exists for this Task. You can not delete this Task.")) + def has_webform_permission(self): + project_user = frappe.db.get_value("Project User", {"parent": self.project, "user":frappe.session.user} , "user") + if project_user: + return True - self.update_nsm_model() + def populate_depends_on(self): + if self.parent_task: + parent = frappe.get_doc('Task', self.parent_task) + if self.name not in [row.task for row in parent.depends_on]: + parent.append("depends_on", { + "doctype": "Task Depends On", + "task": self.name, + "subject": self.subject + }) + parent.save() - def after_delete(self): - self.update_project() + def on_trash(self): + if check_if_child_exists(self.name): + throw(_("Child Task exists for this Task. You can not delete this Task.")) - def update_status(self): - if self.status not in ('Cancelled', 'Completed') and self.exp_end_date: - from datetime import datetime - if self.exp_end_date < datetime.now().date(): - self.db_set('status', 'Overdue', update_modified=False) - self.update_project() + self.update_nsm_model() + + def after_delete(self): + self.update_project() + + def update_status(self): + if self.status not in ('Cancelled', 'Completed') and self.exp_end_date: + from datetime import datetime + if self.exp_end_date < datetime.now().date(): + self.db_set('status', 'Overdue', update_modified=False) + self.update_project() @frappe.whitelist() def check_if_child_exists(name): - child_tasks = frappe.get_all("Task", filters={"parent_task": name}) - child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks] - return child_tasks + child_tasks = frappe.get_all("Task", filters={"parent_task": name}) + child_tasks = [get_link_to_form("Task", task.name) for task in child_tasks] + return child_tasks @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_project(doctype, txt, searchfield, start, page_len, filters): - from erpnext.controllers.queries import get_match_cond - return frappe.db.sql(""" select name from `tabProject` - where %(key)s like %(txt)s - %(mcond)s - order by name - limit %(start)s, %(page_len)s""" % { - 'key': searchfield, - 'txt': frappe.db.escape('%' + txt + '%'), - 'mcond':get_match_cond(doctype), - 'start': start, - 'page_len': page_len - }) + from erpnext.controllers.queries import get_match_cond + meta = frappe.get_meta(doctype) + searchfields = meta.get_search_fields() + search_columns = ", " + ", ".join(searchfields) if searchfields else '' + search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields]) + + return frappe.db.sql(""" select name {search_columns} from `tabProject` + where %(key)s like %(txt)s + %(mcond)s + {search_condition} + order by name + limit %(start)s, %(page_len)s""".format(search_columns = search_columns, + search_condition=search_cond), { + 'key': searchfield, + 'txt': '%' + txt + '%', + 'mcond':get_match_cond(doctype), + 'start': start, + 'page_len': page_len + }) @frappe.whitelist() def set_multiple_status(names, status): - names = json.loads(names) - for name in names: - task = frappe.get_doc("Task", name) - task.status = status - task.save() + names = json.loads(names) + for name in names: + task = frappe.get_doc("Task", name) + task.status = status + task.save() def set_tasks_as_overdue(): - tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"]) - for task in tasks: - if task.status == "Pending Review": - if getdate(task.review_date) > getdate(today()): - continue - frappe.get_doc("Task", task.name).update_status() + tasks = frappe.get_all("Task", filters={"status": ["not in", ["Cancelled", "Completed"]]}, fields=["name", "status", "review_date"]) + for task in tasks: + if task.status == "Pending Review": + if getdate(task.review_date) > getdate(today()): + continue + frappe.get_doc("Task", task.name).update_status() @frappe.whitelist() def make_timesheet(source_name, target_doc=None, ignore_permissions=False): - def set_missing_values(source, target): - target.append("time_logs", { - "hours": source.actual_time, - "completed": source.status == "Completed", - "project": source.project, - "task": source.name - }) + def set_missing_values(source, target): + target.append("time_logs", { + "hours": source.actual_time, + "completed": source.status == "Completed", + "project": source.project, + "task": source.name + }) - doclist = get_mapped_doc("Task", source_name, { - "Task": { - "doctype": "Timesheet" - } - }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions) + doclist = get_mapped_doc("Task", source_name, { + "Task": { + "doctype": "Timesheet" + } + }, target_doc, postprocess=set_missing_values, ignore_permissions=ignore_permissions) - return doclist + return doclist @frappe.whitelist() def get_children(doctype, parent, task=None, project=None, is_root=False): - filters = [['docstatus', '<', '2']] + filters = [['docstatus', '<', '2']] - if task: - filters.append(['parent_task', '=', task]) - elif parent and not is_root: - # via expand child - filters.append(['parent_task', '=', parent]) - else: - filters.append(['ifnull(`parent_task`, "")', '=', '']) + if task: + filters.append(['parent_task', '=', task]) + elif parent and not is_root: + # via expand child + filters.append(['parent_task', '=', parent]) + else: + filters.append(['ifnull(`parent_task`, "")', '=', '']) - if project: - filters.append(['project', '=', project]) + if project: + filters.append(['project', '=', project]) - tasks = frappe.get_list(doctype, fields=[ - 'name as value', - 'subject as title', - 'is_group as expandable' - ], filters=filters, order_by='name') + tasks = frappe.get_list(doctype, fields=[ + 'name as value', + 'subject as title', + 'is_group as expandable' + ], filters=filters, order_by='name') - # return tasks - return tasks + # return tasks + return tasks @frappe.whitelist() def add_node(): - from frappe.desk.treeview import make_tree_args - args = frappe.form_dict - args.update({ - "name_field": "subject" - }) - args = make_tree_args(**args) + from frappe.desk.treeview import make_tree_args + args = frappe.form_dict + args.update({ + "name_field": "subject" + }) + args = make_tree_args(**args) - if args.parent_task == 'All Tasks' or args.parent_task == args.project: - args.parent_task = None + if args.parent_task == 'All Tasks' or args.parent_task == args.project: + args.parent_task = None - frappe.get_doc(args).insert() + frappe.get_doc(args).insert() @frappe.whitelist() def add_multiple_tasks(data, parent): - data = json.loads(data) - new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""} - new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or "" + data = json.loads(data) + new_doc = {'doctype': 'Task', 'parent_task': parent if parent!="All Tasks" else ""} + new_doc['project'] = frappe.db.get_value('Task', {"name": parent}, 'project') or "" - for d in data: - if not d.get("subject"): continue - new_doc['subject'] = d.get("subject") - new_task = frappe.get_doc(new_doc) - new_task.insert() + for d in data: + if not d.get("subject"): continue + new_doc['subject'] = d.get("subject") + new_task = frappe.get_doc(new_doc) + new_task.insert() def on_doctype_update(): - frappe.db.add_index("Task", ["lft", "rgt"]) + frappe.db.add_index("Task", ["lft", "rgt"]) def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date): - if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: - frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) + if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: + frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) - if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: - frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) + if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: + frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) From 027db0b41e74685be3e6ecb5703757f81e973d6f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Feb 2021 17:48:33 +0530 Subject: [PATCH 04/31] fix(HR): hide "more" button from team updates --- erpnext/hr/page/team_updates/team_updates.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/page/team_updates/team_updates.js b/erpnext/hr/page/team_updates/team_updates.js index 13d0074660b..991b316e1e9 100644 --- a/erpnext/hr/page/team_updates/team_updates.js +++ b/erpnext/hr/page/team_updates/team_updates.js @@ -36,7 +36,7 @@ frappe.team_updates = { start: me.start }, callback: function(r) { - if(r.message) { + if (r.message && r.message.length > 0) { r.message.forEach(function(d) { me.add_row(d); }); @@ -77,4 +77,4 @@ frappe.team_updates = { $(frappe.render_template('team_update_row', data)).appendTo(me.body) } -} \ No newline at end of file +} From 980383543691fe1f97f45d360389bca179d392ba Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 26 Feb 2021 00:33:48 -0600 Subject: [PATCH 05/31] Add "description" field to Skill doctype Lets us describe a skill past just the title. Closes #23592 --- erpnext/hr/doctype/skill/skill.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/skill/skill.json b/erpnext/hr/doctype/skill/skill.json index a10381fac19..67f3d372eb2 100644 --- a/erpnext/hr/doctype/skill/skill.json +++ b/erpnext/hr/doctype/skill/skill.json @@ -46,6 +46,11 @@ "set_only_once": 0, "translatable": 0, "unique": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text", + "label": "Description" } ], "has_web_view": 0, @@ -56,7 +61,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2021-02-24 09:55:00.536328", + "modified": "2021-02-26 09:55:00.536328", "modified_by": "Administrator", "module": "HR", "name": "Skill", From aa09628358974a9938277df5c2ace6dff18d47ee Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 26 Feb 2021 15:24:34 -0600 Subject: [PATCH 06/31] Skill fields: enable allow_in_quick_entry --- erpnext/hr/doctype/skill/skill.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/skill/skill.json b/erpnext/hr/doctype/skill/skill.json index 67f3d372eb2..4c8a8c92c10 100644 --- a/erpnext/hr/doctype/skill/skill.json +++ b/erpnext/hr/doctype/skill/skill.json @@ -16,7 +16,7 @@ "fields": [ { "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, + "allow_in_quick_entry": 1, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -48,6 +48,7 @@ "unique": 1 }, { + "allow_in_quick_entry": 1, "fieldname": "description", "fieldtype": "Text", "label": "Description" @@ -61,7 +62,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2021-02-26 09:55:00.536328", + "modified": "2021-02-26 10:55:00.536328", "modified_by": "Administrator", "module": "HR", "name": "Skill", From 56f6dbb66662a50b7cf3d3d35be17312ff49bfe3 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 26 Feb 2021 21:11:56 -0600 Subject: [PATCH 07/31] Remove obsolete section titles --- .../doctype/quality_meeting/quality_meeting.json | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json index ead403d453a..36e56e5c144 100644 --- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json +++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json @@ -33,8 +33,7 @@ }, { "fieldname": "sb_00", - "fieldtype": "Section Break", - "label": "Agenda" + "fieldtype": "Section Break" }, { "fieldname": "agenda", @@ -44,13 +43,12 @@ }, { "fieldname": "sb_01", - "fieldtype": "Section Break", - "label": "Minutes" + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-27 16:36:45.657883", + "modified": "2021-02-26 16:36:45.657883", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality Meeting", @@ -85,4 +83,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 28da945ecdb2084ba65a0932ef0fb0fb49e6f895 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 26 Feb 2021 21:14:12 -0600 Subject: [PATCH 08/31] Update quality_meeting.json --- .../doctype/quality_meeting/quality_meeting.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json index 36e56e5c144..e2125c3933a 100644 --- a/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json +++ b/erpnext/quality_management/doctype/quality_meeting/quality_meeting.json @@ -48,7 +48,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-02-26 16:36:45.657883", + "modified": "2021-02-27 16:36:45.657883", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality Meeting", From fa777555b7e61fd20d3bc170b52733e46d1fd779 Mon Sep 17 00:00:00 2001 From: Saqib Date: Sun, 28 Feb 2021 20:46:23 +0530 Subject: [PATCH 09/31] fix(india): inflated item tax rate for e-invoicing (#24752) --- erpnext/regional/india/e_invoice/utils.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index eea85cd2d60..96f7f1b224f 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -202,9 +202,11 @@ def update_item_taxes(invoice, item): item[attr] = 0 for t in invoice.taxes: - # this contains item wise tax rate & tax amount (incl. discount) - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) - if t.account_head in gst_accounts_list: + is_applicable = t.tax_amount and t.account_head in gst_accounts_list + if is_applicable: + # this contains item wise tax rate & tax amount (incl. discount) + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + item_tax_rate = item_tax_detail[0] # item tax amount excluding discount amount item_tax_amount = (item_tax_rate / 100) * item.base_net_amount @@ -229,7 +231,7 @@ def get_invoice_value_details(invoice): if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: invoice_value_details.base_total = abs(invoice.base_total) - invoice_value_details.invoice_discount_amt = invoice.base_discount_amount + invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount) else: invoice_value_details.base_total = abs(invoice.base_net_total) # since tax already considers discount amount From 8755339cbababe84992c59de229fb7fcd1d5863e Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 1 Mar 2021 10:44:13 +0530 Subject: [PATCH 10/31] fix: to update sales person's incentives on save (#24179) --- erpnext/controllers/selling_controller.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index c61b67b0a48..fb52c1f6caa 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -142,6 +142,11 @@ class SellingController(StockController): self.base_net_total * sales_person.allocated_percentage / 100.0, self.precision("allocated_amount", sales_person)) + if sales_person.commission_rate: + sales_person.incentives = flt( + sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0, + self.precision("incentives", sales_person)) + total += sales_person.allocated_percentage if sales_team and total != 100.0: From eb6b3cfe6da8ad800bd78748fda2042ddb249082 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 Mar 2021 11:32:39 +0530 Subject: [PATCH 11/31] fix: allow to select item code in batch naming --- erpnext/stock/doctype/batch/batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index c8424f13e12..8fdda565d20 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -93,7 +93,7 @@ class Batch(Document): if create_new_batch: if batch_number_series: - self.batch_id = make_autoname(batch_number_series) + self.batch_id = make_autoname(batch_number_series, doc=self) elif batch_uses_naming_series(): self.batch_id = self.get_name_from_naming_series() else: From 10e4b9d4e8b7bcc594de32dff10effeed6b970c0 Mon Sep 17 00:00:00 2001 From: UrvashiKishnani <41088003+UrvashiKishnani@users.noreply.github.com> Date: Mon, 1 Mar 2021 12:50:07 +0400 Subject: [PATCH 12/31] fix: GL Entries for AP/AR Summary SQL query modified to fetch only those GL Entries for Accounts Payable Summary and Accounts Receivable Summary reports where the corresponding payment entry is in submitted state. --- erpnext/accounts/party.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 38b228477f7..7d53db2dc37 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -606,18 +606,20 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur cond = "1=1" if posting_date: if future_payment: - cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) + cond = "gle.posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) else: - cond = "posting_date <= '{0}'".format(posting_date) + cond = "gle.posting_date <= '{0}'".format(posting_date) if company: - cond += "and company = {0}".format(frappe.db.escape(company)) + cond += "and gle.company = {0}".format(frappe.db.escape(company)) - data = frappe.db.sql(""" SELECT party, sum({0}) as amount - FROM `tabGL Entry` + data = frappe.db.sql(""" SELECT gle.party, sum(gle.{0}) as amount + FROM `tabGL Entry` gle + INNER JOIN `tabPayment Entry` pe ON pe.name = gle.voucher_no WHERE - party_type = %s and against_voucher is null - and {1} GROUP BY party""" + gle.party_type = %s and gle.against_voucher is null + and pe.docstatus = 1 + and {1} GROUP BY gle.party""" .format(("credit") if party_type == "Customer" else "debit", cond) , party_type) if data: From 2efdfa26b4e80b77628713e6022382d6e69cafec Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Mon, 1 Mar 2021 04:14:16 -0600 Subject: [PATCH 13/31] fix: Change FieldType from Text to Text Editor in Non-Conformance DocType (#24760) * Enable Text Editors Enable rich Text Editors for Corrective Action and Preventive Action fields. Closes #24759 * Update non_conformance.json --- .../doctype/non_conformance/non_conformance.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/quality_management/doctype/non_conformance/non_conformance.json b/erpnext/quality_management/doctype/non_conformance/non_conformance.json index bfeb96bcaf0..8dfe2d6859d 100644 --- a/erpnext/quality_management/doctype/non_conformance/non_conformance.json +++ b/erpnext/quality_management/doctype/non_conformance/non_conformance.json @@ -70,18 +70,18 @@ }, { "fieldname": "corrective_action", - "fieldtype": "Text", + "fieldtype": "Text Editor", "label": "Corrective Action" }, { "fieldname": "preventive_action", - "fieldtype": "Text", + "fieldtype": "Text Editor", "label": "Preventive Action" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-26 15:27:47.247814", + "modified": "2021-02-26 15:27:47.247814", "modified_by": "Administrator", "module": "Quality Management", "name": "Non Conformance", @@ -115,4 +115,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 810a36105ca10d555e7f92c3870199cf5af98710 Mon Sep 17 00:00:00 2001 From: UrvashiKishnani <41088003+UrvashiKishnani@users.noreply.github.com> Date: Tue, 2 Mar 2021 08:20:03 +0400 Subject: [PATCH 14/31] fix: GL Entries for AP/AR Summary without SQL join SQL query modified to fetch only those GL Entries for Accounts Payable Summary and Accounts Receivable Summary reports where the corresponding payment entry is not in cancelled state. --- erpnext/accounts/party.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 7d53db2dc37..e01cb6e151e 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -606,20 +606,19 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur cond = "1=1" if posting_date: if future_payment: - cond = "gle.posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) + cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) else: - cond = "gle.posting_date <= '{0}'".format(posting_date) + cond = "posting_date <= '{0}'".format(posting_date) if company: - cond += "and gle.company = {0}".format(frappe.db.escape(company)) + cond += "and company = {0}".format(frappe.db.escape(company)) - data = frappe.db.sql(""" SELECT gle.party, sum(gle.{0}) as amount - FROM `tabGL Entry` gle - INNER JOIN `tabPayment Entry` pe ON pe.name = gle.voucher_no + data = frappe.db.sql(""" SELECT party, sum({0}) as amount + FROM `tabGL Entry` WHERE - gle.party_type = %s and gle.against_voucher is null - and pe.docstatus = 1 - and {1} GROUP BY gle.party""" + party_type = %s and against_voucher is null + and is_cancelled = 0 + and {1} GROUP BY party""" .format(("credit") if party_type == "Customer" else "debit", cond) , party_type) if data: From 47ce85484b56ea2aad159dadaeed53f4fe87174a Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 28 Feb 2021 16:09:26 +0530 Subject: [PATCH 15/31] feat: provistion to pull timesheet in sales invoice --- .../doctype/sales_invoice/sales_invoice.js | 60 +++++++++++++++++++ .../projects/doctype/timesheet/timesheet.py | 10 ++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index d3e8a4474d6..b361c0c3457 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -695,6 +695,7 @@ frappe.ui.form.on('Sales Invoice', { refresh_field(['timesheets']) } }) + frm.refresh(); }, onload: function(frm) { @@ -810,6 +811,65 @@ frappe.ui.form.on('Sales Invoice', { }, refresh: function(frm) { + if (frm.doc.project) { + frm.add_custom_button(__('Fetch Timesheet'), function() { + let d = new frappe.ui.Dialog({ + title: __('Fetch Timesheet'), + fields: [ + { + "label" : "From", + "fieldname": "from_time", + "fieldtype": "Date", + "reqd": 1, + }, + { + fieldtype: 'Column Break', + fieldname: 'col_break_1', + }, + { + "label" : "To", + "fieldname": "to_time", + "fieldtype": "Date", + "reqd": 1, + } + ], + primary_action: function() { + let data = d.get_values(); + frappe.call({ + method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data", + args: { + from_time: data.from_time, + to_time: data.to_time, + project: frm.doc.project + }, + callback: function(r) { + if(!r.exc) { + if(r.message.length > 0) { + frm.clear_table('timesheets') + r.message.forEach((d) => { + frm.add_child('timesheets',{ + 'time_sheet': d.parent, + 'billing_hours': d.billing_hours, + 'billing_amount': d.billing_amt, + 'timesheet_detail': d.name + }); + }); + frm.refresh_field('timesheets') + } + else { + frappe.msgprint(__('No Timesheet Found.')) + } + d.hide(); + } + } + }); + }, + primary_action_label: __('Get Timesheets') + }); + d.show(); + }) + } + if (frappe.boot.active_domains.includes("Healthcare")) { frm.set_df_property("patient", "hidden", 0); frm.set_df_property("patient_name", "hidden", 0); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index ea81b3eb644..ed02f79c2dd 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -204,14 +204,16 @@ class Timesheet(Document): ts_detail.billing_rate = 0.0 @frappe.whitelist() -def get_projectwise_timesheet_data(project, parent=None): - cond = '' +def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None): + condition = '' if parent: - cond = "and parent = %(parent)s" + condition = "AND parent = %(parent)s" + if from_time and to_time: + condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s" return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1 - and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1) + and sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs From bd10d7c02841cd8d0ab0fcd862a2a60ebe8b9aa3 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Mar 2021 13:37:45 +0530 Subject: [PATCH 16/31] fix: reposting patch fixes (#24775) --- .../item_reposting_for_incorrect_sl_and_gl.py | 17 +++++++++++++---- .../purchase_receipt/purchase_receipt.py | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index 3200363e01a..d968e1fb763 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -1,13 +1,24 @@ import frappe from frappe import _ -from frappe.utils import getdate, get_time +from frappe.utils import getdate, get_time, today from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after def execute(): - frappe.reload_doc('stock', 'doctype', 'repost_item_valuation') + for doctype in ('repost_item_valuation', 'stock_entry_detail', 'purchase_receipt_item', + 'purchase_invoice_item', 'delivery_note_item', 'sales_invoice_item', 'packed_item'): + frappe.reload_doc('stock', 'doctype', doctype) + frappe.reload_doc('buying', 'doctype', 'purchase_receipt_item_supplied') reposting_project_deployed_on = get_creation_time() + posting_date = getdate(reposting_project_deployed_on) + posting_time = get_time(reposting_project_deployed_on) + + if posting_date == today(): + return + + frappe.clear_cache() + frappe.flags.warehouse_account_map = {} data = frappe.db.sql(''' SELECT @@ -41,8 +52,6 @@ def execute(): print("Reposting General Ledger Entries...") - posting_date = getdate(reposting_project_deployed_on) - posting_time = get_time(reposting_project_deployed_on) for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): update_gl_entries_after(posting_date, posting_time, company=row.name) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d72101412ea..70687bdac26 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -324,10 +324,12 @@ class PurchaseReceipt(BuyingController): else: loss_account = self.get_company_default("default_expense_account") + cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") + gl_entries.append(self.get_gl_dict({ "account": loss_account, "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, + "cost_center": cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": divisional_loss, "project": d.project From 190106a8b8eeb1be9e35e4fb5c062fbe4e0f3e50 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Mar 2021 13:38:14 +0530 Subject: [PATCH 17/31] fix: rounding of earned leave is optional (#24782) --- .../leave_allocation/leave_allocation.py | 6 --- .../leave_policy_assignment.json | 6 ++- .../leave_policy_assignment.py | 48 ++++++++++++++++--- erpnext/hr/doctype/leave_type/leave_type.json | 5 +- erpnext/hr/utils.py | 23 ++++++--- 5 files changed, 65 insertions(+), 23 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 5e3822e2dad..69d605d0633 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -18,7 +18,6 @@ class ValueMultiplierError(frappe.ValidationError): pass class LeaveAllocation(Document): def validate(self): self.validate_period() - self.validate_new_leaves_allocated_value() self.validate_allocation_overlap() self.validate_back_dated_allocation() self.set_total_leaves_allocated() @@ -72,11 +71,6 @@ class LeaveAllocation(Document): if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"): frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)) - def validate_new_leaves_allocated_value(self): - """validate that leave allocation is in multiples of 0.5""" - if flt(self.new_leaves_allocated) % 0.5: - frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) - def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" SELECT diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json index a0327bdaa0b..3373350e733 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json @@ -106,12 +106,14 @@ "fieldname": "leaves_allocated", "fieldtype": "Check", "hidden": 1, - "label": "Leaves Allocated" + "label": "Leaves Allocated", + "no_copy": 1, + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-12-31 16:43:30.695206", + "modified": "2021-03-01 17:54:01.014509", "modified_by": "Administrator", "module": "HR", "name": "Leave Policy Assignment", diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index a5068bc26d8..4064c56e44c 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _, bold -from frappe.utils import getdate, date_diff, comma_and, formatdate +from frappe.utils import getdate, date_diff, comma_and, formatdate, get_datetime, flt from math import ceil import json from six import string_types @@ -84,17 +84,52 @@ class LeavePolicyAssignment(Document): return allocation.name, new_leaves_allocated def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining): + from frappe.model.meta import get_field_precision + precision = get_field_precision(frappe.get_meta("Leave Allocation").get_field("new_leaves_allocated")) + + # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 + if leave_type_details.get(leave_type).is_compensatory == 1: + new_leaves_allocated = 0 + + elif leave_type_details.get(leave_type).is_earned_leave == 1: + if self.assignment_based_on == "Leave Period": + new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining) + else: + new_leaves_allocated = 0 # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period - if getdate(date_of_joining) > getdate(self.effective_from): + elif getdate(date_of_joining) > getdate(self.effective_from): remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1)) new_leaves_allocated = ceil(new_leaves_allocated * remaining_period) - # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 - if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1: - new_leaves_allocated = 0 + return flt(new_leaves_allocated, precision) + + def get_leaves_for_passed_months(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining): + from erpnext.hr.utils import get_monthly_earned_leave + + current_month = get_datetime().month + current_year = get_datetime().year + + from_date = frappe.db.get_value("Leave Period", self.leave_period, "from_date") + if getdate(date_of_joining) > getdate(from_date): + from_date = date_of_joining + + from_date_month = get_datetime(from_date).month + from_date_year = get_datetime(from_date).year + + months_passed = 0 + if current_year == from_date_year and current_month > from_date_month: + months_passed = current_month - from_date_month + elif current_year > from_date_year: + months_passed = (12 - from_date_month) + current_month + + if months_passed > 0: + monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated, + leave_type_details.get(leave_type).earned_leave_frequency, leave_type_details.get(leave_type).rounding) + new_leaves_allocated = monthly_earned_leave * months_passed return new_leaves_allocated + @frappe.whitelist() def grant_leave_for_multiple_employees(leave_policy_assignments): leave_policy_assignments = json.loads(leave_policy_assignments) @@ -156,7 +191,8 @@ def automatically_allocate_leaves_based_on_leave_policy(): def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all("Leave Type", - fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"]) + fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", + "is_carry_forward", "expire_carry_forwarded_leaves_after_days", "earned_leave_frequency", "rounding"]) for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index a2092919f8f..fc577ef1d3d 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -172,7 +172,7 @@ "fieldname": "rounding", "fieldtype": "Select", "label": "Rounding", - "options": "0.5\n1.0" + "options": "\n0.25\n0.5\n1.0" }, { "depends_on": "is_carry_forward", @@ -197,6 +197,7 @@ "label": "Based On Date Of Joining" }, { + "default": "0", "depends_on": "eval:doc.is_lwp == 0", "fieldname": "is_ppl", "fieldtype": "Check", @@ -213,7 +214,7 @@ "icon": "fa fa-flag", "idx": 1, "links": [], - "modified": "2020-10-15 15:49:47.555105", + "modified": "2021-03-02 11:22:33.776320", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index e2aa7a4e727..d57ef5955dc 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -316,13 +316,7 @@ def allocate_earned_leaves(): update_previous_leave_allocation(allocation, annual_allocation, e_leave_type) def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type): - divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} - if annual_allocation: - earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency] - if e_leave_type.rounding == "0.5": - earned_leaves = round(earned_leaves * 2) / 2 - else: - earned_leaves = round(earned_leaves) + earned_leaves = get_monthly_earned_leave(annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding) allocation = frappe.get_doc('Leave Allocation', allocation.name) new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves) @@ -335,6 +329,21 @@ def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type today_date = today() create_additional_leave_ledger_entry(allocation, earned_leaves, today_date) +def get_monthly_earned_leave(annual_leaves, frequency, rounding): + earned_leaves = 0.0 + divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} + if annual_leaves: + earned_leaves = flt(annual_leaves) / divide_by_frequency[frequency] + if rounding: + if rounding == "0.25": + earned_leaves = round(earned_leaves * 4) / 4 + elif rounding == "0.5": + earned_leaves = round(earned_leaves * 2) / 2 + else: + earned_leaves = round(earned_leaves) + + return earned_leaves + def get_leave_allocations(date, leave_type): return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy From b27d4f6095686b36e0c15e325bec0719d0265b4d Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 2 Mar 2021 17:44:42 +0530 Subject: [PATCH 18/31] fix: salary slip working hours increment (#24784) --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 60aff02b38d..1f8bfea03ce 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1123,6 +1123,7 @@ class SalarySlip(TransactionBase): #calculate total working hours, earnings based on hourly wages and totals def calculate_total_for_salary_slip_based_on_timesheet(self): if self.timesheets: + self.total_working_hours = 0 for timesheet in self.timesheets: if timesheet.working_hours: self.total_working_hours += timesheet.working_hours From 7d892438f0e00fe9aacc7989420466212f892051 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 2 Mar 2021 17:50:58 +0530 Subject: [PATCH 19/31] fix: track setting changes --- .../buying/doctype/buying_settings/buying_settings.json | 7 ++++--- .../doctype/mpesa_settings/mpesa_settings.json | 5 +++-- .../doctype/plaid_settings/plaid_settings.json | 5 +++-- .../doctype/shopify_settings/shopify_settings.json | 5 +++-- .../payroll/doctype/payroll_settings/payroll_settings.json | 5 +++-- .../selling/doctype/selling_settings/selling_settings.json | 5 +++-- .../shopping_cart_settings/shopping_cart_settings.json | 5 +++-- 7 files changed, 22 insertions(+), 15 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 618212da804..248cb9a8a0e 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -96,7 +96,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-13 12:00:23.276329", + "modified": "2021-03-02 17:34:04.190677", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", @@ -113,5 +113,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" -} + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json index 407f82616ff..8f3b4271c18 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.json @@ -103,7 +103,7 @@ } ], "links": [], - "modified": "2021-01-29 12:02:16.106942", + "modified": "2021-03-02 17:35:14.084342", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Mpesa Settings", @@ -147,5 +147,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json index 122aa41f4b9..e7176ea945c 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -70,7 +70,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-10-29 20:24:56.916104", + "modified": "2021-03-02 17:35:27.544259", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Plaid Settings", @@ -88,5 +88,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json index 20ec06373e7..308e7d163f3 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json @@ -330,7 +330,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-11-05 20:44:03.664891", + "modified": "2021-03-02 17:35:41.953317", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Shopify Settings", @@ -348,5 +348,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json index c47caa1227b..76565d55cbf 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json @@ -109,7 +109,7 @@ "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2020-06-22 17:00:58.408030", + "modified": "2021-03-02 17:49:59.579723", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Settings", @@ -126,5 +126,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 4044f09c855..2104c0131c4 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -140,7 +140,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-13 12:12:56.784014", + "modified": "2021-03-02 17:35:53.603607", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -157,5 +157,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json index 36917213022..7a4bb20136f 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json @@ -190,7 +190,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2021-02-11 18:48:30.433058", + "modified": "2021-03-02 17:34:57.642565", "modified_by": "Administrator", "module": "Shopping Cart", "name": "Shopping Cart Settings", @@ -207,5 +207,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file From a5c4558f8b273752b786813ce3d78603c8fed207 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:36:48 +0530 Subject: [PATCH 20/31] feat: Additon of leave details in Salary Slip (#24674) * feat: Additon of leave details in Salary Slip * fix: Change leaves to leave --- .../payroll_settings/payroll_settings.json | 50 +++++------- .../doctype/salary_slip/salary_slip.json | 16 +++- .../doctype/salary_slip/salary_slip.py | 18 +++++ .../doctype/salary_slip_leave/__init__.py | 0 .../salary_slip_leave/salary_slip_leave.json | 78 +++++++++++++++++++ .../salary_slip_leave/salary_slip_leave.py | 10 +++ 6 files changed, 140 insertions(+), 32 deletions(-) create mode 100644 erpnext/payroll/doctype/salary_slip_leave/__init__.py create mode 100644 erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json create mode 100644 erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json index c47caa1227b..680e518ca04 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json @@ -15,6 +15,7 @@ "daily_wages_fraction_for_half_day", "email_salary_slip_to_employee", "encrypt_salary_slips_in_emails", + "show_leave_balances_in_salary_slip", "password_policy" ], "fields": [ @@ -23,58 +24,44 @@ "fieldname": "payroll_based_on", "fieldtype": "Select", "label": "Calculate Payroll Working Days Based On", - "options": "Leave\nAttendance", - "show_days": 1, - "show_seconds": 1 + "options": "Leave\nAttendance" }, { "fieldname": "max_working_hours_against_timesheet", "fieldtype": "Float", - "label": "Max working hours against Timesheet", - "show_days": 1, - "show_seconds": 1 + "label": "Max working hours against Timesheet" }, { "default": "0", "description": "If checked, Total no. of Working Days will include holidays, and this will reduce the value of Salary Per Day", "fieldname": "include_holidays_in_total_working_days", "fieldtype": "Check", - "label": "Include holidays in Total no. of Working Days", - "show_days": 1, - "show_seconds": 1 + "label": "Include holidays in Total no. of Working Days" }, { "default": "0", "description": "If checked, hides and disables Rounded Total field in Salary Slips", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total", - "show_days": 1, - "show_seconds": 1 + "label": "Disable Rounded Total" }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0.5", "description": "The fraction of daily wages to be paid for half-day attendance", "fieldname": "daily_wages_fraction_for_half_day", "fieldtype": "Float", - "label": "Fraction of Daily Salary for Half Day", - "show_days": 1, - "show_seconds": 1 + "label": "Fraction of Daily Salary for Half Day" }, { "default": "1", "description": "Emails salary slip to employee based on preferred email selected in Employee", "fieldname": "email_salary_slip_to_employee", "fieldtype": "Check", - "label": "Email Salary Slip to Employee", - "show_days": 1, - "show_seconds": 1 + "label": "Email Salary Slip to Employee" }, { "default": "0", @@ -82,9 +69,7 @@ "description": "The salary slip emailed to the employee will be password protected, the password will be generated based on the password policy.", "fieldname": "encrypt_salary_slips_in_emails", "fieldtype": "Check", - "label": "Encrypt Salary Slips in Emails", - "show_days": 1, - "show_seconds": 1 + "label": "Encrypt Salary Slips in Emails" }, { "depends_on": "eval: doc.encrypt_salary_slips_in_emails == 1", @@ -92,24 +77,27 @@ "fieldname": "password_policy", "fieldtype": "Data", "in_list_view": 1, - "label": "Password Policy", - "show_days": 1, - "show_seconds": 1 + "label": "Password Policy" }, { "depends_on": "eval:doc.payroll_based_on == 'Attendance'", "fieldname": "consider_unmarked_attendance_as", "fieldtype": "Select", "label": "Consider Unmarked Attendance As", - "options": "Present\nAbsent", - "show_days": 1, - "show_seconds": 1 + "options": "Present\nAbsent" + }, + { + "default": "0", + "fieldname": "show_leave_balances_in_salary_slip", + "fieldtype": "Check", + "label": "Show Leave Balances in Salary Slip" } ], "icon": "fa fa-cog", + "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-06-22 17:00:58.408030", + "modified": "2021-02-19 11:07:55.873991", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Settings", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 9f9691b59d1..66883682625 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -80,6 +80,8 @@ "total_in_words", "column_break_69", "base_total_in_words", + "leave_details_section", + "leave_details", "section_break_75", "amended_from" ], @@ -612,13 +614,25 @@ "label": "Month To Date(Company Currency)", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "leave_details_section", + "fieldtype": "Section Break", + "label": "Leave Details" + }, + { + "fieldname": "leave_details", + "fieldtype": "Table", + "label": "Leave Details", + "options": "Salary Slip Leave", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-01-14 13:37:38.180920", + "modified": "2021-02-19 11:48:05.383945", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 1f8bfea03ce..d9aadbf3aa5 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_application.employee_benefit_appli 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 six import iteritems class SalarySlip(TransactionBase): def __init__(self, *args, **kwargs): @@ -53,6 +54,7 @@ class SalarySlip(TransactionBase): self.compute_year_to_date() self.compute_month_to_date() self.compute_component_wise_year_to_date() + self.add_leave_balances() if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"): max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet") @@ -1213,6 +1215,22 @@ class SalarySlip(TransactionBase): return period_start_date, period_end_date + def add_leave_balances(self): + self.set('leave_details', []) + + if frappe.db.get_single_value('Payroll Settings', 'show_leave_balances_in_salary_slip'): + from erpnext.hr.doctype.leave_application.leave_application import get_leave_details + leave_details = get_leave_details(self.employee, self.end_date) + + for leave_type, leave_values in iteritems(leave_details['leave_allocation']): + self.append('leave_details', { + 'leave_type': leave_type, + 'total_allocated_leaves': flt(leave_values.get('total_leaves')), + 'expired_leaves': flt(leave_values.get('expired_leaves')), + 'used_leaves': flt(leave_values.get('leaves_taken')), + 'pending_leaves': flt(leave_values.get('pending_leaves')), + 'available_leaves': flt(leave_values.get('remaining_leaves')) + }) def unlink_ref_doc_from_salary_slip(ref_no): linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip` diff --git a/erpnext/payroll/doctype/salary_slip_leave/__init__.py b/erpnext/payroll/doctype/salary_slip_leave/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json new file mode 100644 index 00000000000..7ac453b3c3d --- /dev/null +++ b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json @@ -0,0 +1,78 @@ +{ + "actions": [], + "creation": "2021-02-19 11:45:18.173417", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "leave_type", + "total_allocated_leaves", + "expired_leaves", + "used_leaves", + "pending_leaves", + "available_leaves" + ], + "fields": [ + { + "fieldname": "leave_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Leave Type", + "no_copy": 1, + "options": "Leave Type", + "read_only": 1 + }, + { + "fieldname": "total_allocated_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Allocated Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "expired_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Expired Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "used_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Used Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "pending_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Pending Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "available_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Available Leave", + "no_copy": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-02-19 10:47:48.546724", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Salary Slip Leave", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py new file mode 100644 index 00000000000..7a92bf18f76 --- /dev/null +++ b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class SalarySlipLeave(Document): + pass From ecde26409b9264e3b4185861cb35f4a5318c83fe Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:54:50 +0530 Subject: [PATCH 21/31] Update team_updates.js --- erpnext/hr/page/team_updates/team_updates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/page/team_updates/team_updates.js b/erpnext/hr/page/team_updates/team_updates.js index 991b316e1e9..358329748e6 100644 --- a/erpnext/hr/page/team_updates/team_updates.js +++ b/erpnext/hr/page/team_updates/team_updates.js @@ -75,6 +75,6 @@ frappe.team_updates = { } me.last_feed_date = date; - $(frappe.render_template('team_update_row', data)).appendTo(me.body) + $(frappe.render_template('team_update_row', data)).appendTo(me.body); } } From a44df63a9119148adda806bae077f96330192bfb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Mar 2021 17:12:53 +0530 Subject: [PATCH 22/31] fix: Add warning for invalid GST invoice numbers GST Invoice numbers should be 16 characters alphanumeric with dash(/) or slash(-) only. Add check for doc.name before saving and warn about naming series. --- erpnext/hooks.py | 3 +++ erpnext/regional/india/utils.py | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 59639ffc437..f87769c182c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -278,6 +278,9 @@ doc_events = { ('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { 'validate': ['erpnext.regional.india.utils.set_place_of_supply'] }, + ('Sales Invoice', 'Purchase Invoice'): { + 'validate': ['erpnext.regional.india.utils.validate_document_name'] + }, "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index cb30605291c..7980d0b9ffe 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe, re, json from frappe import _ import erpnext -from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words +from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate from erpnext.regional.india import states, state_numbers from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -148,6 +148,20 @@ def get_itemised_tax_breakup_data(doc, account_wise=False): def set_place_of_supply(doc, method=None): doc.place_of_supply = get_place_of_supply(doc, doc.doctype) +def validate_document_name(doc, method=None): + """Validate GST invoice number requirements.""" + country = frappe.get_cached_value("Company", doc.company, "country") + + if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"): + return + + if len(doc.name) > 16: + frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series.")) + + gst_doc_name_pattern = re.compile(r"^[a-zA-Z0-9\-/]+$") + if not gst_doc_name_pattern.match(doc.name): + frappe.throw(_("Document name should only contain alphanumeric values, dash(-) and slash(/) characters as per GST rules. Please change the naming series.")) + # don't remove this function it is used in tests def test_method(): '''test function''' From 9b3f5d5f678507027e8ffd1349259db9f46f6615 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 3 Mar 2021 12:38:52 +0530 Subject: [PATCH 23/31] test: add tests for gst invoice name checks --- erpnext/regional/india/test_utils.py | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 erpnext/regional/india/test_utils.py diff --git a/erpnext/regional/india/test_utils.py b/erpnext/regional/india/test_utils.py new file mode 100644 index 00000000000..7ce27f6cf5a --- /dev/null +++ b/erpnext/regional/india/test_utils.py @@ -0,0 +1,38 @@ +from __future__ import unicode_literals + +import unittest +import frappe +from unittest.mock import patch +from erpnext.regional.india.utils import validate_document_name + + +class TestIndiaUtils(unittest.TestCase): + @patch("frappe.get_cached_value") + def test_validate_document_name(self, mock_get_cached): + mock_get_cached.return_value = "India" # mock country + posting_date = "2021-05-01" + + invalid_names = [ "SI$1231", "012345678901234567", "SI 2020 05", + "SI.2020.0001", "PI2021 - 001" ] + for name in invalid_names: + doc = frappe._dict(name=name, posting_date=posting_date) + self.assertRaises(frappe.ValidationError, validate_document_name, doc) + + valid_names = [ "012345678901236", "SI/2020/0001", "SI/2020-0001", + "2020-PI-0001", "PI2020-0001" ] + for name in valid_names: + doc = frappe._dict(name=name, posting_date=posting_date) + try: + validate_document_name(doc) + except frappe.ValidationError: + self.fail("Valid name {} throwing error".format(name)) + + @patch("frappe.get_cached_value") + def test_validate_document_name_not_india(self, mock_get_cached): + mock_get_cached.return_value = "Not India" + doc = frappe._dict(name="SI$123", posting_date="2021-05-01") + + try: + validate_document_name(doc) + except frappe.ValidationError: + self.fail("Regional validation related to India are being applied to other countries") From 7c4c42ad67c5b358c055bc7e1664f3ea04dd3c9c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 3 Mar 2021 14:56:19 +0530 Subject: [PATCH 24/31] refactor: move regex patterns to global variables --- erpnext/regional/india/utils.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 7980d0b9ffe..1a618d6cf56 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -14,6 +14,13 @@ from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.utils import get_account_currency from frappe.model.utils import get_fetch_values + +GST_INVOICE_NUMBER_FORMAT = re.compile(r"^[a-zA-Z0-9\-/]+$") #alphanumeric and - / +GSTIN_FORMAT = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") +GSTIN_UIN_FORMAT = re.compile("^[0-9]{4}[A-Z]{3}[0-9]{5}[0-9A-Z]{3}") +PAN_NUMBER_FORMAT = re.compile("[A-Z]{5}[0-9]{4}[A-Z]{1}") + + def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: doc.gst_state_number = state_numbers[doc.gst_state] @@ -37,12 +44,10 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters.")) if gst_category and gst_category == 'UIN Holders': - p = re.compile("^[0-9]{4}[A-Z]{3}[0-9]{5}[0-9A-Z]{3}") - if not p.match(doc.gstin): + if not GSTIN_UIN_FORMAT.match(doc.gstin): frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers")) else: - p = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") - if not p.match(doc.gstin): + if not GSTIN_FORMAT.match(doc.gstin): frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN.")) validate_gstin_check_digit(doc.gstin) @@ -59,8 +64,7 @@ def validate_pan_for_india(doc, method): if doc.get('country') != 'India' or not doc.pan: return - p = re.compile("[A-Z]{5}[0-9]{4}[A-Z]{1}") - if not p.match(doc.pan): + if not PAN_NUMBER_FORMAT.match(doc.pan): frappe.throw(_("Invalid PAN No. The input you've entered doesn't match the format of PAN.")) def validate_tax_category(doc, method): @@ -152,14 +156,14 @@ def validate_document_name(doc, method=None): """Validate GST invoice number requirements.""" country = frappe.get_cached_value("Company", doc.company, "country") + # Date was chosen as start of next FY to avoid irritating current users. if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"): return if len(doc.name) > 16: frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series.")) - gst_doc_name_pattern = re.compile(r"^[a-zA-Z0-9\-/]+$") - if not gst_doc_name_pattern.match(doc.name): + if not GST_INVOICE_NUMBER_FORMAT.match(doc.name): frappe.throw(_("Document name should only contain alphanumeric values, dash(-) and slash(/) characters as per GST rules. Please change the naming series.")) # don't remove this function it is used in tests @@ -814,4 +818,4 @@ def get_regional_round_off_accounts(company, account_list): account_list.extend(gst_account_list) - return account_list \ No newline at end of file + return account_list From 53d261702afd06aaf95fb9a8022d01d2f9f8cf3f Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 3 Mar 2021 11:52:48 +0100 Subject: [PATCH 25/31] fix: make quick entry for Tax Category work --- erpnext/accounts/doctype/tax_category/tax_category.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_category/tax_category.json b/erpnext/accounts/doctype/tax_category/tax_category.json index 6f682a0466d..f7145af44c3 100644 --- a/erpnext/accounts/doctype/tax_category/tax_category.json +++ b/erpnext/accounts/doctype/tax_category/tax_category.json @@ -11,15 +11,18 @@ ], "fields": [ { + "allow_in_quick_entry": 1, "fieldname": "title", "fieldtype": "Data", + "in_list_view": 1, "label": "Title", + "reqd": 1, "unique": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-08-30 19:41:25.783852", + "modified": "2021-03-03 11:50:38.748872", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Category", From 1521b31795aa79dff94ac44f17ee61a55fee5639 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 3 Mar 2021 12:33:48 +0100 Subject: [PATCH 26/31] fix: use set_value instead of sql --- erpnext/regional/india/setup.py | 5 +++-- erpnext/regional/italy/setup.py | 4 +--- erpnext/regional/united_states/setup.py | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 526198424f3..ee46a52f1ce 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -105,8 +105,9 @@ def add_print_formats(): frappe.reload_doc("accounts", "print_format", "gst_pos_invoice") frappe.reload_doc("accounts", "print_format", "GST E-Invoice") - frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where - name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """) + frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0) + frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0) + frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0) def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py index 217d623a8d5..95b92e76a69 100644 --- a/erpnext/regional/italy/setup.py +++ b/erpnext/regional/italy/setup.py @@ -189,9 +189,7 @@ def make_custom_fields(update=True): def setup_report(): report_name = 'Electronic Invoice Register' - - frappe.db.sql(""" update `tabReport` set disabled = 0 where - name = %s """, report_name) + frappe.db.set_value("Report", report_name, "disabled", 0) if not frappe.db.get_value('Custom Role', dict(report=report_name)): frappe.get_doc(dict( diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py index 2b0ecafebc5..24ab1cf049f 100644 --- a/erpnext/regional/united_states/setup.py +++ b/erpnext/regional/united_states/setup.py @@ -36,5 +36,4 @@ def make_custom_fields(update=True): def add_print_formats(): frappe.reload_doc("regional", "print_format", "irs_1099_form") - frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where - name in('IRS 1099 Form') """) + frappe.db.set_value("Print Format", "IRS 1099 Form", "disabled", 0) From 27ea23223d67b072c0ee62acebb9c72cf28a9bf1 Mon Sep 17 00:00:00 2001 From: UrvashiKishnani <41088003+UrvashiKishnani@users.noreply.github.com> Date: Thu, 4 Mar 2021 09:21:25 +0400 Subject: [PATCH 27/31] fix: total row in AR/AP summary report --- .../report/accounts_receivable/accounts_receivable.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 79a6aabd987..f4fd06ba037 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -258,7 +258,7 @@ {% } %} {% } else { %} {% if(data[i]["party"]|| " ") { %} - {% if((data[i]["party"]) != __("'Total'")) { %} + {% if(!data[i]["is_total_row"]) { %} {% if(!(filters.customer || filters.supplier)) { %} {%= data[i]["party"] %} From 9b4a258c896dd19bbd57367b8c55ef5ec0c38226 Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Thu, 4 Mar 2021 00:15:46 -0600 Subject: [PATCH 28/31] Chart Of Accounts -> Chart of Accounts --- erpnext/accounts/workspace/accounting/accounting.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 8d24ca82918..fadb66535f5 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -1061,7 +1061,7 @@ "type": "Link" } ], - "modified": "2020-12-01 13:38:35.349024", + "modified": "2021-03-04 00:38:35.349024", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", @@ -1071,7 +1071,7 @@ "pin_to_top": 0, "shortcuts": [ { - "label": "Chart Of Accounts", + "label": "Chart of Accounts", "link_to": "Account", "type": "DocType" }, @@ -1116,4 +1116,4 @@ "type": "Dashboard" } ] -} \ No newline at end of file +} From 92b0691c68a401c5b54567e6dc563d16cf441e54 Mon Sep 17 00:00:00 2001 From: Saqib Date: Sat, 6 Mar 2021 19:00:08 +0530 Subject: [PATCH 29/31] fix: einvoice button visiblity condition (#24800) --- .../print_format/gst_e_invoice/gst_e_invoice.html | 6 +++--- erpnext/regional/india/e_invoice/einvoice.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html index 8eef2adce3e..71c26e8c55a 100644 --- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -22,8 +22,8 @@

{% endif %} +
1. Transaction Details
-
1. Transaction Details
@@ -54,8 +54,8 @@
+
2. Party Details
-
2. Party Details
{%- set seller = einvoice.SellerDtls -%}
Seller
@@ -89,7 +89,7 @@
-
3. Item Details
+
3. Item Details
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index a756b57eb7e..7cd64f2fc07 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -1,12 +1,12 @@ erpnext.setup_einvoice_actions = (doctype) => { frappe.ui.form.on(doctype, { - refresh(frm) { - const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); + async refresh(frm) { + const einvoicing_enabled = await frappe.db.get_single_value("E Invoice Settings", "enable"); const supply_type = frm.doc.gst_category; const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type); const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin; - if (!einvoicing_enabled || !valid_supply_type || company_transaction) return; + if (cint(einvoicing_enabled) == 0 || !valid_supply_type || company_transaction) return; const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; @@ -83,7 +83,7 @@ erpnext.setup_einvoice_actions = (doctype) => { const action = () => { const d = new frappe.ui.Dialog({ title: __('Generate E-Way Bill'), - wide: 1, + size: "large", fields: get_ewaybill_fields(frm), primary_action: function() { const data = d.get_values(); @@ -252,7 +252,7 @@ const request_irn_generation = (frm) => { const get_preview_dialog = (frm, action) => { const dialog = new frappe.ui.Dialog({ title: __("Preview"), - wide: 1, + size: "large", fields: [ { "label": "Preview", From 4c089b585208f235656955a2a0287b189f02ae2d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 7 Mar 2021 11:25:03 +0530 Subject: [PATCH 30/31] minor fixes --- .../manufacturing/doctype/work_order/work_order.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index ca530bbaddc..3d64ad4318d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -528,6 +528,10 @@ class WorkOrder(Document): if not reset_only_qty: self.required_items = [] + operation = None + if self.get('operations') and len(self.operations) == 1: + operation = self.operations[0].operation + if self.bom_no and self.qty: item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty, fetch_exploded = self.use_multi_level_bom) @@ -536,6 +540,9 @@ class WorkOrder(Document): for d in self.get("required_items"): if item_dict.get(d.item_code): d.required_qty = item_dict.get(d.item_code).get("qty") + + if not d.operation: + d.operation = operation else: # Attribute a big number (999) to idx for sorting putpose in case idx is NULL # For instance in BOM Explosion Item child table, the items coming from sub assembly items @@ -543,7 +550,7 @@ class WorkOrder(Document): self.append('required_items', { 'rate': item.rate, 'amount': item.amount, - 'operation': item.operation, + 'operation': item.operation or operation, 'item_code': item.item_code, 'item_name': item.item_name, 'description': item.description, @@ -879,7 +886,7 @@ def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto doc.schedule_time_logs(row) doc.insert() - frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name))) + frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True) return doc From d95b59e90c4d1b0a58a8e2ddb0f2ae908070a6bd Mon Sep 17 00:00:00 2001 From: Afshan Date: Mon, 8 Mar 2021 18:19:53 +0530 Subject: [PATCH 31/31] fix: call function when arguments available --- .../public/js/controllers/taxes_and_totals.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index d81321b2916..3a3ee3858bf 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -158,16 +158,18 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ let me = this; frappe.flags.round_off_applicable_accounts = []; - return frappe.call({ - "method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts", - "args": { - "company": me.frm.doc.company, - "account_list": frappe.flags.round_off_applicable_accounts - }, - callback: function(r) { - frappe.flags.round_off_applicable_accounts.push(...r.message); - } - }); + if (me.frm.doc.company) { + return frappe.call({ + "method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts", + "args": { + "company": me.frm.doc.company, + "account_list": frappe.flags.round_off_applicable_accounts + }, + callback: function(r) { + frappe.flags.round_off_applicable_accounts.push(...r.message); + } + }); + } }, determine_exclusive_rate: function() {