From 67519d6b79f0bdc1136ccaeb87a4e5fde2421689 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 3 Jun 2021 18:07:00 +0530 Subject: [PATCH 001/253] fix: boarding status in Employee Onboarding and Separation - make boarding status read-only as its dependent on project - update boarding status in onboarding/separation on project update - update tests to check status changes --- .../employee_onboarding.js | 15 ---- .../employee_onboarding.json | 72 +++++------------ .../test_employee_onboarding.py | 81 ++++++++++++------- .../employee_separation.js | 14 ---- .../employee_separation.json | 7 +- .../test_employee_separation.py | 48 ++++++++--- erpnext/hr/utils.py | 25 +++--- erpnext/projects/doctype/project/project.py | 3 + 8 files changed, 130 insertions(+), 135 deletions(-) diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js index d6047e1846..bd72629c0d 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js @@ -50,21 +50,6 @@ frappe.ui.form.on('Employee Onboarding', { }, __('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) { diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json index 783c7574ef..673e228395 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/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py index 336e13c9b7..5f7756bcad 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.employee_onboarding.employee_onboarding import Incomplet 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 @@ class TestEmployeeOnboarding(unittest.TestCase): 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 @@ class TestEmployeeOnboarding(unittest.TestCase): 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 @@ def get_job_applicant(): 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 9a75c16317..33830796b6 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.js +++ b/erpnext/hr/doctype/employee_separation/employee_separation.js @@ -23,20 +23,6 @@ frappe.ui.form.on('Employee Separation', { 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) { diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json index 7af209887f..c10da5c35e 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/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py index 713fcf526b..f787d9c656 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 @@ from __future__ import unicode_literals 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/utils.py b/erpnext/hr/utils.py index 80189e87b7..8af00b36ca 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -114,16 +114,23 @@ def get_onboarding_details(parent, parenttype): filters={"parent": parent, "parenttype": parenttype}, order_by= "idx") -@frappe.whitelist() -def get_boarding_status(project): +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 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 + 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) def set_employee_name(doc): if doc.employee and not doc.employee_name: diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index c8fbe0bf7b..0ee9990620 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -14,6 +14,7 @@ from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_e 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.hr.utils import update_employee_boarding_status class Project(Document): def get_feed(self): @@ -37,6 +38,7 @@ class Project(Document): 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 @@ class Project(Document): def update_project(self): '''Called externally by Task''' self.update_percent_complete() + update_employee_boarding_status(self) self.update_costing() self.db_update() From 672c8bb11230692cf24c81b85d9d0fd84f27d910 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 4 Jun 2021 16:44:30 +0530 Subject: [PATCH 002/253] feature: report for cost of goods sold by item group --- .../report/cogs_by_item_group/__init__.py | 0 .../cogs_by_item_group/cogs_by_item_group.js | 46 ++++++ .../cogs_by_item_group.json | 32 ++++ .../cogs_by_item_group/cogs_by_item_group.py | 155 ++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 erpnext/stock/report/cogs_by_item_group/__init__.py create mode 100644 erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js create mode 100644 erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json create mode 100644 erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py diff --git a/erpnext/stock/report/cogs_by_item_group/__init__.py b/erpnext/stock/report/cogs_by_item_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js new file mode 100644 index 0000000000..c17da4ed97 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -0,0 +1,46 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["COGS By Item Group"] = { + "filters": [ + { + label: __("Company"), + fieldname: "company", + fieldtype: "Link", + options: "Company", + mandatory: true, + default: frappe.defaults.get_user_default("Company"), + }, + { + label: __("Account"), + fieldname: "account", + fieldtype: "Link", + options: "Account", + mandatory: true, + get_query() { + var company = frappe.query_report.get_filter_value('company'); + return { + "doctype": "Account", + "filters": { + "company": company, + } + } + }, + }, + { + label: __("From Date"), + fieldname: "from_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.year_start(), + }, + { + label: __("To Date"), + fieldname: "to_date", + fieldtype: "Date", + mandatory: true, + default: frappe.datetime.get_today(), + }, + ] +}; diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json new file mode 100644 index 0000000000..a14adf8a45 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-06-02 18:59:19.830928", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-06-02 18:59:55.470621", + "modified_by": "Administrator", + "module": "Stock", + "name": "COGS By Item Group", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "COGS By Item Group", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py new file mode 100644 index 0000000000..d4ddd595d9 --- /dev/null +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -0,0 +1,155 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.utils import date_diff +from collections import OrderedDict +from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries + + +def execute(filters=None): + print(filters) + validate_filters(filters) + columns = get_columns() + data = get_data(filters) + return columns, data + + +def validate_filters(filters): + if not filters.get("from_date") and not filters.get("to_date"): + frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) + + if filters.from_date > filters.to_date: + frappe.throw(_("From Date must be before To Date")) + + +def get_columns(): + return [ + { + 'label': 'Item Group', + 'fieldname': 'item_group', + 'fieldtype': 'Data', + 'width': '200' + }, + { + 'label': 'COGS Debit', + 'fieldname': 'cogs_debit', + 'fieldtype': 'Currency', + 'width': '200' + } + ] + + +def get_data(filters): + entries = get_filtered_entries(filters) + item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) + item_groups_dict = get_item_groups_dict(item_groups_list) + levels_dict = get_levels_dict(item_groups_dict) + + update_levels_dict(levels_dict) + assign_self_values(levels_dict, entries) + assign_agg_values(levels_dict) + + data = [] + for _, i in levels_dict.items(): + if i['agg_value'] == 0: + continue + data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level'])) + if i['self_value'] < i['agg_value'] and i['self_value'] > 0: + data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1)) + return data + + +def get_filtered_entries(filters): + gl_entries = get_gl_entries(filters, []) + entries = [frappe.get_doc(gle.voucher_type, gle.voucher_no)for gle in gl_entries] + filtered_entries = [] + for entry in entries: + posting_date = entry.get("posting_date") + from_date = filters.get("from_date") + if date_diff(from_date, posting_date) > 0: + continue + filtered_entries.append(entry) + return filtered_entries + + +def append_blank(data): + if len(data) == 0: + data.append(get_row("", 0, 0, 0)) + + +def get_item_groups_dict(item_groups_list): + return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} + for i in item_groups_list } + + +def get_levels_dict(item_groups_dict): + lr_list = sorted(item_groups_dict, key=lambda x : x[0]) + levels = OrderedDict() + current_level = 0 + nesting_r = [] + for l,r in lr_list: + while current_level > 0 and nesting_r[-1] < l: + nesting_r.pop() + current_level -= 1 + + levels[(l,r)] = { + 'level' : current_level, + 'name' : item_groups_dict[(l,r)]['name'], + 'is_group' : item_groups_dict[(l,r)]['is_group'] + } + + if r - l > 1: + current_level += 1 + nesting_r.append(r) + return levels + + +def update_levels_dict(levels_dict): + for k in levels_dict: levels_dict[k].update({'self_value':0, 'agg_value':0}) + + +def assign_self_values(levels_dict, entries): + names_dict = {v['name']:k for k, v in levels_dict.items()} + for entry in entries: + items = entry.get("items") + items = [] if items is None else items + for item in items: + qty = item.get("qty") + incoming_rate = item.get("incoming_rate") + item_group = item.get("item_group") + key = names_dict[item_group] + levels_dict[key]['self_value'] += (incoming_rate * qty) + + +def assign_agg_values(levels_dict): + keys = list(levels_dict.keys())[::-1] + prev_level = levels_dict[keys[-1]]['level'] + accu = [0] + for k in keys[:-1]: + curr_level = levels_dict[k]['level'] + if curr_level == prev_level: + accu[-1] += levels_dict[k]['self_value'] + levels_dict[k]['agg_value'] = levels_dict[k]['self_value'] + + elif curr_level > prev_level: + accu.append(levels_dict[k]['self_value']) + levels_dict[k]['agg_value'] = accu[-1] + + elif curr_level < prev_level: + accu[-1] += levels_dict[k]['self_value'] + levels_dict[k]['agg_value'] = accu[-1] + + prev_level = curr_level + + # root node + rk = keys[-1] + levels_dict[rk]['agg_value'] = sum(accu) + levels_dict[rk]['self_value'] + + +def get_row(name:str, value:float, is_bold:int, indent:int): + item_group = name + if is_bold: + item_group = frappe.bold(item_group) + return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent) From 23b907df1af0a84a25954079afeac1179eccdea4 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 7 Jun 2021 13:52:26 +0530 Subject: [PATCH 003/253] fix: use stock value diff for calculation --- .../cogs_by_item_group/cogs_by_item_group.py | 127 ++++++++++-------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index d4ddd595d9..7599da4322 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -9,7 +9,6 @@ from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries def execute(filters=None): - print(filters) validate_filters(filters) columns = get_columns() data = get_data(filters) @@ -17,9 +16,6 @@ def execute(filters=None): def validate_filters(filters): - if not filters.get("from_date") and not filters.get("to_date"): - frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) - if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) @@ -42,110 +38,100 @@ def get_columns(): def get_data(filters): - entries = get_filtered_entries(filters) - item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) - item_groups_dict = get_item_groups_dict(item_groups_list) - levels_dict = get_levels_dict(item_groups_dict) + filtered_entries = get_filtered_entries(filters) + svd_list = get_stock_value_difference_list(filtered_entries) + leveled_dict = get_leveled_dict() - update_levels_dict(levels_dict) - assign_self_values(levels_dict, entries) - assign_agg_values(levels_dict) + assign_self_values(leveled_dict, svd_list) + assign_agg_values(leveled_dict) data = [] - for _, i in levels_dict.items(): + for _, i in leveled_dict.items(): if i['agg_value'] == 0: continue data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level'])) if i['self_value'] < i['agg_value'] and i['self_value'] > 0: data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1)) + # append_blank() return data def get_filtered_entries(filters): gl_entries = get_gl_entries(filters, []) - entries = [frappe.get_doc(gle.voucher_type, gle.voucher_no)for gle in gl_entries] filtered_entries = [] - for entry in entries: - posting_date = entry.get("posting_date") - from_date = filters.get("from_date") + for entry in gl_entries: + posting_date = entry.get('posting_date') + from_date = filters.get('from_date') if date_diff(from_date, posting_date) > 0: continue filtered_entries.append(entry) return filtered_entries -def append_blank(data): - if len(data) == 0: - data.append(get_row("", 0, 0, 0)) +def get_stock_value_difference_list(filtered_entries): + voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] + svd_list = frappe.get_list('Stock Ledger Entry', + fields=['item_code','stock_value_difference'], + filters=[('voucher_no', 'in', voucher_nos)]) + assign_item_groups_to_svd_list(svd_list) + return svd_list -def get_item_groups_dict(item_groups_list): - return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} - for i in item_groups_list } - - -def get_levels_dict(item_groups_dict): - lr_list = sorted(item_groups_dict, key=lambda x : x[0]) - levels = OrderedDict() +def get_leveled_dict(): + item_groups_dict = get_item_groups_dict() + lr_list = sorted(item_groups_dict, key=lambda x : int(x[0])) + leveled_dict = OrderedDict() current_level = 0 nesting_r = [] - for l,r in lr_list: + for l, r in lr_list: while current_level > 0 and nesting_r[-1] < l: nesting_r.pop() current_level -= 1 - levels[(l,r)] = { + leveled_dict[(l,r)] = { 'level' : current_level, 'name' : item_groups_dict[(l,r)]['name'], 'is_group' : item_groups_dict[(l,r)]['is_group'] } - if r - l > 1: + if int(r) - int(l) > 1: current_level += 1 nesting_r.append(r) - return levels - -def update_levels_dict(levels_dict): - for k in levels_dict: levels_dict[k].update({'self_value':0, 'agg_value':0}) + update_leveled_dict(leveled_dict) + return leveled_dict -def assign_self_values(levels_dict, entries): - names_dict = {v['name']:k for k, v in levels_dict.items()} - for entry in entries: - items = entry.get("items") - items = [] if items is None else items - for item in items: - qty = item.get("qty") - incoming_rate = item.get("incoming_rate") - item_group = item.get("item_group") - key = names_dict[item_group] - levels_dict[key]['self_value'] += (incoming_rate * qty) +def assign_self_values(leveled_dict, svd_list): + key_dict = {v['name']:k for k, v in leveled_dict.items()} + for item in svd_list: + key = key_dict[item.get("item_group")] + leveled_dict[key]['self_value'] += -item.get("stock_value_difference") -def assign_agg_values(levels_dict): - keys = list(levels_dict.keys())[::-1] - prev_level = levels_dict[keys[-1]]['level'] +def assign_agg_values(leveled_dict): + keys = list(leveled_dict.keys())[::-1] + prev_level = leveled_dict[keys[-1]]['level'] accu = [0] for k in keys[:-1]: - curr_level = levels_dict[k]['level'] + curr_level = leveled_dict[k]['level'] if curr_level == prev_level: - accu[-1] += levels_dict[k]['self_value'] - levels_dict[k]['agg_value'] = levels_dict[k]['self_value'] + accu[-1] += leveled_dict[k]['self_value'] + leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value'] elif curr_level > prev_level: - accu.append(levels_dict[k]['self_value']) - levels_dict[k]['agg_value'] = accu[-1] + accu.append(leveled_dict[k]['self_value']) + leveled_dict[k]['agg_value'] = accu[-1] elif curr_level < prev_level: - accu[-1] += levels_dict[k]['self_value'] - levels_dict[k]['agg_value'] = accu[-1] + accu[-1] += leveled_dict[k]['self_value'] + leveled_dict[k]['agg_value'] = accu[-1] prev_level = curr_level # root node rk = keys[-1] - levels_dict[rk]['agg_value'] = sum(accu) + levels_dict[rk]['self_value'] + leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value'] def get_row(name:str, value:float, is_bold:int, indent:int): @@ -153,3 +139,32 @@ def get_row(name:str, value:float, is_bold:int, indent:int): if is_bold: item_group = frappe.bold(item_group) return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent) + + +def assign_item_groups_to_svd_list(svd_list): + ig_map = get_item_groups_map(svd_list) + for item in svd_list: + item.item_group = ig_map[item.get("item_code")] + +def get_item_groups_map(svd_list): + # for items in svd_list: [{'item_code':'item_group'}] + item_codes = set([i['item_code'] for i in svd_list]) + ig_list = frappe.get_list('Item', + fields=['item_code','item_group'], + filters=[('item_code', 'in', item_codes)]) + return {i['item_code']:i['item_group'] for i in ig_list} + + +def append_blank(data): + if len(data) == 0: + data.append(get_row("", 0, 0, 0)) + + +def get_item_groups_dict(): + item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) + return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} + for i in item_groups_list } + + +def update_leveled_dict(leveled_dict): + for k in leveled_dict: leveled_dict[k].update({'self_value':0, 'agg_value':0}) From 6f79c4c3481b89fa080e69e5ce5a567b1610ea13 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 7 Jun 2021 13:58:45 +0530 Subject: [PATCH 004/253] fix: add account filter --- .../cogs_by_item_group/cogs_by_item_group.js | 35 ++++++++++--------- .../cogs_by_item_group/cogs_by_item_group.py | 6 ++++ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js index c17da4ed97..bb780e50b2 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -2,8 +2,9 @@ // For license information, please see license.txt /* eslint-disable */ + frappe.query_reports["COGS By Item Group"] = { - "filters": [ + filters: [ { label: __("Company"), fieldname: "company", @@ -12,22 +13,22 @@ frappe.query_reports["COGS By Item Group"] = { mandatory: true, default: frappe.defaults.get_user_default("Company"), }, - { - label: __("Account"), - fieldname: "account", - fieldtype: "Link", - options: "Account", - mandatory: true, - get_query() { - var company = frappe.query_report.get_filter_value('company'); - return { - "doctype": "Account", - "filters": { - "company": company, - } - } - }, - }, + // { + // label: __("Account"), + // fieldname: "account", + // fieldtype: "Link", + // options: "Account", + // mandatory: true, + // get_query() { + // const company = frappe.query_report.get_filter_value('company'); + // return { + // "doctype": "Account", + // "filters": { + // "company": company, + // } + // } + // }, + // }, { label: __("From Date"), fieldname: "from_date", diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index 7599da4322..e2c6f7928c 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -9,12 +9,18 @@ from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries def execute(filters=None): + update_filters_with_account(filters) validate_filters(filters) columns = get_columns() data = get_data(filters) return columns, data +def update_filters_with_account(filters): + account = frappe.get_value("Company", filters.get("company"), "default_expense_account") + filters.update(dict(account=account)) + + def validate_filters(filters): if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) From dc448c2f51210e4b7481ad229cf9c339e81ffd69 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 17 Jun 2021 00:28:03 +0530 Subject: [PATCH 005/253] refactor: lead --- erpnext/crm/doctype/lead/lead.js | 5 - erpnext/crm/doctype/lead/lead.json | 186 ++++++++++++++++------------- erpnext/crm/doctype/lead/lead.py | 84 +------------ 3 files changed, 107 insertions(+), 168 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index ebe85241d2..815bb41a76 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -68,11 +68,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller }) } - organization_lead () { - this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead); - this.frm.toggle_reqd("company_name", this.frm.doc.organization_lead); - } - company_name () { if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) { this.frm.set_value("lead_name", this.frm.doc.company_name); diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 1b33fd73ac..ed33d896f8 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -9,30 +9,32 @@ "email_append_to": 1, "engine": "InnoDB", "field_order": [ - "organization_lead", "lead_details", "naming_series", - "lead_name", - "company_name", - "email_id", - "col_break123", - "lead_owner", - "status", "salutation", + "first_name", + "middle_name", + "last_name", + "lead_name", + "email_id", + "mobile_no", + "phone", + "col_break123", + "status", + "company_name", "designation", "gender", - "source", - "customer", - "campaign_name", - "image", - "section_break_12", - "contact_by", - "column_break_14", - "contact_date", - "ends_on", - "notes_section", - "notes", - "address_info", + "additional_information_section", + "no_of_employees", + "industry", + "market_segment", + "type", + "request_type", + "column_break_22", + "whatsapp_no", + "fax", + "website", + "address_section", "address_html", "address_type", "address_title", @@ -45,35 +47,33 @@ "state", "country", "pincode", - "contact_section", - "phone", - "mobile_no", - "fax", - "website", - "more_info", - "type", - "market_segment", - "industry", - "request_type", - "column_break3", + "section_break_12", + "lead_owner", + "ends_on", + "column_break_14", + "contact_by", + "contact_date", + "lead_source_details_section", "company", "territory", "language", + "column_break_50", + "source", + "campaign_name", "unsubscribed", "blog_subscriber", + "notes_section", + "notes", + "other_information_section", + "customer", + "image", "title" ], "fields": [ - { - "default": "0", - "fieldname": "organization_lead", - "fieldtype": "Check", - "label": "Lead is an Organization", - "set_only_once": 1 - }, { "fieldname": "lead_details", "fieldtype": "Section Break", + "label": "Lead Details", "options": "fa fa-user" }, { @@ -90,7 +90,8 @@ "fieldname": "lead_name", "fieldtype": "Data", "in_global_search": 1, - "label": "Person Name", + "label": "Full Name", + "mandatory_depends_on": "eval: !(doc.company_name)", "oldfieldname": "lead_name", "oldfieldtype": "Data", "search_index": 1 @@ -99,7 +100,9 @@ "fieldname": "company_name", "fieldtype": "Data", "in_list_view": 1, + "in_standard_filter": 1, "label": "Organization Name", + "mandatory_depends_on": "eval: !(doc.lead_name)", "oldfieldname": "company_name", "oldfieldtype": "Data" }, @@ -121,7 +124,6 @@ "default": "__user", "fieldname": "lead_owner", "fieldtype": "Link", - "in_list_view": 1, "label": "Lead Owner", "oldfieldname": "lead_owner", "oldfieldtype": "Link", @@ -241,46 +243,39 @@ "read_only": 1 }, { - "depends_on": "eval: doc.__islocal", "description": "Home, Work, etc.", "fieldname": "address_title", "fieldtype": "Data", "label": "Address Title" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "address_line1", "fieldtype": "Data", "label": "Address Line 1", "mandatory_depends_on": "eval: doc.address_title && doc.address_type" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "address_line2", "fieldtype": "Data", "label": "Address Line 2" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "city", "fieldtype": "Data", "label": "City/Town", "mandatory_depends_on": "eval: doc.address_title && doc.address_type" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "county", "fieldtype": "Data", "label": "County" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "state", "fieldtype": "Data", "label": "State" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "country", "fieldtype": "Link", "label": "Country", @@ -288,7 +283,7 @@ "options": "Country" }, { - "depends_on": "eval: doc.__islocal", + "collapsible_depends_on": "eval: doc.__islocal", "fieldname": "pincode", "fieldtype": "Data", "label": "Postal Code" @@ -329,14 +324,6 @@ "oldfieldname": "fax", "oldfieldtype": "Data" }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break", - "options": "fa fa-file-text" - }, { "fieldname": "type", "fieldtype": "Select", @@ -369,12 +356,6 @@ "oldfieldtype": "Select", "options": "\nProduct Enquiry\nRequest for Information\nSuggestions\nOther" }, - { - "fieldname": "column_break3", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, { "fieldname": "company", "fieldtype": "Link", @@ -389,11 +370,14 @@ "fieldtype": "Data", "label": "Website", "oldfieldname": "website", - "oldfieldtype": "Data" + "oldfieldtype": "Data", + "options": "URL" }, { "fieldname": "territory", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Territory", "oldfieldname": "territory", "oldfieldtype": "Link", @@ -422,28 +406,13 @@ { "fieldname": "designation", "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Designation", "options": "Designation" }, - { - "collapsible": 1, - "collapsible_depends_on": "eval: doc.__islocal", - "fieldname": "address_info", - "fieldtype": "Section Break", - "label": "Address & Contact", - "oldfieldtype": "Column Break", - "options": "fa fa-map-marker" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval: doc.__islocal", - "fieldname": "contact_section", - "fieldtype": "Section Break", - "label": "Contact" - }, { "default": "Billing", - "depends_on": "eval: doc.__islocal", "fieldname": "address_type", "fieldtype": "Select", "label": "Address Type", @@ -454,13 +423,70 @@ "fieldtype": "Link", "label": "Print Language", "options": "Language" + }, + { + "fieldname": "first_name", + "fieldtype": "Data", + "label": "First Name" + }, + { + "fieldname": "middle_name", + "fieldtype": "Data", + "label": "Middle Name" + }, + { + "fieldname": "last_name", + "fieldtype": "Data", + "label": "Last Name" + }, + { + "collapsible": 1, + "fieldname": "additional_information_section", + "fieldtype": "Section Break", + "label": "Additional Information" + }, + { + "fieldname": "no_of_employees", + "fieldtype": "Int", + "label": "No. of Employees" + }, + { + "fieldname": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "whatsapp_no", + "fieldtype": "Data", + "label": "WhatsApp No.", + "options": "Phone" + }, + { + "collapsible": 1, + "depends_on": "eval:!doc.__islocal", + "fieldname": "address_section", + "fieldtype": "Section Break", + "label": "Address" + }, + { + "fieldname": "lead_source_details_section", + "fieldtype": "Section Break", + "label": "Lead Source Details" + }, + { + "fieldname": "column_break_50", + "fieldtype": "Column Break" + }, + { + "fieldname": "other_information_section", + "fieldtype": "Section Break", + "label": "Other Information" } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", "links": [], - "modified": "2021-01-06 19:39:58.748978", + "modified": "2021-06-17 00:20:37.768449", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d1d096843b..b18129b14f 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -21,14 +21,6 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) - def before_insert(self): - if self.address_title and self.address_type: - self.address_doc = self.create_address() - self.contact_doc = self.create_contact() - - def after_insert(self): - self.update_links() - def validate(self): self.set_lead_name() self.set_title() @@ -120,85 +112,11 @@ class Lead(SellingController): self.lead_name = self.email_id.split("@")[0] def set_title(self): - if self.organization_lead: + if self.company_name: self.title = self.company_name else: self.title = self.lead_name - def create_address(self): - address_fields = ["address_type", "address_title", "address_line1", "address_line2", - "city", "county", "state", "country", "pincode"] - info_fields = ["email_id", "phone", "fax"] - - # do not create an address if no fields are available, - # skipping country since the system auto-sets it from system defaults - address = frappe.new_doc("Address") - - address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) - address.update({info_field: self.get(info_field) for info_field in info_fields}) - address.insert() - - return address - - def create_contact(self): - if not self.lead_name: - self.set_lead_name() - - names = self.lead_name.strip().split(" ") - if len(names) > 1: - first_name, last_name = names[0], " ".join(names[1:]) - else: - first_name, last_name = self.lead_name, None - - contact = frappe.new_doc("Contact") - contact.update({ - "first_name": first_name, - "last_name": last_name, - "salutation": self.salutation, - "gender": self.gender, - "designation": self.designation, - }) - - if self.email_id: - contact.append("email_ids", { - "email_id": self.email_id, - "is_primary": 1 - }) - - if self.phone: - contact.append("phone_nos", { - "phone": self.phone, - "is_primary": 1 - }) - - if self.mobile_no: - contact.append("phone_nos", { - "phone": self.mobile_no - }) - - contact.insert(ignore_permissions=True) - - return contact - - def update_links(self): - # update address links - if hasattr(self, 'address_doc'): - self.address_doc.append("links", { - "link_doctype": "Lead", - "link_name": self.name, - "link_title": self.lead_name - }) - self.address_doc.save() - - # update contact links - if self.contact_doc: - self.contact_doc.append("links", { - "link_doctype": "Lead", - "link_name": self.name, - "link_title": self.lead_name - }) - self.contact_doc.save() - @frappe.whitelist() def make_customer(source_name, target_doc=None): return _make_customer(source_name, target_doc) From 357657fa7333a97f2cd8c5196bb361a86883a583 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 24 Jun 2021 13:00:50 +0530 Subject: [PATCH 006/253] chore: create a separate controller file for EmployeeBoardingController --- .../employee_boarding_controller.py | 128 ++++++++++++++++++ .../employee_onboarding.js | 2 +- .../employee_onboarding.py | 8 +- .../employee_separation.js | 2 +- .../employee_separation.py | 2 +- erpnext/hr/utils.py | 119 ---------------- erpnext/projects/doctype/project/project.py | 2 +- 7 files changed, 136 insertions(+), 127 deletions(-) create mode 100644 erpnext/controllers/employee_boarding_controller.py diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py new file mode 100644 index 0000000000..e7f7130407 --- /dev/null +++ b/erpnext/controllers/employee_boarding_controller.py @@ -0,0 +1,128 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import erpnext +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_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js index bd72629c0d..5d1a024ebb 100644 --- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js @@ -56,7 +56,7 @@ frappe.ui.form.on('Employee Onboarding', { 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.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py index 6cc2bf5cd8..55fe317b9e 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 @@ class EmployeeOnboarding(EmployeeBoardingController): 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 @@ class EmployeeOnboarding(EmployeeBoardingController): 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_separation/employee_separation.js b/erpnext/hr/doctype/employee_separation/employee_separation.js index 33830796b6..d9011b2001 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation.js +++ b/erpnext/hr/doctype/employee_separation/employee_separation.js @@ -29,7 +29,7 @@ frappe.ui.form.on('Employee Separation', { 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.py b/erpnext/hr/doctype/employee_separation/employee_separation.py index b64668157b..8afee25d31 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/utils.py b/erpnext/hr/utils.py index 97420efe64..3cc1a014d7 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -13,125 +13,6 @@ from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, 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") - -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) - 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 0ee9990620..1e4b2b0b86 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -14,7 +14,7 @@ from erpnext.hr.doctype.daily_work_summary.daily_work_summary import get_users_e 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.hr.utils import update_employee_boarding_status +from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status class Project(Document): def get_feed(self): From e81775d6f7cdc352a913db931882c0cd025150ce Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 24 Jun 2021 13:18:49 +0530 Subject: [PATCH 007/253] chore: remove unused import --- erpnext/controllers/employee_boarding_controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py index e7f7130407..1898222916 100644 --- a/erpnext/controllers/employee_boarding_controller.py +++ b/erpnext/controllers/employee_boarding_controller.py @@ -1,7 +1,6 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import erpnext import frappe from frappe import _ from frappe.desk.form import assign_to From 98c9b0e9edf8031b1afdb1647f4a3d48b3b39834 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 25 Jun 2021 16:11:17 +0530 Subject: [PATCH 008/253] refactor: remove unused func, sider fixes --- .../cogs_by_item_group/cogs_by_item_group.js | 18 +---------- .../cogs_by_item_group/cogs_by_item_group.py | 30 +++++++++---------- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js index bb780e50b2..d7c50a6697 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js @@ -11,24 +11,8 @@ frappe.query_reports["COGS By Item Group"] = { fieldtype: "Link", options: "Company", mandatory: true, - default: frappe.defaults.get_user_default("Company"), + default: frappe.defaults.get_user_default("Company"), }, - // { - // label: __("Account"), - // fieldname: "account", - // fieldtype: "Link", - // options: "Account", - // mandatory: true, - // get_query() { - // const company = frappe.query_report.get_filter_value('company'); - // return { - // "doctype": "Account", - // "filters": { - // "company": company, - // } - // } - // }, - // }, { label: __("From Date"), fieldname: "from_date", diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index e2c6f7928c..0d601738ff 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -52,13 +52,13 @@ def get_data(filters): assign_agg_values(leveled_dict) data = [] - for _, i in leveled_dict.items(): + for item in leveled_dict.items(): + i = item[1] if i['agg_value'] == 0: continue data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level'])) if i['self_value'] < i['agg_value'] and i['self_value'] > 0: data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1)) - # append_blank() return data @@ -76,9 +76,10 @@ def get_filtered_entries(filters): def get_stock_value_difference_list(filtered_entries): voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] - svd_list = frappe.get_list('Stock Ledger Entry', - fields=['item_code','stock_value_difference'], - filters=[('voucher_no', 'in', voucher_nos)]) + svd_list = frappe.get_list( + 'Stock Ledger Entry', fields=['item_code','stock_value_difference'], + filters=[('voucher_no', 'in', voucher_nos)] + ) assign_item_groups_to_svd_list(svd_list) return svd_list @@ -155,22 +156,19 @@ def assign_item_groups_to_svd_list(svd_list): def get_item_groups_map(svd_list): # for items in svd_list: [{'item_code':'item_group'}] item_codes = set([i['item_code'] for i in svd_list]) - ig_list = frappe.get_list('Item', - fields=['item_code','item_group'], - filters=[('item_code', 'in', item_codes)]) + ig_list = frappe.get_list( + 'Item', fields=['item_code','item_group'], + filters=[('item_code', 'in', item_codes)] + ) return {i['item_code']:i['item_group'] for i in ig_list} -def append_blank(data): - if len(data) == 0: - data.append(get_row("", 0, 0, 0)) - - def get_item_groups_dict(): item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) - return { (i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} - for i in item_groups_list } + return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} + for i in item_groups_list} def update_leveled_dict(leveled_dict): - for k in leveled_dict: leveled_dict[k].update({'self_value':0, 'agg_value':0}) + for k in leveled_dict: + leveled_dict[k].update({'self_value':0, 'agg_value':0}) From e9bc2f354b9d85f7810c30f46e0590b8ac5d17da Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 28 Jun 2021 19:01:52 +0530 Subject: [PATCH 009/253] feat: Increase number of supported currency exchanges Switch from frankfurter.app to exchange rate.host to accommodate more currency usage. --- erpnext/setup/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 13269a8282..33af28687c 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -93,21 +93,21 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No try: cache = frappe.cache() - key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency) + key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency) value = cache.get(key) if not value: import requests - api_url = "https://frankfurter.app/{0}".format(transaction_date) + api_url = "https://api.exchangerate.host/convert" response = requests.get(api_url, params={ - "base": from_currency, - "symbols": to_currency + "date": transaction_date, + "from": from_currency, + "to": to_currency }) # expire in 6 hours response.raise_for_status() - value = response.json()["rates"][to_currency] - - cache.set_value(key, value, expires_in_sec=6 * 60 * 60) + value = response.json()["result"] + cache.setex(key, value, 6 * 60 * 60) return flt(value) except: frappe.log_error(title="Get Exchange Rate") From 31afc61190b882515c2da3ce92c1bce1fb7f85b0 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 28 Jun 2021 19:05:51 +0530 Subject: [PATCH 010/253] fix: Update test cases to suit values from exchangerate.host --- .../test_currency_exchange.py | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index c5c01c5775..4ff2dd7e0e 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -62,12 +62,12 @@ class TestCurrencyExchange(unittest.TestCase): exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) - - # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io + + # Exchange rate as on 15th Dec, 2015 self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling") self.assertFalse(exchange_rate == 60) - self.assertEqual(flt(exchange_rate, 3), 66.894) + self.assertEqual(flt(exchange_rate, 3), 66.999) def test_exchange_rate_strict(self): # strict currency settings @@ -77,28 +77,17 @@ class TestCurrencyExchange(unittest.TestCase): exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying") self.assertEqual(exchange_rate, 60.0) - # Will fetch from fixer.io self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 67.79) + self.assertEqual(flt(exchange_rate, 3), 67.235) exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling") self.assertEqual(exchange_rate, 62.9) - # Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io + # Exchange rate as on 15th Dec, 2015 self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 66.894) - - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling") - self.assertEqual(exchange_rate, 65.1) - - # NGN is not available on fixer.io so these should return 0 - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling") - self.assertEqual(exchange_rate, 0) - - exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling") - self.assertEqual(exchange_rate, 0) + self.assertEqual(flt(exchange_rate, 3), 66.999) def test_exchange_rate_strict_switched(self): # Start with allow_stale is True @@ -111,4 +100,4 @@ class TestCurrencyExchange(unittest.TestCase): # Will fetch from fixer.io self.clear_cache() exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying") - self.assertEqual(flt(exchange_rate, 3), 67.79) + self.assertEqual(flt(exchange_rate, 3), 67.235) From 03f7609a8b14e951bf51ba093b3a8001a11a348b Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 30 Jun 2021 18:18:03 +0530 Subject: [PATCH 011/253] fix: moving campaign from selling to CRM --- erpnext/crm/doctype/campaign/__init__.py | 0 erpnext/crm/doctype/campaign/campaign.js | 18 ++++++++++ .../doctype/campaign/campaign.json | 34 ++++++++++--------- .../doctype/campaign/campaign.py | 7 ++-- erpnext/crm/doctype/campaign/test_campaign.py | 8 +++++ erpnext/selling/doctype/campaign/README.md | 1 - erpnext/selling/doctype/campaign/__init__.py | 1 - erpnext/selling/doctype/campaign/campaign.js | 15 -------- .../doctype/campaign/campaign_dashboard.py | 17 ---------- .../selling/doctype/campaign/test_campaign.py | 7 ---- .../doctype/campaign/test_records.json | 10 ------ 11 files changed, 46 insertions(+), 72 deletions(-) create mode 100644 erpnext/crm/doctype/campaign/__init__.py create mode 100644 erpnext/crm/doctype/campaign/campaign.js rename erpnext/{selling => crm}/doctype/campaign/campaign.json (95%) rename erpnext/{selling => crm}/doctype/campaign/campaign.py (54%) create mode 100644 erpnext/crm/doctype/campaign/test_campaign.py delete mode 100644 erpnext/selling/doctype/campaign/README.md delete mode 100644 erpnext/selling/doctype/campaign/__init__.py delete mode 100644 erpnext/selling/doctype/campaign/campaign.js delete mode 100644 erpnext/selling/doctype/campaign/campaign_dashboard.py delete mode 100644 erpnext/selling/doctype/campaign/test_campaign.py delete mode 100644 erpnext/selling/doctype/campaign/test_records.json diff --git a/erpnext/crm/doctype/campaign/__init__.py b/erpnext/crm/doctype/campaign/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js new file mode 100644 index 0000000000..04876541ba --- /dev/null +++ b/erpnext/crm/doctype/campaign/campaign.js @@ -0,0 +1,18 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Campaign', { + refresh: function(frm) { + erpnext.toggle_naming_series(); + + if(frm.doc.__islocal) { + frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series"); + } + else { + cur_frm.add_custom_button(__("View Leads"), function() { + frappe.route_options = {"source": "Campaign","campaign_name": frm.doc.name} + frappe.set_route("List", "Lead"); + }, "fa fa-list", true); + } + } +}); diff --git a/erpnext/selling/doctype/campaign/campaign.json b/erpnext/crm/doctype/campaign/campaign.json similarity index 95% rename from erpnext/selling/doctype/campaign/campaign.json rename to erpnext/crm/doctype/campaign/campaign.json index 986ac1306c..f833f4c9d1 100644 --- a/erpnext/selling/doctype/campaign/campaign.json +++ b/erpnext/crm/doctype/campaign/campaign.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", @@ -39,17 +40,9 @@ "set_only_once": 1 }, { - "fieldname": "description", - "fieldtype": "Text", - "in_list_view": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "width": "300px" - }, - { - "fieldname": "description_section", - "fieldtype": "Section Break" + "fieldname": "campaign_schedules_section", + "fieldtype": "Section Break", + "label": "Campaign Schedules" }, { "fieldname": "campaign_schedules", @@ -58,16 +51,25 @@ "options": "Campaign Email Schedule" }, { - "fieldname": "campaign_schedules_section", - "fieldtype": "Section Break", - "label": "Campaign Schedules" + "fieldname": "description_section", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "width": "300px" } ], "icon": "fa fa-bullhorn", "idx": 1, - "modified": "2019-07-22 12:03:39.832342", + "links": [], + "modified": "2021-06-30 18:05:06.412712", "modified_by": "Administrator", - "module": "Selling", + "module": "CRM", "name": "Campaign", "owner": "Administrator", "permissions": [ diff --git a/erpnext/selling/doctype/campaign/campaign.py b/erpnext/crm/doctype/campaign/campaign.py similarity index 54% rename from erpnext/selling/doctype/campaign/campaign.py rename to erpnext/crm/doctype/campaign/campaign.py index 10945428ae..34331952c0 100644 --- a/erpnext/selling/doctype/campaign/campaign.py +++ b/erpnext/crm/doctype/campaign/campaign.py @@ -1,11 +1,8 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# 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 -from frappe.model.naming import set_name_by_naming_series class Campaign(Document): def autoname(self): diff --git a/erpnext/crm/doctype/campaign/test_campaign.py b/erpnext/crm/doctype/campaign/test_campaign.py new file mode 100644 index 0000000000..939bb8f464 --- /dev/null +++ b/erpnext/crm/doctype/campaign/test_campaign.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +import unittest + +class TestCampaign(unittest.TestCase): + pass diff --git a/erpnext/selling/doctype/campaign/README.md b/erpnext/selling/doctype/campaign/README.md deleted file mode 100644 index a837318402..0000000000 --- a/erpnext/selling/doctype/campaign/README.md +++ /dev/null @@ -1 +0,0 @@ -Sales campaign / promotion, like special discount, exhibition, newsletter etc. \ No newline at end of file diff --git a/erpnext/selling/doctype/campaign/__init__.py b/erpnext/selling/doctype/campaign/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/selling/doctype/campaign/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/selling/doctype/campaign/campaign.js b/erpnext/selling/doctype/campaign/campaign.js deleted file mode 100644 index 72a90d053f..0000000000 --- a/erpnext/selling/doctype/campaign/campaign.js +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.ui.form.on("Campaign", "refresh", function(frm) { - erpnext.toggle_naming_series(); - if(frm.doc.__islocal) { - frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series"); - } - else{ - cur_frm.add_custom_button(__("View Leads"), function() { - frappe.route_options = {"source": "Campaign","campaign_name": frm.doc.name} - frappe.set_route("List", "Lead"); - }, "fa fa-list", true); - } -}) diff --git a/erpnext/selling/doctype/campaign/campaign_dashboard.py b/erpnext/selling/doctype/campaign/campaign_dashboard.py deleted file mode 100644 index 3cef560c32..0000000000 --- a/erpnext/selling/doctype/campaign/campaign_dashboard.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ - -def get_data(): - return { - 'fieldname': 'campaign_name', - 'transactions': [ - { - 'label': _('Email Campaigns'), - 'items': ['Email Campaign'] - }, - { - 'label': _('Social Media Campaigns'), - 'items': ['Social Media Post'] - } - ] - } diff --git a/erpnext/selling/doctype/campaign/test_campaign.py b/erpnext/selling/doctype/campaign/test_campaign.py deleted file mode 100644 index 4d062ff84c..0000000000 --- a/erpnext/selling/doctype/campaign/test_campaign.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - - -import frappe -test_records = frappe.get_test_records('Campaign') \ No newline at end of file diff --git a/erpnext/selling/doctype/campaign/test_records.json b/erpnext/selling/doctype/campaign/test_records.json deleted file mode 100644 index 625d3b377b..0000000000 --- a/erpnext/selling/doctype/campaign/test_records.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "campaign_name": "_Test Campaign", - "doctype": "Campaign" - }, - { - "campaign_name": "_Test Campaign 1", - "doctype": "Campaign" - } -] \ No newline at end of file From 865900fd2d634491e61f4f9191faac7fc880b07f Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 28 Jun 2021 12:52:22 +0530 Subject: [PATCH 012/253] refactor: add type hints, remove comment, sort imports --- .../cogs_by_item_group/cogs_by_item_group.py | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py index 0d601738ff..9e5e63e37e 100644 --- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py +++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py @@ -1,14 +1,28 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +from collections import OrderedDict +import datetime +from typing import Dict, List, Tuple, Union + import frappe from frappe import _ from frappe.utils import date_diff -from collections import OrderedDict + from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries -def execute(filters=None): +Filters = frappe._dict +Row = frappe._dict +Data = List[Row] +Columns = List[Dict[str, str]] +DateTime = Union[datetime.date, datetime.datetime] +FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]] +ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]] +SVDList = List[frappe._dict] + + +def execute(filters: Filters) -> Tuple[Columns, Data]: update_filters_with_account(filters) validate_filters(filters) columns = get_columns() @@ -16,17 +30,17 @@ def execute(filters=None): return columns, data -def update_filters_with_account(filters): +def update_filters_with_account(filters: Filters) -> None: account = frappe.get_value("Company", filters.get("company"), "default_expense_account") filters.update(dict(account=account)) -def validate_filters(filters): +def validate_filters(filters: Filters) -> None: if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) -def get_columns(): +def get_columns() -> Columns: return [ { 'label': 'Item Group', @@ -43,7 +57,7 @@ def get_columns(): ] -def get_data(filters): +def get_data(filters: Filters) -> Data: filtered_entries = get_filtered_entries(filters) svd_list = get_stock_value_difference_list(filtered_entries) leveled_dict = get_leveled_dict() @@ -62,7 +76,7 @@ def get_data(filters): return data -def get_filtered_entries(filters): +def get_filtered_entries(filters: Filters) -> FilteredEntries: gl_entries = get_gl_entries(filters, []) filtered_entries = [] for entry in gl_entries: @@ -74,7 +88,7 @@ def get_filtered_entries(filters): return filtered_entries -def get_stock_value_difference_list(filtered_entries): +def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList: voucher_nos = [fe.get('voucher_no') for fe in filtered_entries] svd_list = frappe.get_list( 'Stock Ledger Entry', fields=['item_code','stock_value_difference'], @@ -84,7 +98,7 @@ def get_stock_value_difference_list(filtered_entries): return svd_list -def get_leveled_dict(): +def get_leveled_dict() -> OrderedDict: item_groups_dict = get_item_groups_dict() lr_list = sorted(item_groups_dict, key=lambda x : int(x[0])) leveled_dict = OrderedDict() @@ -109,14 +123,14 @@ def get_leveled_dict(): return leveled_dict -def assign_self_values(leveled_dict, svd_list): +def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None: key_dict = {v['name']:k for k, v in leveled_dict.items()} for item in svd_list: key = key_dict[item.get("item_group")] leveled_dict[key]['self_value'] += -item.get("stock_value_difference") -def assign_agg_values(leveled_dict): +def assign_agg_values(leveled_dict: OrderedDict) -> None: keys = list(leveled_dict.keys())[::-1] prev_level = leveled_dict[keys[-1]]['level'] accu = [0] @@ -141,21 +155,21 @@ def assign_agg_values(leveled_dict): leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value'] -def get_row(name:str, value:float, is_bold:int, indent:int): +def get_row(name:str, value:float, is_bold:int, indent:int) -> Row: item_group = name if is_bold: item_group = frappe.bold(item_group) return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent) -def assign_item_groups_to_svd_list(svd_list): +def assign_item_groups_to_svd_list(svd_list: SVDList) -> None: ig_map = get_item_groups_map(svd_list) for item in svd_list: item.item_group = ig_map[item.get("item_code")] -def get_item_groups_map(svd_list): - # for items in svd_list: [{'item_code':'item_group'}] - item_codes = set([i['item_code'] for i in svd_list]) + +def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]: + item_codes = set(i['item_code'] for i in svd_list) ig_list = frappe.get_list( 'Item', fields=['item_code','item_group'], filters=[('item_code', 'in', item_codes)] @@ -163,12 +177,12 @@ def get_item_groups_map(svd_list): return {i['item_code']:i['item_group'] for i in ig_list} -def get_item_groups_dict(): +def get_item_groups_dict() -> ItemGroupsDict: item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt")) return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']} for i in item_groups_list} -def update_leveled_dict(leveled_dict): +def update_leveled_dict(leveled_dict: OrderedDict) -> None: for k in leveled_dict: leveled_dict[k].update({'self_value':0, 'agg_value':0}) From 991d3cdd76a0f4c20f405799ddc54d98951b28e7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 1 Jul 2021 21:17:17 +0530 Subject: [PATCH 013/253] fix: Incorrect discount amount on amended document --- erpnext/public/js/controllers/taxes_and_totals.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 1de9ec1a7d..52efbb5f6c 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -67,6 +67,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ calculate_discount_amount: function(){ if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) { + this.calculate_item_values(); + this.calculate_net_total(); this.set_discount_amount(); this.apply_discount_amount(); } From 5a62467a3e028bb6d857a27a661d9bbeaee9d184 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 9 Jul 2021 00:53:42 +0530 Subject: [PATCH 014/253] feat: Fetch Payment Terms from linked Purchase Order --- .../purchase_invoice/purchase_invoice.js | 21 +++++++++- .../purchase_invoice/purchase_invoice.py | 39 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 7562418fd2..10b83c027f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -508,6 +508,8 @@ frappe.ui.form.on("Purchase Invoice", { } } } + + frm.events.set_payment_terms(frm); }, refresh: function(frm) { @@ -570,4 +572,21 @@ frappe.ui.form.on("Purchase Invoice", { company: function(frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, -}) + + set_payment_terms: function (frm) { + frappe.call({ + 'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.set_payment_terms_from_po', + 'args': { + doc: frm.doc + }, + 'callback': (r) => { + if (r.message) { + var doc = frappe.model.sync(r.message)[0]; + console.log("doc: ", doc) + frappe.set_route("Form", doc.doctype, doc.name); + } + } + + }); + }, +}) \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c1cc092554..fb143b45ce 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -7,6 +7,8 @@ import frappe, erpnext from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate, get_link_to_form from frappe import _, throw import frappe.defaults +import json +import six from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.controllers.buying_controller import BuyingController @@ -1171,6 +1173,43 @@ class PurchaseInvoice(BuyingController): if update: self.db_set('status', self.status, update_modified = update_modified) +@frappe.whitelist() +def set_payment_terms_from_po(doc): + if isinstance(doc, six.string_types): + doc = json.loads(doc) + + purchase_order = doc.get('items')[0].get('purchase_order') + + if linked_po_has_payment_terms(doc, purchase_order) and all_items_have_same_po(doc, purchase_order): + purchase_order = frappe.get_cached_doc('Purchase Order', purchase_order) + doc['payment_terms_template'] = purchase_order.payment_terms_template + doc['terms'] = purchase_order.payment_terms_template + + for schedule in purchase_order.payment_schedule: + payment_schedule = { + 'payment_term': schedule.payment_term, + 'due_date': schedule.due_date, + 'invoice_portion': schedule.invoice_portion, + 'discount_type': schedule.discount_type, + 'discount': schedule.discount, + 'base_payment_amount': schedule.base_payment_amount, + 'payment_amount': schedule.payment_amount, + 'outstanding': schedule.outstanding + } + doc['payment_schedule'].append(payment_schedule) + + return doc + +def linked_po_has_payment_terms(doc, purchase_order): + return not doc.get('payment_schedule') and purchase_order + +def all_items_have_same_po(doc, purchase_order): + for item in doc.get('items'): + if item.get('purchase_order') != purchase_order: + return False + + return True + # to get details of purchase invoice/receipt from which this doc was created for exchange rate difference handling def get_purchase_document_details(doc): if doc.doctype == 'Purchase Invoice': From bf245e5e39cdeb2ccdd51a63ad1e2385fbedc24d Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 9 Jul 2021 01:44:34 +0530 Subject: [PATCH 015/253] fix: Clear Payment Schedule if PI has default Payment Schedule, but linked PO doensn't --- .../doctype/purchase_invoice/purchase_invoice.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fb143b45ce..f30af0c349 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1180,10 +1180,14 @@ def set_payment_terms_from_po(doc): purchase_order = doc.get('items')[0].get('purchase_order') - if linked_po_has_payment_terms(doc, purchase_order) and all_items_have_same_po(doc, purchase_order): + if purchase_order and all_items_have_same_po(doc, purchase_order): purchase_order = frappe.get_cached_doc('Purchase Order', purchase_order) + else: + return + + if has_default_payment_terms(doc) and not has_default_payment_terms(purchase_order): + doc['payment_schedule'] = [] doc['payment_terms_template'] = purchase_order.payment_terms_template - doc['terms'] = purchase_order.payment_terms_template for schedule in purchase_order.payment_schedule: payment_schedule = { @@ -1200,9 +1204,6 @@ def set_payment_terms_from_po(doc): return doc -def linked_po_has_payment_terms(doc, purchase_order): - return not doc.get('payment_schedule') and purchase_order - def all_items_have_same_po(doc, purchase_order): for item in doc.get('items'): if item.get('purchase_order') != purchase_order: @@ -1210,6 +1211,11 @@ def all_items_have_same_po(doc, purchase_order): return True +def has_default_payment_terms(doc): + if doc.get('payment_schedule')[0].get('invoice_portion') == 100: + return True + return False + # to get details of purchase invoice/receipt from which this doc was created for exchange rate difference handling def get_purchase_document_details(doc): if doc.doctype == 'Purchase Invoice': From 38fa3a3f8927abf7242481d61c2d72e6879af608 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 18:04:24 +0530 Subject: [PATCH 016/253] fix: Unallocated amount in Payment Entry after taxes --- .../doctype/payment_entry/payment_entry.py | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index adaf99a790..889c59762c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -404,9 +404,15 @@ class PaymentEntry(AccountsController): if not self.advance_tax_account: frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction")) - reference_doclist = [] net_total = self.paid_amount - included_in_paid_amount = 0 + + for reference in self.get("references"): + net_total_for_tds = 0 + if reference.reference_doctype == 'Purchase Order': + net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total')) + + if net_total_for_tds: + net_total = net_total_for_tds # Adding args as purchase invoice to get TDS amount args = frappe._dict({ @@ -423,7 +429,6 @@ class PaymentEntry(AccountsController): return tax_withholding_details.update({ - 'included_in_paid_amount': included_in_paid_amount, 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) }) @@ -509,18 +514,17 @@ class PaymentEntry(AccountsController): self.base_total_allocated_amount = abs(base_total_allocated_amount) def set_unallocated_amount(self): - self.unallocated_amount = 0 if self.party: total_deductions = sum(flt(d.amount) for d in self.get("deductions")) if self.payment_type == "Receive" \ - and self.base_total_allocated_amount < self.base_received_amount_after_tax + total_deductions \ - and self.total_allocated_amount < self.paid_amount_after_tax + (total_deductions / self.source_exchange_rate): - self.unallocated_amount = (self.received_amount_after_tax + total_deductions - + and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ + and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): + self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate elif self.payment_type == "Pay" \ - and self.base_total_allocated_amount < (self.base_paid_amount_after_tax - total_deductions) \ - and self.total_allocated_amount < self.received_amount_after_tax + (total_deductions / self.target_exchange_rate): - self.unallocated_amount = (self.base_paid_amount_after_tax - (total_deductions + + and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ + and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): + self.unallocated_amount = (self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate def set_difference_amount(self): @@ -530,11 +534,11 @@ class PaymentEntry(AccountsController): base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount) if self.payment_type == "Receive": - self.difference_amount = base_party_amount - self.base_received_amount_after_tax + self.difference_amount = base_party_amount - self.base_received_amount elif self.payment_type == "Pay": - self.difference_amount = self.base_paid_amount_after_tax - base_party_amount + self.difference_amount = self.base_paid_amount - base_party_amount else: - self.difference_amount = self.base_paid_amount_after_tax - flt(self.base_received_amount_after_tax) + self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) total_deductions = sum(flt(d.amount) for d in self.get("deductions")) @@ -683,8 +687,8 @@ class PaymentEntry(AccountsController): "account": self.paid_from, "account_currency": self.paid_from_account_currency, "against": self.party if self.payment_type=="Pay" else self.paid_to, - "credit_in_account_currency": self.paid_amount_after_tax, - "credit": self.base_paid_amount_after_tax, + "credit_in_account_currency": self.paid_amount, + "credit": self.base_paid_amount, "cost_center": self.cost_center }, item=self) ) @@ -694,8 +698,8 @@ class PaymentEntry(AccountsController): "account": self.paid_to, "account_currency": self.paid_to_account_currency, "against": self.party if self.payment_type=="Receive" else self.paid_from, - "debit_in_account_currency": self.received_amount_after_tax, - "debit": self.base_received_amount_after_tax, + "debit_in_account_currency": self.received_amount, + "debit": self.base_received_amount, "cost_center": self.cost_center }, item=self) ) @@ -708,15 +712,17 @@ class PaymentEntry(AccountsController): if self.payment_type in ('Pay', 'Internal Transfer'): dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit" + against = self.party or self.paid_from elif self.payment_type == 'Receive': dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit" + against = self.party or self.paid_to payment_or_advance_account = self.get_party_account_for_taxes() gl_entries.append( self.get_gl_dict({ "account": d.account_head, - "against": self.party if self.payment_type=="Receive" else self.paid_from, + "against": against, dr_or_cr: d.base_tax_amount, dr_or_cr + "_in_account_currency": d.base_tax_amount if account_currency==self.company_currency @@ -728,14 +734,12 @@ class PaymentEntry(AccountsController): gl_entries.append( self.get_gl_dict({ "account": payment_or_advance_account, - "against": self.party if self.payment_type=="Receive" else self.paid_from, + "against": against, dr_or_cr: -1 * d.base_tax_amount, dr_or_cr + "_in_account_currency": -1*d.base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, - "party_type": self.party_type, - "party": self.party }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): @@ -760,9 +764,9 @@ class PaymentEntry(AccountsController): if self.advance_tax_account: return self.advance_tax_account elif self.payment_type == 'Receive': - return self.paid_from - elif self.payment_type in ('Pay', 'Internal Transfer'): return self.paid_to + elif self.payment_type in ('Pay', 'Internal Transfer'): + return self.paid_from def update_advance_paid(self): if self.payment_type in ("Receive", "Pay") and self.party: @@ -1634,12 +1638,6 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta if dt == "Employee Advance": paid_amount = received_amount * doc.get('exchange_rate', 1) - if dt == "Purchase Order" and doc.apply_tds: - if party_account_currency == bank.account_currency: - paid_amount = received_amount = doc.base_net_total - else: - paid_amount = received_amount = doc.base_net_total * doc.get('exchange_rate', 1) - return paid_amount, received_amount def apply_early_payment_discount(paid_amount, received_amount, doc): From 171ee515074e183ff3a59c120ea29b2ea84b96e9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 18:52:12 +0530 Subject: [PATCH 017/253] fix: Hide amount after tax fields --- .../accounts/doctype/payment_entry/payment_entry.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 51f18a5a4e..6f362c1fbb 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -667,6 +667,7 @@ { "fieldname": "base_paid_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Paid Amount After Tax (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 @@ -693,21 +694,25 @@ "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'", "fieldname": "received_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Received Amount After Tax", - "options": "paid_to_account_currency" + "options": "paid_to_account_currency", + "read_only": 1 }, { "depends_on": "doc.received_amount", "fieldname": "base_received_amount_after_tax", "fieldtype": "Currency", + "hidden": 1, "label": "Received Amount After Tax (Company Currency)", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-22 20:37:06.154206", + "modified": "2021-07-09 08:58:15.008761", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", From c13ac4ab11495b5067e41355c14140dc19c8782c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 20:00:55 +0530 Subject: [PATCH 018/253] fix: Remove unintentional changes --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 889c59762c..e3dbc22ca0 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -521,6 +521,7 @@ class PaymentEntry(AccountsController): and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate + print(self.unallocated_amount, "#@#@#@#@#") elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): From eae7c1891fa434f380487e7f51db68a92b04596d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 9 Jul 2021 20:08:29 +0530 Subject: [PATCH 019/253] fix: Remove unintentional changes --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e3dbc22ca0..85b98843ee 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -514,6 +514,7 @@ class PaymentEntry(AccountsController): self.base_total_allocated_amount = abs(base_total_allocated_amount) def set_unallocated_amount(self): + self.unallocated_amount = 0 if self.party: total_deductions = sum(flt(d.amount) for d in self.get("deductions")) if self.payment_type == "Receive" \ @@ -521,7 +522,6 @@ class PaymentEntry(AccountsController): and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate - print(self.unallocated_amount, "#@#@#@#@#") elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): From 77f2d2d01ea19c53cb3e726cbfaaa0b2fceb0eb4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Jul 2021 10:06:38 +0530 Subject: [PATCH 020/253] fix: Unable to download GSTR-1 json --- erpnext/regional/report/gstr_1/gstr_1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 10961593e1..cfcb8c3444 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -584,7 +584,7 @@ class Gstr1Report(object): def get_json(filters, report_name, data): filters = json.loads(filters) report_data = json.loads(data) - gstin = get_company_gstin_number(filters["company"], filters["company_address"]) + gstin = get_company_gstin_number(filters.get("company"), filters.get("company_address")) fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year) From e282effaed59e27e82875f90c6237ae4f99ba668 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 10 Jul 2021 20:23:52 +0530 Subject: [PATCH 021/253] fix: Error on creation of company for India --- erpnext/regional/india/setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 5f9d5ed0d6..5ef04b66c7 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -122,10 +122,12 @@ def add_print_formats(): def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] + sales_invoice_series = frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n") + ['SINV-.YY.-', 'SRET-.YY.-', ''] + purchase_invoice_series = frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n") + ['PINV-.YY.-', 'PRET-.YY.-', ''] if not patch: - make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '') - make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '') + make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '') + make_property_setter('Purchase Invoice', 'naming_series', 'options', '\n'.join(purchase_invoice_series), '') make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') def make_custom_fields(update=True): From 38994bd49480c55484c07e71aa52eb30ca91b485 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 12 Jul 2021 13:01:31 +0530 Subject: [PATCH 022/253] fix: Added Company filters for Loan (#26294) * fix: loan validations * fix: added company filter while fetching loans * fix: tests --- erpnext/loan_management/doctype/loan/loan.js | 3 +- .../loan_application/loan_application.js | 7 ++++ .../doctype/salary_slip/salary_slip.py | 1 + .../doctype/salary_slip/test_salary_slip.py | 15 +++++--- .../salary_structure/test_salary_structure.py | 37 +++++++++---------- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js index 28af3a9c41..f9c201ab60 100644 --- a/erpnext/loan_management/doctype/loan/loan.js +++ b/erpnext/loan_management/doctype/loan/loan.js @@ -28,7 +28,8 @@ frappe.ui.form.on('Loan', { frm.set_query("loan_type", function () { return { "filters": { - "docstatus": 1 + "docstatus": 1, + "company": frm.doc.company } }; }); diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.js b/erpnext/loan_management/doctype/loan_application/loan_application.js index 1365274971..eccbdc3e91 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.js +++ b/erpnext/loan_management/doctype/loan_application/loan_application.js @@ -14,6 +14,13 @@ frappe.ui.form.on('Loan Application', { refresh: function(frm) { frm.trigger("toggle_fields"); frm.trigger("add_toolbar_buttons"); + frm.set_query('loan_type', () => { + return { + filters: { + company: frm.doc.company + } + }; + }); }, repayment_method: function(frm) { frm.doc.repayment_amount = frm.doc.repayment_periods = "" diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 877503b41c..bead880ef7 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1091,6 +1091,7 @@ class SalarySlip(TransactionBase): "applicant": self.employee, "docstatus": 1, "repay_from_salary": 1, + "company": self.company }) def make_loan_repayment_entry(self): diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index ce88cc3f1e..6e8d3b3f30 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -482,14 +482,19 @@ def make_employee_salary_slip(user, payroll_frequency, salary_structure=None): salary_structure = payroll_frequency + " Salary Structure Test for Salary Slip" - employee = frappe.db.get_value("Employee", {"user_id": user}) - salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee) + employee = frappe.db.get_value("Employee", + { + "user_id": user + }, + ["name", "company", "employee_name"], + as_dict=True) + + salary_structure_doc = make_salary_structure(salary_structure, payroll_frequency, employee=employee.name, company=employee.company) salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": user})}) if not salary_slip_name: - salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee) - salary_slip.employee_name = frappe.get_value("Employee", - {"name":frappe.db.get_value("Employee", {"user_id": user})}, "employee_name") + salary_slip = make_salary_slip(salary_structure_doc.name, employee = employee.name) + salary_slip.employee_name = employee.employee_name salary_slip.payroll_frequency = payroll_frequency salary_slip.posting_date = nowdate() salary_slip.insert() diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py index e7d123c996..3957d834d3 100644 --- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py @@ -119,26 +119,25 @@ def make_salary_structure(salary_structure, payroll_frequency, employee=None, if test_tax: frappe.db.sql("""delete from `tabSalary Structure` where name=%s""",(salary_structure)) - if not frappe.db.exists('Salary Structure', salary_structure): - details = { - "doctype": "Salary Structure", - "name": salary_structure, - "company": company or erpnext.get_default_company(), - "earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), - "deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), - "payroll_frequency": payroll_frequency, - "payment_account": get_random("Account", filters={'account_currency': currency}), - "currency": currency - } - if other_details and isinstance(other_details, dict): - details.update(other_details) - salary_structure_doc = frappe.get_doc(details) - salary_structure_doc.insert() - if not dont_submit: - salary_structure_doc.submit() + if frappe.db.exists("Salary Structure", salary_structure): + frappe.db.delete("Salary Structure", salary_structure) - else: - salary_structure_doc = frappe.get_doc("Salary Structure", salary_structure) + details = { + "doctype": "Salary Structure", + "name": salary_structure, + "company": company or erpnext.get_default_company(), + "earnings": make_earning_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), + "deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]), + "payroll_frequency": payroll_frequency, + "payment_account": get_random("Account", filters={'account_currency': currency}), + "currency": currency + } + if other_details and isinstance(other_details, dict): + details.update(other_details) + salary_structure_doc = frappe.get_doc(details) + salary_structure_doc.insert() + if not dont_submit: + salary_structure_doc.submit() filters = {'employee':employee, 'docstatus': 1} if not from_date and payroll_period: From 45e6cffa4f9eacd4d299e4a5bf220b970a6c9105 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 12 Jul 2021 13:24:43 +0530 Subject: [PATCH 023/253] refactor: Optimized code for reposting item valuation --- .../stock/doctype/stock_entry/stock_entry.py | 2 +- .../stock_ledger_entry/stock_ledger_entry.py | 1 + erpnext/stock/stock_ledger.py | 61 +++++++++++++++---- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8f27ef4356..90b81ddb1d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -529,7 +529,7 @@ class StockEntry(StockController): scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item]) # Get raw materials cost from BOM if multiple material consumption entries - if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"): + if frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True): bom_items = self.get_bom_raw_materials(finished_item_qty) outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()]) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 0febcb6891..cb939e63c2 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -178,3 +178,4 @@ def on_doctype_update(): frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"]) + frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"]) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 4e9c7689ae..c15d1eda7d 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -6,13 +6,14 @@ import frappe import erpnext import copy from frappe import _ -from frappe.utils import cint, flt, cstr, now, get_link_to_form +from frappe.utils import cint, flt, cstr, now, get_link_to_form, getdate from frappe.model.meta import get_field_precision from erpnext.stock.utils import get_valuation_method, get_incoming_outgoing_rate_for_cancel from erpnext.stock.utils import get_bin import json from six import iteritems + # future reposting class NegativeStockError(frappe.ValidationError): pass class SerialNoExistsInFutureTransaction(frappe.ValidationError): @@ -130,7 +131,13 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat if not args and voucher_type and voucher_no: args = get_args_for_voucher(voucher_type, voucher_no) - distinct_item_warehouses = [(d.item_code, d.warehouse) for d in args] + distinct_item_warehouses = {} + for i, d in enumerate(args): + distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({ + "reposting_status": False, + "sle": d, + "args_idx": i + })) i = 0 while i < len(args): @@ -139,13 +146,21 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat "warehouse": args[i].warehouse, "posting_date": args[i].posting_date, "posting_time": args[i].posting_time, - "creation": args[i].get("creation") + "creation": args[i].get("creation"), + "distinct_item_warehouses": distinct_item_warehouses }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - for item_wh, new_sle in iteritems(obj.new_items): - if item_wh not in distinct_item_warehouses: - args.append(new_sle) + distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True + if obj.new_items_found: + for item_wh, data in iteritems(distinct_item_warehouses): + if ('args_idx' not in data and not data.reposting_status) or (data.sle_changed and data.reposting_status): + data.args_idx = len(args) + args.append(data.sle) + elif data.sle_changed and not data.reposting_status: + args[data.args_idx] = data.sle + + data.sle_changed = False i += 1 def get_args_for_voucher(voucher_type, voucher_no): @@ -186,11 +201,12 @@ class update_entries_after(object): self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company") self.get_precision() self.valuation_method = get_valuation_method(self.item_code) - self.new_items = {} + + self.new_items_found = False + self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict()) self.data = frappe._dict() self.initialize_previous_data(self.args) - self.build() def get_precision(self): @@ -296,11 +312,29 @@ class update_entries_after(object): elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse: return entries_to_fix elif dependant_sle.item_code != self.item_code: - if (dependant_sle.item_code, dependant_sle.warehouse) not in self.new_items: - self.new_items[(dependant_sle.item_code, dependant_sle.warehouse)] = dependant_sle + self.update_distinct_item_warehouses(dependant_sle) return entries_to_fix elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse in self.data: return entries_to_fix + else: + return self.append_future_sle_for_dependant(dependant_sle, entries_to_fix) + + def update_distinct_item_warehouses(self, dependant_sle): + key = (dependant_sle.item_code, dependant_sle.warehouse) + val = frappe._dict({ + "sle": dependant_sle + }) + if key not in self.distinct_item_warehouses: + self.distinct_item_warehouses[key] = val + self.new_items_found = True + else: + existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date") + if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date): + val.sle_changed = True + self.distinct_item_warehouses[key] = val + self.new_items_found = True + + def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix): self.initialize_previous_data(dependant_sle) args = self.data[dependant_sle.warehouse].previous_sle \ @@ -393,6 +427,7 @@ class update_entries_after(object): rate = 0 # Material Transfer, Repack, Manufacturing if sle.voucher_type == "Stock Entry": + self.recalculate_amounts_in_stock_entry(sle.voucher_no) rate = frappe.db.get_value("Stock Entry Detail", sle.voucher_detail_no, "valuation_rate") # Sales and Purchase Return elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"): @@ -442,7 +477,11 @@ class update_entries_after(object): frappe.db.set_value("Stock Entry Detail", sle.voucher_detail_no, "basic_rate", outgoing_rate) # Update outgoing item's rate, recalculate FG Item's rate and total incoming/outgoing amount - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no, for_update=True) + if not sle.dependant_sle_voucher_detail_no: + self.recalculate_amounts_in_stock_entry(sle.voucher_no) + + def recalculate_amounts_in_stock_entry(self, voucher_no): + stock_entry = frappe.get_doc("Stock Entry", voucher_no, for_update=True) stock_entry.calculate_rate_and_amount(reset_outgoing_rate=False, raise_error_if_no_rate=False) stock_entry.db_update() for d in stock_entry.items: From b75b556bbbe3760d3bcd75c378cb081f349836e2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Jul 2021 14:32:37 +0530 Subject: [PATCH 024/253] fix: move the rename abbreviation job to long queue (#26435) --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 915e6a4f31..36a7d20a8f 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -395,7 +395,7 @@ class Company(NestedSet): @frappe.whitelist() def enqueue_replace_abbr(company, old, new): - kwargs = dict(company=company, old=old, new=new) + kwargs = dict(queue="long", company=company, old=old, new=new) frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs) From 1298956482515f6067781f7eb0b404fa25f512a9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Jul 2021 18:29:52 +0530 Subject: [PATCH 025/253] fix: Use update flag for company dependant fixtures --- erpnext/regional/india/setup.py | 11 +++++++---- erpnext/setup/doctype/company/company.py | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 5ef04b66c7..92654608da 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -12,7 +12,10 @@ from erpnext.accounts.utils import get_fiscal_year, FiscalYearError from frappe.utils import today def setup(company=None, patch=True): - setup_company_independent_fixtures(patch=patch) + # Company independent fixtures should be called only once at the first company setup + if frappe.db.count('Company', {'country': 'India'}) <=1: + setup_company_independent_fixtures(patch=patch) + if not patch: make_fixtures(company) @@ -122,8 +125,8 @@ def add_print_formats(): def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC'] - sales_invoice_series = frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n") + ['SINV-.YY.-', 'SRET-.YY.-', ''] - purchase_invoice_series = frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n") + ['PINV-.YY.-', 'PRET-.YY.-', ''] + sales_invoice_series = ['SINV-.YY.-', 'SRET-.YY.-', ''] + frappe.get_meta("Sales Invoice").get_options("naming_series").split("\n") + purchase_invoice_series = ['PINV-.YY.-', 'PRET-.YY.-', ''] + frappe.get_meta("Purchase Invoice").get_options("naming_series").split("\n") if not patch: make_property_setter('Sales Invoice', 'naming_series', 'options', '\n'.join(sales_invoice_series), '') @@ -788,7 +791,7 @@ def set_tax_withholding_category(company): doc.flags.ignore_mandatory = True doc.insert() else: - doc = frappe.get_doc("Tax Withholding Category", d.get("name")) + doc = frappe.get_doc("Tax Withholding Category", d.get("name"), for_update=True) if accounts: doc.append("accounts", accounts[0]) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 915e6a4f31..382510d0be 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -291,7 +291,7 @@ class Company(NestedSet): cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name') if cash and self.default_cash_account \ and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}): - mode_of_payment = frappe.get_doc('Mode of Payment', cash) + mode_of_payment = frappe.get_doc('Mode of Payment', cash, for_update=True) mode_of_payment.append('accounts', { 'company': self.name, 'default_account': self.default_cash_account From 7fb64d1645f65c4b1789cb0ed4e41ecd8893bd3d Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Jul 2021 18:33:16 +0530 Subject: [PATCH 026/253] fix: exchange gain loss not set for advances linked with invoices (#26436) --- .../doctype/payment_entry/payment_entry.py | 18 +- .../payment_entry_reference.json | 12 +- .../purchase_invoice/purchase_invoice.py | 1 + .../purchase_invoice/test_purchase_invoice.py | 103 ++++++ .../purchase_invoice_advance.json | 330 ++++++----------- .../doctype/sales_invoice/sales_invoice.py | 1 + .../sales_invoice_advance.json | 331 ++++++------------ erpnext/accounts/utils.py | 14 +- erpnext/controllers/accounts_controller.py | 86 ++++- 9 files changed, 441 insertions(+), 455 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0c21aae944..ff00fde523 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -183,6 +183,13 @@ class PaymentEntry(AccountsController): d.reference_name, self.party_account_currency) for field, value in iteritems(ref_details): + if d.exchange_gain_loss: + # for cases where gain/loss is booked into invoice + # exchange_gain_loss is calculated from invoice & populated + # and row.exchange_rate is already set to payment entry's exchange rate + # refer -> `update_reference_in_payment_entry()` in utils.py + continue + if field == 'exchange_rate' or not d.get(field) or force: d.db_set(field, value) @@ -664,8 +671,8 @@ class PaymentEntry(AccountsController): gl_entries.append(gle) if self.unallocated_amount: - base_unallocated_amount = self.unallocated_amount * \ - (self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate) + exchange_rate = self.get_exchange_rate() + base_unallocated_amount = (self.unallocated_amount * exchange_rate) gle = party_gl_dict.copy() @@ -806,10 +813,17 @@ class PaymentEntry(AccountsController): if account_details: row.update(account_details) + + if not row.get('amount'): + # if no difference amount + return self.append('deductions', row) self.set_unallocated_amount() + def get_exchange_rate(self): + return self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate + def initialize_taxes(self): for tax in self.get("taxes"): validate_taxes_and_charges(tax) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 912ad0977a..43eb0b6e2a 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -14,7 +14,8 @@ "total_amount", "outstanding_amount", "allocated_amount", - "exchange_rate" + "exchange_rate", + "exchange_gain_loss" ], "fields": [ { @@ -90,12 +91,19 @@ "fieldtype": "Link", "label": "Payment Term", "options": "Payment Term" + }, + { + "fieldname": "exchange_gain_loss", + "fieldtype": "Currency", + "label": "Exchange Gain/Loss", + "options": "Company:company:default_currency", + "read_only": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-10 11:25:47.144392", + "modified": "2021-04-21 13:30:11.605388", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 45d89ad1c8..f7992797ed 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -451,6 +451,7 @@ class PurchaseInvoice(BuyingController): self.get_asset_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) + self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) self.allocate_advance_taxes(gl_entries) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 311745d3cd..c9384be6eb 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -953,6 +953,109 @@ class TestPurchaseInvoice(unittest.TestCase): acc_settings.submit_journal_entriessubmit_journal_entries = 0 acc_settings.save() + def test_gain_loss_with_advance_entry(self): + unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice") + frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) + pay = frappe.get_doc({ + 'doctype': 'Payment Entry', + 'company': '_Test Company', + 'payment_type': 'Pay', + 'party_type': 'Supplier', + 'party': '_Test Supplier USD', + 'paid_to': '_Test Payable USD - _TC', + 'paid_from': 'Cash - _TC', + 'paid_amount': 70000, + 'target_exchange_rate': 70, + 'received_amount': 1000, + }) + pay.insert() + pay.submit() + + pi = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD", + conversion_rate=75, rate=500, do_not_save=1, qty=1) + pi.cost_center = "_Test Cost Center - _TC" + pi.advances = [] + pi.append("advances", { + "reference_type": "Payment Entry", + "reference_name": pay.name, + "advance_amount": 1000, + "remarks": pay.remarks, + "allocated_amount": 500, + "ref_exchange_rate": 70 + }) + pi.save() + pi.submit() + + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 37500.0], + ["_Test Payable USD - _TC", -40000.0], + ["Exchange Gain/Loss - _TC", 2500.0] + ] + + gl_entries = frappe.db.sql(""" + select account, sum(debit - credit) as balance from `tabGL Entry` + where voucher_no=%s + group by account order by account asc""", (pi.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.balance) + + pi_2 = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD", + conversion_rate=73, rate=500, do_not_save=1, qty=1) + pi_2.cost_center = "_Test Cost Center - _TC" + pi_2.advances = [] + pi_2.append("advances", { + "reference_type": "Payment Entry", + "reference_name": pay.name, + "advance_amount": 500, + "remarks": pay.remarks, + "allocated_amount": 500, + "ref_exchange_rate": 70 + }) + pi_2.save() + pi_2.submit() + + expected_gle = [ + ["_Test Account Cost for Goods Sold - _TC", 36500.0], + ["_Test Payable USD - _TC", -38000.0], + ["Exchange Gain/Loss - _TC", 1500.0] + ] + + gl_entries = frappe.db.sql(""" + select account, sum(debit - credit) as balance from `tabGL Entry` + where voucher_no=%s + group by account order by account asc""", (pi_2.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.balance) + + expected_gle = [ + ["_Test Payable USD - _TC", 70000.0], + ["Cash - _TC", -70000.0] + ] + + gl_entries = frappe.db.sql(""" + select account, sum(debit - credit) as balance from `tabGL Entry` + where voucher_no=%s and is_cancelled=0 + group by account order by account asc""", (pay.name), as_dict=1) + + for i, gle in enumerate(gl_entries): + self.assertEqual(expected_gle[i][0], gle.account) + self.assertEqual(expected_gle[i][1], gle.balance) + + pi.reload() + pi.cancel() + + pi_2.reload() + pi_2.cancel() + + pay.reload() + pay.cancel() + + frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled) + def test_purchase_invoice_advance_taxes(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json index 5801b17f66..63dfff8921 100644 --- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json +++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -1,235 +1,127 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-03-08 15:36:46", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "actions": [], + "creation": "2013-03-08 15:36:46", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_type", + "reference_name", + "remarks", + "reference_row", + "col_break1", + "advance_amount", + "allocated_amount", + "exchange_gain_loss", + "ref_exchange_rate" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Type", - "length": 0, - "no_copy": 1, - "oldfieldname": "journal_voucher", - "oldfieldtype": "Link", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "180px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "no_copy": 1, + "oldfieldname": "journal_voucher", + "oldfieldtype": "Link", + "options": "DocType", + "print_width": "180px", + "read_only": 1, "width": "180px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Reference Name", - "length": 0, - "no_copy": 1, - "options": "reference_type", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 3, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "no_copy": 1, + "options": "reference_type", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "remarks", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Remarks", - "length": 0, - "no_copy": 1, - "oldfieldname": "remarks", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 3, + "fieldname": "remarks", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Remarks", + "no_copy": 1, + "oldfieldname": "remarks", + "oldfieldtype": "Small Text", + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_row", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Row", - "length": 0, - "no_copy": 1, - "oldfieldname": "jv_detail_no", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "80px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row", + "no_copy": 1, + "oldfieldname": "jv_detail_no", + "oldfieldtype": "Date", + "print_hide": 1, + "print_width": "80px", + "read_only": 1, "width": "80px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "advance_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Advance Amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "advance_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "advance_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Advance Amount", + "no_copy": 1, + "oldfieldname": "advance_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "100px", + "read_only": 1, "width": "100px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "allocated_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Allocated Amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "allocated_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Allocated Amount", + "no_copy": 1, + "oldfieldname": "allocated_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "100px", "width": "100px" + }, + { + "fieldname": "exchange_gain_loss", + "fieldtype": "Currency", + "label": "Exchange Gain/Loss", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "ref_exchange_rate", + "fieldtype": "Float", + "label": "Reference Exchange Rate", + "non_negative": 1, + "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "menu_index": 0, - "modified": "2016-08-26 02:30:54.407138", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Purchase Invoice Advance", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "DESC", - "track_seen": 0 + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-20 16:26:53.820530", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Purchase Invoice Advance", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 55a5b99907..6d1f6249c1 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -840,6 +840,7 @@ class SalesInvoice(SellingController): self.make_customer_gl_entry(gl_entries) self.make_tax_gl_entries(gl_entries) + self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries) self.allocate_advance_taxes(gl_entries) diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json index 14bf4d8133..29422d68cf 100644 --- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json +++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json @@ -1,235 +1,128 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:27:41", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "actions": [], + "creation": "2013-02-22 01:27:41", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_type", + "reference_name", + "remarks", + "reference_row", + "col_break1", + "advance_amount", + "allocated_amount", + "exchange_gain_loss", + "ref_exchange_rate" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Type", - "length": 0, - "no_copy": 1, - "oldfieldname": "journal_voucher", - "oldfieldtype": "Link", - "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "250px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_type", + "fieldtype": "Link", + "label": "Reference Type", + "no_copy": 1, + "oldfieldname": "journal_voucher", + "oldfieldtype": "Link", + "options": "DocType", + "print_width": "250px", + "read_only": 1, "width": "250px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Reference Name", - "length": 0, - "no_copy": 1, - "options": "reference_type", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "columns": 3, + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "no_copy": 1, + "options": "reference_type", + "print_hide": 1, + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "remarks", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Remarks", - "length": 0, - "no_copy": 1, - "oldfieldname": "remarks", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 3, + "fieldname": "remarks", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Remarks", + "no_copy": 1, + "oldfieldname": "remarks", + "oldfieldtype": "Small Text", + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_row", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Reference Row", - "length": 0, - "no_copy": 1, - "oldfieldname": "jv_detail_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "reference_row", + "fieldtype": "Data", + "hidden": 1, + "label": "Reference Row", + "no_copy": 1, + "oldfieldname": "jv_detail_no", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "120px", + "read_only": 1, "width": "120px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "col_break1", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "advance_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Advance amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "advance_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "advance_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Advance amount", + "no_copy": 1, + "oldfieldname": "advance_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "120px", + "read_only": 1, "width": "120px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "allocated_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Allocated amount", - "length": 0, - "no_copy": 1, - "oldfieldname": "allocated_amount", - "oldfieldtype": "Currency", - "options": "party_account_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "120px", - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "columns": 2, + "fieldname": "allocated_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Allocated amount", + "no_copy": 1, + "oldfieldname": "allocated_amount", + "oldfieldtype": "Currency", + "options": "party_account_currency", + "print_width": "120px", "width": "120px" + }, + { + "fieldname": "exchange_gain_loss", + "fieldtype": "Currency", + "label": "Exchange Gain/Loss", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "ref_exchange_rate", + "fieldtype": "Float", + "label": "Reference Exchange Rate", + "non_negative": 1, + "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "menu_index": 0, - "modified": "2016-08-26 02:36:10.718057", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Invoice Advance", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "DESC", - "track_seen": 0 + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-04 20:25:49.832052", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Advance", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index ed6e28da1e..1cdbd8d38a 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -472,7 +472,8 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): "total_amount": d.grand_total, "outstanding_amount": d.outstanding_amount, "allocated_amount": d.allocated_amount, - "exchange_rate": d.exchange_rate + "exchange_rate": d.exchange_rate if not d.exchange_gain_loss else payment_entry.get_exchange_rate(), + "exchange_gain_loss": d.exchange_gain_loss # only populated from invoice in case of advance allocation } if d.voucher_detail_no: @@ -498,12 +499,15 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False): payment_entry.set_amounts() if d.difference_amount and d.difference_account: - payment_entry.set_gain_or_loss(account_details={ + account_details = { 'account': d.difference_account, 'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company', - payment_entry.company, "cost_center"), - 'amount': d.difference_amount - }) + payment_entry.company, "cost_center") + } + if d.difference_amount: + account_details['amount'] = d.difference_amount + + payment_entry.set_gain_or_loss(account_details=account_details) if not do_not_save: payment_entry.save(ignore_permissions=True) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1c086e9edc..a9860ed2f0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -124,6 +124,8 @@ class AccountsController(TransactionBase): if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): self.set_advances() + self.set_advance_gain_or_loss() + if self.is_return: self.validate_qty() else: @@ -584,15 +586,18 @@ class AccountsController(TransactionBase): allocated_amount = min(amount - advance_allocated, d.amount) advance_allocated += flt(allocated_amount) - self.append("advances", { + advance_row = { "doctype": self.doctype + " Advance", "reference_type": d.reference_type, "reference_name": d.reference_name, "reference_row": d.reference_row, "remarks": d.remarks, "advance_amount": flt(d.amount), - "allocated_amount": allocated_amount - }) + "allocated_amount": allocated_amount, + "ref_exchange_rate": flt(d.exchange_rate) # exchange_rate of advance entry + } + + self.append("advances", advance_row) def get_advance_entries(self, include_unallocated=True): if self.doctype == "Sales Invoice": @@ -650,6 +655,66 @@ class AccountsController(TransactionBase): "Payment Entry {0} is linked against Order {1}, check if it should be pulled as advance in this invoice.") .format(d.reference_name, d.against_order)) + def set_advance_gain_or_loss(self): + if not self.get("advances"): + return + + for d in self.get("advances"): + advance_exchange_rate = d.ref_exchange_rate + if (d.allocated_amount and self.conversion_rate != 1 + and self.conversion_rate != advance_exchange_rate): + + base_allocated_amount_in_ref_rate = advance_exchange_rate * d.allocated_amount + base_allocated_amount_in_inv_rate = self.conversion_rate * d.allocated_amount + difference = base_allocated_amount_in_ref_rate - base_allocated_amount_in_inv_rate + + d.exchange_gain_loss = difference + + def make_exchange_gain_loss_gl_entries(self, gl_entries): + if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']: + for d in self.get("advances"): + if d.exchange_gain_loss: + party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer + party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to + party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer" + + gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account') + account_currency = get_account_currency(gain_loss_account) + if account_currency != self.company_currency: + frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency)) + + # for purchase + dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit' + # just reverse for sales? + dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' + + gl_entries.append( + self.get_gl_dict({ + "account": gain_loss_account, + "account_currency": account_currency, + "against": party, + dr_or_cr + "_in_account_currency": abs(d.exchange_gain_loss), + dr_or_cr: abs(d.exchange_gain_loss), + "cost_center": self.cost_center, + "project": self.project + }, item=d) + ) + + dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' + + gl_entries.append( + self.get_gl_dict({ + "account": party_account, + "party_type": party_type, + "party": party, + "against": gain_loss_account, + dr_or_cr + "_in_account_currency": flt(abs(d.exchange_gain_loss) / self.conversion_rate), + dr_or_cr: abs(d.exchange_gain_loss), + "cost_center": self.cost_center, + "project": self.project + }, self.party_account_currency, item=self) + ) + def update_against_document_in_jv(self): """ Links invoice and advance voucher: @@ -690,7 +755,9 @@ class AccountsController(TransactionBase): if self.party_account_currency != self.company_currency else 1), 'grand_total': (self.base_grand_total if self.party_account_currency == self.company_currency else self.grand_total), - 'outstanding_amount': self.outstanding_amount + 'outstanding_amount': self.outstanding_amount, + 'difference_account': frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account'), + 'exchange_gain_loss': flt(d.get('exchange_gain_loss')) }) lst.append(args) @@ -1289,6 +1356,8 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, party_account_field = "paid_from" if party_type == "Customer" else "paid_to" currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency" payment_type = "Receive" if party_type == "Customer" else "Pay" + exchange_rate_field = "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate" + payment_entries_against_order, unallocated_payment_entries = [], [] limit_cond = "limit %s" % limit if limit else "" @@ -1305,27 +1374,28 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, "Payment Entry" as reference_type, t1.name as reference_name, t1.remarks, t2.allocated_amount as amount, t2.name as reference_row, t2.reference_name as against_order, t1.posting_date, - t1.{0} as currency + t1.{0} as currency, t1.{4} as exchange_rate from `tabPayment Entry` t1, `tabPayment Entry Reference` t2 where t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s and t1.party_type = %s and t1.party = %s and t1.docstatus = 1 and t2.reference_doctype = %s {2} order by t1.posting_date {3} - """.format(currency_field, party_account_field, reference_condition, limit_cond), + """.format(currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field), [party_account, payment_type, party_type, party, order_doctype] + order_list, as_dict=1) if include_unallocated: unallocated_payment_entries = frappe.db.sql(""" select "Payment Entry" as reference_type, name as reference_name, - remarks, unallocated_amount as amount + remarks, unallocated_amount as amount, {2} as exchange_rate from `tabPayment Entry` where {0} = %s and party_type = %s and party = %s and payment_type = %s and docstatus = 1 and unallocated_amount > 0 order by posting_date {1} - """.format(party_account_field, limit_cond), (party_account, party_type, party, payment_type), as_dict=1) + """.format(party_account_field, limit_cond, exchange_rate_field), + (party_account, party_type, party, payment_type), as_dict=1) return list(payment_entries_against_order) + list(unallocated_payment_entries) From 855e9030f2097c77ebc7c6114d2da48d9bc878c6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 12 Jul 2021 22:11:57 +0530 Subject: [PATCH 027/253] fix: Deduct included taxes from unallocated amount --- .../doctype/payment_entry/payment_entry.py | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 85b98843ee..cf40e9cf2f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -517,16 +517,19 @@ class PaymentEntry(AccountsController): self.unallocated_amount = 0 if self.party: total_deductions = sum(flt(d.amount) for d in self.get("deductions")) + included_taxes = self.get_included_taxes() if self.payment_type == "Receive" \ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): self.unallocated_amount = (self.received_amount + total_deductions - self.base_total_allocated_amount) / self.source_exchange_rate + self.unallocated_amount -= included_taxes elif self.payment_type == "Pay" \ and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate): self.unallocated_amount = (self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)) / self.target_exchange_rate + self.unallocated_amount -= included_taxes def set_difference_amount(self): base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) @@ -542,10 +545,22 @@ class PaymentEntry(AccountsController): self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) total_deductions = sum(flt(d.amount) for d in self.get("deductions")) + included_taxes = self.get_included_taxes() - self.difference_amount = flt(self.difference_amount - total_deductions, + self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")) + def get_included_taxes(self): + included_taxes = 0 + for tax in self.get('taxes'): + if tax.included_in_paid_amount: + if tax.add_deduct_tax == 'Add': + included_taxes += tax.base_tax_amount + else: + included_taxes -= tax.base_tax_amount + + return included_taxes + # Paid amount is auto allocated in the reference document by default. # Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast def clear_unallocated_reference_document_rows(self): @@ -719,6 +734,10 @@ class PaymentEntry(AccountsController): against = self.party or self.paid_to payment_or_advance_account = self.get_party_account_for_taxes() + tax_amount = d.tax_amount + + if self.advance_tax_account: + tax_amount = -1* tax_amount gl_entries.append( self.get_gl_dict({ @@ -732,16 +751,17 @@ class PaymentEntry(AccountsController): }, account_currency, item=d)) #Intentionally use -1 to get net values in party account - gl_entries.append( - self.get_gl_dict({ - "account": payment_or_advance_account, - "against": against, - dr_or_cr: -1 * d.base_tax_amount, - dr_or_cr + "_in_account_currency": -1*d.base_tax_amount - if account_currency==self.company_currency - else d.tax_amount, - "cost_center": self.cost_center, - }, account_currency, item=d)) + if not d.included_in_paid_amount or self.advance_tax_account: + gl_entries.append( + self.get_gl_dict({ + "account": payment_or_advance_account, + "against": against, + dr_or_cr: -1 * d.base_tax_amount, + dr_or_cr + "_in_account_currency": -1*d.base_tax_amount + if account_currency==self.company_currency + else d.tax_amount, + "cost_center": self.cost_center, + }, account_currency, item=d)) def add_deductions_gl_entries(self, gl_entries): for d in self.get("deductions"): From 4a2e4748ac8a815214a6de9c5b1ce54abda5d807 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 13 Jul 2021 11:22:55 +0530 Subject: [PATCH 028/253] fix: Unallocated amount for inclusive charges --- .../accounts/doctype/payment_entry/payment_entry.py | 13 ++++++++----- erpnext/controllers/accounts_controller.py | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 0bc3d94d2c..46904f7c57 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -436,6 +436,7 @@ class PaymentEntry(AccountsController): return tax_withholding_details.update({ + 'add_deduct_tax': 'Add', 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) }) @@ -742,16 +743,18 @@ class PaymentEntry(AccountsController): payment_or_advance_account = self.get_party_account_for_taxes() tax_amount = d.tax_amount + base_tax_amount = d.base_tax_amount if self.advance_tax_account: - tax_amount = -1* tax_amount + tax_amount = -1 * tax_amount + base_tax_amount = -1 * base_tax_amount gl_entries.append( self.get_gl_dict({ "account": d.account_head, "against": against, - dr_or_cr: d.base_tax_amount, - dr_or_cr + "_in_account_currency": d.base_tax_amount + dr_or_cr: tax_amount, + dr_or_cr + "_in_account_currency": base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": d.cost_center @@ -763,8 +766,8 @@ class PaymentEntry(AccountsController): self.get_gl_dict({ "account": payment_or_advance_account, "against": against, - dr_or_cr: -1 * d.base_tax_amount, - dr_or_cr + "_in_account_currency": -1*d.base_tax_amount + dr_or_cr: -1 * tax_amount, + dr_or_cr + "_in_account_currency": -1 * base_tax_amount if account_currency==self.company_currency else d.tax_amount, "cost_center": self.cost_center, diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index a9860ed2f0..4c313c43a7 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -818,11 +818,11 @@ class AccountsController(TransactionBase): account_currency = get_account_currency(tax.account_head) if self.doctype == "Purchase Invoice": - dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" - rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" - else: dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + else: + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" + rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" party = self.supplier if self.doctype == "Purchase Invoice" else self.customer unallocated_amount = tax.tax_amount - tax.allocated_amount From 5265ba39f0be132db770fcaae527afc945f54b16 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 13 Jul 2021 14:58:17 +0530 Subject: [PATCH 029/253] feat: Added fields for dispatch address in Sales Order, Sales Invoice, Delivery Note for Eway Bill --- .../doctype/sales_invoice/sales_invoice.json | 19 +++++++++++++++++- erpnext/regional/india/utils.py | 10 ++++++---- .../doctype/sales_order/sales_order.json | 20 ++++++++++++++++++- erpnext/selling/sales_common.js | 8 ++++++-- .../doctype/delivery_note/delivery_note.json | 19 +++++++++++++++++- 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e7dd6b8a60..0a9a105b7c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -48,6 +48,8 @@ "shipping_address", "company_address", "company_address_display", + "dispatch_address_name", + "dispatch_address", "currency_and_price_list", "currency", "conversion_rate", @@ -1966,6 +1968,21 @@ "fieldname": "disable_rounded_total", "fieldtype": "Check", "label": "Disable Rounded Total" + }, + { + "allow_on_submit": 1, + "fieldname": "dispatch_address_name", + "fieldtype": "Link", + "label": "Dispatch Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "dispatch_address", + "fieldtype": "Small Text", + "label": "Dispatch Address", + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -1978,7 +1995,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-05-20 22:48:33.988881", + "modified": "2021-07-08 14:03:55.502522", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 81c0918b99..61f5a0578e 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -431,9 +431,11 @@ def get_ewb_data(dt, dn): company_address = frappe.get_doc('Address', doc.company_address) billing_address = frappe.get_doc('Address', doc.customer_address) + #added dispatch address + dispatch_address = frappe.get_doc('Address', doc.dispatch_address_name) shipping_address = frappe.get_doc('Address', doc.shipping_address_name) - data = get_address_details(data, doc, company_address, billing_address) + data = get_address_details(data, doc, company_address, billing_address, dispatch_address) data.itemList = [] data.totalValue = doc.total @@ -519,10 +521,10 @@ def get_gstins_for_company(company): `tabDynamic Link`.link_name = %(company)s""", {"company": company}) return company_gstins -def get_address_details(data, doc, company_address, billing_address): +def get_address_details(data, doc, company_address, billing_address, dispatch_address): data.fromPincode = validate_pincode(company_address.pincode, 'Company Address') - data.fromStateCode = data.actualFromStateCode = validate_state_code( - company_address.gst_state_number, 'Company Address') + data.fromStateCode = validate_state_code(company_address.gst_state_number, 'Company Address') + data.actualFromStateCode = validate_state_code(dispatch_address.gst_state_number, 'Company Address') if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15: data.toGstin = 'URP' diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 762b6f1d6c..d31db820ab 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -38,6 +38,8 @@ "col_break46", "shipping_address_name", "shipping_address", + "dispatch_address_name", + "dispatch_address", "customer_group", "territory", "currency_and_price_list", @@ -1486,13 +1488,29 @@ "fieldname": "disable_rounded_total", "fieldtype": "Check", "label": "Disable Rounded Total" + }, + { + "allow_on_submit": 1, + "fieldname": "dispatch_address_name", + "fieldtype": "Link", + "label": "Dispatch Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "depends_on": "dispatch_address_name", + "fieldname": "dispatch_address", + "fieldtype": "Small Text", + "label": "Dispatch Address", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-04-15 23:55:13.439068", + "modified": "2021-07-08 21:37:44.177493", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index eb02867720..f515baf31b 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -26,7 +26,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran } }; }); - } + } setup_queries() { var me = this; @@ -85,7 +85,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran refresh() { super.refresh(); - + frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} this.frm.toggle_display("customer_name", @@ -114,6 +114,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name"); } + dispatch_address_name() { + erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address"); + } + sales_partner() { this.apply_pricing_rule(); } diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index f20e76f5bf..dbfeb4a10b 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -32,6 +32,8 @@ "contact_info", "shipping_address_name", "shipping_address", + "dispatch_address_name", + "dispatch_address", "contact_person", "contact_display", "contact_mobile", @@ -1282,13 +1284,28 @@ "fieldname": "disable_rounded_total", "fieldtype": "Check", "label": "Disable Rounded Total" + }, + { + "fieldname": "dispatch_address_name", + "fieldtype": "Link", + "label": "Dispatch Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "depends_on": "dispatch_address_name", + "fieldname": "dispatch_address", + "fieldtype": "Small Text", + "label": "Dispatch Address", + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-06-11 19:27:30.901112", + "modified": "2021-07-08 21:37:20.802652", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 0d190bb930a990932c6537c0c56fddb7d5d0eb87 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 13 Jul 2021 11:45:41 +0530 Subject: [PATCH 030/253] fix: multi-currency issue --- erpnext/manufacturing/doctype/bom/bom.py | 3 ++- erpnext/stock/get_item_details.py | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c32a8a95a1..9da461f497 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -713,7 +713,8 @@ def get_bom_item_rate(args, bom_doc): "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function "conversion_factor": args.get("conversion_factor") or 1, "plc_conversion_rate": 1, - "ignore_party": True + "ignore_party": True, + "ignore_conversion_rate": True }) item_doc = frappe.get_cached_doc("Item", args.get("item_code")) out = frappe._dict() diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index ca174a3f63..4657700dbb 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -441,7 +441,7 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t if item_tax_templates is None: item_tax_templates = {} - + if item_rates is None: item_rates = {} @@ -807,10 +807,14 @@ def check_packing_list(price_list_rate_name, desired_qty, item_code): def validate_conversion_rate(args, meta): from erpnext.controllers.accounts_controller import validate_conversion_rate - if (not args.conversion_rate - and args.currency==frappe.get_cached_value('Company', args.company, "default_currency")): + company_currency = frappe.get_cached_value('Company', args.company, "default_currency") + if (not args.conversion_rate and args.currency==company_currency): args.conversion_rate = 1.0 + if (not args.ignore_conversion_rate and args.conversion_rate == 1 and args.currency!=company_currency): + args.conversion_rate = get_exchange_rate(args.currency, + company_currency, args.transaction_date, "for_buying") or 1.0 + # validate currency conversion rate validate_conversion_rate(args.currency, args.conversion_rate, meta.get_label("conversion_rate"), args.company) From adfdc71844a94a68884c50f45c5f90f3d1640a95 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 14 Jul 2021 09:59:41 +0530 Subject: [PATCH 031/253] fix: Tax calculation for Recurring additional salary (#24206) * fix: Tax calculation for Recurring additional salary * fix: conflicts --- .../additional_salary/additional_salary.py | 6 +++--- .../doctype/salary_detail/salary_detail.json | 11 +++++++++- .../doctype/salary_slip/salary_slip.py | 21 ++++++++++++++----- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index ebeddf97f9..7db4b8686a 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -110,11 +110,11 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days +@frappe.whitelist() def get_additional_salaries(employee, start_date, end_date, component_type): additional_salary_list = frappe.db.sql(""" - select name, salary_component as component, type, amount, - overwrite_salary_structure_amount as overwrite, - deduct_full_tax_on_selected_payroll_date + select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite, + deduct_full_tax_on_selected_payroll_date, is_recurring from `tabAdditional Salary` where employee=%(employee)s and docstatus = 1 diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index 393f647cc8..97608d72f3 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.json +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json @@ -12,6 +12,7 @@ "year_to_date", "section_break_5", "additional_salary", + "is_recurring_additional_salary", "statistical_component", "depends_on_payment_days", "exempted_from_income_tax", @@ -235,11 +236,19 @@ "label": "Year To Date", "options": "currency", "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='earnings' && doc.additional_salary", + "fieldname": "is_recurring_additional_salary", + "fieldtype": "Check", + "label": "Is Recurring Additional Salary", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-01-14 13:39:15.847158", + "modified": "2021-03-14 13:39:15.847158", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Detail", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index bead880ef7..81e5dc9f87 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -7,12 +7,12 @@ import datetime, math from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day from frappe.model.naming import make_autoname +from frappe.utils.background_jobs import enqueue from frappe import msgprint, _ from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase -from frappe.utils.background_jobs import enqueue from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount @@ -616,7 +616,8 @@ class SalarySlip(TransactionBase): get_salary_component_data(additional_salary.component), additional_salary.amount, component_type, - additional_salary + additional_salary, + is_recurring = additional_salary.is_recurring ) def add_tax_components(self, payroll_period): @@ -637,7 +638,7 @@ class SalarySlip(TransactionBase): tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, component_data, amount, component_type, additional_salary=None): + def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0): component_row = None for d in self.get(component_type): if d.salary_component != component_data.salary_component: @@ -678,6 +679,7 @@ class SalarySlip(TransactionBase): component_row.set('abbr', abbr) if additional_salary: + component_row.is_recurring_additional_salary = is_recurring component_row.default_amount = 0 component_row.additional_amount = amount component_row.additional_salary = additional_salary.name @@ -711,6 +713,7 @@ class SalarySlip(TransactionBase): # get remaining numbers of sub-period (period for which one salary is processed) remaining_sub_periods = get_period_factor(self.employee, self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1] + # get taxable_earnings, paid_taxes for previous period previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date, self.start_date, tax_slab.allow_tax_exemption) @@ -870,8 +873,16 @@ class SalarySlip(TransactionBase): if earning.is_tax_applicable: if additional_amount: - taxable_earnings += (amount - additional_amount) - additional_income += additional_amount + if not earning.is_recurring_additional_salary: + taxable_earnings += (amount - additional_amount) + additional_income += additional_amount + else: + to_date = frappe.db.get_value("Additional Salary", earning.additional_salary, 'to_date') + period = (getdate(to_date).month - getdate(self.start_date).month) + 1 + if period > 0: + taxable_earnings += (amount - additional_amount) * period + additional_income += additional_amount * period + if earning.deduct_full_tax_on_selected_payroll_date: additional_income_with_full_tax += additional_amount continue From cbf7e1b676d5f89f1be85c6633fe083c31ebe690 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Jul 2021 11:40:47 +0530 Subject: [PATCH 032/253] fix: pos item cart dom updates (#26460) --- .../selling/page/point_of_sale/pos_item_cart.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 38508c219b..f7b2c1d93c 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -965,8 +965,23 @@ erpnext.PointOfSale.ItemCart = class { }); } + attach_refresh_field_event(frm) { + $(frm.wrapper).off('refresh-fields'); + $(frm.wrapper).on('refresh-fields', () => { + if (frm.doc.items.length) { + frm.doc.items.forEach(item => { + this.update_item_html(item); + }); + } + this.update_totals_section(frm); + }); + } + load_invoice() { const frm = this.events.get_frm(); + + this.attach_refresh_field_event(frm); + this.fetch_customer_details(frm.doc.customer).then(() => { this.events.customer_details_updated(this.customer_info); this.update_customer_section(); From 1dfb5eb5355eef7dbfbc511288be54c012765e12 Mon Sep 17 00:00:00 2001 From: Frappe Date: Wed, 14 Jul 2021 12:33:45 +0530 Subject: [PATCH 033/253] feat: South Africa VAT report --- .../south_africa_vat_account/__init__.py | 0 .../south_africa_vat_account.json | 34 +++ .../south_africa_vat_account.py | 8 + .../south_africa_vat_settings/__init__.py | 0 .../south_africa_vat_settings.js | 25 ++ .../south_africa_vat_settings.json | 76 ++++++ .../south_africa_vat_settings.py | 8 + .../test_south_africa_vat_settings.py | 8 + .../report/vat_audit_report/__init__.py | 0 .../vat_audit_report/vat_audit_report.js | 31 +++ .../vat_audit_report/vat_audit_report.json | 32 +++ .../vat_audit_report/vat_audit_report.py | 246 ++++++++++++++++++ erpnext/regional/south_africa/__init__.py | 0 erpnext/regional/south_africa/setup.py | 19 ++ 14 files changed, 487 insertions(+) create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/__init__.py create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/__init__.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py create mode 100644 erpnext/regional/report/vat_audit_report/__init__.py create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.js create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.json create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.py create mode 100644 erpnext/regional/south_africa/__init__.py create mode 100644 erpnext/regional/south_africa/setup.py diff --git a/erpnext/accounts/doctype/south_africa_vat_account/__init__.py b/erpnext/accounts/doctype/south_africa_vat_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json new file mode 100644 index 0000000000..fa1aa7da59 --- /dev/null +++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json @@ -0,0 +1,34 @@ +{ + "actions": [], + "autoname": "account", + "creation": "2021-07-08 22:04:24.634967", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "in_preview": 1, + "label": "Account", + "options": "Account" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-07-08 22:35:33.202911", + "modified_by": "Administrator", + "module": "Accounts", + "name": "South Africa VAT Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py new file mode 100644 index 0000000000..4bd8c65a04 --- /dev/null +++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SouthAfricaVATAccount(Document): + pass diff --git a/erpnext/regional/doctype/south_africa_vat_settings/__init__.py b/erpnext/regional/doctype/south_africa_vat_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js new file mode 100644 index 0000000000..3f0b466d82 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js @@ -0,0 +1,25 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('South Africa VAT Settings', { + refresh: function(frm) { + frm.set_query("company", function() { + return { + filters: { + country: "South Africa", + } + } + }); + frm.set_query("account", "vat_accounts", function(doc, cdt, cdn) { + var row = locals[cdt][cdn]; + console.log(row); + return { + filters: { + company: frm.doc.company, + account_type: "Tax", + is_group: 0 + } + } + }) + } +}); diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json new file mode 100644 index 0000000000..8a51829c41 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json @@ -0,0 +1,76 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-08 22:34:33.668015", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "vat_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "vat_accounts", + "fieldtype": "Table", + "label": "VAT Accounts", + "options": "South Africa VAT Account", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-07-14 02:17:52.476762", + "modified_by": "Administrator", + "module": "Regional", + "name": "South Africa VAT Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Auditor", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py new file mode 100644 index 0000000000..d74154bfe7 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SouthAfricaVATSettings(Document): + pass diff --git a/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py new file mode 100644 index 0000000000..1c36652ad6 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestSouthAfricaVATSettings(unittest.TestCase): + pass diff --git a/erpnext/regional/report/vat_audit_report/__init__.py b/erpnext/regional/report/vat_audit_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.js b/erpnext/regional/report/vat_audit_report/vat_audit_report.js new file mode 100644 index 0000000000..39ef9b563a --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.js @@ -0,0 +1,31 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["VAT Audit Report"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -2), + "width": "80" + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ] +}; diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.json b/erpnext/regional/report/vat_audit_report/vat_audit_report.json new file mode 100644 index 0000000000..8917e8f3c7 --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-09 11:07:43.473518", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-09 11:07:43.473518", + "modified_by": "Administrator", + "module": "Regional", + "name": "VAT Audit Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "VAT Audit Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py new file mode 100644 index 0000000000..e66212689e --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -0,0 +1,246 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, json +from frappe import _ +from frappe.utils import flt, formatdate, now_datetime, getdate +from datetime import date + +def execute(filters=None): + return VATAuditReport(filters).run() + +class VATAuditReport(object): + + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + self.columns = [] + self.data = [] + self.doctypes = ["Purchase Invoice", "Sales Invoice"] + + def run(self): + self.get_sa_vat_accounts() + for doctype in self.doctypes: + self.get_columns(doctype) + self.select_columns = """ + name as invoice_number, + posting_date, remarks""" + columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \ + else ", customer as party, debit_to as account" + self.select_columns += columns + + self.get_invoice_data(doctype) + + if self.invoices: + self.get_invoice_items(doctype) + self.get_items_based_on_tax_rate(doctype) + self.get_data(doctype) + + return self.columns, self.data + + def get_sa_vat_accounts(self): + self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", \ + filters = {"parent":self.filters.company}, pluck="account") + if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: + frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) + + def get_invoice_data(self,doctype): + conditions = self.get_conditions() + self.invoices = frappe._dict() + + invoice_data = frappe.db.sql(""" + SELECT + {select_columns} + FROM + `tab{doctype}` + WHERE + docstatus = 1 {where_conditions} + and is_opening = 'No' + ORDER BY + posting_date DESC + """.format(select_columns=self.select_columns, doctype=doctype, + where_conditions=conditions), self.filters, as_dict=1) + + for d in invoice_data: + self.invoices.setdefault(d.invoice_number, d) + + def get_invoice_items(self,doctype): + self.invoice_items = frappe._dict() + self.item_tax_rate = frappe._dict() + + items = frappe.db.sql(""" + SELECT + item_code, parent, taxable_value, base_net_amount, item_tax_rate + FROM + `tab%s Item` + WHERE + parent in (%s) + """ % (doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) + for d in items: + if d.item_code not in self.invoice_items.get(d.parent, {}): + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, + sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items + if i.item_code == d.item_code and i.parent == d.parent)) + + def get_items_based_on_tax_rate(self,doctype): + self.items_based_on_tax_rate = frappe._dict() + self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \ + else "Sales Taxes and Charges" + self.tax_details = frappe.db.sql(""" + SELECT + parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount + FROM + `tab%s` + WHERE + parenttype = %s and docstatus = 1 + and parent in (%s) + ORDER BY + account_head + """ % (self.tax_doctype, '%s', ', '.join(['%s']*len(self.invoices.keys()))), + tuple([doctype] + list(self.invoices.keys()))) + + for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: + if item_wise_tax_detail: + try: + if account in self.sa_vat_accounts: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + else: + continue + for item_code, taxes in item_wise_tax_detail.items(): + tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes) + + if tax_rate is not None: + rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}) \ + .setdefault(tax_rate, []) + if item_code not in rate_based_dict: + rate_based_dict.append(item_code) + except ValueError: + continue + + def get_item_amount_map(self, parent, item_code, taxes): + net_amount = abs(self.invoice_items.get(parent).get(item_code)) + tax_rate = taxes[0] + tax_amount = taxes[1] + gross_amount = net_amount + tax_amount + item_amount_map = self.item_tax_rate.setdefault(parent, {}) \ + .setdefault(item_code, []) + amount_dict = { + "tax_rate": tax_rate, + "gross_amount": gross_amount, + "tax_amount": tax_amount, + "net_amount": net_amount + } + item_amount_map.append(amount_dict) + + return tax_rate, item_amount_map + + def get_conditions(self): + conditions = "" + for opts in (("company", " and company=%(company)s"), + ("from_date", " and posting_date>=%(from_date)s"), + ("to_date", " and posting_date<=%(to_date)s")): + if self.filters.get(opts[0]): + conditions += opts[1] + + return conditions + + def get_data(self,doctype): + consolidated_data = self.get_consolidated_data() + section_name = _("Purchases ") if doctype == "Purchase Invoice" else _("Sales ") + + for rate, section in consolidated_data.items(): + rate = int(rate) + label = frappe.bold(_("Standard Rate ") + section_name + str(rate) + "%") + section_head = {"posting_date": label } + total_gross = total_tax = total_net = 0 + self.data.append(section_head) + for row in section.get("data"): + self.data.append(row) + total_gross += row["gross_amount"] + total_tax += row["tax_amount"] + total_net += row["net_amount"] + + total = { + "posting_date": frappe.bold(_("Total")), + "gross_amount": total_gross, + "tax_amount": total_tax, + "net_amount": total_net, + "bold":1 + } + self.data.append(total) + self.data.append({}) + + def get_consolidated_data(self): + consolidated_data_map={} + for inv, inv_data in self.invoices.items(): + for rate, items in self.items_based_on_tax_rate.get(inv).items(): + consolidated_data_map.setdefault(rate, {"data": []}) + for item in items: + row = {} + item_details = self.item_tax_rate.get(inv).get(item) + row["account"] = inv_data.get("account") + row["posting_date"] = formatdate(inv_data.get("posting_date"), 'dd-mm-yyyy') + row["invoice_number"] = inv + row["party"] = inv_data.get("party") + row["remarks"] = inv_data.get("remarks") + row["gross_amount"]= item_details[0].get("gross_amount") + row["tax_amount"]= item_details[0].get("tax_amount") + row["net_amount"]= item_details[0].get("net_amount") + consolidated_data_map[rate]["data"].append(row) + + return consolidated_data_map + + def get_columns(self,doctype): + self.columns = [ + { + "fieldname": "posting_date", + "label": "Posting Date", + "fieldtype": "Data", + "width": 200 + }, + { + "fieldname": "account", + "label": "Account", + "fieldtype": "Link", + "options": "Account", + "width": 140 + }, + { + "fieldname": "invoice_number", + "label": "Reference", + "fieldtype": "Link", + "options": doctype, + "width": 140 + }, + { + "fieldname": "party", + "label": "Party", + "fieldtype": "Link", + "options": "Supplier" if doctype == "Purchase Invoice" else "Customer", + "width": 140 + }, + { + "fieldname": "remarks", + "label": "Details", + "fieldtype": "Data", + "width": 140 + }, + { + "fieldname": "net_amount", + "label": "Net Amount", + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "tax_amount", + "label": "Tax Amount", + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "gross_amount", + "label": "Gross Amount", + "fieldtype": "Currency", + "width": 140 + }, + ] diff --git a/erpnext/regional/south_africa/__init__.py b/erpnext/regional/south_africa/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py new file mode 100644 index 0000000000..65e9f5bc33 --- /dev/null +++ b/erpnext/regional/south_africa/setup.py @@ -0,0 +1,19 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe, os, json +from frappe.permissions import add_permission, update_permission_property + +def setup(): + add_permissions() + +def add_permissions(): + """Add Permissions for South Africa VAT Settings and South Africa VAT Account""" + for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'): + add_permission(doctype, 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission(doctype, role, 0) + update_permission_property(doctype, role, 0, 'write', 1) + update_permission_property(doctype, role, 0, 'create', 1) \ No newline at end of file From 9168bb369a3186b9efc09c0039e6b38a624d1359 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 14 Jul 2021 13:57:14 +0530 Subject: [PATCH 034/253] fix: filter by accounts with group by accounts (#26439) --- erpnext/accounts/report/general_ledger/general_ledger.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index e724e9b51b..1759fa3a48 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -55,9 +55,11 @@ def validate_filters(filters, account_details): if not account_details.get(account): frappe.throw(_("Account {0} does not exists").format(account)) - if (filters.get("account") and filters.get("group_by") == _('Group by Account') - and account_details[filters.account].is_group == 0): - frappe.throw(_("Can not filter based on Account, if grouped by Account")) + if (filters.get("account") and filters.get("group_by") == _('Group by Account')): + filters.account = frappe.parse_json(filters.get('account')) + for account in filters.account: + if account_details[account].is_group == 0: + frappe.throw(_("Can not filter based on Child Account, if grouped by Account")) if (filters.get("voucher_no") and filters.get("group_by") in [_('Group by Voucher')]): From 9c04079d04962607b9a8bdaebd0cbe907f1fd28e Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Jul 2021 14:45:11 +0530 Subject: [PATCH 035/253] fix: test fails due to improper gain loss account set (#26482) (#26484) --- .../purchase_invoice/test_purchase_invoice.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index c9384be6eb..ca4d009956 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -954,8 +954,17 @@ class TestPurchaseInvoice(unittest.TestCase): acc_settings.save() def test_gain_loss_with_advance_entry(self): - unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice") - frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1) + unlink_enabled = frappe.db.get_value( + "Accounts Settings", "Accounts Settings", + "unlink_payment_on_cancel_of_invoice") + + frappe.db.set_value( + "Accounts Settings", "Accounts Settings", + "unlink_payment_on_cancel_of_invoice", 1) + + original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account") + frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC") + pay = frappe.get_doc({ 'doctype': 'Payment Entry', 'company': '_Test Company', @@ -995,7 +1004,8 @@ class TestPurchaseInvoice(unittest.TestCase): gl_entries = frappe.db.sql(""" select account, sum(debit - credit) as balance from `tabGL Entry` where voucher_no=%s - group by account order by account asc""", (pi.name), as_dict=1) + group by account + order by account asc""", (pi.name), as_dict=1) for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) @@ -1055,6 +1065,7 @@ class TestPurchaseInvoice(unittest.TestCase): pay.cancel() frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled) + frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) def test_purchase_invoice_advance_taxes(self): from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order From b37ff0d3cf3a05734e15a7fc31079b64237ac6e1 Mon Sep 17 00:00:00 2001 From: Anuja Pawar Date: Wed, 14 Jul 2021 15:06:56 +0530 Subject: [PATCH 036/253] fix: sider fixes --- .../south_africa_vat_settings.js | 7 ++-- .../vat_audit_report/vat_audit_report.py | 34 +++++++++---------- erpnext/regional/south_africa/setup.py | 2 +- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js index 3f0b466d82..7d4ef12a9a 100644 --- a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js @@ -8,18 +8,17 @@ frappe.ui.form.on('South Africa VAT Settings', { filters: { country: "South Africa", } - } + }; }); frm.set_query("account", "vat_accounts", function(doc, cdt, cdn) { var row = locals[cdt][cdn]; - console.log(row); return { filters: { company: frm.doc.company, account_type: "Tax", is_group: 0 } - } - }) + }; + }); } }); diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index e66212689e..6918aa1e64 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -2,10 +2,10 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json +import frappe +import json from frappe import _ -from frappe.utils import flt, formatdate, now_datetime, getdate -from datetime import date +from frappe.utils import formatdate def execute(filters=None): return VATAuditReport(filters).run() @@ -39,7 +39,7 @@ class VATAuditReport(object): return self.columns, self.data def get_sa_vat_accounts(self): - self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", \ + self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", filters = {"parent":self.filters.company}, pluck="account") if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) @@ -84,7 +84,7 @@ class VATAuditReport(object): def get_items_based_on_tax_rate(self,doctype): self.items_based_on_tax_rate = frappe._dict() - self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \ + self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \ else "Sales Taxes and Charges" self.tax_details = frappe.db.sql(""" SELECT @@ -125,10 +125,10 @@ class VATAuditReport(object): item_amount_map = self.item_tax_rate.setdefault(parent, {}) \ .setdefault(item_code, []) amount_dict = { - "tax_rate": tax_rate, - "gross_amount": gross_amount, - "tax_amount": tax_amount, - "net_amount": net_amount + "tax_rate": tax_rate, + "gross_amount": gross_amount, + "tax_amount": tax_amount, + "net_amount": net_amount } item_amount_map.append(amount_dict) @@ -139,19 +139,19 @@ class VATAuditReport(object): for opts in (("company", " and company=%(company)s"), ("from_date", " and posting_date>=%(from_date)s"), ("to_date", " and posting_date<=%(to_date)s")): - if self.filters.get(opts[0]): - conditions += opts[1] + if self.filters.get(opts[0]): + conditions += opts[1] return conditions def get_data(self,doctype): consolidated_data = self.get_consolidated_data() - section_name = _("Purchases ") if doctype == "Purchase Invoice" else _("Sales ") + section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales") for rate, section in consolidated_data.items(): rate = int(rate) - label = frappe.bold(_("Standard Rate ") + section_name + str(rate) + "%") - section_head = {"posting_date": label } + label = frappe.bold(_("Standard Rate ") + section_name + " " + str(rate) + "%") + section_head = {"posting_date": label} total_gross = total_tax = total_net = 0 self.data.append(section_head) for row in section.get("data"): @@ -178,11 +178,11 @@ class VATAuditReport(object): for item in items: row = {} item_details = self.item_tax_rate.get(inv).get(item) - row["account"] = inv_data.get("account") + row["account"] = inv_data.get("account") row["posting_date"] = formatdate(inv_data.get("posting_date"), 'dd-mm-yyyy') row["invoice_number"] = inv - row["party"] = inv_data.get("party") - row["remarks"] = inv_data.get("remarks") + row["party"] = inv_data.get("party") + row["remarks"] = inv_data.get("remarks") row["gross_amount"]= item_details[0].get("gross_amount") row["tax_amount"]= item_details[0].get("tax_amount") row["net_amount"]= item_details[0].get("net_amount") diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py index 65e9f5bc33..2c44c6e6b1 100644 --- a/erpnext/regional/south_africa/setup.py +++ b/erpnext/regional/south_africa/setup.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals -import frappe, os, json +# import frappe, os, json from frappe.permissions import add_permission, update_permission_property def setup(): From ac721ae1470923eb4c1a551ff0034998c31c4df9 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 14 Jul 2021 15:20:14 +0530 Subject: [PATCH 037/253] fix: tds computation summary shows cancelled invoices (#26485) --- .../report/tds_computation_summary/tds_computation_summary.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index e15715dccd..6b9df41f54 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -75,7 +75,8 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f select voucher_no, credit from `tabGL Entry` where party in (%s) and credit > 0 - and company=%s and posting_date between %s and %s + and company=%s and is_cancelled = 0 + and posting_date between %s and %s """, (supplier, company, from_date, to_date), as_dict=1) supplier_credit_amount = flt(sum(d.credit for d in entries)) From c5d7a13513ea46d5e72ad643cec3d06cb156d760 Mon Sep 17 00:00:00 2001 From: Anuja Pawar Date: Wed, 14 Jul 2021 15:49:22 +0530 Subject: [PATCH 038/253] fix: sider & translation fixes --- .../south_africa_vat_settings/south_africa_vat_settings.js | 1 - erpnext/regional/report/vat_audit_report/vat_audit_report.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js index 7d4ef12a9a..393f4ec10e 100644 --- a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js @@ -11,7 +11,6 @@ frappe.ui.form.on('South Africa VAT Settings', { }; }); frm.set_query("account", "vat_accounts", function(doc, cdt, cdn) { - var row = locals[cdt][cdn]; return { filters: { company: frm.doc.company, diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index 6918aa1e64..5633b64bfb 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -150,7 +150,7 @@ class VATAuditReport(object): for rate, section in consolidated_data.items(): rate = int(rate) - label = frappe.bold(_("Standard Rate ") + section_name + " " + str(rate) + "%") + label = frappe.bold(_("Standard Rate") + " " + section_name + " " + str(rate) + "%") section_head = {"posting_date": label} total_gross = total_tax = total_net = 0 self.data.append(section_head) From 7a890331631533521c03e3991911d09f5158f454 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Wed, 14 Jul 2021 16:02:49 +0530 Subject: [PATCH 039/253] fix: update integration links in help.js (#26483) --- erpnext/public/js/help_links.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index aa9bba17c7..140c9da2ee 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -54,7 +54,7 @@ frappe.help.help_links["permission-manager"] = [ frappe.help.help_links["Form/System Settings"] = [ { - label: "Naming Series", + label: "System Settings", url: docsUrl + "user/manual/en/setting-up/settings/system-settings", }, ]; @@ -206,7 +206,7 @@ frappe.help.help_links["Form/PayPal Settings"] = [ label: "PayPal Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/paypal-integration", + "user/manual/en/erpnext_integration/paypal-integration", }, ]; @@ -215,14 +215,14 @@ frappe.help.help_links["Form/Razorpay Settings"] = [ label: "Razorpay Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/razorpay-integration", + "user/manual/en/erpnext_integration/razorpay-integration", }, ]; frappe.help.help_links["Form/Dropbox Settings"] = [ { label: "Dropbox Settings", - url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup", + url: docsUrl + "user/manual/en/erpnext_integration/dropbox-backup", }, ]; @@ -230,7 +230,7 @@ frappe.help.help_links["Form/LDAP Settings"] = [ { label: "LDAP Settings", url: - docsUrl + "user/manual/en/setting-up/integrations/ldap-integration", + docsUrl + "user/manual/en/erpnext_integration/ldap-integration", }, ]; @@ -239,7 +239,7 @@ frappe.help.help_links["Form/Stripe Settings"] = [ label: "Stripe Settings", url: docsUrl + - "user/manual/en/setting-up/integrations/stripe-integration", + "user/manual/en/erpnext_integration/stripe-integration", }, ]; From 513375f264034b2eaa5e0b563811aa1c12aca790 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Fri, 9 Jul 2021 21:52:50 +0530 Subject: [PATCH 040/253] fix: Nested/Multi-level BOM help link (#26409) Updated the link for multi-level boms. Current link is broken. --- erpnext/public/js/help_links.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 140c9da2ee..d0c935f488 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -991,7 +991,7 @@ frappe.help.help_links["Form/BOM"] = [ label: "Nested BOM Structure", url: docsUrl + - "user/manual/en/manufacturing/articles/nested-bom-structure", + "user/manual/en/manufacturing/articles/managing-multi-level-bom", }, ]; From 2c67894135544c04dddeab014ab0c854f8aaef9e Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 14 Jul 2021 16:28:40 +0530 Subject: [PATCH 041/253] fix: validation check for batch for stock reconciliation type in stock entry(bp #26370 ) (#26487) * fix(ux): added filter for valid batch nos. * fix: not validating batch no if entry type stock reconciliation * test: validate batch_no --- .../stock_ledger_entry/stock_ledger_entry.py | 19 ++++++++--------- .../stock_reconciliation.js | 8 +++++++ .../test_stock_reconciliation.py | 21 +++++++++++++++++++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index cb939e63c2..93482e8bea 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -89,17 +89,16 @@ class StockLedgerEntry(Document): if item_det.is_stock_item != 1: frappe.throw(_("Item {0} must be a stock Item").format(self.item_code)) - # check if batch number is required - if self.voucher_type != 'Stock Reconciliation': - if item_det.has_batch_no == 1: - batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name - if not self.batch_no: - frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item)) - elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): - frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) + # check if batch number is valid + if item_det.has_batch_no == 1: + batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name + if not self.batch_no: + frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item)) + elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}): + frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)) - elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0: - frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) + elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0: + frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code)) if item_det.has_variants: frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code), diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index a01db80da4..349e59f31d 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -17,6 +17,14 @@ frappe.ui.form.on("Stock Reconciliation", { } } }); + frm.set_query("batch_no", "items", function(doc, cdt, cdn) { + var item = locals[cdt][cdn]; + return { + filters: { + 'item': item.item_code + } + }; + }); if (frm.doc.company) { erpnext.queries.setup_queries(frm, "Warehouse", function() { diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 84cdc49128..c192582531 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -16,6 +16,7 @@ from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valua from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + class TestStockReconciliation(unittest.TestCase): @classmethod def setUpClass(self): @@ -352,6 +353,26 @@ class TestStockReconciliation(unittest.TestCase): dn2.cancel() pr1.cancel() + def test_valid_batch(self): + create_batch_item_with_batch("Testing Batch Item 1", "001") + create_batch_item_with_batch("Testing Batch Item 2", "002") + sr = create_stock_reconciliation(item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002" + , do_not_submit=True) + self.assertRaises(frappe.ValidationError, sr.submit) + +def create_batch_item_with_batch(item_name, batch_id): + batch_item_doc = create_item(item_name, is_stock_item=1) + if not batch_item_doc.has_batch_no: + batch_item_doc.has_batch_no = 1 + batch_item_doc.create_new_batch = 1 + batch_item_doc.save(ignore_permissions=True) + + if not frappe.db.exists('Batch', batch_id): + b = frappe.new_doc('Batch') + b.item = item_name + b.batch_id = batch_id + b.save() + def insert_existing_sle(warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry From 7558e7f1157db7456521b8071b93aa4c0e8970c7 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 13 Jul 2021 15:34:25 +0530 Subject: [PATCH 042/253] fix: show child item group items on portal --- erpnext/setup/doctype/item_group/item_group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 1c72cebfa9..5fcad00af1 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -87,8 +87,8 @@ class ItemGroup(NestedSet, WebsiteGenerator): if not field_filters: field_filters = {} - # Ensure the query remains within current item group - field_filters['item_group'] = self.name + # Ensure the query remains within current item group & sub group + field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)] engine = ProductQuery() context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name) From e244560fb96b66899fe123dcb5799c7fefe053da Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 13 Jul 2021 17:27:55 +0530 Subject: [PATCH 043/253] fix: set item group as a persistent filter --- erpnext/portal/product_configurator/utils.py | 6 ++++++ erpnext/templates/generators/item_group.html | 2 +- erpnext/www/all-products/index.js | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index d77eb2c396..211b94a9cf 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -2,6 +2,7 @@ import frappe from frappe.utils import cint from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager from erpnext.shopping_cart.product_info import get_product_info_for_website +from erpnext.setup.doctype.item_group.item_group import get_child_groups def get_field_filter_data(): product_settings = get_product_settings() @@ -89,6 +90,7 @@ def get_products_for_website(field_filters=None, attribute_filters=None, search= def get_products_html_for_website(field_filters=None, attribute_filters=None): field_filters = frappe.parse_json(field_filters) attribute_filters = frappe.parse_json(attribute_filters) + set_item_group_filters(field_filters) items = get_products_for_website(field_filters, attribute_filters) html = ''.join(get_html_for_items(items)) @@ -98,6 +100,10 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None): return html +def set_item_group_filters(field_filters): + if 'item_group' in field_filters: + field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])] + def get_item_codes_by_attributes(attribute_filters, template_item_code=None): items = [] diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html index 393c3a43af..95eb8f493f 100644 --- a/erpnext/templates/generators/item_group.html +++ b/erpnext/templates/generators/item_group.html @@ -9,7 +9,7 @@ {% endblock %} {% block page_content %} -
+
{% if slideshow %} {{ web_block( diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js index 0721056816..1c641b59ad 100644 --- a/erpnext/www/all-products/index.js +++ b/erpnext/www/all-products/index.js @@ -124,6 +124,10 @@ $(() => { attribute_filters: if_key_exists(attribute_filters) }; + const item_group = $(".item-group-content").data('item-group'); + if (item_group) { + Object.assign(field_filters, { item_group }); + } return new Promise((resolve, reject) => { frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args) .then(r => { From b43974a9a6d072b511d8f9f77e7c312e74fd9546 Mon Sep 17 00:00:00 2001 From: Anuja Pawar Date: Wed, 14 Jul 2021 20:37:48 +0530 Subject: [PATCH 044/253] fix: more sider fixes --- .../south_africa_vat_settings/south_africa_vat_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js index 393f4ec10e..e37a61ac85 100644 --- a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js @@ -10,7 +10,7 @@ frappe.ui.form.on('South Africa VAT Settings', { } }; }); - frm.set_query("account", "vat_accounts", function(doc, cdt, cdn) { + frm.set_query("account", "vat_accounts", function() { return { filters: { company: frm.doc.company, From 219623279ffd50f3410382de6ca255b6743f30dc Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 14 Jul 2021 20:01:36 +0530 Subject: [PATCH 045/253] fix: Paging buttons not working on item group portal page --- erpnext/templates/generators/item_group.html | 29 +++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html index 95eb8f493f..9050cc388a 100644 --- a/erpnext/templates/generators/item_group.html +++ b/erpnext/templates/generators/item_group.html @@ -127,15 +127,36 @@
-
-
+
+
+
+
{% if frappe.form_dict.start|int > 0 %} - + {% endif %} {% if items|length >= page_length %} - + {% endif %}
+ + {% endblock %} \ No newline at end of file From ca1169eeba6d61ffad707bd6f3b01e4d6a277f75 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 15 Jul 2021 12:03:41 +0530 Subject: [PATCH 046/253] fix: check if field_filters is None --- erpnext/portal/product_configurator/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 211b94a9cf..d60b1a2b05 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -101,7 +101,7 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None): return html def set_item_group_filters(field_filters): - if 'item_group' in field_filters: + if field_filters is not None and 'item_group' in field_filters: field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])] From 12f7befa13882f04cfcb23afec0c363e7103420b Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Thu, 15 Jul 2021 12:03:41 +0530 Subject: [PATCH 047/253] fix: check if field_filters is None --- erpnext/portal/product_configurator/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 211b94a9cf..d60b1a2b05 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -101,7 +101,7 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None): return html def set_item_group_filters(field_filters): - if 'item_group' in field_filters: + if field_filters is not None and 'item_group' in field_filters: field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])] From 74b97b5ec9a16ae10a988b782c1818ce7f823ebd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jul 2021 14:08:58 +0530 Subject: [PATCH 048/253] fix: FG item not fetched in manufacture entry --- .../doctype/work_order/test_work_order.py | 54 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.py | 22 +++++--- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 68de0b29d3..bf1ccb7159 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -513,6 +513,60 @@ class TestWorkOrder(unittest.TestCase): work_order1.save() self.assertEqual(work_order1.operations[0].time_in_mins, 40.0) + def test_batch_size_for_fg_item(self): + fg_item = "Test Batch Size Item For BOM 3" + rm1 = "Test Batch Size Item RM 1 For BOM 3" + + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0) + for item in ["Test Batch Size Item For BOM 3", "Test Batch Size Item RM 1 For BOM 3"]: + item_args = { + "include_item_in_manufacturing": 1, + "is_stock_item": 1 + } + + if item == fg_item: + item_args['has_batch_no'] = 1 + item_args['create_new_batch'] = 1 + item_args['batch_number_series'] = 'TBSI3.#####' + + make_item(item, item_args) + + bom_name = frappe.db.get_value("BOM", + {"item": fg_item, "is_active": 1, "with_operations": 1}, "name") + + if not bom_name: + bom = make_bom(item=fg_item, rate=1000, raw_materials = [rm1], do_not_save=True) + bom.save() + bom.submit() + bom_name = bom.name + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1) + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + + work_order = make_wo_order_test_record(item=fg_item, skip_transfer=True, planned_start_date=now(), + qty=30, do_not_save = True) + work_order.batch_size = 10 + work_order.insert() + work_order.submit() + self.assertEqual(work_order.has_batch_no, 1) + ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30)) + for row in ste1.get('items'): + if row.is_finished_item: + self.assertEqual(row.item_code, fg_item) + self.assertEqual(row.qty, 10) + + frappe.db.set_value('Manufacturing Settings', None, 'make_serial_no_batch_from_work_order', 0) + def test_partial_material_consumption(self): frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1) wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 90b81ddb1d..c9838d75f1 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1090,13 +1090,13 @@ class StockEntry(StockController): "is_finished_item": 1 } - if self.work_order and self.pro_doc.has_batch_no: + if self.work_order and self.pro_doc.has_batch_no and cint(frappe.db.get_single_value('Manufacturing Settings', + 'make_serial_no_batch_from_work_order', cache=True)): self.set_batchwise_finished_goods(args, item) else: - self.add_finisged_goods(args, item) + self.add_finished_goods(args, item) def set_batchwise_finished_goods(self, args, item): - qty = flt(self.fg_completed_qty) filters = { "reference_name": self.pro_doc.name, "reference_doctype": self.pro_doc.doctype, @@ -1105,7 +1105,17 @@ class StockEntry(StockController): fields = ["qty_to_produce as qty", "produced_qty", "name"] - for row in frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc"): + data = frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc") + + if not data: + self.add_finished_goods(args, item) + else: + self.add_batchwise_finished_good(data, args, item) + + def add_batchwise_finished_good(self, data, args, item): + qty = flt(self.fg_completed_qty) + + for row in data: batch_qty = flt(row.qty) - flt(row.produced_qty) if not batch_qty: continue @@ -1121,9 +1131,9 @@ class StockEntry(StockController): args["qty"] = fg_qty args["batch_no"] = row.name - self.add_finisged_goods(args, item) + self.add_finished_goods(args, item) - def add_finisged_goods(self, args, item): + def add_finished_goods(self, args, item): self.add_to_stock_entry_detail({ item.name: args }, bom_no = self.bom_no) From d319e1088352cc911b54b57b4e3c89c8200a52fe Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 15 Jul 2021 16:49:55 +0530 Subject: [PATCH 049/253] fix: set default operation time to 0 (#26511) --- erpnext/manufacturing/doctype/sub_operation/sub_operation.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json index f63d2b9864..10cee32398 100644 --- a/erpnext/manufacturing/doctype/sub_operation/sub_operation.json +++ b/erpnext/manufacturing/doctype/sub_operation/sub_operation.json @@ -19,6 +19,7 @@ "options": "Operation" }, { + "default": "0", "description": "Time in mins", "fieldname": "time_in_mins", "fieldtype": "Float", @@ -38,7 +39,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-12-07 18:09:18.005578", + "modified": "2021-07-15 16:39:41.635362", "modified_by": "Administrator", "module": "Manufacturing", "name": "Sub Operation", From 26a9d385472d90dccf067ded8b0929447fad8033 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 15 Jul 2021 16:50:41 +0530 Subject: [PATCH 050/253] fix: WIP needs to be set before submit on skip_transfer (#26500) --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 779ae42d65..0a8e5329c1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -239,7 +239,7 @@ class WorkOrder(Document): self.create_serial_no_batch_no() def on_submit(self): - if not self.wip_warehouse: + if not self.wip_warehouse and not self.skip_transfer: frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) if not self.fg_warehouse: frappe.throw(_("For Warehouse is required before Submit")) From 9b9b18c28622a84795e656a203ae6470ab8c7ba7 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 15 Jul 2021 18:11:22 +0530 Subject: [PATCH 051/253] fix: improving ux for additional discount field (#26502) --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index f7b2c1d93c..6e36d2809a 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -367,15 +367,16 @@ erpnext.PointOfSale.ItemCart = class { `
` ); const me = this; + const frm = me.events.get_frm(); + let discount = frm.doc.additional_discount_percentage; this.discount_field = frappe.ui.form.make_control({ df: { label: __('Discount'), fieldtype: 'Data', - placeholder: __('Enter discount percentage.'), + placeholder: ( discount ? discount + '%' : __('Enter discount percentage.') ), input_class: 'input-xs', onchange: function() { - const frm = me.events.get_frm(); if (flt(this.value) != 0) { frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value)); me.hide_discount_control(this.value); From b164070a4f0e73ca82bdc2b0c4a673e8e6a602e3 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 15 Jul 2021 19:31:59 +0530 Subject: [PATCH 052/253] ci: make semgrep ignore existing errors (bp #26516) --- .../semgrep_rules/frappe_correctness.yml | 2 - .github/workflows/semgrep.yml | 38 ++++++------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index faab3344a6..d9603e89aa 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -98,8 +98,6 @@ rules: languages: [python] severity: WARNING paths: - exclude: - - test_*.py include: - "*/**/doctype/*" diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 389524e968..701c5c7cbe 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -1,34 +1,20 @@ name: Semgrep on: - pull_request: - branches: - - develop - - version-13-hotfix - - version-13-pre-release + pull_request: { } + push: + branches: ["develop"] + jobs: semgrep: name: Frappe Linter runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Setup python3 - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Setup semgrep - run: | - python -m pip install -q semgrep - git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q - - - name: Semgrep errors - run: | - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - [[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files - semgrep --config="r/python.lang.correctness" --quiet --error $files - - - name: Semgrep warnings - run: | - files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF) - [[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files + - uses: actions/checkout@v2 + - uses: returntocorp/semgrep-action@v1 + env: + SEMGREP_TIMEOUT: 120 + with: + config: >- + r/python.lang.correctness + .github/helper/semgrep_rules From 627a8a8cfd86ed5cd990d455d145d357f1ec8f8d Mon Sep 17 00:00:00 2001 From: Ankush Date: Fri, 16 Jul 2021 13:03:18 +0530 Subject: [PATCH 053/253] chore: disable semgrep on push events (bp #26523) --- .github/workflows/semgrep.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 701c5c7cbe..e27b406df0 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -2,8 +2,6 @@ name: Semgrep on: pull_request: { } - push: - branches: ["develop"] jobs: semgrep: From 13e9aa59564d5230aee949049bff32170646b486 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jul 2021 16:32:23 +0530 Subject: [PATCH 054/253] fix: added patch to fix missing FG item --- erpnext/patches.txt | 1 + .../add_missing_fg_item_for_stock_entry.py | 110 ++++++++++++++++++ .../repost_item_valuation.py | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 4 + 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 29376f00a1..f63c7edea2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -291,3 +291,4 @@ erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef +erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py new file mode 100644 index 0000000000..48999e6f99 --- /dev/null +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -0,0 +1,110 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.utils import cstr, flt, cint +from erpnext.stock.stock_ledger import make_sl_entries +from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + +def execute(): + if not frappe.db.has_column('Work Order', 'has_batch_no'): + return + + if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')): + return + + frappe.reload_doc('manufacturing', 'doctype', 'work_order') + filters = { + 'docstatus': 1, + 'produced_qty': ('>', 0), + 'creation': ('>=', '2021-06-29 00:00:00'), + 'has_batch_no': 1 + } + + fields = ['name', 'production_item'] + + work_orders = [d.name for d in frappe.get_all('Work Order', filters = filters, fields=fields)] + + if not work_orders: + return + + repost_stock_entries = [] + stock_entries = frappe.db.sql_list(''' + SELECT + se.name + FROM + `tabStock Entry` se + WHERE + se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in {work_orders} + and not exists( + select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1 + ) + Order BY + se.posting_date, se.posting_time + '''.format(work_orders=tuple(work_orders))) + + if stock_entries: + print('Length of stock entries', len(stock_entries)) + + for stock_entry in stock_entries: + doc = frappe.get_doc('Stock Entry', stock_entry) + doc.set_work_order_details() + doc.load_items_from_bom() + doc.calculate_rate_and_amount() + set_expense_account(doc) + doc.make_batches('t_warehouse') + + if doc.docstatus == 0: + doc.save() + else: + repost_stock_entry(doc) + repost_stock_entries.append(doc) + + for repost_doc in repost_stock_entries: + repost_future_sle_and_gle(repost_doc) + +def set_expense_account(doc): + for row in doc.items: + if row.is_finished_item and not row.expense_account: + row.expense_account = frappe.get_cached_value('Company', doc.company, 'stock_adjustment_account') + +def repost_stock_entry(doc): + doc.db_update() + for child_row in doc.items: + if child_row.is_finished_item: + child_row.db_update() + + sl_entries = [] + finished_item_row = doc.get_finished_item_row() + get_sle_for_target_warehouse(doc, sl_entries, finished_item_row) + + if sl_entries: + try: + make_sl_entries(sl_entries, True) + except Exception: + print(f'SLE entries not posted for the stock entry {doc.name}') + traceback = frappe.get_traceback() + frappe.log_error(traceback) + +def get_sle_for_target_warehouse(doc, sl_entries, finished_item_row): + for d in doc.get('items'): + if cstr(d.t_warehouse) and finished_item_row and d.name == finished_item_row.name: + sle = doc.get_sl_entries(d, { + "warehouse": cstr(d.t_warehouse), + "actual_qty": flt(d.transfer_qty), + "incoming_rate": flt(d.valuation_rate) + }) + + sle.recalculate_rate = 1 + sl_entries.append(sle) + +def repost_future_sle_and_gle(doc): + args = frappe._dict({ + "posting_date": doc.posting_date, + "posting_time": doc.posting_time, + "voucher_type": doc.doctype, + "voucher_no": doc.name, + "company": doc.company + }) + + create_repost_item_valuation_entry(args) \ No newline at end of file diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 55f2ebb224..5f31d9caf0 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -133,6 +133,6 @@ def repost_entries(): def get_repost_item_valuation_entries(): return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation` - WHERE status != 'Completed' and creation <= %s and docstatus = 1 + WHERE status in ('Queued', 'In Progress') and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc """, now(), as_dict=1) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index c9838d75f1..872b1d0516 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -719,6 +719,10 @@ class StockEntry(StockController): frappe.throw(_("Multiple items cannot be marked as finished item")) if self.purpose == "Manufacture": + if not finished_items: + frappe.throw(_('Finished Good has not set in the stock entry {0}') + .format(self.name)) + allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")) From 3362c080b634401f4cd26c2775320ac3b2c8910c Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Fri, 16 Jul 2021 15:00:08 +0530 Subject: [PATCH 055/253] fix: validation check when no conversion_factor (#26527) --- erpnext/stock/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 8a6a3a3e4a..b57b2aa6b8 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -314,13 +314,16 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto for row_idx, row in enumerate(result): data = row.items() if is_dict_obj else enumerate(row) for key, value in data: - if key not in convertible_columns or not conversion_factors[row_idx-1]: + if key not in convertible_columns: continue + # If no conversion factor for the UOM, defaults to 1 + if not conversion_factors[row_idx]: + conversion_factors[row_idx] = 1 if convertible_columns.get(key) == 'rate': - new_value = flt(value) * conversion_factors[row_idx-1] + new_value = flt(value) * conversion_factors[row_idx] else: - new_value = flt(value) / conversion_factors[row_idx-1] + new_value = flt(value) / conversion_factors[row_idx] if not is_dict_obj: row.insert(key+1, new_value) @@ -386,4 +389,4 @@ def is_reposting_item_valuation_in_progress(): reposting_in_progress = frappe.db.exists("Repost Item Valuation", {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]}) if reposting_in_progress: - frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) \ No newline at end of file + frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1) From d8ed9dfcf476214a3171ef4f7aaeac884fb99c25 Mon Sep 17 00:00:00 2001 From: Ankush Date: Sat, 17 Jul 2021 12:49:56 +0530 Subject: [PATCH 056/253] chore: update CODEOWNERS (#26536) (#26537) --- CODEOWNERS | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 7cf65a7a73..219b6bb782 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,16 +3,33 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -manufacturing/ @rohitwaghchaure @marination -accounts/ @deepeshgarg007 @nextchamp-saqib -loan_management/ @deepeshgarg007 @rohitwaghchaure -pos* @nextchamp-saqib @rohitwaghchaure -assets/ @nextchamp-saqib @deepeshgarg007 -stock/ @marination @rohitwaghchaure -buying/ @marination @deepeshgarg007 -hr/ @Anurag810 @rohitwaghchaure -projects/ @hrwX @nextchamp-saqib -support/ @hrwX @marination -healthcare/ @ruchamahabal @marination -erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib -requirements.txt @gavindsouza +erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 +erpnext/assets/ @nextchamp-saqib @deepeshgarg007 +erpnext/erpnext_integrations/ @nextchamp-saqib +erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007 +erpnext/regional @nextchamp-saqib @deepeshgarg007 +erpnext/selling @nextchamp-saqib @deepeshgarg007 +erpnext/support/ @nextchamp-saqib @deepeshgarg007 +pos* @nextchamp-saqib + +erpnext/buying/ @marination @rohitwaghchaure @ankush +erpnext/e_commerce/ @marination +erpnext/maintenance/ @marination @rohitwaghchaure +erpnext/manufacturing/ @marination @rohitwaghchaure @ankush +erpnext/portal/ @marination +erpnext/quality_management/ @marination @rohitwaghchaure +erpnext/shopping_cart/ @marination +erpnext/stock/ @marination @rohitwaghchaure @ankush + +erpnext/crm/ @ruchamahabal +erpnext/education/ @ruchamahabal +erpnext/healthcare/ @ruchamahabal +erpnext/hr/ @ruchamahabal +erpnext/non_profit/ @ruchamahabal +erpnext/payroll @ruchamahabal +erpnext/projects/ @ruchamahabal + +erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination + +.github/ @surajshetty3416 @ankush +requirements.txt @gavindsouza From cde0cc06785bb56521d4d1a13d23476070b3ac94 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sat, 17 Jul 2021 22:53:21 +0530 Subject: [PATCH 057/253] fix: Fetch Payment Terms from Sales/Purchase Orders --- .../purchase_invoice/purchase_invoice.js | 19 -------- .../purchase_invoice/purchase_invoice.py | 43 ------------------- .../doctype/purchase_order/purchase_order.py | 12 +++--- erpnext/controllers/accounts_controller.py | 43 +++++++++++++++++++ .../doctype/sales_order/sales_order.py | 6 +++ .../doctype/delivery_note/delivery_note.py | 6 +++ .../purchase_receipt/purchase_receipt.py | 5 +++ 7 files changed, 66 insertions(+), 68 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 10b83c027f..54b10f583f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -508,8 +508,6 @@ frappe.ui.form.on("Purchase Invoice", { } } } - - frm.events.set_payment_terms(frm); }, refresh: function(frm) { @@ -572,21 +570,4 @@ frappe.ui.form.on("Purchase Invoice", { company: function(frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); }, - - set_payment_terms: function (frm) { - frappe.call({ - 'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.set_payment_terms_from_po', - 'args': { - doc: frm.doc - }, - 'callback': (r) => { - if (r.message) { - var doc = frappe.model.sync(r.message)[0]; - console.log("doc: ", doc) - frappe.set_route("Form", doc.doctype, doc.name); - } - } - - }); - }, }) \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f30af0c349..10c6cd6a49 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1173,49 +1173,6 @@ class PurchaseInvoice(BuyingController): if update: self.db_set('status', self.status, update_modified = update_modified) -@frappe.whitelist() -def set_payment_terms_from_po(doc): - if isinstance(doc, six.string_types): - doc = json.loads(doc) - - purchase_order = doc.get('items')[0].get('purchase_order') - - if purchase_order and all_items_have_same_po(doc, purchase_order): - purchase_order = frappe.get_cached_doc('Purchase Order', purchase_order) - else: - return - - if has_default_payment_terms(doc) and not has_default_payment_terms(purchase_order): - doc['payment_schedule'] = [] - doc['payment_terms_template'] = purchase_order.payment_terms_template - - for schedule in purchase_order.payment_schedule: - payment_schedule = { - 'payment_term': schedule.payment_term, - 'due_date': schedule.due_date, - 'invoice_portion': schedule.invoice_portion, - 'discount_type': schedule.discount_type, - 'discount': schedule.discount, - 'base_payment_amount': schedule.base_payment_amount, - 'payment_amount': schedule.payment_amount, - 'outstanding': schedule.outstanding - } - doc['payment_schedule'].append(payment_schedule) - - return doc - -def all_items_have_same_po(doc, purchase_order): - for item in doc.get('items'): - if item.get('purchase_order') != purchase_order: - return False - - return True - -def has_default_payment_terms(doc): - if doc.get('payment_schedule')[0].get('invoice_portion') == 100: - return True - return False - # to get details of purchase invoice/receipt from which this doc was created for exchange rate difference handling def get_purchase_document_details(doc): if doc.doctype == 'Purchase Invoice': diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index eaa502ff7f..a0bac51046 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -443,6 +443,8 @@ def make_purchase_invoice_from_portal(purchase_order_name): frappe.response.location = '/purchase-invoices/' + doc.name def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False): + from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order + def postprocess(source, target): target.flags.ignore_permissions = ignore_permissions set_missing_values(source, target) @@ -489,15 +491,13 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions }, } - if frappe.get_single("Accounts Settings").automatically_fetch_payment_terms == 1: - fields["Payment Schedule"] = { - "doctype": "Payment Schedule", - "add_if_empty": True - } - doc = get_mapped_doc("Purchase Order", source_name, fields, target_doc, postprocess, ignore_permissions=ignore_permissions) + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) + if automatically_fetch_payment_terms: + fetch_payment_terms_from_order(doc) + return doc @frappe.whitelist() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 1c086e9edc..d38c2cbdb5 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1730,3 +1730,46 @@ def validate_regional(doc): @erpnext.allow_regional def validate_einvoice_fields(doc): pass + +def fetch_payment_terms_from_order(doc): + """ + Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice. + """ + + if doc.doctype == "Sales Invoice": + po_or_so = doc.get('items')[0].get('sales_order') + po_or_so_doctype = "Sales Order" + po_or_so_doctype_name = "sales_order" + else: + po_or_so = doc.get('items')[0].get('purchase_order') + po_or_so_doctype = "Purchase Order" + po_or_so_doctype_name = "purchase_order" + + if po_or_so and all_items_have_same_po_or_so(doc, po_or_so, po_or_so_doctype_name): + po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so) + else: + doc.set_payment_schedule() + return + + doc.payment_schedule = [] + doc.payment_terms_template = po_or_so.payment_terms_template + + for schedule in po_or_so.payment_schedule: + payment_schedule = { + 'payment_term': schedule.payment_term, + 'due_date': schedule.due_date, + 'invoice_portion': schedule.invoice_portion, + 'discount_type': schedule.discount_type, + 'discount': schedule.discount, + 'base_payment_amount': schedule.base_payment_amount, + 'payment_amount': schedule.payment_amount, + 'outstanding': schedule.outstanding + } + doc.append("payment_schedule", payment_schedule) + +def all_items_have_same_po_or_so(doc, po_or_so, po_or_so_fieldname): + for item in doc.get('items'): + if item.get(po_or_so_fieldname) != po_or_so: + return False + + return True \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 41f57a34d3..a58c381df3 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -621,6 +621,8 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): + from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order + def postprocess(source, target): set_missing_values(source, target) #Get the advance paid Journal Entries in Sales Invoice Advance @@ -693,6 +695,10 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): } }, target_doc, postprocess, ignore_permissions=ignore_permissions) + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) + if automatically_fetch_payment_terms: + fetch_payment_terms_from_order(doclist) + return doclist @frappe.whitelist() diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 4808e948fc..1628f93019 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -414,6 +414,8 @@ def get_returned_qty_map(delivery_note): @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None): + from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order + doc = frappe.get_doc('Delivery Note', source_name) to_make_invoice_qty_map = {} @@ -503,6 +505,10 @@ def make_sales_invoice(source_name, target_doc=None): } }, target_doc, set_missing_values) + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) + if automatically_fetch_payment_terms: + fetch_payment_terms_from_order(doc) + return doc @frappe.whitelist() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 41800e3715..6d72a5fa0b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -604,6 +604,7 @@ def update_billing_percentage(pr_doc, update_modified=True): @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): from erpnext.accounts.party import get_payment_terms_template + from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order doc = frappe.get_doc('Purchase Receipt', source_name) returned_qty_map = get_returned_qty_map(source_name) @@ -675,6 +676,10 @@ def make_purchase_invoice(source_name, target_doc=None): } }, target_doc, set_missing_values) + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) + if automatically_fetch_payment_terms: + fetch_payment_terms_from_order(doclist) + return doclist def get_invoiced_qty_map(purchase_receipt): From 831f5edd027173f843d4475986bda3fd31589201 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sat, 17 Jul 2021 22:55:24 +0530 Subject: [PATCH 058/253] fix: Remove unused imports --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 10c6cd6a49..c1cc092554 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -7,8 +7,6 @@ import frappe, erpnext from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate, get_link_to_form from frappe import _, throw import frappe.defaults -import json -import six from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.controllers.buying_controller import BuyingController From d5ff6361594e771c399c7b8f8384253f49ba434b Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 17 Jul 2021 14:42:38 -0400 Subject: [PATCH 059/253] fix: missing parameter 'country' --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 4fd8413d83..8456b49c8e 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -391,5 +391,5 @@ def set_default_accounts(company): }) company.save() - install_country_fixtures(company.name) + install_country_fixtures(company.name, company.country) company.create_default_tax_template() From 2686d04800fe3d7450e9eb4a1f8bbdb070c66aa3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 18 Jul 2021 17:54:35 +0530 Subject: [PATCH 060/253] fix: Typo and remove duplicate function --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index c375dacb8b..cbb3dc881f 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -183,16 +183,6 @@ def make_item_tax_template(company_name, template): doc.insert(ignore_permissions=True) return doc -def make_tax_category(tax_category): - """ Make tax category based on title if not already created """ - doctype = 'Tax Category' - if not frappe.db.exists(doctype, tax_category['title']): - tax_category['doctype'] = doctype - doc = frappe.get_doc(tax_category) - doc.flags.ignore_links = True - doc.flags.ignore_validate = True - doc.insert(ignore_permissions=True) - def get_or_create_account(company_name, account): """ Check if account already exists. If not, create it. @@ -284,7 +274,7 @@ def get_or_create_tax_group(company_name, root_type): return tax_group_name -def make_tax_catgory(tax_category): +def make_tax_category(tax_category): doctype = 'Tax Category' if isinstance(tax_category, str): tax_category = {'title': tax_category} From b24a149dbc5bbf9e5213d283cce76fd678ae2710 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 19 Jul 2021 14:37:12 +0530 Subject: [PATCH 061/253] test: Updated test case for Eway bill --- .../sales_invoice/test_sales_invoice.py | 27 +++++++++++++++++++ erpnext/regional/india/utils.py | 4 +-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index fe531d3b22..6d31c12be6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1908,6 +1908,8 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['sgstValue'], 5400) self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) + self.assertEqual(data['billLists'][0]['actualFromStateCode'],7) + self.assertEqual(data['billLists'][0]['fromStateCode'],27) def test_einvoice_submission_without_irn(self): # init @@ -2061,6 +2063,30 @@ def make_test_address_for_ewaybill(): address.save() + if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'): + address = frappe.get_doc({ + "address_line1": "_Test Dispatch Address Line 1", + "address_title": "_Test Dispatch-Address for Eway bill", + "address_type": "Shipping", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 0, + "phone": "+910000000000", + "gstin": "07AAACC1206D1ZI", + "gst_state": "Delhi", + "gst_state_number": "07", + "pincode": "1100101" + }).insert() + + address.append("links", { + "link_doctype": "Company", + "link_name": "_Test Company" + }) + + address.save() + def make_test_transporter_for_ewaybill(): if not frappe.db.exists('Supplier', '_Test Transporter'): frappe.get_doc({ @@ -2099,6 +2125,7 @@ def make_sales_invoice_for_ewaybill(): si.distance = 2000 si.company_address = "_Test Address for Eway bill-Billing" si.customer_address = "_Test Customer-Address for Eway bill-Shipping" + si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping" si.vehicle_no = "KA12KA1234" si.gst_category = "Registered Regular" si.mode_of_transport = 'Road' diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 61f5a0578e..fbe47d0532 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -432,7 +432,7 @@ def get_ewb_data(dt, dn): billing_address = frappe.get_doc('Address', doc.customer_address) #added dispatch address - dispatch_address = frappe.get_doc('Address', doc.dispatch_address_name) + dispatch_address = frappe.get_doc('Address', doc.dispatch_address_name) if doc.dispatch_address_name else company_address shipping_address = frappe.get_doc('Address', doc.shipping_address_name) data = get_address_details(data, doc, company_address, billing_address, dispatch_address) @@ -524,7 +524,7 @@ def get_gstins_for_company(company): def get_address_details(data, doc, company_address, billing_address, dispatch_address): data.fromPincode = validate_pincode(company_address.pincode, 'Company Address') data.fromStateCode = validate_state_code(company_address.gst_state_number, 'Company Address') - data.actualFromStateCode = validate_state_code(dispatch_address.gst_state_number, 'Company Address') + data.actualFromStateCode = validate_state_code(dispatch_address.gst_state_number, 'Dispatch Address') if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15: data.toGstin = 'URP' From 23b1c25ff5652b120585bab11d1a0e33a3ba24a2 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 19 Jul 2021 20:09:37 +0530 Subject: [PATCH 062/253] fix:Ignore mandatory fields while creating payment reconciliation Journal Entry --- .../doctype/payment_reconciliation/payment_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 6635128f9e..d788d91855 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company): } ] }) - + jv.flags.ignore_mandatory = True jv.submit() \ No newline at end of file From 80e269887d5e23845a48723a73573249233c0ee6 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 19 Jul 2021 20:42:44 +0530 Subject: [PATCH 063/253] fix(ux): item description should fall back to name (#26339) Don't set item description = item code from front end. This is already being set to item_name in before_insert and item_name is better fallback than item code for description. Also fixed wrong condition for erasing description while duplicating item. --- erpnext/stock/doctype/item/item.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 568f0ef451..87bd9e61fe 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -100,10 +100,11 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__('Duplicate'), function() { var new_item = frappe.model.copy_doc(frm.doc); - if(new_item.item_name===new_item.item_code) { + // Duplicate item could have different name, causing "copy paste" error. + if (new_item.item_name===new_item.item_code) { new_item.item_name = null; } - if(new_item.description===new_item.description) { + if (new_item.item_code===new_item.description || new_item.item_code===new_item.description) { new_item.description = null; } frappe.set_route('Form', 'Item', new_item.name); @@ -186,8 +187,6 @@ frappe.ui.form.on("Item", { item_code: function(frm) { if(!frm.doc.item_name) frm.set_value("item_name", frm.doc.item_code); - if(!frm.doc.description) - frm.set_value("description", frm.doc.item_code); }, is_stock_item: function(frm) { From 92273cade025ec4fc782905a702fe6a2d454474e Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 19 Jul 2021 20:44:05 +0530 Subject: [PATCH 064/253] fix(ux): item description should fall back to name (#26339) (#26552) Don't set item description = item code from front end. This is already being set to item_name in before_insert and item_name is better fallback than item code for description. Also fixed wrong condition for erasing description while duplicating item. --- erpnext/stock/doctype/item/item.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index b55374b8d8..264baeaa47 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -100,10 +100,11 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__('Duplicate'), function() { var new_item = frappe.model.copy_doc(frm.doc); - if(new_item.item_name===new_item.item_code) { + // Duplicate item could have different name, causing "copy paste" error. + if (new_item.item_name===new_item.item_code) { new_item.item_name = null; } - if(new_item.description===new_item.description) { + if (new_item.item_code===new_item.description || new_item.item_code===new_item.description) { new_item.description = null; } frappe.set_route('Form', 'Item', new_item.name); @@ -186,8 +187,6 @@ frappe.ui.form.on("Item", { item_code: function(frm) { if(!frm.doc.item_name) frm.set_value("item_name", frm.doc.item_code); - if(!frm.doc.description) - frm.set_value("description", frm.doc.item_code); }, is_stock_item: function(frm) { From c077314568eec088758e0d1292b97cb21f253d52 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 20 Jul 2021 09:57:18 +0530 Subject: [PATCH 065/253] fix: Pass doc and other parameters to properly prefill information - while creating customer from form dashboard --- erpnext/public/js/utils/customer_quick_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/customer_quick_entry.js b/erpnext/public/js/utils/customer_quick_entry.js index efb8dd9d5c..d2c5c721cc 100644 --- a/erpnext/public/js/utils/customer_quick_entry.js +++ b/erpnext/public/js/utils/customer_quick_entry.js @@ -1,8 +1,8 @@ frappe.provide('frappe.ui.form'); frappe.ui.form.CustomerQuickEntryForm = class CustomerQuickEntryForm extends frappe.ui.form.QuickEntryForm { - constructor(doctype, after_insert) { - super(doctype, after_insert); + constructor(doctype, after_insert, init_callback, doc, force) { + super(doctype, after_insert, init_callback, doc, force); this.skip_redirect_on_error = true; } From 85c8daae9c83b3045735c85d31b14e0e1949bafd Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 20 Jul 2021 09:57:18 +0530 Subject: [PATCH 066/253] fix: Pass doc and other parameters to properly prefill information - while creating customer from form dashboard --- erpnext/public/js/utils/customer_quick_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/customer_quick_entry.js b/erpnext/public/js/utils/customer_quick_entry.js index ebe6cd98f8..7bd21df67b 100644 --- a/erpnext/public/js/utils/customer_quick_entry.js +++ b/erpnext/public/js/utils/customer_quick_entry.js @@ -1,9 +1,9 @@ frappe.provide('frappe.ui.form'); frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ - init: function(doctype, after_insert) { + init: function(doctype, after_insert, init_callback, doc, force) { + this._super(doctype, after_insert, init_callback, doc, force); this.skip_redirect_on_error = true; - this._super(doctype, after_insert); }, render_dialog: function() { From 47f200a70d050ef44258ba7a927d067e2bd8fd8d Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Tue, 20 Jul 2021 16:13:49 +0530 Subject: [PATCH 067/253] chore: Update stale.yml reduce `daysUntilStale` & `daysUntilClose` to keep the contributors active. --- .github/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index dabc66eb73..9322ae87bf 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,11 +1,11 @@ # Configuration for probot-stale - https://github.com/probot/stale # Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 30 +daysUntilStale: 15 # Number of days of inactivity before a stale Issue or Pull Request is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 7 +daysUntilClose: 3 # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: From c14aa45720507515f6e8ea73f7053ca5371df656 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jul 2021 18:19:15 +0530 Subject: [PATCH 068/253] fix: incorrect valuation rate calculation in gross profit report --- erpnext/accounts/report/gross_profit/gross_profit.py | 3 ++- erpnext/controllers/queries.py | 1 + erpnext/manufacturing/doctype/bom/bom.py | 2 +- erpnext/stock/doctype/batch/batch.py | 6 +++--- erpnext/stock/doctype/pick_list/pick_list.py | 1 + erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.py | 4 ++-- .../supplier_wise_sales_analytics.py | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 84c74543da..6d8623c189 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -241,6 +241,7 @@ class GrossProfitGenerator(object): sle.voucher_detail_no == row.item_row: previous_stock_value = len(my_sle) > i+1 and \ flt(my_sle[i+1].stock_value) or 0.0 + if previous_stock_value: return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty)) else: @@ -335,7 +336,7 @@ class GrossProfitGenerator(object): res = frappe.db.sql("""select item_code, voucher_type, voucher_no, voucher_detail_no, stock_value, warehouse, actual_qty as qty from `tabStock Ledger Entry` - where company=%(company)s + where company=%(company)s and is_cancelled = 0 order by item_code desc, warehouse desc, posting_date desc, posting_time desc, creation desc""", self.filters, as_dict=True) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 280319321f..21c052a391 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -407,6 +407,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): INNER JOIN `tabBatch` batch on sle.batch_no = batch.name where batch.disabled = 0 + and sle.is_cancelled = 0 and sle.item_code = %(item_code)s and sle.warehouse = %(warehouse)s and (sle.batch_no like %(txt)s diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 9da461f497..2fbbca4b19 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -748,7 +748,7 @@ def get_valuation_rate(args): if valuation_rate <= 0: last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` - where item_code = %s and valuation_rate > 0 + where item_code = %s and valuation_rate > 0 and is_cancelled = 0 order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code']) valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index b6eef6ca48..b37ae3f4f6 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -162,19 +162,19 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No out = float(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and batch_no=%s {0}""".format(cond), + where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond), (warehouse, batch_no))[0][0] or 0) if batch_no and not warehouse: out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty from `tabStock Ledger Entry` - where batch_no=%s + where is_cancelled = 0 and batch_no=%s group by warehouse''', batch_no, as_dict=1) if not batch_no and item_code and warehouse: out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty from `tabStock Ledger Entry` - where item_code = %s and warehouse=%s + where is_cancelled = 0 and item_code = %s and warehouse=%s group by batch_no''', (item_code, warehouse), as_dict=1) return out diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index e795742ea4..516ae43089 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -239,6 +239,7 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re and sle.`item_code`=%(item_code)s and sle.`company` = %(company)s and batch.disabled = 0 + and sle.is_cancelled=0 and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s {warehouse_condition} GROUP BY diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 872b1d0516..654755ec2f 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1789,7 +1789,7 @@ def get_expired_batch_items(): from `tabBatch` b, `tabStock Ledger Entry` sle where b.expiry_date <= %s and b.expiry_date is not NULL - and b.batch_id = sle.batch_no + and b.batch_id = sle.batch_no and sle.is_cancelled = 0 group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1) @frappe.whitelist() diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 93482e8bea..b4f458388b 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -60,7 +60,7 @@ class StockLedgerEntry(Document): if self.batch_no and not self.get("allow_negative_stock"): batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` - where warehouse=%s and item_code=%s and batch_no=%s""", + where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""", (self.warehouse, self.item_code, self.batch_no))[0][0]) if batch_bal_after_transaction < 0: @@ -152,7 +152,7 @@ class StockLedgerEntry(Document): last_transaction_time = frappe.db.sql(""" select MAX(timestamp(posting_date, posting_time)) as posting_time from `tabStock Ledger Entry` - where docstatus = 1 and item_code = %s + where docstatus = 1 and is_cancelled = 0 and item_code = %s and warehouse = %s""", (self.item_code, self.warehouse))[0][0] cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00") diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py index 5873a7a300..4108a57554 100644 --- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py +++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py @@ -69,7 +69,7 @@ def get_consumed_details(filters): i.stock_uom, sle.actual_qty, sle.stock_value_difference, sle.voucher_no, sle.voucher_type from `tabStock Ledger Entry` sle, `tabItem` i - where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1): + where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1): consumed_details.setdefault(d.item_code, []).append(d) return consumed_details From 2d225e621fe54db10cb4f18f097e8f11d4c5e517 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 20 Jul 2021 20:41:04 +0530 Subject: [PATCH 069/253] fix: Price list rate not fetched for return sales invoice fixed (#26560) Co-authored-by: Subin Tom --- erpnext/stock/get_item_details.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 4657700dbb..cf52803fca 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,9 +74,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - if not doc or cint(doc.get('is_return')) == 0: - # get price list rate only if the invoice is not a credit or debit note - get_price_list_rate(args, item, out) + + get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) From 41705acbd91be747f75b7eec583c5c5264164ed4 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 20 Jul 2021 20:48:57 +0530 Subject: [PATCH 070/253] fix: delete child docs when parent doc is deleted (#26518) * fix: Make code more readable * fix: Delete child table info when parent doc is deleted * fix: Sider issues * fix: Remove trailing whitespace --- .../transaction_deletion_record.py | 186 +++++++++++------- 1 file changed, 112 insertions(+), 74 deletions(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index ece9fb5699..c3db27f81c 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -12,10 +12,14 @@ from frappe.desk.notifications import clear_notifications class TransactionDeletionRecord(Document): def validate(self): frappe.only_for('System Manager') + self.validate_doctypes_to_be_ignored() + + def validate_doctypes_to_be_ignored(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() for doctype in self.doctypes_to_be_ignored: if doctype.doctype_name not in doctypes_to_be_ignored_list: - frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed")) + frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."), + title=_("Not Allowed")) def before_submit(self): if not self.doctypes_to_be_ignored: @@ -23,54 +27,9 @@ class TransactionDeletionRecord(Document): self.delete_bins() self.delete_lead_addresses() - - company_obj = frappe.get_doc('Company', self.company) - # reset company values - company_obj.total_monthly_sales = 0 - company_obj.sales_monthly_history = None - company_obj.save() - # Clear notification counts + self.reset_company_values() clear_notifications() - - singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') - tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') - doctypes_to_be_ignored_list = singles - for doctype in self.doctypes_to_be_ignored: - doctypes_to_be_ignored_list.append(doctype.doctype_name) - - docfields = frappe.get_all('DocField', - filters = { - 'fieldtype': 'Link', - 'options': 'Company', - 'parent': ['not in', doctypes_to_be_ignored_list]}, - fields=['parent', 'fieldname']) - - for docfield in docfields: - if docfield['parent'] != self.doctype: - no_of_docs = frappe.db.count(docfield['parent'], { - docfield['fieldname'] : self.company - }) - - if no_of_docs > 0: - self.delete_version_log(docfield['parent'], docfield['fieldname']) - self.delete_communications(docfield['parent'], docfield['fieldname']) - - # populate DocTypes table - if docfield['parent'] not in tables: - self.append('doctypes', { - 'doctype_name' : docfield['parent'], - 'no_of_docs' : no_of_docs - }) - - # delete the docs linked with the specified company - frappe.db.delete(docfield['parent'], { - docfield['fieldname'] : self.company - }) - - naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') - if naming_series: - if '#' in naming_series: - self.update_naming_series(naming_series, docfield['parent']) + self.delete_company_transactions() def populate_doctypes_to_be_ignored_table(self): doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() @@ -79,6 +38,111 @@ class TransactionDeletionRecord(Document): 'doctype_name' : doctype }) + def delete_bins(self): + frappe.db.sql("""delete from tabBin where warehouse in + (select name from tabWarehouse where company=%s)""", self.company) + + def delete_lead_addresses(self): + """Delete addresses to which leads are linked""" + leads = frappe.get_all('Lead', filters={'company': self.company}) + leads = ["'%s'" % row.get("name") for row in leads] + addresses = [] + if leads: + addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name + in ({leads})""".format(leads=",".join(leads))) + + if addresses: + addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] + + frappe.db.sql("""delete from tabAddress where name in ({addresses}) and + name not in (select distinct dl1.parent from `tabDynamic Link` dl1 + inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent + and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) + + frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' + and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) + + frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) + + def reset_company_values(self): + company_obj = frappe.get_doc('Company', self.company) + company_obj.total_monthly_sales = 0 + company_obj.sales_monthly_history = None + company_obj.save() + + def delete_company_transactions(self): + doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() + docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) + + tables = self.get_all_child_doctypes() + for docfield in docfields: + if docfield['parent'] != self.doctype: + no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname']) + + if no_of_docs > 0: + self.delete_version_log(docfield['parent'], docfield['fieldname']) + self.delete_communications(docfield['parent'], docfield['fieldname']) + self.populate_doctypes_table(tables, docfield['parent'], no_of_docs) + + self.delete_child_tables(docfield['parent'], docfield['fieldname']) + self.delete_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname']) + + naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname') + if naming_series: + if '#' in naming_series: + self.update_naming_series(naming_series, docfield['parent']) + + def get_doctypes_to_be_ignored_list(self): + singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name') + doctypes_to_be_ignored_list = singles + for doctype in self.doctypes_to_be_ignored: + doctypes_to_be_ignored_list.append(doctype.doctype_name) + + return doctypes_to_be_ignored_list + + def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list): + docfields = frappe.get_all('DocField', + filters = { + 'fieldtype': 'Link', + 'options': 'Company', + 'parent': ['not in', doctypes_to_be_ignored_list]}, + fields=['parent', 'fieldname']) + + return docfields + + def get_all_child_doctypes(self): + return frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name') + + def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname): + return frappe.db.count(doctype, {company_fieldname : self.company}) + + def populate_doctypes_table(self, tables, doctype, no_of_docs): + if doctype not in tables: + self.append('doctypes', { + 'doctype_name' : doctype, + 'no_of_docs' : no_of_docs + }) + + def delete_child_tables(self, doctype, company_fieldname): + parent_docs_to_be_deleted = frappe.get_all(doctype, { + company_fieldname : self.company + }, pluck = 'name') + + child_tables = frappe.get_all('DocField', filters = { + 'fieldtype': 'Table', + 'parent': doctype + }, pluck = 'options') + + for table in child_tables: + frappe.db.delete(table, { + 'parent': ['in', parent_docs_to_be_deleted] + }) + + def delete_docs_linked_with_specified_company(self, doctype, company_fieldname): + frappe.db.delete(doctype, { + company_fieldname : self.company + }) + def update_naming_series(self, naming_series, doctype_name): if '.' in naming_series: prefix, hashes = naming_series.rsplit('.', 1) @@ -107,32 +171,6 @@ class TransactionDeletionRecord(Document): frappe.delete_doc('Communication', communication_names, ignore_permissions=True) - def delete_bins(self): - frappe.db.sql("""delete from tabBin where warehouse in - (select name from tabWarehouse where company=%s)""", self.company) - - def delete_lead_addresses(self): - """Delete addresses to which leads are linked""" - leads = frappe.get_all('Lead', filters={'company': self.company}) - leads = ["'%s'" % row.get("name") for row in leads] - addresses = [] - if leads: - addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name - in ({leads})""".format(leads=",".join(leads))) - - if addresses: - addresses = ["%s" % frappe.db.escape(addr) for addr in addresses] - - frappe.db.sql("""delete from tabAddress where name in ({addresses}) and - name not in (select distinct dl1.parent from `tabDynamic Link` dl1 - inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent - and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))) - - frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead' - and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))) - - frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) - @frappe.whitelist() def get_doctypes_to_be_ignored(): doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget', From 013b3526398311366d103a44c3261eb276e25d9c Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 20 Jul 2021 21:03:11 +0530 Subject: [PATCH 071/253] fix: Price list rate not fetched for return sales invoice fixed (#26559) Co-authored-by: Subin Tom Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- erpnext/stock/get_item_details.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 4657700dbb..cf52803fca 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,9 +74,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - if not doc or cint(doc.get('is_return')) == 0: - # get price list rate only if the invoice is not a credit or debit note - get_price_list_rate(args, item, out) + + get_price_list_rate(args, item, out) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) From a758071532c900a6cdd657d0a4c16df454a1abc7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 21 Jul 2021 00:24:09 +0530 Subject: [PATCH 072/253] feat(Non Profit): API Endpoint to update halted Razorpay subscriptions (#26427) * feat: Update Subscription Activated field to Subscription Status to accomodate Halted status * feat: API Endpoint to halt Razorpay subscription * fix: sider * fix: validation message * test: halted razorpay subscription --- erpnext/non_profit/doctype/member/member.json | 16 ++-- erpnext/non_profit/doctype/member/member.py | 4 +- .../doctype/membership/membership.py | 92 ++++++++++++++----- .../doctype/membership/test_membership.py | 56 +++++++++-- erpnext/patches.txt | 1 + ...date_subscription_status_in_memberships.py | 9 ++ 6 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 erpnext/patches/v13_0/update_subscription_status_in_memberships.py diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index f190cfae75..7c1baf1a8d 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -26,7 +26,7 @@ "razorpay_details_section", "subscription_id", "customer_id", - "subscription_activated", + "subscription_status", "column_break_21", "subscription_start", "subscription_end" @@ -151,12 +151,6 @@ "fieldname": "column_break_21", "fieldtype": "Column Break" }, - { - "default": "0", - "fieldname": "subscription_activated", - "fieldtype": "Check", - "label": "Subscription Activated" - }, { "fieldname": "subscription_start", "fieldtype": "Date", @@ -166,11 +160,17 @@ "fieldname": "subscription_end", "fieldtype": "Date", "label": "Subscription End" + }, + { + "fieldname": "subscription_status", + "fieldtype": "Select", + "label": "Subscription Status", + "options": "\nActive\nHalted" } ], "image_field": "image", "links": [], - "modified": "2020-11-09 12:12:10.174647", + "modified": "2021-07-11 14:27:26.368039", "modified_by": "Administrator", "module": "Non Profit", "name": "Member", diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 30be585e9a..67828d6efc 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -84,7 +84,9 @@ def create_member(user_details): "email_id": user_details.email, "pan_number": user_details.pan or None, "membership_type": user_details.plan_id, - "subscription_id": user_details.subscription_id or None + "customer_id": user_details.customer_id or None, + "subscription_id": user_details.subscription_id or None, + "subscription_status": user_details.subscription_status or "" }) member.insert(ignore_permissions=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index e8ae6187b7..b584116df3 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings): return invoice -def get_member_based_on_subscription(subscription_id, email): - members = frappe.get_all("Member", filters={ - "subscription_id": subscription_id, - "email_id": email - }, order_by="creation desc") +def get_member_based_on_subscription(subscription_id, email=None, customer_id=None): + filters = {"subscription_id": subscription_id} + if email: + filters.update({"email_id": email}) + if customer_id: + filters.update({"customer_id": customer_id}) + + members = frappe.get_all("Member", filters=filters, order_by="creation desc") try: return frappe.get_doc("Member", members[0]["name"]) @@ -209,8 +212,6 @@ def get_member_based_on_subscription(subscription_id, email): def verify_signature(data, endpoint="Membership"): - if frappe.flags.in_test or os.environ.get("CI"): - return True signature = frappe.request.headers.get("X-Razorpay-Signature") settings = frappe.get_doc("Non Profit Settings") @@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"): @frappe.whitelist(allow_guest=True) def trigger_razorpay_subscription(*args, **kwargs): data = frappe.request.get_data(as_text=True) - try: - verify_signature(data) - except Exception as e: - log = frappe.log_error(e, "Membership Webhook Verification Error") - notify_failure(log) - return { "status": "Failed", "reason": e} - - if isinstance(data, six.string_types): - data = json.loads(data) - data = frappe._dict(data) + data = process_request_data(data) subscription = data.payload.get("subscription", {}).get("entity", {}) subscription = frappe._dict(subscription) @@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs): # Update membership values member.subscription_start = datetime.fromtimestamp(subscription.start_at) member.subscription_end = datetime.fromtimestamp(subscription.end_at) - member.subscription_activated = 1 + member.subscription_status = "Active" member.flags.ignore_mandatory = True member.save() @@ -294,9 +286,67 @@ def trigger_razorpay_subscription(*args, **kwargs): message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) - return { "status": "Failed", "reason": e} + return {"status": "Failed", "reason": e} - return { "status": "Success" } + return {"status": "Success"} + + +@frappe.whitelist(allow_guest=True) +def update_halted_razorpay_subscription(*args, **kwargs): + """ + When all retries have been exhausted, Razorpay moves the subscription to the halted state. + The customer has to manually retry the charge or change the card linked to the subscription, + for the subscription to move back to the active state. + """ + if frappe.request: + data = frappe.request.get_data(as_text=True) + data = process_request_data(data) + elif frappe.flags.in_test: + data = kwargs.get("data") + data = frappe._dict(data) + else: + return + + if not data.event == "subscription.halted": + return + + subscription = data.payload.get("subscription", {}).get("entity", {}) + subscription = frappe._dict(subscription) + + try: + member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id) + if not member: + frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id)) + + member.subscription_status = "Halted" + member.flags.ignore_mandatory = True + member.save() + + if subscription.get("notes"): + member = get_additional_notes(member, subscription) + + except Exception as e: + message = "{0}\n\n{1}".format(e, frappe.get_traceback()) + log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name)) + notify_failure(log) + return {"status": "Failed", "reason": e} + + return {"status": "Success"} + + +def process_request_data(data): + try: + verify_signature(data) + except Exception as e: + log = frappe.log_error(e, "Membership Webhook Verification Error") + notify_failure(log) + return {"status": "Failed", "reason": e} + + if isinstance(data, six.string_types): + data = json.loads(data) + data = frappe._dict(data) + + return data def get_company_for_memberships(): @@ -362,4 +412,4 @@ def set_expired_status(): `tabMembership` SET `status` = 'Expired' WHERE `status` not in ('Cancelled') AND `to_date` < %s - """, (nowdate())) \ No newline at end of file + """, (nowdate())) diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py index 31da792e53..0f5a9bed82 100644 --- a/erpnext/non_profit/doctype/membership/test_membership.py +++ b/erpnext/non_profit/doctype/membership/test_membership.py @@ -6,6 +6,7 @@ import unittest import frappe import erpnext from erpnext.non_profit.doctype.member.member import create_member +from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription from frappe.utils import nowdate, add_months class TestMembership(unittest.TestCase): @@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase): plan = setup_membership() # make test member - self.member_doc = create_member(frappe._dict({ - 'fullname': "_Test_Member", - 'email': "_test_member_erpnext@example.com", - 'plan_id': plan.name - })) + self.member_doc = create_member( + frappe._dict({ + "fullname": "_Test_Member", + "email": "_test_member_erpnext@example.com", + "plan_id": plan.name, + "subscription_id": "sub_DEX6xcJ1HSW4CR", + "customer_id": "cust_C0WlbKhp3aLA7W", + "subscription_status": "Active" + }) + ) self.member_doc.make_customer_and_link() self.member = self.member_doc.name @@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase): "to_date": add_months(nowdate(), 3), }) + def test_halted_memberships(self): + make_membership(self.member, { + "from_date": add_months(nowdate(), 2), + "to_date": add_months(nowdate(), 3) + }) + + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active") + payload = get_subscription_payload() + update_halted_razorpay_subscription(data=payload) + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted") + + def tearDown(self): + frappe.db.rollback() + def set_config(key, value): frappe.db.set_value("Non Profit Settings", None, key, value) @@ -115,4 +135,28 @@ def setup_membership(): else: plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm") - return plan \ No newline at end of file + return plan + +def get_subscription_payload(): + return { + "entity": "event", + "account_id": "acc_BFQ7uQEaa7j2z7", + "event": "subscription.halted", + "contains": [ + "subscription" + ], + "payload": { + "subscription": { + "entity": { + "id": "sub_DEX6xcJ1HSW4CR", + "entity": "subscription", + "plan_id": "_rzpy_test_milythm", + "customer_id": "cust_C0WlbKhp3aLA7W", + "status": "halted", + "notes": { + "Important": "Notes for Internal Reference" + }, + } + } + } + } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0ae8130ad4..8debf86432 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -295,3 +295,4 @@ erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry +erpnext.patches.v13_0.update_subscription_status_in_memberships diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py new file mode 100644 index 0000000000..28e650e9ce --- /dev/null +++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + if frappe.db.exists('DocType', 'Member'): + frappe.reload_doc('Non Profit', 'doctype', 'Member') + + if frappe.db.has_column('Member', 'subscription_activated'): + frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1') + frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated') \ No newline at end of file From 57514f7b1dbb94171e7d7426f6554eee334bcc39 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 21 Jul 2021 00:46:34 +0530 Subject: [PATCH 073/253] feat(Non Profit): API Endpoint to update halted Razorpay subscriptions (#26427) (#26564) * feat: Update Subscription Activated field to Subscription Status to accomodate Halted status * feat: API Endpoint to halt Razorpay subscription * fix: sider * fix: validation message * test: halted razorpay subscription --- erpnext/non_profit/doctype/member/member.json | 16 ++-- erpnext/non_profit/doctype/member/member.py | 4 +- .../doctype/membership/membership.py | 92 ++++++++++++++----- .../doctype/membership/test_membership.py | 56 +++++++++-- erpnext/patches.txt | 1 + ...date_subscription_status_in_memberships.py | 9 ++ 6 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 erpnext/patches/v13_0/update_subscription_status_in_memberships.py diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index f190cfae75..7c1baf1a8d 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -26,7 +26,7 @@ "razorpay_details_section", "subscription_id", "customer_id", - "subscription_activated", + "subscription_status", "column_break_21", "subscription_start", "subscription_end" @@ -151,12 +151,6 @@ "fieldname": "column_break_21", "fieldtype": "Column Break" }, - { - "default": "0", - "fieldname": "subscription_activated", - "fieldtype": "Check", - "label": "Subscription Activated" - }, { "fieldname": "subscription_start", "fieldtype": "Date", @@ -166,11 +160,17 @@ "fieldname": "subscription_end", "fieldtype": "Date", "label": "Subscription End" + }, + { + "fieldname": "subscription_status", + "fieldtype": "Select", + "label": "Subscription Status", + "options": "\nActive\nHalted" } ], "image_field": "image", "links": [], - "modified": "2020-11-09 12:12:10.174647", + "modified": "2021-07-11 14:27:26.368039", "modified_by": "Administrator", "module": "Non Profit", "name": "Member", diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 30be585e9a..67828d6efc 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -84,7 +84,9 @@ def create_member(user_details): "email_id": user_details.email, "pan_number": user_details.pan or None, "membership_type": user_details.plan_id, - "subscription_id": user_details.subscription_id or None + "customer_id": user_details.customer_id or None, + "subscription_id": user_details.subscription_id or None, + "subscription_status": user_details.subscription_status or "" }) member.insert(ignore_permissions=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index e8ae6187b7..b584116df3 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings): return invoice -def get_member_based_on_subscription(subscription_id, email): - members = frappe.get_all("Member", filters={ - "subscription_id": subscription_id, - "email_id": email - }, order_by="creation desc") +def get_member_based_on_subscription(subscription_id, email=None, customer_id=None): + filters = {"subscription_id": subscription_id} + if email: + filters.update({"email_id": email}) + if customer_id: + filters.update({"customer_id": customer_id}) + + members = frappe.get_all("Member", filters=filters, order_by="creation desc") try: return frappe.get_doc("Member", members[0]["name"]) @@ -209,8 +212,6 @@ def get_member_based_on_subscription(subscription_id, email): def verify_signature(data, endpoint="Membership"): - if frappe.flags.in_test or os.environ.get("CI"): - return True signature = frappe.request.headers.get("X-Razorpay-Signature") settings = frappe.get_doc("Non Profit Settings") @@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"): @frappe.whitelist(allow_guest=True) def trigger_razorpay_subscription(*args, **kwargs): data = frappe.request.get_data(as_text=True) - try: - verify_signature(data) - except Exception as e: - log = frappe.log_error(e, "Membership Webhook Verification Error") - notify_failure(log) - return { "status": "Failed", "reason": e} - - if isinstance(data, six.string_types): - data = json.loads(data) - data = frappe._dict(data) + data = process_request_data(data) subscription = data.payload.get("subscription", {}).get("entity", {}) subscription = frappe._dict(subscription) @@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs): # Update membership values member.subscription_start = datetime.fromtimestamp(subscription.start_at) member.subscription_end = datetime.fromtimestamp(subscription.end_at) - member.subscription_activated = 1 + member.subscription_status = "Active" member.flags.ignore_mandatory = True member.save() @@ -294,9 +286,67 @@ def trigger_razorpay_subscription(*args, **kwargs): message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) - return { "status": "Failed", "reason": e} + return {"status": "Failed", "reason": e} - return { "status": "Success" } + return {"status": "Success"} + + +@frappe.whitelist(allow_guest=True) +def update_halted_razorpay_subscription(*args, **kwargs): + """ + When all retries have been exhausted, Razorpay moves the subscription to the halted state. + The customer has to manually retry the charge or change the card linked to the subscription, + for the subscription to move back to the active state. + """ + if frappe.request: + data = frappe.request.get_data(as_text=True) + data = process_request_data(data) + elif frappe.flags.in_test: + data = kwargs.get("data") + data = frappe._dict(data) + else: + return + + if not data.event == "subscription.halted": + return + + subscription = data.payload.get("subscription", {}).get("entity", {}) + subscription = frappe._dict(subscription) + + try: + member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id) + if not member: + frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id)) + + member.subscription_status = "Halted" + member.flags.ignore_mandatory = True + member.save() + + if subscription.get("notes"): + member = get_additional_notes(member, subscription) + + except Exception as e: + message = "{0}\n\n{1}".format(e, frappe.get_traceback()) + log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name)) + notify_failure(log) + return {"status": "Failed", "reason": e} + + return {"status": "Success"} + + +def process_request_data(data): + try: + verify_signature(data) + except Exception as e: + log = frappe.log_error(e, "Membership Webhook Verification Error") + notify_failure(log) + return {"status": "Failed", "reason": e} + + if isinstance(data, six.string_types): + data = json.loads(data) + data = frappe._dict(data) + + return data def get_company_for_memberships(): @@ -362,4 +412,4 @@ def set_expired_status(): `tabMembership` SET `status` = 'Expired' WHERE `status` not in ('Cancelled') AND `to_date` < %s - """, (nowdate())) \ No newline at end of file + """, (nowdate())) diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py index 31da792e53..0f5a9bed82 100644 --- a/erpnext/non_profit/doctype/membership/test_membership.py +++ b/erpnext/non_profit/doctype/membership/test_membership.py @@ -6,6 +6,7 @@ import unittest import frappe import erpnext from erpnext.non_profit.doctype.member.member import create_member +from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription from frappe.utils import nowdate, add_months class TestMembership(unittest.TestCase): @@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase): plan = setup_membership() # make test member - self.member_doc = create_member(frappe._dict({ - 'fullname': "_Test_Member", - 'email': "_test_member_erpnext@example.com", - 'plan_id': plan.name - })) + self.member_doc = create_member( + frappe._dict({ + "fullname": "_Test_Member", + "email": "_test_member_erpnext@example.com", + "plan_id": plan.name, + "subscription_id": "sub_DEX6xcJ1HSW4CR", + "customer_id": "cust_C0WlbKhp3aLA7W", + "subscription_status": "Active" + }) + ) self.member_doc.make_customer_and_link() self.member = self.member_doc.name @@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase): "to_date": add_months(nowdate(), 3), }) + def test_halted_memberships(self): + make_membership(self.member, { + "from_date": add_months(nowdate(), 2), + "to_date": add_months(nowdate(), 3) + }) + + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active") + payload = get_subscription_payload() + update_halted_razorpay_subscription(data=payload) + self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted") + + def tearDown(self): + frappe.db.rollback() + def set_config(key, value): frappe.db.set_value("Non Profit Settings", None, key, value) @@ -115,4 +135,28 @@ def setup_membership(): else: plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm") - return plan \ No newline at end of file + return plan + +def get_subscription_payload(): + return { + "entity": "event", + "account_id": "acc_BFQ7uQEaa7j2z7", + "event": "subscription.halted", + "contains": [ + "subscription" + ], + "payload": { + "subscription": { + "entity": { + "id": "sub_DEX6xcJ1HSW4CR", + "entity": "subscription", + "plan_id": "_rzpy_test_milythm", + "customer_id": "cust_C0WlbKhp3aLA7W", + "status": "halted", + "notes": { + "Important": "Notes for Internal Reference" + }, + } + } + } + } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f63c7edea2..2a83635117 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -292,3 +292,4 @@ erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry +erpnext.patches.v13_0.update_subscription_status_in_memberships diff --git a/erpnext/patches/v13_0/update_subscription_status_in_memberships.py b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py new file mode 100644 index 0000000000..28e650e9ce --- /dev/null +++ b/erpnext/patches/v13_0/update_subscription_status_in_memberships.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + if frappe.db.exists('DocType', 'Member'): + frappe.reload_doc('Non Profit', 'doctype', 'Member') + + if frappe.db.has_column('Member', 'subscription_activated'): + frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1') + frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated') \ No newline at end of file From f3158ea448ea9c545db6fd2ebc97110b9d77f998 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 21 Jul 2021 19:47:41 +0530 Subject: [PATCH 074/253] fix: removed Remarks column from AR/AP report --- .../report/accounts_receivable/accounts_receivable.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a11b77a6f6..b54646fd27 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -99,7 +99,6 @@ class ReceivablePayableReport(object): voucher_no = gle.voucher_no, party = gle.party, posting_date = gle.posting_date, - remarks = gle.remarks, account_currency = gle.account_currency, invoiced = 0.0, paid = 0.0, @@ -579,7 +578,7 @@ class ReceivablePayableReport(object): self.gl_entries = frappe.db.sql(""" select name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, - against_voucher_type, against_voucher, account_currency, remarks, {0} + against_voucher_type, against_voucher, account_currency, {0} from `tabGL Entry` where @@ -792,8 +791,6 @@ class ReceivablePayableReport(object): self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link', options='Supplier Group') - self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200) - def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120): if not fieldname: fieldname = scrub(label) if fieldtype=='Currency': options='currency' From 28d52c4a9521759625bb13e5ea0c50c9112e4fed Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 21 Jul 2021 19:54:06 +0530 Subject: [PATCH 075/253] chore: remove warning rules semgrep-action doesn't consider severity, hence ignoring these rules for now. --- .github/helper/semgrep_rules/security.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml index 5a5098bf50..8b21979208 100644 --- a/.github/helper/semgrep_rules/security.yml +++ b/.github/helper/semgrep_rules/security.yml @@ -8,18 +8,3 @@ rules: dynamic content. Avoid it or use safe_eval(). languages: [python] severity: ERROR - -- id: frappe-sqli-format-strings - patterns: - - pattern-inside: | - @frappe.whitelist() - def $FUNC(...): - ... - - pattern-either: - - pattern: frappe.db.sql("..." % ...) - - pattern: frappe.db.sql(f"...", ...) - - pattern: frappe.db.sql("...".format(...), ...) - message: | - Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines - languages: [python] - severity: WARNING From 6928fc17c695c05c25d52be70b9e5ed8110cb4dd Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 21 Jul 2021 19:54:06 +0530 Subject: [PATCH 076/253] chore: remove warning rules semgrep-action doesn't consider severity, hence ignoring these rules for now. --- .github/helper/semgrep_rules/security.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml index 5a5098bf50..8b21979208 100644 --- a/.github/helper/semgrep_rules/security.yml +++ b/.github/helper/semgrep_rules/security.yml @@ -8,18 +8,3 @@ rules: dynamic content. Avoid it or use safe_eval(). languages: [python] severity: ERROR - -- id: frappe-sqli-format-strings - patterns: - - pattern-inside: | - @frappe.whitelist() - def $FUNC(...): - ... - - pattern-either: - - pattern: frappe.db.sql("..." % ...) - - pattern: frappe.db.sql(f"...", ...) - - pattern: frappe.db.sql("...".format(...), ...) - message: | - Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines - languages: [python] - severity: WARNING From f9da88cb15c831e50dc823acaeacbaa822a7fabf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Jul 2021 21:45:33 +0530 Subject: [PATCH 077/253] fix: Additional discount calculations in Invoices --- .../public/js/controllers/taxes_and_totals.js | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 1de9ec1a7d..b5aa6265be 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -34,9 +34,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.set_value(item.doctype, item.name, "rate", item_rate); }, - calculate_taxes_and_totals: function(update_paid_amount) { + calculate_taxes_and_totals: async function(update_paid_amount) { this.discount_amount_applied = false; - this._calculate_taxes_and_totals(); + await this._calculate_taxes_and_totals(); this.calculate_discount_amount(); // Advance calculation applicable to Sales /Purchase Invoice @@ -72,19 +72,20 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ } }, - _calculate_taxes_and_totals: function() { - frappe.run_serially([ - () => this.validate_conversion_rate(), - () => this.calculate_item_values(), - () => this.update_item_tax_map(), - () => this.initialize_taxes(), - () => this.determine_exclusive_rate(), - () => this.calculate_net_total(), - () => this.calculate_taxes(), - () => this.manipulate_grand_total_for_inclusive_tax(), - () => this.calculate_totals(), - () => this._cleanup() - ]); + _calculate_taxes_and_totals: async function() { + this.validate_conversion_rate(); + this.calculate_item_values(); + await this.update_item_tax_map(); + }, + + _calculate_tax_values : function() { + this.initialize_taxes(); + this.determine_exclusive_rate(); + this.calculate_net_total(); + this.calculate_taxes(); + this.manipulate_grand_total_for_inclusive_tax(); + this.calculate_totals(); + this._cleanup(); }, validate_conversion_rate: function() { @@ -105,7 +106,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ }, calculate_item_values: function() { - var me = this; + let me = this; if (!this.discount_amount_applied) { $.each(this.frm.doc["items"] || [], function(i, item) { frappe.model.round_floats_in(item); @@ -266,7 +267,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); }, - update_item_tax_map: function() { + update_item_tax_map: async function() { let me = this; let item_codes = []; let item_rates = {}; @@ -301,6 +302,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ } }); } + + me._calculate_tax_values(); } }); } From 50b188214d03dc8d2113e8567f8a11dd16a221fa Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Jul 2021 12:41:48 +0530 Subject: [PATCH 078/253] revert: Client side handling for Dynamic GST Rates --- .../public/js/controllers/taxes_and_totals.js | 54 ++----------------- 1 file changed, 4 insertions(+), 50 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index b5aa6265be..263570cb66 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -34,9 +34,9 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.set_value(item.doctype, item.name, "rate", item_rate); }, - calculate_taxes_and_totals: async function(update_paid_amount) { + calculate_taxes_and_totals: function(update_paid_amount) { this.discount_amount_applied = false; - await this._calculate_taxes_and_totals(); + this._calculate_taxes_and_totals(); this.calculate_discount_amount(); // Advance calculation applicable to Sales /Purchase Invoice @@ -65,20 +65,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.frm.refresh_fields(); }, - calculate_discount_amount: function(){ + calculate_discount_amount: function() { if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) { this.set_discount_amount(); this.apply_discount_amount(); } }, - _calculate_taxes_and_totals: async function() { + _calculate_taxes_and_totals: function() { this.validate_conversion_rate(); this.calculate_item_values(); - await this.update_item_tax_map(); - }, - - _calculate_tax_values : function() { this.initialize_taxes(); this.determine_exclusive_rate(); this.calculate_net_total(); @@ -267,48 +263,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]); }, - update_item_tax_map: async function() { - let me = this; - let item_codes = []; - let item_rates = {}; - let item_tax_templates = {}; - - $.each(this.frm.doc.items || [], function(i, item) { - if (item.item_code) { - // Use combination of name and item code in case same item is added multiple times - item_codes.push([item.item_code, item.name]); - item_rates[item.name] = item.net_rate; - item_tax_templates[item.name] = item.item_tax_template; - } - }); - - if (item_codes.length) { - return this.frm.call({ - method: "erpnext.stock.get_item_details.get_item_tax_info", - args: { - company: me.frm.doc.company, - tax_category: cstr(me.frm.doc.tax_category), - item_codes: item_codes, - item_rates: item_rates, - item_tax_templates: item_tax_templates - }, - callback: function(r) { - if (!r.exc) { - $.each(me.frm.doc.items || [], function(i, item) { - if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) { - item.item_tax_template = r.message[item.name].item_tax_template; - item.item_tax_rate = r.message[item.name].item_tax_rate; - me.add_taxes_from_item_tax_template(item.item_tax_rate); - } - }); - } - - me._calculate_tax_values(); - } - }); - } - }, - add_taxes_from_item_tax_template: function(item_tax_map) { let me = this; From 72eb72f66f64518f859de681adbb4de63a235d28 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Jul 2021 15:45:04 +0530 Subject: [PATCH 079/253] fix: Add update item tax template method back --- erpnext/public/js/controllers/transaction.js | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index b3af3d67ea..6eb6775b28 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1787,6 +1787,46 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ ]); }, + update_item_tax_map: function() { + let me = this; + let item_codes = []; + let item_rates = {}; + let item_tax_templates = {}; + + $.each(this.frm.doc.items || [], function(i, item) { + if (item.item_code) { + // Use combination of name and item code in case same item is added multiple times + item_codes.push([item.item_code, item.name]); + item_rates[item.name] = item.net_rate; + item_tax_templates[item.name] = item.item_tax_template; + } + }); + + if (item_codes.length) { + return this.frm.call({ + method: "erpnext.stock.get_item_details.get_item_tax_info", + args: { + company: me.frm.doc.company, + tax_category: cstr(me.frm.doc.tax_category), + item_codes: item_codes, + item_rates: item_rates, + item_tax_templates: item_tax_templates + }, + callback: function(r) { + if (!r.exc) { + $.each(me.frm.doc.items || [], function(i, item) { + if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) { + item.item_tax_template = r.message[item.name].item_tax_template; + item.item_tax_rate = r.message[item.name].item_tax_rate; + me.add_taxes_from_item_tax_template(item.item_tax_rate); + } + }); + } + } + }); + } + }, + item_tax_template: function(doc, cdt, cdn) { var me = this; if(me.frm.updating_party_details) return; From 9fa92c912b484c82b49e77b9c00527ecd165d6df Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 20 Jul 2021 17:02:05 +0530 Subject: [PATCH 080/253] fix: Revert refresh field --- erpnext/public/js/controllers/taxes_and_totals.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 263570cb66..53d5278bbf 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -587,8 +587,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); }); } - - this.frm.refresh_fields(); }, set_discount_amount: function() { From 9ab18b534141c4a4bdf2a4c48541febaa55cbe23 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Jul 2021 23:15:15 +0530 Subject: [PATCH 081/253] fix: add company change trigger --- erpnext/public/js/controllers/transaction.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 6eb6775b28..5475383759 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -826,9 +826,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.run_serially([ () => me.frm.script_manager.trigger("currency"), + () => me.update_item_tax_map(), () => me.apply_default_taxes(), - () => me.apply_pricing_rule(), - () => me.calculate_taxes_and_totals() + () => me.apply_pricing_rule() ]); } } From 4323f4bcac77f4c5e06439550cc94fee32fad32c Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 22 Jul 2021 05:57:42 +0530 Subject: [PATCH 082/253] fix: Modify set_payment_schedule() to include fetch_payment_terms_from_order() --- .../doctype/purchase_order/purchase_order.py | 4 +- erpnext/controllers/accounts_controller.py | 111 +++++++++++------- .../doctype/sales_order/sales_order.py | 4 +- .../doctype/delivery_note/delivery_note.py | 4 +- .../purchase_receipt/purchase_receipt.py | 3 +- 5 files changed, 70 insertions(+), 56 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index a0bac51046..f68d81909a 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -443,8 +443,6 @@ def make_purchase_invoice_from_portal(purchase_order_name): frappe.response.location = '/purchase-invoices/' + doc.name def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False): - from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order - def postprocess(source, target): target.flags.ignore_permissions = ignore_permissions set_missing_values(source, target) @@ -496,7 +494,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) if automatically_fetch_payment_terms: - fetch_payment_terms_from_order(doc) + doc.set_payment_schedule() return doc diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d38c2cbdb5..77234aa999 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1038,7 +1038,14 @@ class AccountsController(TransactionBase): data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) - else: + + elif self.doctype in ["Sales Invoice", "Purchase Invoice"]: + po_or_so, doctype, fieldname = self.get_order_details() + + if self.linked_order_has_payment_terms(po_or_so, fieldname): + self.fetch_payment_terms_from_order(po_or_so, doctype) + + elif self.doctype not in ["Purchase Receipt"]: data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) else: @@ -1048,6 +1055,63 @@ class AccountsController(TransactionBase): d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) d.outstanding = d.payment_amount + def get_order_details(self): + if self.doctype == "Sales Invoice": + po_or_so = self.get('items')[0].get('sales_order') + po_or_so_doctype = "Sales Order" + po_or_so_doctype_name = "sales_order" + + else: + po_or_so = self.get('items')[0].get('purchase_order') + po_or_so_doctype = "Purchase Order" + po_or_so_doctype_name = "purchase_order" + + return po_or_so, po_or_so_doctype, po_or_so_doctype_name + + def linked_order_has_payment_terms(self, po_or_so, fieldname): + if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname): + if self.linked_order_has_payment_terms_template(po_or_so): + return True + elif self.linked_order_has_payment_schedule(po_or_so): + return True + + return False + + def all_items_have_same_po_or_so(self, po_or_so, fieldname): + for item in self.get('items'): + if item.get(fieldname) != po_or_so: + return False + + return True + + def linked_order_has_payment_terms_template(self, po_or_so): + return frappe.get_value('Sales Order', po_or_so, 'payment_terms_template') + + def linked_order_has_payment_schedule(self, po_or_so): + return frappe.get_all('Payment Schedule', filters={'parent': po_or_so}) + + def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype): + """ + Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice. + """ + po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so) + + self.payment_schedule = [] + self.payment_terms_template = po_or_so.payment_terms_template + + for schedule in po_or_so.payment_schedule: + payment_schedule = { + 'payment_term': schedule.payment_term, + 'due_date': schedule.due_date, + 'invoice_portion': schedule.invoice_portion, + 'discount_type': schedule.discount_type, + 'discount': schedule.discount, + 'base_payment_amount': schedule.base_payment_amount, + 'payment_amount': schedule.payment_amount, + 'outstanding': schedule.outstanding + } + self.append("payment_schedule", payment_schedule) + def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] if due_dates: @@ -1729,47 +1793,4 @@ def validate_regional(doc): @erpnext.allow_regional def validate_einvoice_fields(doc): - pass - -def fetch_payment_terms_from_order(doc): - """ - Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice. - """ - - if doc.doctype == "Sales Invoice": - po_or_so = doc.get('items')[0].get('sales_order') - po_or_so_doctype = "Sales Order" - po_or_so_doctype_name = "sales_order" - else: - po_or_so = doc.get('items')[0].get('purchase_order') - po_or_so_doctype = "Purchase Order" - po_or_so_doctype_name = "purchase_order" - - if po_or_so and all_items_have_same_po_or_so(doc, po_or_so, po_or_so_doctype_name): - po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so) - else: - doc.set_payment_schedule() - return - - doc.payment_schedule = [] - doc.payment_terms_template = po_or_so.payment_terms_template - - for schedule in po_or_so.payment_schedule: - payment_schedule = { - 'payment_term': schedule.payment_term, - 'due_date': schedule.due_date, - 'invoice_portion': schedule.invoice_portion, - 'discount_type': schedule.discount_type, - 'discount': schedule.discount, - 'base_payment_amount': schedule.base_payment_amount, - 'payment_amount': schedule.payment_amount, - 'outstanding': schedule.outstanding - } - doc.append("payment_schedule", payment_schedule) - -def all_items_have_same_po_or_so(doc, po_or_so, po_or_so_fieldname): - for item in doc.get('items'): - if item.get(po_or_so_fieldname) != po_or_so: - return False - - return True \ No newline at end of file + pass \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index a58c381df3..2b9d516e21 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -621,8 +621,6 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): - from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order - def postprocess(source, target): set_missing_values(source, target) #Get the advance paid Journal Entries in Sales Invoice Advance @@ -697,7 +695,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) if automatically_fetch_payment_terms: - fetch_payment_terms_from_order(doclist) + doclist.set_payment_schedule() return doclist diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 1628f93019..f99a01b820 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -414,8 +414,6 @@ def get_returned_qty_map(delivery_note): @frappe.whitelist() def make_sales_invoice(source_name, target_doc=None): - from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order - doc = frappe.get_doc('Delivery Note', source_name) to_make_invoice_qty_map = {} @@ -507,7 +505,7 @@ def make_sales_invoice(source_name, target_doc=None): automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) if automatically_fetch_payment_terms: - fetch_payment_terms_from_order(doc) + doc.set_payment_schedule() return doc diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6d72a5fa0b..36f21465b5 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -604,7 +604,6 @@ def update_billing_percentage(pr_doc, update_modified=True): @frappe.whitelist() def make_purchase_invoice(source_name, target_doc=None): from erpnext.accounts.party import get_payment_terms_template - from erpnext.controllers.accounts_controller import fetch_payment_terms_from_order doc = frappe.get_doc('Purchase Receipt', source_name) returned_qty_map = get_returned_qty_map(source_name) @@ -678,7 +677,7 @@ def make_purchase_invoice(source_name, target_doc=None): automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) if automatically_fetch_payment_terms: - fetch_payment_terms_from_order(doclist) + doc.set_payment_schedule() return doclist From 4ee657178478a4fa6b882cdbfb5898d64bda6563 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 22 Jul 2021 13:13:46 +0530 Subject: [PATCH 083/253] fix: SQL error on fetching RM in production plan (#26592) * fix: SQL error on fetching RM in production plan * refactor: avoid passing by reference and mutations --- .../production_plan/production_plan.py | 15 ++++-------- .../production_plan/test_production_plan.py | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 38a0ee77ad..6a024f275a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -747,9 +747,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) -def get_warehouse_list(warehouses, warehouse_list=None): - if not warehouse_list: - warehouse_list = [] +def get_warehouse_list(warehouses): + warehouse_list = [] if isinstance(warehouses, str): warehouses = json.loads(warehouses) @@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None): else: warehouse_list.append(row.get("warehouse")) + return warehouse_list + @frappe.whitelist() def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) - warehouse_list = [] if warehouses: - get_warehouse_list(warehouses, warehouse_list) - - if warehouse_list: - warehouses = list(set(warehouse_list)) + warehouses = list(set(get_warehouse_list(warehouses))) if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses: warehouses.remove(doc.get("for_warehouse")) - warehouse_list = None - doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index cce1bb61b6..93e6d7a97f 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -10,7 +10,7 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests +from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list class TestProductionPlan(unittest.TestCase): def setUp(self): @@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase): pln.cancel() frappe.delete_doc("Production Plan", pln.name) + def test_get_warehouse_list_group(self): + """Check if required warehouses are returned""" + warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"} + + missing_warehouse = expected_warehouses - warehouses + + self.assertTrue(len(missing_warehouse) == 0, + msg=f"Following warehouses were expected {', '.join(missing_warehouse)}") + + def test_get_warehouse_list_single(self): + warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Scrap Warehouse - _TC", } + + self.assertEqual(warehouses, expected_warehouses) + + def create_production_plan(**args): args = frappe._dict(args) From eecfc4c0aef57eb8146ff15d1b9210c62c73c489 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 22 Jul 2021 13:23:54 +0530 Subject: [PATCH 084/253] fix: Clean Serial No input on Server Side --- erpnext/controllers/stock_controller.py | 7 +++++++ erpnext/public/js/controllers/transaction.js | 2 +- erpnext/stock/doctype/serial_no/serial_no.py | 10 ++++++++-- erpnext/stock/doctype/stock_entry/stock_entry.py | 1 + .../stock_reconciliation/stock_reconciliation.py | 1 + 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2526e6df0e..f15af0a697 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -27,6 +27,7 @@ class StockController(AccountsController): if not self.get('is_return'): self.validate_inspection() self.validate_serialized_batch() + self.clean_serial_nos() self.validate_customer_provided_item() self.set_rate_of_stock_uom() self.validate_internal_transfer() @@ -67,6 +68,12 @@ class StockController(AccountsController): frappe.throw(_("Row #{0}: The batch {1} has already expired.") .format(d.idx, get_link_to_form("Batch", d.get("batch_no")))) + def clean_serial_nos(self): + for row in self.get("items"): + if hasattr(row, "serial_no") and row.serial_no: + # replace commas by linefeed and remove all spaces in string + row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "") + def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8360337ef7..e75e0a32fc 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -752,7 +752,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.trigger("item_code", cdt, cdn); } else { - // Replacing all occurences of comma with carriage return + // Replace all occurences of comma with line feed item.serial_no = item.serial_no.replace(/,/g, '\n'); item.conversion_factor = item.conversion_factor || 1; refresh_field("serial_no", item.name, item.parentfield); diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index bad7b608ac..70312bc543 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -165,8 +165,14 @@ class SerialNo(StockController): ) ORDER BY posting_date desc, posting_time desc, creation desc""", - (self.item_code, self.company, - serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'), as_dict=1): + ( + self.item_code, self.company, + serial_no, + serial_no+'\n%', + '%\n'+serial_no, + '%\n'+serial_no+'\n%' + ), + as_dict=1): if serial_no.upper() in get_serial_nos(sle.serial_no): if cint(sle.actual_qty) > 0: sle_dict.setdefault("incoming", []).append(sle) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index fcb6f0f4c2..395f339787 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -76,6 +76,7 @@ class StockEntry(StockController): self.validate_difference_account() self.set_job_card_data() self.set_purpose_for_stock_entry() + self.clean_serial_nos() self.validate_duplicate_serial_no() if not self.from_bom: diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 9875491593..07c32a943d 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -31,6 +31,7 @@ class StockReconciliation(StockController): self.validate_expense_account() self.validate_customer_provided_item() self.set_zero_value_for_customer_provided_items() + self.clean_serial_nos() self.set_total_qty_and_amount() self.validate_putaway_capacity() From 0d968fabfecafe544fe5700922eae206a4cb5816 Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Sat, 17 Jul 2021 14:42:38 -0400 Subject: [PATCH 085/253] fix: missing parameter 'country' --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 4fd8413d83..8456b49c8e 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -391,5 +391,5 @@ def set_default_accounts(company): }) company.save() - install_country_fixtures(company.name) + install_country_fixtures(company.name, company.country) company.create_default_tax_template() From 5b32fa5ccd8c4bf5a017a9ff7ad7b4112daaabd0 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 22 Jul 2021 14:00:01 +0530 Subject: [PATCH 086/253] fix: SQL error on fetching RM in production plan (bp #26592) * fix: SQL error on fetching RM in production plan * refactor: avoid passing by reference and mutations --- .../production_plan/production_plan.py | 15 ++++-------- .../production_plan/test_production_plan.py | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 38a0ee77ad..6a024f275a 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -747,9 +747,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) -def get_warehouse_list(warehouses, warehouse_list=None): - if not warehouse_list: - warehouse_list = [] +def get_warehouse_list(warehouses): + warehouse_list = [] if isinstance(warehouses, str): warehouses = json.loads(warehouses) @@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None): else: warehouse_list.append(row.get("warehouse")) + return warehouse_list + @frappe.whitelist() def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): if isinstance(doc, str): doc = frappe._dict(json.loads(doc)) - warehouse_list = [] if warehouses: - get_warehouse_list(warehouses, warehouse_list) - - if warehouse_list: - warehouses = list(set(warehouse_list)) + warehouses = list(set(get_warehouse_list(warehouses))) if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses: warehouses.remove(doc.get("for_warehouse")) - warehouse_list = None - doc['mr_items'] = [] po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index cce1bb61b6..93e6d7a97f 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -10,7 +10,7 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests +from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list class TestProductionPlan(unittest.TestCase): def setUp(self): @@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase): pln.cancel() frappe.delete_doc("Production Plan", pln.name) + def test_get_warehouse_list_group(self): + """Check if required warehouses are returned""" + warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"} + + missing_warehouse = expected_warehouses - warehouses + + self.assertTrue(len(missing_warehouse) == 0, + msg=f"Following warehouses were expected {', '.join(missing_warehouse)}") + + def test_get_warehouse_list_single(self): + warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]' + + warehouses = set(get_warehouse_list(warehouse_json)) + expected_warehouses = {"_Test Scrap Warehouse - _TC", } + + self.assertEqual(warehouses, expected_warehouses) + + def create_production_plan(**args): args = frappe._dict(args) From 56c67743abe7520d3e0df19b807636abbee54d2d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 22 Jul 2021 16:10:06 +0530 Subject: [PATCH 087/253] fix: incorrect bom name (#26600) --- erpnext/manufacturing/doctype/bom/bom.js | 7 ++++--- erpnext/manufacturing/doctype/bom/bom.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 15a7c316c9..bfbc6790b2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -83,7 +83,7 @@ frappe.ui.form.on("BOM", { if (!frm.doc.__islocal && frm.doc.docstatus<2) { frm.add_custom_button(__("Update Cost"), function() { - frm.events.update_cost(frm); + frm.events.update_cost(frm, true); }); frm.add_custom_button(__("Browse BOM"), function() { frappe.route_options = { @@ -318,14 +318,15 @@ frappe.ui.form.on("BOM", { }) }, - update_cost: function(frm) { + update_cost: function(frm, save_doc=false) { return frappe.call({ doc: frm.doc, method: "update_cost", freeze: true, args: { update_parent: true, - from_child_bom:false + save: save_doc, + from_child_bom: false }, callback: function(r) { refresh_field("items"); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 2fbbca4b19..af081c449c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -330,7 +330,7 @@ class BOM(WebsiteGenerator): frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) if not from_child_bom: - frappe.msgprint(_("Cost Updated")) + frappe.msgprint(_("Cost Updated"), alert=True) def update_parent_cost(self): if self.total_cost: From ce3e877c4076ca0ae231788c8baa825041cd9f60 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 22 Jul 2021 16:10:58 +0530 Subject: [PATCH 088/253] fix: incorrect bom name (bp #26600) --- erpnext/manufacturing/doctype/bom/bom.js | 7 ++++--- erpnext/manufacturing/doctype/bom/bom.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index c56668840e..3f50b41be1 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -83,7 +83,7 @@ frappe.ui.form.on("BOM", { if (!frm.doc.__islocal && frm.doc.docstatus<2) { frm.add_custom_button(__("Update Cost"), function() { - frm.events.update_cost(frm); + frm.events.update_cost(frm, true); }); frm.add_custom_button(__("Browse BOM"), function() { frappe.route_options = { @@ -318,14 +318,15 @@ frappe.ui.form.on("BOM", { }) }, - update_cost: function(frm) { + update_cost: function(frm, save_doc=false) { return frappe.call({ doc: frm.doc, method: "update_cost", freeze: true, args: { update_parent: true, - from_child_bom:false + save: save_doc, + from_child_bom: false }, callback: function(r) { refresh_field("items"); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 9da461f497..8692f3dc48 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -330,7 +330,7 @@ class BOM(WebsiteGenerator): frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) if not from_child_bom: - frappe.msgprint(_("Cost Updated")) + frappe.msgprint(_("Cost Updated"), alert=True) def update_parent_cost(self): if self.total_cost: From 7551bcf421f134e3510ded387326d273ef6add82 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 22 Jul 2021 17:25:51 +0550 Subject: [PATCH 089/253] bumped to version 13.7.1 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 1166549628..a181c2d42c 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.7.0' +__version__ = '13.7.1' def get_default_company(user=None): '''Get default company for user''' From caa106b3bd2d33c3c6952f6b475e7fa5cf67a016 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 22 Jul 2021 22:58:45 +0530 Subject: [PATCH 090/253] fix: Add test to check if payment terms are fetched when creating a Sales Invoice --- .../doctype/sales_order/test_sales_order.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 974648d6d4..bf6925473a 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1229,7 +1229,42 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, so.cancel) + def test_payment_terms_are_fetched_when_creating_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + automatically_fetch_payment_terms() + + so = make_sales_order(uom="Nos", do_not_save=1) + create_payment_terms_template() + so.payment_terms_template = 'Test Receivable Template' + so.submit() + + si = create_sales_invoice(qty=10, do_not_save=1) + si.items[0].sales_order = so.name + si.items[0].so_detail = so.items[0].name + si.insert() + + self.assertEqual(so.payment_terms_template, si.payment_terms_template) + compare_payment_schedules(self, so, si) + +def automatically_fetch_payment_terms(enable=1): + accounts_settings = frappe.get_doc("Accounts Settings") + accounts_settings.automatically_fetch_payment_terms = enable + accounts_settings.save() + +def compare_payment_schedules(doc, doc1, doc2): + payment_schedule1 = frappe.db.sql("""select payment_term, description, due_date, mode_of_payment, invoice_portion, payment_amount + from `tabPayment Schedule` + where parenttype=%s and parent=%s + order by payment_term asc""", (doc1.doctype, doc1.name), as_dict=1) + + payment_schedule2 = frappe.db.sql("""select payment_term, description, due_date, mode_of_payment, invoice_portion, payment_amount + from `tabPayment Schedule` + where parenttype=%s and parent=%s + order by payment_term asc""", (doc2.doctype, doc2.name), as_dict=1) + + doc.assertEqual(payment_schedule1, payment_schedule2) def make_sales_order(**args): so = frappe.new_doc("Sales Order") From 9c07454f91e4eb619c7bf196b9e233d7eadaffec Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 23 Jul 2021 03:23:29 +0530 Subject: [PATCH 091/253] fix: Add test to check if payment terms are fetched when creating a Sales Invoice --- .../delivery_note/test_delivery_note.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index f981aeb13b..8b1245eb40 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -17,7 +17,8 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry \ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoWarehouseError from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \ import create_stock_reconciliation, set_valuation_method -from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so +from erpnext.selling.doctype.sales_order.test_sales_order \ + import make_sales_order, create_dn_against_so, automatically_fetch_payment_terms, compare_payment_schedules from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.doctype.item.test_item import make_item @@ -759,6 +760,30 @@ class TestDeliveryNote(unittest.TestCase): self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item") + def test_payment_terms_are_fetched_when_creating_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + automatically_fetch_payment_terms() + + so = make_sales_order(uom="Nos", do_not_save=1) + create_payment_terms_template() + so.payment_terms_template = 'Test Receivable Template' + so.submit() + + dn = create_dn_against_so(so.name, delivered_qty=10) + + si = create_sales_invoice(qty=10, do_not_save=1) + si.items[0].delivery_note= dn.name + si.items[0].dn_detail = dn.items[0].name + si.items[0].sales_order = so.name + si.items[0].so_detail = so.items[0].name + + si.insert() + si.submit() + + self.assertEqual(so.payment_terms_template, si.payment_terms_template) + compare_payment_schedules(self, so, si) def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") From de6d960381ff807c8632fb3fdf5321cff969a2ee Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 23 Jul 2021 03:36:37 +0530 Subject: [PATCH 092/253] fix: Add test to check if payment terms are fetched when creating a Purchase Invoice --- .../purchase_order/test_purchase_order.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 8563b97ab7..11cf39e5e2 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -968,8 +968,25 @@ class TestPurchaseOrder(unittest.TestCase): # To test if the PO does NOT have a Blanket Order self.assertEqual(po_doc.items[0].blanket_order, None) + def test_payment_terms_are_fetched_when_creating_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice + from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules + automatically_fetch_payment_terms() + po = create_purchase_order(qty=10, rate=100, do_not_save=1) + create_payment_terms_template() + po.payment_terms_template = 'Test Receivable Template' + po.submit() + + pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1) + pi.items[0].purchase_order = po.name + pi.items[0].po_detail = po.items[0].name + pi.insert() + + # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) + compare_payment_schedules(self, po, pi) def make_pr_against_po(po, received_qty=0): pr = make_purchase_receipt(po) From 13e7103ee096547f2b5288346f6d3f54b9cfa8a9 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 23 Jul 2021 03:44:56 +0530 Subject: [PATCH 093/253] fix: Add test to check if payment terms are fetched when creating a Purchase Invoice --- .../purchase_receipt/test_purchase_receipt.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index dbba21fde1..8980973360 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1079,6 +1079,31 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) + def test_payment_terms_are_fetched_when_creating_invoice(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template + from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, make_pr_against_po + from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules + + automatically_fetch_payment_terms() + + po = create_purchase_order(qty=10, rate=100, do_not_save=1) + create_payment_terms_template() + po.payment_terms_template = 'Test Receivable Template' + po.submit() + + pr = make_pr_against_po(po.name, received_qty=10) + + pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1) + pi.items[0].purchase_receipt = pr.name + pi.items[0].pr_detail = pr.items[0].name + pi.items[0].purchase_order = po.name + pi.items[0].po_detail = po.items[0].name + pi.insert() + + # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) + compare_payment_schedules(self, po, pi) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s From aed39a23dc00396a23d49a8d9dbadcd054c76a4b Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 23 Jul 2021 03:45:59 +0530 Subject: [PATCH 094/253] fix: Rename tests --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 2 +- erpnext/selling/doctype/sales_order/test_sales_order.py | 2 +- erpnext/stock/doctype/delivery_note/test_delivery_note.py | 2 +- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 11cf39e5e2..474c9cf3df 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -968,7 +968,7 @@ class TestPurchaseOrder(unittest.TestCase): # To test if the PO does NOT have a Blanket Order self.assertEqual(po_doc.items[0].blanket_order, None) - def test_payment_terms_are_fetched_when_creating_invoice(self): + def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index bf6925473a..3fbe0afd10 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1229,7 +1229,7 @@ class TestSalesOrder(unittest.TestCase): self.assertRaises(frappe.ValidationError, so.cancel) - def test_payment_terms_are_fetched_when_creating_invoice(self): + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 8b1245eb40..ca8d8b99d3 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -760,7 +760,7 @@ class TestDeliveryNote(unittest.TestCase): self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item") - def test_payment_terms_are_fetched_when_creating_invoice(self): + def test_payment_terms_are_fetched_when_creating_sales_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 8980973360..875814dc64 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1079,7 +1079,7 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) - def test_payment_terms_are_fetched_when_creating_invoice(self): + def test_payment_terms_are_fetched_when_creating_purchase_invoice(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, make_pr_against_po From add2030553e1a6c633682856de78cd1873ca071d Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 23 Jul 2021 03:58:27 +0530 Subject: [PATCH 095/253] fix: Sider issues --- erpnext/selling/doctype/sales_order/test_sales_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 3fbe0afd10..f4a089bcef 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1249,9 +1249,9 @@ class TestSalesOrder(unittest.TestCase): compare_payment_schedules(self, so, si) def automatically_fetch_payment_terms(enable=1): - accounts_settings = frappe.get_doc("Accounts Settings") - accounts_settings.automatically_fetch_payment_terms = enable - accounts_settings.save() + accounts_settings = frappe.get_doc("Accounts Settings") + accounts_settings.automatically_fetch_payment_terms = enable + accounts_settings.save() def compare_payment_schedules(doc, doc1, doc2): payment_schedule1 = frappe.db.sql("""select payment_term, description, due_date, mode_of_payment, invoice_portion, payment_amount From 45d506c489df5b37d599c1c72ae6b16cc8809002 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 23 Jul 2021 16:40:45 +0530 Subject: [PATCH 096/253] fix: serial no and batch validation --- erpnext/controllers/stock_controller.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2526e6df0e..17bd7354f9 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -53,12 +53,17 @@ class StockController(AccountsController): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos for d in self.get("items"): if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no: - serial_nos = get_serial_nos(d.serial_no) - for serial_no_data in frappe.get_all("Serial No", - filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]): - if serial_no_data.batch_no != d.batch_no: + serial_nos = frappe.get_all("Serial No", + fields=["batch_no", "name", "warehouse"], + filters={ + "name": ("in", get_serial_nos(d.serial_no)) + } + ) + + for row in serial_nos: + if row.warehouse and row.batch_no != d.batch_no: frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}") - .format(d.idx, serial_no_data.name, d.batch_no)) + .format(d.idx, row.name, d.batch_no)) if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2: expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date") From 4128aa762887ff7a82621420c7f4c8bfa637de76 Mon Sep 17 00:00:00 2001 From: Anurag0911 <67339426+Anurag0911@users.noreply.github.com> Date: Fri, 23 Jul 2021 16:52:42 +0530 Subject: [PATCH 097/253] fix: syntax error (#26610) Removed "," at erpnext/public/js/controllers/taxes_and_totals.js:87 --- erpnext/public/js/controllers/taxes_and_totals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index e1f71f796a..a495a9b0c1 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -84,7 +84,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.manipulate_grand_total_for_inclusive_tax(); this.calculate_totals(); this._cleanup(); - }, + } validate_conversion_rate() { this.frm.doc.conversion_rate = flt(this.frm.doc.conversion_rate, (cur_frm) ? precision("conversion_rate") : 9); From 9ef157b23b566369fd04ad68533288c41445b527 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 23 Jul 2021 20:44:34 +0530 Subject: [PATCH 098/253] fix: wrong operation time in Work Order (#26613) * fix: wrong operation time in Work Order Top level item time operation was not considering the BOM.quantity Co-authored-by: Ankush Menat --- .../doctype/work_order/work_order.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 0a8e5329c1..69812c7452 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -487,21 +487,20 @@ class WorkOrder(Document): return operations = [] - if not self.use_multi_level_bom: - bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") - operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) - else: + + if self.use_multi_level_bom: bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation() - bom_traversal = list(reversed(bom_tree.level_order_traversal())) - bom_traversal.append(bom_tree) # add operation on top level item last + bom_traversal = reversed(bom_tree.level_order_traversal()) - for d in bom_traversal: - if d.is_bom: - operations.extend(_get_operations(d.name, qty=d.exploded_qty)) + for node in bom_traversal: + if node.is_bom: + operations.extend(_get_operations(node.name, qty=node.exploded_qty)) - for correct_index, operation in enumerate(operations, start=1): - operation.idx = correct_index + bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") + operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) + for correct_index, operation in enumerate(operations, start=1): + operation.idx = correct_index self.set('operations', operations) self.calculate_time() From fac88a3329425fe075e0114fd3ca4ef8f613cf85 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Fri, 23 Jul 2021 21:23:48 +0530 Subject: [PATCH 099/253] fix: Supplier Invoice Importer fix --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- erpnext/controllers/accounts_controller.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 8456b49c8e..4fd8413d83 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -391,5 +391,5 @@ def set_default_accounts(company): }) company.save() - install_country_fixtures(company.name, company.country) + install_country_fixtures(company.name) company.create_default_tax_template() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4c313c43a7..cdd865ac4a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1112,8 +1112,11 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) - d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount')) d.outstanding = d.payment_amount + elif not d.invoice_portion: + d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount')) + def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] From 017ed3f5c11b8e3467c24544f0df4dc7a36b6271 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 24 Jul 2021 00:08:02 +0530 Subject: [PATCH 100/253] fix: employee status server-side validation (#26615) --- erpnext/hr/doctype/appraisal/appraisal.py | 3 +- erpnext/hr/doctype/attendance/attendance.py | 2 ++ .../attendance_request/attendance_request.py | 3 +- .../compensatory_leave_request.py | 3 +- erpnext/hr/doctype/employee/employee.py | 8 +++-- erpnext/hr/doctype/employee/test_employee.py | 30 +++++++++++++++++-- .../employee_advance/employee_advance.py | 6 ++-- .../employee_checkin/employee_checkin.py | 4 ++- .../employee_promotion/employee_promotion.py | 5 ++-- .../employee_referral/employee_referral.py | 2 ++ .../employee_transfer/employee_transfer.py | 4 --- .../hr/doctype/expense_claim/expense_claim.py | 3 +- .../leave_application/leave_application.py | 3 +- .../leave_encashment/leave_encashment.py | 3 +- .../shift_assignment/shift_assignment.py | 2 ++ .../hr/doctype/shift_request/shift_request.py | 3 +- .../doctype/travel_request/travel_request.py | 4 ++- erpnext/hr/utils.py | 11 +++++-- .../additional_salary/additional_salary.py | 2 ++ .../employee_benefit_application.py | 3 +- .../employee_benefit_claim.py | 3 +- .../employee_incentive/employee_incentive.py | 2 ++ .../employee_tax_exemption_declaration.py | 3 +- ...employee_tax_exemption_proof_submission.py | 3 +- .../retention_bonus/retention_bonus.py | 5 ++-- .../doctype/salary_slip/salary_slip.py | 2 ++ .../projects/doctype/timesheet/timesheet.py | 3 ++ 27 files changed, 91 insertions(+), 34 deletions(-) diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py index f7601870fa..c2ed457984 100644 --- a/erpnext/hr/doctype/appraisal/appraisal.py +++ b/erpnext/hr/doctype/appraisal/appraisal.py @@ -9,7 +9,7 @@ from frappe.utils import flt, getdate from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee class Appraisal(Document): def validate(self): @@ -19,6 +19,7 @@ class Appraisal(Document): if not self.goals: frappe.throw(_("Goals cannot be empty")) + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_existing_appraisal() diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 3412675d81..f79f0fe418 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -8,11 +8,13 @@ from frappe.utils import getdate, nowdate from frappe import _ from frappe.model.document import Document from frappe.utils import cstr, get_datetime, formatdate +from erpnext.hr.utils import validate_active_employee class Attendance(Document): def validate(self): from erpnext.controllers.status_updater import validate_status validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"]) + validate_active_employee(self.employee) self.validate_attendance_date() self.validate_duplicate_record() self.validate_employee_status() diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py index 090d53262c..7f88fed73a 100644 --- a/erpnext/hr/doctype/attendance_request/attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/attendance_request.py @@ -8,10 +8,11 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import date_diff, add_days, getdate from erpnext.hr.doctype.employee.employee import is_holiday -from erpnext.hr.utils import validate_dates +from erpnext.hr.utils import validate_dates, validate_active_employee class AttendanceRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.from_date, self.to_date) if self.half_day: if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date): diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index a6fe429be1..0d7fded921 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.utils import date_diff, add_days, getdate, cint, format_date from frappe.model.document import Document -from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \ +from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \ get_holidays_for_employee, create_additional_leave_ledger_entry class CompensatoryLeaveRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.work_from_date, self.work_end_date) if self.half_day: if not self.half_day_date: diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index fa017d9d4c..5ca47560b1 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -13,8 +13,10 @@ from frappe.model.document import Document from erpnext.utilities.transaction_base import delete_events from frappe.utils.nestedset import NestedSet -class EmployeeUserDisabledError(frappe.ValidationError): pass -class EmployeeLeftValidationError(frappe.ValidationError): pass +class EmployeeUserDisabledError(frappe.ValidationError): + pass +class InactiveEmployeeStatusError(frappe.ValidationError): + pass class Employee(NestedSet): nsm_parent_field = 'reports_to' @@ -196,7 +198,7 @@ class Employee(NestedSet): message += "

  • " + "
  • ".join(link_to_employees) message += "

" message += _("Please make sure the employees above report to another Active employee.") - throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee")) + throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee")) if not self.relieving_date: throw(_("Please enter relieving date.")) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 7d652a7366..8fc7cf1934 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -7,7 +7,7 @@ import frappe import erpnext import unittest import frappe.utils -from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError +from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError test_records = frappe.get_test_records('Employee') @@ -45,10 +45,33 @@ class TestEmployee(unittest.TestCase): employee2_doc.save() employee1_doc.reload() employee1_doc.status = 'Left' - self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) + self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) + + def test_employee_status_inactive(self): + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip + from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + + employee = make_employee("test_employee_status@company.com") + employee_doc = frappe.get_doc("Employee", employee) + employee_doc.status = "Inactive" + employee_doc.save() + employee_doc.reload() + + make_holiday_list() + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") + + frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""") + salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly", + employee=employee_doc.name, company=employee_doc.company) + salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name) + + self.assertRaises(InactiveEmployeeStatusError, salary_slip.save) + + def tearDown(self): + frappe.db.rollback() def make_employee(user, company=None, **kwargs): - "" if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", @@ -80,4 +103,5 @@ def make_employee(user, company=None, **kwargs): employee.insert() return employee.name else: + frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active") return frappe.get_value("Employee", {"employee_name":user}, "name") diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index cb72f6b6d9..ece627c50d 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import flt, nowdate from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account +from erpnext.hr.utils import validate_active_employee class EmployeeAdvanceOverPayment(frappe.ValidationError): pass @@ -18,6 +19,7 @@ class EmployeeAdvance(Document): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.set_status() def on_cancel(self): @@ -183,9 +185,9 @@ def make_return_entry(employee, company, employee_advance_name, return_amount, 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) diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index 15fbd4e015..60ea0f9895 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -9,9 +9,11 @@ from frappe.model.document import Document from frappe import _ from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift +from erpnext.hr.utils import validate_active_employee class EmployeeCheckin(Document): def validate(self): + validate_active_employee(self.employee) self.validate_duplicate_log() self.fetch_shift() @@ -122,7 +124,7 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): """Given a set of logs in chronological order calculates the total working hours based on the parameters. Zero is returned for all invalid cases. - + :param logs: The List of 'Employee Checkin'. :param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin' :param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out' diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index 83fb235f92..a3a61834c8 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -7,12 +7,11 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee +from erpnext.hr.utils import update_employee, validate_active_employee class EmployeePromotion(Document): def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot promote Employee with status Left or Inactive")) + validate_active_employee(self.employee) def before_submit(self): if getdate(self.promotion_date) > getdate(): diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py index 45d68729ce..0493306166 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -7,9 +7,11 @@ import frappe from frappe import _ from frappe.utils import get_link_to_form from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeReferral(Document): def validate(self): + validate_active_employee(self.referrer) self.set_full_name() self.set_referral_bonus_payment_status() diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index 6eec9fa12a..c2007747fb 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -10,10 +10,6 @@ from frappe.utils import getdate from erpnext.hr.utils import update_employee class EmployeeTransfer(Document): - def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot transfer Employee with status Left or Inactive")) - def before_submit(self): if getdate(self.transfer_date) > getdate(): frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"), diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 5010fc3f75..8f8dbb2224 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -6,7 +6,7 @@ import frappe, erpnext from frappe import _ from frappe.utils import get_fullname, flt, cstr, get_link_to_form from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee from erpnext.accounts.party import get_party_account from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account @@ -23,6 +23,7 @@ class ExpenseClaim(AccountsController): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.validate_advances() self.validate_sanctioned_amount() self.calculate_total_amount() diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index cee6f374fd..93fb19f4a1 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate -from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange @@ -22,6 +22,7 @@ class LeaveApplication(Document): return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type) def validate(self): + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_balance_leaves() diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index e041b7fb8f..912bd8ad92 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate, nowdate, flt -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves @@ -15,6 +15,7 @@ from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leav class LeaveEncashment(Document): def validate(self): set_employee_name(self) + validate_active_employee(self.employee) self.get_leave_details_for_encashment() self.validate_salary_structure() diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index ab65260c09..89ae4d535d 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -9,10 +9,12 @@ from frappe.model.document import Document from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday +from erpnext.hr.utils import validate_active_employee from datetime import timedelta, datetime class ShiftAssignment(Document): def validate(self): + validate_active_employee(self.employee) self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 177c45edc6..6461f07552 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate -from erpnext.hr.utils import share_doc_with_approver +from erpnext.hr.utils import share_doc_with_approver, validate_active_employee class OverlapError(frappe.ValidationError): pass class ShiftRequest(Document): def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_shift_request_overlap_dates() self.validate_approver() diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py index 01d3f34706..60834d3f4a 100644 --- a/erpnext/hr/doctype/travel_request/travel_request.py +++ b/erpnext/hr/doctype/travel_request/travel_request.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class TravelRequest(Document): - pass + def validate(self): + validate_active_employee(self.employee) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index ebb1734347..a6a8406803 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -3,13 +3,12 @@ import erpnext import frappe -from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError from frappe import _ from frappe.desk.form import assign_to from frappe.model.document import Document from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, - get_datetime, getdate, nowdate, today, unique) - + get_datetime, getdate, nowdate, today, unique, get_link_to_form) class DuplicateDeclarationError(frappe.ValidationError): pass @@ -20,6 +19,7 @@ class EmployeeBoardingController(Document): Assign to the concerned person and roles as per the onboarding/separation template ''' def validate(self): + validate_active_employee(self.employee) # remove the task if linked before submitting the form if self.amended_from: for activity in self.activities: @@ -522,3 +522,8 @@ def share_doc_with_approver(doc, user): approver = approvers.get(doc.doctype) if doc_before_save.get(approver) != doc.get(approver): frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver)) + +def validate_active_employee(employee): + if frappe.db.get_value("Employee", employee, "status") == "Inactive": + frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format( + get_link_to_form("Employee", employee)), InactiveEmployeeStatusError) \ No newline at end of file diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 7db4b8686a..b978cbe2b5 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -7,6 +7,7 @@ import frappe from frappe.model.document import Document from frappe import _, bold from frappe.utils import getdate, date_diff, comma_and, formatdate +from erpnext.hr.utils import validate_active_employee class AdditionalSalary(Document): def on_submit(self): @@ -19,6 +20,7 @@ class AdditionalSalary(Document): self.update_employee_referral(cancel=True) def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_salary_structure() self.validate_recurring_additional_salary_overlap() diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py index 27df30a459..5ebe514ac0 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py @@ -9,10 +9,11 @@ from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt from frappe.model.document import Document from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure -from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount +from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee class EmployeeBenefitApplication(Document): def validate(self): + validate_active_employee(self.employee) self.validate_duplicate_on_payroll_period() if not self.max_benefits: self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period) diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py index d9937a7bb9..c6713f3aa4 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -8,12 +8,13 @@ from frappe import _ from frappe.utils import flt from frappe.model.document import Document from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits -from erpnext.hr.utils import get_previous_claimed_amount +from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure class EmployeeBenefitClaim(Document): def validate(self): + validate_active_employee(self.employee) max_benefits = get_max_benefits(self.employee, self.claim_date) if not max_benefits or max_benefits <= 0: frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee)) diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py index ead3db126f..6b918ba76d 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py @@ -6,9 +6,11 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeIncentive(Document): def validate(self): + validate_active_employee(self.employee) self.validate_salary_structure() def validate_salary_structure(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index fb71a2877a..e11d60a464 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -8,11 +8,12 @@ from frappe.model.document import Document from frappe import _ from frappe.utils import flt from frappe.model.mapper import get_mapped_doc -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionDeclaration(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.declarations) validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee) self.set_total_declared_amount() diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py index 5bc33a65f2..8131ae0fa8 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py @@ -7,11 +7,12 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import flt -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionProofSubmission(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.tax_exemption_proofs) self.set_total_actual_amount() self.set_total_exemption_amount() diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py index 049ea265cc..055bea7410 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py @@ -7,11 +7,10 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import getdate - +from erpnext.hr.utils import validate_active_employee class RetentionBonus(Document): def validate(self): - if frappe.get_value('Employee', self.employee, 'status') != 'Active': - frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees')) + validate_active_employee(self.employee) if getdate(self.bonus_payment_date) < getdate(): frappe.throw(_('Bonus Payment Date cannot be a past date')) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 81e5dc9f87..3e82c0d428 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 erpnext.hr.utils import validate_active_employee from six import iteritems class SalarySlip(TransactionBase): @@ -39,6 +40,7 @@ class SalarySlip(TransactionBase): def validate(self): self.status = self.get_status() + validate_active_employee(self.employee) self.validate_dates() self.check_existing() if not self.salary_slip_based_on_timesheet: diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index c8bd80fca0..ae38d4ca19 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -15,12 +15,15 @@ from erpnext.manufacturing.doctype.workstation.workstation import (check_if_with WorkstationHolidayError) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.setup.utils import get_exchange_rate +from erpnext.hr.utils import validate_active_employee class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass class Timesheet(Document): def validate(self): + if self.employee: + validate_active_employee(self.employee) self.set_employee_name() self.set_status() self.validate_dates() From 057a0a98428b138b20646b820e7ab27c99bc5f22 Mon Sep 17 00:00:00 2001 From: Ankush Date: Sun, 25 Jul 2021 12:49:05 +0530 Subject: [PATCH 101/253] ci: auto backport squashed commits based on labels (#26622) --- .github/workflows/backport.yml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 7c6b8432b8..cc98f4544f 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -1,16 +1,25 @@ name: Backport on: - pull_request: + pull_request_target: types: - closed - labeled jobs: - backport: - runs-on: ubuntu-18.04 - name: Backport + main: + runs-on: ubuntu-latest steps: - - name: Backport - uses: tibdex/backport@v1 + - name: Checkout Actions + uses: actions/checkout@v2 with: - github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + repository: "ankush/backport" + path: ./actions + ref: develop + - name: Install Actions + run: npm install --production --prefix ./actions + - name: Run backport + uses: ./actions/backport + with: + token: ${{secrets.BACKPORT_BOT_TOKEN}} + labelsToAdd: "backport" + title: "{{originalTitle}}" From 96caae1f56a6bdd0ab70e8bc3bb686cc31c76ccc Mon Sep 17 00:00:00 2001 From: Ankush Date: Sun, 25 Jul 2021 13:01:21 +0530 Subject: [PATCH 102/253] fix: wrong operation time in Work Order (#26613) (#26617) * fix: wrong operation time in Work Order Top level item time operation was not considering the BOM.quantity Co-authored-by: Ankush Menat Co-authored-by: rohitwaghchaure --- .../doctype/work_order/work_order.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 0a8e5329c1..69812c7452 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -487,21 +487,20 @@ class WorkOrder(Document): return operations = [] - if not self.use_multi_level_bom: - bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") - operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) - else: + + if self.use_multi_level_bom: bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation() - bom_traversal = list(reversed(bom_tree.level_order_traversal())) - bom_traversal.append(bom_tree) # add operation on top level item last + bom_traversal = reversed(bom_tree.level_order_traversal()) - for d in bom_traversal: - if d.is_bom: - operations.extend(_get_operations(d.name, qty=d.exploded_qty)) + for node in bom_traversal: + if node.is_bom: + operations.extend(_get_operations(node.name, qty=node.exploded_qty)) - for correct_index, operation in enumerate(operations, start=1): - operation.idx = correct_index + bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") + operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty)) + for correct_index, operation in enumerate(operations, start=1): + operation.idx = correct_index self.set('operations', operations) self.calculate_time() From cd12d95a246bcf5c49eda78fe8ce4fa9c90e6b76 Mon Sep 17 00:00:00 2001 From: Ankush Date: Sun, 25 Jul 2021 13:10:50 +0530 Subject: [PATCH 103/253] fix: incorrect amount in work order required items table. (#26585) * fix: amount in work order not equal to rate * qty * fix: patch for amount in work order required items --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- erpnext/patches.txt | 1 + .../v13_0/update_amt_in_work_order_required_items.py | 10 ++++++++++ 4 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v13_0/update_amt_in_work_order_required_items.py diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 8692f3dc48..bc092ef14f 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -774,7 +774,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite item.image, bom.project, bom_item.rate, - bom_item.amount, + sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount, item.stock_uom, item.item_group, item.allow_alternative_item, diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 69812c7452..282b5d0afe 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -655,7 +655,7 @@ class WorkOrder(Document): for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999): self.append('required_items', { 'rate': item.rate, - 'amount': item.amount, + 'amount': item.rate * item.qty, 'operation': item.operation or operation, 'item_code': item.item_code, 'item_name': item.item_name, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8debf86432..a029627ab1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -296,3 +296,4 @@ erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships +erpnext.patches.v13_0.update_amt_in_work_order_required_items diff --git a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py new file mode 100644 index 0000000000..eae5ff60b9 --- /dev/null +++ b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py @@ -0,0 +1,10 @@ +import frappe + +def execute(): + """ Correct amount in child table of required items table.""" + + frappe.reload_doc("manufacturing", "doctype", "work_order") + frappe.reload_doc("manufacturing", "doctype", "work_order_item") + + frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""") + From f4701c174a79b848ad30325036832235458fec85 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 19:46:20 +0530 Subject: [PATCH 104/253] fix: Exchange rate revaluation posting date and precision fixes --- .../exchange_rate_revaluation.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 56193216a2..e94875f2d7 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -99,10 +99,12 @@ class ExchangeRateRevaluation(Document): sum(debit) - sum(credit) as balance from `tabGL Entry` where account in (%s) + and posting_date <= %s + and is_cancelled = 0 group by account, party_type, party having sum(debit) != sum(credit) order by account - """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1) + """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1) return account_details @@ -143,9 +145,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate":d.get("new_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name, }) @@ -154,9 +156,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - reverse_dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate": d.get("current_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name }) @@ -185,9 +187,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N account_details = {} company_currency = erpnext.get_company_currency(company) - balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False) + balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False) if balance: - balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party) + balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party) current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0 new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date) new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate From c485d5c3b7503136ad03cc29dd69317e2ad17696 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 19:46:50 +0530 Subject: [PATCH 105/253] fix: Ignore GL Entry on cancel --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index e94875f2d7..c8d5737d75 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document): if not (self.company and self.posting_date): frappe.throw(_("Please select Company and Posting Date to getting entries")) + def on_cancel(self): + self.ignore_linked_doctypes = ('GL Entry') + @frappe.whitelist() def check_journal_entry_condition(self): total_debit = frappe.db.get_value("Journal Entry Account", { From 67273b955123778e4018aeb8dcde01bf1a60cbd4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 21:26:22 +0530 Subject: [PATCH 106/253] fix: Convert null values to empty string on grouping --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index c8d5737d75..f2b0a8c08a 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -104,7 +104,7 @@ class ExchangeRateRevaluation(Document): where account in (%s) and posting_date <= %s and is_cancelled = 0 - group by account, party_type, party + group by account, NULLIF(party_type,''), NULLIF(party,'') having sum(debit) != sum(credit) order by account """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1) From 2d439f235522b55e236a7f31e407bf56f89d153e Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Mon, 26 Jul 2021 11:08:31 +0530 Subject: [PATCH 107/253] chore: Updated CODEOWNERS --- CODEOWNERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 219b6bb782..330a8dba01 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,13 +21,13 @@ erpnext/quality_management/ @marination @rohitwaghchaure erpnext/shopping_cart/ @marination erpnext/stock/ @marination @rohitwaghchaure @ankush -erpnext/crm/ @ruchamahabal +erpnext/crm/ @ruchamahabal @pateljannat erpnext/education/ @ruchamahabal -erpnext/healthcare/ @ruchamahabal -erpnext/hr/ @ruchamahabal +erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand +erpnext/hr/ @ruchamahabal @pateljannat erpnext/non_profit/ @ruchamahabal -erpnext/payroll @ruchamahabal -erpnext/projects/ @ruchamahabal +erpnext/payroll @ruchamahabal @pateljannat +erpnext/projects/ @ruchamahabal @pateljannat erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination From cbddedab7bf2fc7637b861214c3373a742da830b Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 26 Jul 2021 12:54:35 +0530 Subject: [PATCH 108/253] fix: included company in Link Document Type filters for contact (#26576) --- erpnext/hooks.py | 3 ++- erpnext/public/js/contact.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 erpnext/public/js/contact.js diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9717bb9b17..59b011d1a9 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -25,7 +25,8 @@ doctype_js = { "Address": "public/js/address.js", "Communication": "public/js/communication.js", "Event": "public/js/event.js", - "Newsletter": "public/js/newsletter.js" + "Newsletter": "public/js/newsletter.js", + "Contact": "public/js/contact.js" } override_doctype_class = { diff --git a/erpnext/public/js/contact.js b/erpnext/public/js/contact.js new file mode 100644 index 0000000000..41a0e8a9f9 --- /dev/null +++ b/erpnext/public/js/contact.js @@ -0,0 +1,16 @@ + + +frappe.ui.form.on("Contact", { + refresh(frm) { + frm.set_query('link_doctype', "links", function() { + return { + query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes", + filters: { + fieldtype: ["in", ["HTML", "Text Editor"]], + fieldname: ["in", ["contact_html", "company_description"]], + } + }; + }); + frm.refresh_field("links"); + } +}); From 64454a5dc8510c6a9b76c9a53ef8a00900a62f4d Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 26 Jul 2021 12:54:35 +0530 Subject: [PATCH 109/253] fix: included company in Link Document Type filters for contact (#26576) (cherry picked from commit cbddedab7bf2fc7637b861214c3373a742da830b) --- erpnext/hooks.py | 3 ++- erpnext/public/js/contact.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 erpnext/public/js/contact.js diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 52daec9180..1ba752a146 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -24,7 +24,8 @@ doctype_js = { "Address": "public/js/address.js", "Communication": "public/js/communication.js", "Event": "public/js/event.js", - "Newsletter": "public/js/newsletter.js" + "Newsletter": "public/js/newsletter.js", + "Contact": "public/js/contact.js" } override_doctype_class = { diff --git a/erpnext/public/js/contact.js b/erpnext/public/js/contact.js new file mode 100644 index 0000000000..41a0e8a9f9 --- /dev/null +++ b/erpnext/public/js/contact.js @@ -0,0 +1,16 @@ + + +frappe.ui.form.on("Contact", { + refresh(frm) { + frm.set_query('link_doctype', "links", function() { + return { + query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes", + filters: { + fieldtype: ["in", ["HTML", "Text Editor"]], + fieldname: ["in", ["contact_html", "company_description"]], + } + }; + }); + frm.refresh_field("links"); + } +}); From ed68f11a4670f0ee9d080dbf293818b026e36f50 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 26 Jul 2021 16:47:36 +0530 Subject: [PATCH 110/253] fix: Supplier invoice importer fix pre release (#26636) * fix: Supplier Invoice Importer fix Co-authored-by: Subin Tom --- erpnext/controllers/accounts_controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4c313c43a7..cdd865ac4a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1112,8 +1112,11 @@ class AccountsController(TransactionBase): for d in self.get("payment_schedule"): if d.invoice_portion: d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) - d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount')) d.outstanding = d.payment_amount + elif not d.invoice_portion: + d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount')) + def set_due_date(self): due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date] From 50b76d04bfae7a03472c0d08b104744fc8b941ac Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 19 Jul 2021 20:09:37 +0530 Subject: [PATCH 111/253] fix:Ignore mandatory fields while creating payment reconciliation Journal Entry --- .../doctype/payment_reconciliation/payment_reconciliation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 6635128f9e..d788d91855 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -306,5 +306,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company): } ] }) - + jv.flags.ignore_mandatory = True jv.submit() \ No newline at end of file From 00fd319531438cd6eb31db5b80584ccdf17687d3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Jul 2021 14:36:54 +0530 Subject: [PATCH 112/253] fix: Add missing cess amount in GSTR-3B report --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 641520437f..6de228fbc7 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -322,6 +322,9 @@ class GSTR3BReport(Document): inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100) + if self.invoice_cess.get(inv): + self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2) + self.set_inter_state_supply(inter_state_supply_details) def set_supplies_liable_to_reverse_charge(self): From c468e4a93d991000ce3e4e86b30567c99b1a5fbc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 19 Jul 2021 14:36:54 +0530 Subject: [PATCH 113/253] fix: Add missing cess amount in GSTR-3B report --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 641520437f..6de228fbc7 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -322,6 +322,9 @@ class GSTR3BReport(Document): inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100) + if self.invoice_cess.get(inv): + self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2) + self.set_inter_state_supply(inter_state_supply_details) def set_supplies_liable_to_reverse_charge(self): From 19589b1e21083c42486e70ea405e8d29fdb75b1d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Jul 2021 13:25:53 +0530 Subject: [PATCH 114/253] fix: GST Reports timeout issue --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 5 ++--- erpnext/regional/report/gstr_1/gstr_1.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 641520437f..6a61ae2b42 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -214,9 +214,8 @@ class GSTR3BReport(Document): for d in item_details: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: self.is_nil_exempt.append(d.item_code) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index cfcb8c3444..b81fa810fe 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -217,9 +217,8 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) item_tax_rate = {} From 5fe7d80a6453b4dd7d33be059eeee62bc88814e8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 21 Jul 2021 13:25:53 +0530 Subject: [PATCH 115/253] fix: GST Reports timeout issue --- erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py | 5 ++--- erpnext/regional/report/gstr_1/gstr_1.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 641520437f..6a61ae2b42 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -214,9 +214,8 @@ class GSTR3BReport(Document): for d in item_details: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: self.is_nil_exempt.append(d.item_code) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index cfcb8c3444..b81fa810fe 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -217,9 +217,8 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0) + self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) item_tax_rate = {} From abc63cffa838fb83b565411bfa61cfdc8018ed79 Mon Sep 17 00:00:00 2001 From: Anuja Pawar Date: Mon, 26 Jul 2021 18:11:38 +0530 Subject: [PATCH 116/253] fix: suggested changes --- .../vat_audit_report/vat_audit_report.py | 33 ++++++++++--------- erpnext/regional/south_africa/setup.py | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index 5633b64bfb..feb2a1620d 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -40,7 +40,7 @@ class VATAuditReport(object): def get_sa_vat_accounts(self): self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", - filters = {"parent":self.filters.company}, pluck="account") + filters = {"parent": self.filters.company}, pluck="account") if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) @@ -144,7 +144,7 @@ class VATAuditReport(object): return conditions - def get_data(self,doctype): + def get_data(self, doctype): consolidated_data = self.get_consolidated_data() section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales") @@ -173,20 +173,21 @@ class VATAuditReport(object): def get_consolidated_data(self): consolidated_data_map={} for inv, inv_data in self.invoices.items(): - for rate, items in self.items_based_on_tax_rate.get(inv).items(): - consolidated_data_map.setdefault(rate, {"data": []}) - for item in items: - row = {} - item_details = self.item_tax_rate.get(inv).get(item) - row["account"] = inv_data.get("account") - row["posting_date"] = formatdate(inv_data.get("posting_date"), 'dd-mm-yyyy') - row["invoice_number"] = inv - row["party"] = inv_data.get("party") - row["remarks"] = inv_data.get("remarks") - row["gross_amount"]= item_details[0].get("gross_amount") - row["tax_amount"]= item_details[0].get("tax_amount") - row["net_amount"]= item_details[0].get("net_amount") - consolidated_data_map[rate]["data"].append(row) + if self.items_based_on_tax_rate.get(inv): + for rate, items in self.items_based_on_tax_rate.get(inv).items(): + consolidated_data_map.setdefault(rate, {"data": []}) + for item in items: + row = {} + item_details = self.item_tax_rate.get(inv).get(item) + row["account"] = inv_data.get("account") + row["posting_date"] = formatdate(inv_data.get("posting_date"), 'dd-mm-yyyy') + row["invoice_number"] = inv + row["party"] = inv_data.get("party") + row["remarks"] = inv_data.get("remarks") + row["gross_amount"]= item_details[0].get("gross_amount") + row["tax_amount"]= item_details[0].get("tax_amount") + row["net_amount"]= item_details[0].get("net_amount") + consolidated_data_map[rate]["data"].append(row) return consolidated_data_map diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py index 2c44c6e6b1..a4335bddef 100644 --- a/erpnext/regional/south_africa/setup.py +++ b/erpnext/regional/south_africa/setup.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals # import frappe, os, json from frappe.permissions import add_permission, update_permission_property -def setup(): +def setup(company=None, patch=True): add_permissions() def add_permissions(): From bc82344fc7242ba537c1a30879ec304efb30ac08 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 28 Jun 2021 16:50:20 +0530 Subject: [PATCH 117/253] feat: over transfer allowance for material transfers --- .../doctype/material_request/material_request.py | 9 ++++++++- .../stock/doctype/stock_settings/stock_settings.json | 11 +++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 3ad9909ad0..026b85e26d 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -162,8 +162,15 @@ class MaterialRequest(BuyingController): 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/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 2a9dcfb67e..f75cb56138 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 +} From ba18a96b0cfa452ffcd6bd810ef08142dc4edfb3 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 30 Jun 2021 20:16:50 +0530 Subject: [PATCH 118/253] test: test case for over transfer of materials --- .../material_request/test_material_request.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 72a3a5e67c..b4776ba249 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 @@ class TestMaterialRequest(unittest.TestCase): 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") From 16feebf6856600cca43a4c1a524fbd98b0de9be7 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 26 Jul 2021 18:17:30 +0530 Subject: [PATCH 119/253] Update CODEOWNERS chore: update code owner for education module --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 330a8dba01..a4a14de1b8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -22,7 +22,7 @@ erpnext/shopping_cart/ @marination erpnext/stock/ @marination @rohitwaghchaure @ankush erpnext/crm/ @ruchamahabal @pateljannat -erpnext/education/ @ruchamahabal +erpnext/education/ @ruchamahabal @pateljannat erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand erpnext/hr/ @ruchamahabal @pateljannat erpnext/non_profit/ @ruchamahabal From a661667e2a340afdc495cd78cb4dfb59fda5546e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 23 Jul 2021 20:28:02 +0530 Subject: [PATCH 120/253] fix(India): Default value for export type --- erpnext/patches.txt | 1 + .../v13_0/update_export_type_for_gst.py | 24 +++++++++++++++++++ erpnext/regional/india/setup.py | 2 -- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v13_0/update_export_type_for_gst.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2a83635117..b891719b02 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -293,3 +293,4 @@ erpnext.patches.v13_0.update_job_card_details erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships +erpnext.patches.v13_0.update_export_type_for_gst diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py new file mode 100644 index 0000000000..478a2a6c80 --- /dev/null +++ b/erpnext/patches/v13_0/update_export_type_for_gst.py @@ -0,0 +1,24 @@ +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # Update custom fields + fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'}) + if fieldname: + frappe.db.set_value('Custom Field', fieldname, 'default', '') + + fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'}) + if fieldname: + frappe.db.set_value('Custom Field', fieldname, 'default', '') + + # Update Customer/Supplier Masters + frappe.db.sql(""" + UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export') + """) + + frappe.db.sql(""" + UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas') + """) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 92654608da..e9372f9b8f 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -641,7 +641,6 @@ def make_custom_fields(update=True): 'label': 'Export Type', 'fieldtype': 'Select', 'insert_after': 'gst_category', - 'default': 'Without Payment of Tax', 'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)', 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } @@ -660,7 +659,6 @@ def make_custom_fields(update=True): 'label': 'Export Type', 'fieldtype': 'Select', 'insert_after': 'gst_category', - 'default': 'Without Payment of Tax', 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } From cba847b0517b65fdf415da4224f8c55ea69d6b43 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 26 Jul 2021 18:38:50 +0530 Subject: [PATCH 121/253] fix: Test case for GSTR-3b report --- .../regional/doctype/gstr_3b_report/gstr_3b_report.py | 10 ++++++++-- erpnext/regional/report/gstr_1/gstr_1.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py index 641520437f..6fd135d560 100644 --- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -281,9 +281,15 @@ class GSTR3BReport(Document): if self.get('invoice_items'): # Build itemised tax for export invoices, nil and exempted where tax table is blank for invoice, items in iteritems(self.invoice_items): - if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type') - == "Without Payment of Tax"): + if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \ + == "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) + else: + for item in items.keys(): + if item in self.is_nil_exempt + self.is_non_gst and \ + item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []): + self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, []) + self.items_based_on_tax_rate[invoice][0].append(item) def set_outward_taxable_supplies(self): inter_state_supply_details = {} diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index cfcb8c3444..f9de2d527b 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -287,7 +287,8 @@ class Gstr1Report(object): # Build itemised tax for export invoices where tax table is blank for invoice, items in iteritems(self.invoice_items): if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \ - and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax": + and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \ + and self.invoices.get(invoice, {}).get('gst_category') == "Overseas": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) def get_columns(self): From 356a55258ee4248deea931db7a7ee1b6252116d3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 19:46:20 +0530 Subject: [PATCH 122/253] fix: Exchange rate revaluation posting date and precision fixes --- .../exchange_rate_revaluation.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 56193216a2..e94875f2d7 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -99,10 +99,12 @@ class ExchangeRateRevaluation(Document): sum(debit) - sum(credit) as balance from `tabGL Entry` where account in (%s) + and posting_date <= %s + and is_cancelled = 0 group by account, party_type, party having sum(debit) != sum(credit) order by account - """ % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1) + """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1) return account_details @@ -143,9 +145,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate":d.get("new_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name, }) @@ -154,9 +156,9 @@ class ExchangeRateRevaluation(Document): "party_type": d.get("party_type"), "party": d.get("party"), "account_currency": d.get("account_currency"), - "balance": d.get("balance_in_account_currency"), - reverse_dr_or_cr: abs(d.get("balance_in_account_currency")), - "exchange_rate": d.get("current_exchange_rate"), + "balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")), + reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")), + "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")), "reference_type": "Exchange Rate Revaluation", "reference_name": self.name }) @@ -185,9 +187,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N account_details = {} company_currency = erpnext.get_company_currency(company) - balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False) + balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False) if balance: - balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party) + balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party) current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0 new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date) new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate From 19a0ca1980b69d04310cbbe27e05f117622c3894 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 19:46:50 +0530 Subject: [PATCH 123/253] fix: Ignore GL Entry on cancel --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index e94875f2d7..c8d5737d75 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document): if not (self.company and self.posting_date): frappe.throw(_("Please select Company and Posting Date to getting entries")) + def on_cancel(self): + self.ignore_linked_doctypes = ('GL Entry') + @frappe.whitelist() def check_journal_entry_condition(self): total_debit = frappe.db.get_value("Journal Entry Account", { From 3fcc5e3134f0aa2d29d6baabbc38050437fa42e1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 25 Jul 2021 21:26:22 +0530 Subject: [PATCH 124/253] fix: Convert null values to empty string on grouping --- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index c8d5737d75..f2b0a8c08a 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -104,7 +104,7 @@ class ExchangeRateRevaluation(Document): where account in (%s) and posting_date <= %s and is_cancelled = 0 - group by account, party_type, party + group by account, NULLIF(party_type,''), NULLIF(party,'') having sum(debit) != sum(credit) order by account """ % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1) From 5c82af50f92e54382cda072735716f3ed45d9efe Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 26 Jul 2021 19:41:53 +0530 Subject: [PATCH 125/253] fix: Set Expense account from warehouse only if warehouse exists --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index b99d75ec49..1d80184abf 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -246,7 +246,7 @@ class PurchaseInvoice(BuyingController): and (not item.po_detail or not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")): - if self.update_stock and (not item.from_warehouse): + if self.update_stock and item.warehouse and (not item.from_warehouse): if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]: msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format( item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse)) @@ -657,7 +657,7 @@ class PurchaseInvoice(BuyingController): ) gl_entries.append( self.get_gl_dict({ - "account": self.get_company_default("exchange_gain_loss_account"), + "account": self.get_company_default("exchange_gain_loss_account"), "against": self.supplier, "credit": discrepancy_caused_by_exchange_rate_difference, "cost_center": item.cost_center, @@ -1193,7 +1193,7 @@ def get_purchase_document_details(doc): purchase_receipts_or_invoices.append(item.get(doc_reference)) if item.get(items_reference): items.append(item.get(items_reference)) - + exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in', purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1)) From 77f9f048b672330e48f46685e38aa965e98d4660 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 26 Jul 2021 21:32:15 +0530 Subject: [PATCH 126/253] chore: test case for missing default warehouse on mapping --- .../purchase_invoice/purchase_invoice.py | 6 +- .../sales_invoice/test_sales_invoice.py | 92 ++++++++++++++++++- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 1d80184abf..863c104dff 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -27,6 +27,8 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost +class WarehouseMissingError(frappe.ValidationError): pass + form_grid_templates = { "items": "templates/form_grid/item_grid.html" } @@ -207,8 +209,8 @@ class PurchaseInvoice(BuyingController): if self.update_stock and for_validate: for d in self.get('items'): if not d.warehouse: - frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}"). - format(d.idx, d.item_code, self.company)) + frappe.throw(_("Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}"). + format(d.idx, d.item_code, self.company), exc=WarehouseMissingError) super(PurchaseInvoice, self).validate_warehouse() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index c6e6e3da6f..be20b18bea 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2,13 +2,14 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext import unittest, copy, time from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months from frappe.model.dynamic_links import get_dynamic_link_map from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice +from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import WarehouseMissingError from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency @@ -1073,7 +1074,7 @@ class TestSalesInvoice(unittest.TestCase): def test_gle_made_when_asset_is_returned(self): create_asset_data() asset = create_asset(item_code="Macbook Pro") - + si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000) return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000) @@ -1081,7 +1082,7 @@ class TestSalesInvoice(unittest.TestCase): # Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000 loss_for_si = frappe.get_all( - "GL Entry", + "GL Entry", filters = { "voucher_no": si.name, "account": disposal_account @@ -1090,7 +1091,7 @@ class TestSalesInvoice(unittest.TestCase): )[0] loss_for_return_si = frappe.get_all( - "GL Entry", + "GL Entry", filters = { "voucher_no": return_si.name, "account": disposal_account @@ -1836,6 +1837,89 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.supplier, "_Test Internal Supplier") + def test_inter_company_transaction_without_default_warehouse(self): + "Check mapping (expense account) of inter company SI to PI in absence of default warehouse." + # setup + old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock") + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + + old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled('_Test Company 1') + frappe.local.enable_perpetual_inventory['_Test Company 1'] = 1 + + frappe.db.set_value("Company", '_Test Company 1', "stock_received_but_not_billed", "Stock Received But Not Billed - _TC1") + frappe.db.set_value("Company", '_Test Company 1', "expenses_included_in_valuation", "Expenses Included In Valuation - _TC1") + + + if not frappe.db.exists("Customer", "_Test Internal Customer"): + customer = frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "_Test Internal Customer", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory", + "is_internal_customer": 1, + "represents_company": "_Test Company 1" + }) + + customer.append("companies", { + "company": "Wind Power LLC" + }) + + customer.insert() + + if not frappe.db.exists("Supplier", "_Test Internal Supplier"): + supplier = frappe.get_doc({ + "supplier_group": "_Test Supplier Group", + "supplier_name": "_Test Internal Supplier", + "doctype": "Supplier", + "is_internal_supplier": 1, + "represents_company": "Wind Power LLC" + }) + + supplier.append("companies", { + "company": "_Test Company 1" + }) + + supplier.insert() + + # begin test + si = create_sales_invoice( + company = "Wind Power LLC", + customer = "_Test Internal Customer", + debit_to = "Debtors - WP", + warehouse = "Stores - WP", + income_account = "Sales - WP", + expense_account = "Cost of Goods Sold - WP", + cost_center = "Main - WP", + currency = "USD", + update_stock = 1, + do_not_save = 1 + ) + si.selling_price_list = "_Test Price List Rest of the World" + si.submit() + + target_doc = make_inter_company_transaction("Sales Invoice", si.name) + + # in absence of warehouse Stock Received But Not Billed is set as expense account while mapping + # mapping is not obstructed + self.assertIsNone(target_doc.items[0].warehouse) + self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1") + + target_doc.items[0].update({"cost_center": "Main - _TC1"}) + + # missing warehouse is validated on save, after mapping + self.assertRaises(WarehouseMissingError, target_doc.save) + + target_doc.items[0].update({"warehouse": "Stores - _TC1"}) + target_doc.save() + + # after warehouse is set, linked account or default inventory account is set + self.assertEqual(target_doc.items[0].expense_account, 'Stock In Hand - _TC1') + + # tear down + frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory + frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock) + def test_internal_transfer_gl_entry(self): ## Create internal transfer account account = create_account(account_name="Unrealized Profit", From 03a6c38f06034383b57593145f8c9e77c42d6370 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Jul 2021 22:24:25 +0530 Subject: [PATCH 127/253] test: fix test due to rename change --- erpnext/selling/doctype/sales_order/test_sales_order.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 974648d6d4..1de1e00097 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -673,6 +673,8 @@ class TestSalesOrder(unittest.TestCase): so.cancel() + dn.load_from_db() + self.assertRaises(frappe.CancelledLinkError, dn.submit) def test_service_type_product_bundle(self): From ae9d1d9617015b6e2714a9fcd027a2957053d99a Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 27 Jul 2021 09:40:12 +0530 Subject: [PATCH 128/253] fix: Salary component account filter (#26604) * fix: salary component account filter * fix: cleanup --- .../doctype/salary_component/salary_component.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js index dbf75140ac..e9e6f81862 100644 --- a/erpnext/payroll/doctype/salary_component/salary_component.js +++ b/erpnext/payroll/doctype/salary_component/salary_component.js @@ -4,11 +4,18 @@ frappe.ui.form.on('Salary Component', { setup: function(frm) { frm.set_query("account", "accounts", function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; + let d = frappe.get_doc(cdt, cdn); + + let root_type = "Liability"; + if (frm.doc.type == "Deduction") { + root_type = "Expense"; + } + return { filters: { "is_group": 0, - "company": d.company + "company": d.company, + "root_type": root_type } }; }); From 34353df48c3bf58f61157ebb58accca886fc0b1a Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 27 Jul 2021 09:47:44 +0530 Subject: [PATCH 129/253] fix: sales pipeline graph issue (#26626) --- erpnext/selling/page/sales_funnel/sales_funnel.css | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.css b/erpnext/selling/page/sales_funnel/sales_funnel.css index 89e904fcfc..455d37cb23 100644 --- a/erpnext/selling/page/sales_funnel/sales_funnel.css +++ b/erpnext/selling/page/sales_funnel/sales_funnel.css @@ -1,3 +1,4 @@ .funnel-wrapper { margin: 15px; + width: 100%; } \ No newline at end of file From 6b482ebb0f8b8ce6d7efe4704f853c4508954c45 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 23 Jul 2021 16:40:45 +0530 Subject: [PATCH 130/253] fix: serial no and batch validation --- erpnext/controllers/stock_controller.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 2526e6df0e..17bd7354f9 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -53,12 +53,17 @@ class StockController(AccountsController): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos for d in self.get("items"): if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no: - serial_nos = get_serial_nos(d.serial_no) - for serial_no_data in frappe.get_all("Serial No", - filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]): - if serial_no_data.batch_no != d.batch_no: + serial_nos = frappe.get_all("Serial No", + fields=["batch_no", "name", "warehouse"], + filters={ + "name": ("in", get_serial_nos(d.serial_no)) + } + ) + + for row in serial_nos: + if row.warehouse and row.batch_no != d.batch_no: frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}") - .format(d.idx, serial_no_data.name, d.batch_no)) + .format(d.idx, row.name, d.batch_no)) if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2: expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date") From dfdd1c6e02e74abeb3a9d67d59f33f1e0dbeab48 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 23 Jul 2021 15:50:37 +0530 Subject: [PATCH 131/253] feat: don't recompute taxes --- .../sales_taxes_and_charges.json | 14 ++++++++++++-- erpnext/controllers/taxes_and_totals.py | 7 ++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 1b7a0fe562..cfdb167bbc 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -27,7 +27,8 @@ "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", - "item_wise_tax_detail" + "item_wise_tax_detail", + "dont_recompute_tax" ], "fields": [ { @@ -200,13 +201,22 @@ "fieldname": "included_in_paid_amount", "fieldtype": "Check", "label": "Considered In Paid Amount" + }, + { + "default": "0", + "fieldname": "dont_recompute_tax", + "fieldtype": "Check", + "hidden": 1, + "label": "Dont Recompute tax", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-14 01:44:36.899147", + "modified": "2021-07-27 12:40:59.051803", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 56da5b71da..099c7d4346 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -152,7 +152,7 @@ class calculate_taxes_and_totals(object): validate_taxes_and_charges(tax) validate_inclusive_tax(tax, self.doc) - if not self.doc.get('is_consolidated'): + if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")): tax.item_wise_tax_detail = {} tax_fields = ["total", "tax_amount_after_discount_amount", @@ -347,7 +347,7 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty - if not self.doc.get("is_consolidated"): + if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")): self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount @@ -455,7 +455,8 @@ class calculate_taxes_and_totals(object): def _cleanup(self): if not self.doc.get('is_consolidated'): for tax in self.doc.get("taxes"): - tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) + if not tax.get("dont_recompute_tax"): + tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) def set_discount_amount(self): if self.doc.additional_discount_percentage: From c8d7a8c781f6c448fd872427d611ffab70c136db Mon Sep 17 00:00:00 2001 From: Ankush Date: Tue, 27 Jul 2021 16:39:38 +0530 Subject: [PATCH 132/253] fix: reload manufacturing setting before patch (#26641) --- erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py index 48999e6f99..d7ad1fc696 100644 --- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -10,6 +10,7 @@ def execute(): if not frappe.db.has_column('Work Order', 'has_batch_no'): return + frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings') if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')): return @@ -107,4 +108,4 @@ def repost_future_sle_and_gle(doc): "company": doc.company }) - create_repost_item_valuation_entry(args) \ No newline at end of file + create_repost_item_valuation_entry(args) From f1c697ca758ea710b685f147cc0399c0d0d7e777 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 27 Jul 2021 16:49:04 +0530 Subject: [PATCH 133/253] fix: sider issue --- erpnext/crm/doctype/campaign/campaign.js | 7 +++---- erpnext/crm/doctype/campaign/campaign.py | 1 + erpnext/crm/doctype/campaign/test_campaign.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js index 04876541ba..11bfa74b29 100644 --- a/erpnext/crm/doctype/campaign/campaign.js +++ b/erpnext/crm/doctype/campaign/campaign.js @@ -5,12 +5,11 @@ frappe.ui.form.on('Campaign', { refresh: function(frm) { erpnext.toggle_naming_series(); - if(frm.doc.__islocal) { + if (frm.doc.__islocal) { frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series"); - } - else { + } else { cur_frm.add_custom_button(__("View Leads"), function() { - frappe.route_options = {"source": "Campaign","campaign_name": frm.doc.name} + frappe.route_options = {"source": "Campaign", "campaign_name": frm.doc.name}; frappe.set_route("List", "Lead"); }, "fa fa-list", true); } diff --git a/erpnext/crm/doctype/campaign/campaign.py b/erpnext/crm/doctype/campaign/campaign.py index 34331952c0..e32799f34e 100644 --- a/erpnext/crm/doctype/campaign/campaign.py +++ b/erpnext/crm/doctype/campaign/campaign.py @@ -3,6 +3,7 @@ import frappe from frappe.model.document import Document +from frappe.model.naming import set_name_by_naming_series class Campaign(Document): def autoname(self): diff --git a/erpnext/crm/doctype/campaign/test_campaign.py b/erpnext/crm/doctype/campaign/test_campaign.py index 939bb8f464..7124b8c7d6 100644 --- a/erpnext/crm/doctype/campaign/test_campaign.py +++ b/erpnext/crm/doctype/campaign/test_campaign.py @@ -1,7 +1,7 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -import frappe +# import frappe import unittest class TestCampaign(unittest.TestCase): From 7a97b6d6a831e5bf77f2b5d28264f998aa5a933f Mon Sep 17 00:00:00 2001 From: Ankush Date: Tue, 27 Jul 2021 16:39:38 +0530 Subject: [PATCH 134/253] fix: reload manufacturing setting before patch (#26641) (cherry picked from commit c8d7a8c781f6c448fd872427d611ffab70c136db) --- erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py index 48999e6f99..d7ad1fc696 100644 --- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -10,6 +10,7 @@ def execute(): if not frappe.db.has_column('Work Order', 'has_batch_no'): return + frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings') if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')): return @@ -107,4 +108,4 @@ def repost_future_sle_and_gle(doc): "company": doc.company }) - create_repost_item_valuation_entry(args) \ No newline at end of file + create_repost_item_valuation_entry(args) From 940356d28a0073f139d3b0dbdc9b0b4dee974a16 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 27 Jul 2021 18:43:20 +0530 Subject: [PATCH 135/253] fix: not able to add employee in the job card --- erpnext/manufacturing/doctype/job_card/job_card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 420bb00803..69c7f5c614 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -192,11 +192,11 @@ class JobCard(Document): "completed_qty": args.get("completed_qty") or 0.0 }) elif args.get("start_time"): - new_args = { + new_args = frappe._dict({ "from_time": get_datetime(args.get("start_time")), "operation": args.get("sub_operation"), "completed_qty": 0.0 - } + }) if employees: for name in employees: From 5a7fad8a6ab5ac16d6ad3ded583c092cc6f49c37 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Jul 2021 10:59:25 +0530 Subject: [PATCH 136/253] feat: Enhancements in TDS --- .../tax_withholding_category.json | 350 ++++++------------ .../tax_withholding_category.py | 24 +- .../test_tax_withholding_category.py | 47 ++- 3 files changed, 184 insertions(+), 237 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index f9160e281d..331770fbe8 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -1,263 +1,151 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "Prompt", - "beta": 0, - "creation": "2018-04-13 18:42:06.431683", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2018-04-13 18:42:06.431683", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "category_details_section", + "category_name", + "round_off_tax_amount", + "column_break_2", + "consider_party_ledger_amount", + "tax_on_excess_amount", + "section_break_8", + "rates", + "section_break_7", + "accounts" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "category_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Category Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Tax Withholding Rates", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "show_days": 1, + "show_seconds": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "rates", "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Rates", - "length": 0, - "no_copy": 0, "options": "Tax Withholding Rate", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "show_days": 1, + "show_seconds": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", "label": "Account Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "show_days": 1, + "show_seconds": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "accounts", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Tax Withholding Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Tax Withholding Account", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "category_details_section", + "fieldtype": "Section Break", + "label": "Category Details", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach", + "fieldname": "consider_party_ledger_amount", + "fieldtype": "Check", + "label": "Consider Entire Party Ledger Amount", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "description": "Tax will be withheld only for amount exceeding the cumulative threshold", + "fieldname": "tax_on_excess_amount", + "fieldtype": "Check", + "label": "Only Deduct Tax On Excess Amount ", + "show_days": 1, + "show_seconds": 1 + }, + { + "description": "Checking this will round off the tax amount to the nearest integer", + "fieldname": "round_off_tax_amount", + "fieldtype": "Data", + "label": "Round Off Tax Amount", + "show_days": 1, + "show_seconds": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-07-17 22:53:26.193179", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Withholding Category", - "name_case": "", - "owner": "Administrator", + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-07-26 21:47:34.396071", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Withholding Category", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index b9ee4a0963..45c8e1b49f 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import flt, getdate +from frappe.utils import flt, getdate, cint from erpnext.accounts.utils import get_fiscal_year class TaxWithholdingCategory(Document): @@ -86,7 +86,10 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): "rate": tax_rate_detail.tax_withholding_rate, "threshold": tax_rate_detail.single_threshold, "cumulative_threshold": tax_rate_detail.cumulative_threshold, - "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category + "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category, + "consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount, + "tax_on_excess_amount": tax_withholding.tax_on_excess_amount, + "round_off_tax_amount": tax_withholding.round_off_tax_amount }) def get_tax_withholding_rates(tax_withholding, fiscal_year): @@ -235,10 +238,15 @@ def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): tds_amount = 0 + invoice_filters = { + 'name': ('in', vouchers), + 'docstatus': 1 + } - supp_credit_amt = frappe.db.get_value('Purchase Invoice', { - 'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1 - }, 'sum(net_total)') or 0.0 + if not cint(tax_details.consider_party_ledger_amount): + invoice_filters.update({'apply_tds': 1}) + + supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0 supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', { 'parent': ('in', vouchers), 'docstatus': 1, @@ -255,6 +263,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu cumulative_threshold = tax_details.get('cumulative_threshold', 0) if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): + if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount): + supp_credit_amt -= cumulative_threshold + if ldc and is_valid_certificate( ldc.valid_from, ldc.valid_upto, inv.get('posting_date') or inv.get('transaction_date'), tax_deducted, @@ -263,6 +274,9 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) else: tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 + + if cint(tax_details.round_off_tax_amount): + tds_amount = round(tds_amount) return tds_amount diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index dd26be7c99..2ba22ca435 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -87,6 +87,31 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_tax_withholding_category_checks(self): + invoices = [] + frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category") + + # First Invoice with no tds check + pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True) + pi.apply_tds = 0 + pi.save() + pi.submit() + invoices.append(pi) + + # Second Invoice will apply TDS checked + pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000) + pi1.submit() + invoices.append(pi1) + + # Cumulative threshold is 30000 + # Threshold calculation should be on both the invoices + # TDS should be applied only on 1000 + self.assertEqual(pi1.taxes[0].tax_amount, 1000) + + for d in invoices: + d.cancel() + + def test_cumulative_threshold_tcs(self): frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS") invoices = [] @@ -195,7 +220,7 @@ def create_sales_invoice(**args): def create_records(): # create a new suppliers - for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']: + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']: if frappe.db.exists('Supplier', name): continue @@ -311,3 +336,23 @@ def create_tax_with_holding_category(): 'account': 'TDS - _TC' }] }).insert() + + if not frappe.db.exists("Tax Withholding Category", "New TDS Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "New TDS Category", + "category_name": "New TDS Category", + "round_off_tax_amount": 1, + "consider_party_ledger_amount": 1, + "tax_on_excess_amount": 1, + "rates": [{ + 'fiscal_year': fiscal_year, + 'tax_withholding_rate': 10, + 'single_threshold': 0, + 'cumulative_threshold': 30000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() From 7903aeca7e8bd413994d13177b7905353f7d710f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 27 Jul 2021 18:43:20 +0530 Subject: [PATCH 137/253] fix: not able to add employee in the job card --- erpnext/manufacturing/doctype/job_card/job_card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 420bb00803..69c7f5c614 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -192,11 +192,11 @@ class JobCard(Document): "completed_qty": args.get("completed_qty") or 0.0 }) elif args.get("start_time"): - new_args = { + new_args = frappe._dict({ "from_time": get_datetime(args.get("start_time")), "operation": args.get("sub_operation"), "completed_qty": 0.0 - } + }) if employees: for name in employees: From 5d121c41f33a0e108665457af2dd0acab9279aa2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 21 Jul 2021 19:47:41 +0530 Subject: [PATCH 138/253] fix: removed Remarks column from AR/AP report --- .../report/accounts_receivable/accounts_receivable.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a11b77a6f6..b54646fd27 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -99,7 +99,6 @@ class ReceivablePayableReport(object): voucher_no = gle.voucher_no, party = gle.party, posting_date = gle.posting_date, - remarks = gle.remarks, account_currency = gle.account_currency, invoiced = 0.0, paid = 0.0, @@ -579,7 +578,7 @@ class ReceivablePayableReport(object): self.gl_entries = frappe.db.sql(""" select name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, - against_voucher_type, against_voucher, account_currency, remarks, {0} + against_voucher_type, against_voucher, account_currency, {0} from `tabGL Entry` where @@ -792,8 +791,6 @@ class ReceivablePayableReport(object): self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link', options='Supplier Group') - self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200) - def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120): if not fieldname: fieldname = scrub(label) if fieldtype=='Currency': options='currency' From 3a39c0f19ae22632aa4010102b08933eea2de4ee Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 28 Jul 2021 00:19:32 +0530 Subject: [PATCH 139/253] fix: force reload of Opportunity in patch (#26668) (#26681) (cherry picked from commit ac2e139d5bdfe58fb03ee73ef88cf70d855b8caf) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/patches/v13_0/rename_issue_doctype_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py index fa1dfed643..41c51c36dc 100644 --- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py +++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py @@ -37,7 +37,7 @@ def execute(): if frappe.db.exists('DocType', 'Opportunity'): opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc') - frappe.reload_doc('crm', 'doctype', 'opportunity') + frappe.reload_doctype('Opportunity', force=True) rename_field('Opportunity', 'mins_to_first_response', 'first_response_time') # change fieldtype to duration From 441adf763f23385d1e8f6a2db092721956ea8187 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Jul 2021 10:43:02 +0530 Subject: [PATCH 140/253] fix(minor): Consider grand total for threshold check --- .../tax_withholding_category/tax_withholding_category.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 45c8e1b49f..020de3c3f3 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -243,10 +243,13 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu 'docstatus': 1 } + field = 'sum(net_total)' + if not cint(tax_details.consider_party_ledger_amount): invoice_filters.update({'apply_tds': 1}) + field = 'sum(grand_total)' - supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0 + supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0 supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', { 'parent': ('in', vouchers), 'docstatus': 1, From 868a6cb26dc513708c36957d7d2537b3b0d14d4f Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 28 Jul 2021 13:42:13 +0530 Subject: [PATCH 141/253] fix: documentation link for E Invoicing (#26686) --- .../regional/doctype/e_invoice_settings/e_invoice_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index cc2d9f06d2..54e488610d 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -3,7 +3,7 @@ frappe.ui.form.on('E Invoice Settings', { refresh(frm) { - const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing'; + const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing'; frm.dashboard.set_headline( __("Read {0} for more information on E Invoicing features.", [`documentation`]) ); From 42bc0a7db81fa493e33abae0f879e9786bcf3bb1 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Wed, 28 Jul 2021 13:42:53 +0530 Subject: [PATCH 142/253] fix: documentation link for E Invoicing (#26684) --- .../regional/doctype/e_invoice_settings/e_invoice_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js index cc2d9f06d2..54e488610d 100644 --- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -3,7 +3,7 @@ frappe.ui.form.on('E Invoice Settings', { refresh(frm) { - const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing'; + const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing'; frm.dashboard.set_headline( __("Read {0} for more information on E Invoicing features.", [`documentation`]) ); From 655b5dc1906be3836e1a8e0e0b51958605e434ec Mon Sep 17 00:00:00 2001 From: walstanb Date: Wed, 28 Jul 2021 14:30:11 +0530 Subject: [PATCH 143/253] fix: date as reference_date from bank transactions --- erpnext/public/js/bank_reconciliation_tool/dialog_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index 142fe79ccd..239fbb92b1 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -16,7 +16,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { doctype: "Bank Transaction", filters: { name: this.bank_transaction_name }, fieldname: [ - "date", + "date as reference_date", "deposit", "withdrawal", "currency", From d95f16ac8fb084e33ab936545fc60acd6a4ff618 Mon Sep 17 00:00:00 2001 From: Ankush Date: Wed, 28 Jul 2021 16:38:59 +0530 Subject: [PATCH 144/253] fix(bom): remove manual permission checking (#26689) get_list does the permission checking. --- erpnext/manufacturing/doctype/bom/bom.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index bc092ef14f..c68198b0e2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1069,13 +1069,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if barcodes: or_cond_filters["name"] = ("in", barcodes) - for cond in get_match_cond(doctype, as_condition=False): - for key, value in cond.items(): - if key == doctype: - key = "name" - - query_filters[key] = ("in", value) - if filters and filters.get("item_code"): has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants") if not has_variants: @@ -1084,7 +1077,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if filters and filters.get("is_stock_item"): query_filters["is_stock_item"] = 1 - return frappe.get_all("Item", + return frappe.get_list("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, limit_start=start, limit_page_length=page_len, as_list=1) From 8ed7a21cd515ef6e9e109a62195088b13d0a06af Mon Sep 17 00:00:00 2001 From: Ankush Date: Wed, 28 Jul 2021 16:38:59 +0530 Subject: [PATCH 145/253] fix(bom): remove manual permission checking (#26689) get_list does the permission checking. (cherry picked from commit d95f16ac8fb084e33ab936545fc60acd6a4ff618) --- erpnext/manufacturing/doctype/bom/bom.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index af081c449c..ebd9ae2dc5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1069,13 +1069,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if barcodes: or_cond_filters["name"] = ("in", barcodes) - for cond in get_match_cond(doctype, as_condition=False): - for key, value in cond.items(): - if key == doctype: - key = "name" - - query_filters[key] = ("in", value) - if filters and filters.get("item_code"): has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants") if not has_variants: @@ -1084,7 +1077,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): if filters and filters.get("is_stock_item"): query_filters["is_stock_item"] = 1 - return frappe.get_all("Item", + return frappe.get_list("Item", fields = fields, filters=query_filters, or_filters = or_cond_filters, order_by=order_by, limit_start=start, limit_page_length=page_len, as_list=1) From 8c7d9efa9d93ee42306ada4de58bfcb560b77038 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Jul 2021 12:57:59 +0530 Subject: [PATCH 146/253] fix: Chnage fieldtype from data to check --- .../tax_withholding_category.json | 4 ++-- erpnext/patches.txt | 1 + erpnext/patches/v13_0/update_tds_check_field.py | 8 ++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v13_0/update_tds_check_field.py diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index 331770fbe8..153906ffe9 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -94,7 +94,7 @@ { "description": "Checking this will round off the tax amount to the nearest integer", "fieldname": "round_off_tax_amount", - "fieldtype": "Data", + "fieldtype": "Check", "label": "Round Off Tax Amount", "show_days": 1, "show_seconds": 1 @@ -102,7 +102,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-07-26 21:47:34.396071", + "modified": "2021-07-27 21:47:34.396071", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Category", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b891719b02..32763754d2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -294,3 +294,4 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_export_type_for_gst +erpnext.patches.v13_0.update_tds_check_field #3 diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py new file mode 100644 index 0000000000..16bf76d530 --- /dev/null +++ b/erpnext/patches/v13_0/update_tds_check_field.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + if frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): + frappe.db.sql(""" + UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0 + WHERE round_off_tax_amount IS NULL + """) \ No newline at end of file From 6ac68f3bc74843e7f703a86063238221534267fd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 28 Jul 2021 15:30:05 +0530 Subject: [PATCH 147/253] fix: Patch --- erpnext/patches/v13_0/update_tds_check_field.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py index 16bf76d530..3d149586a0 100644 --- a/erpnext/patches/v13_0/update_tds_check_field.py +++ b/erpnext/patches/v13_0/update_tds_check_field.py @@ -1,7 +1,8 @@ import frappe def execute(): - if frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): + if frappe.db.has_table("Tax Withholding Category") \ + and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"): frappe.db.sql(""" UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0 WHERE round_off_tax_amount IS NULL From 49cfac0ef0eb2333e5ff6291a60550d26faf3930 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 28 Jul 2021 17:51:35 +0530 Subject: [PATCH 148/253] feat: added basic info. of lead in header part --- erpnext/crm/doctype/lead/lead.js | 40 ++++++++++++++++++++++++++++++ erpnext/crm/doctype/lead/lead.json | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 815bb41a76..bd1639bb12 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -42,6 +42,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller if (!this.frm.is_new()) { frappe.contacts.render_address_and_contact(this.frm); + cur_frm.trigger('render_basic_info_html'); } else { frappe.contacts.clear_address_and_contact(this.frm); } @@ -81,6 +82,45 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat)); } } + + render_basic_info_html() { + let html=''; + if (cur_frm.doc.lead_owner){ + html += `
+ Lead Owner +
+
+ ${cur_frm.doc.lead_owner} +
` ; + } + + if (cur_frm.doc.email_id){ + html += `
+ Email +
+
+ ${cur_frm.doc.email_id} +
` ; + } + + if (cur_frm.doc.mobile_no){ + html += `
+ Mobile +
+
+ ${cur_frm.doc.mobile_no} +
` ; + } + + html += `
+ Status +
+
+ ${cur_frm.doc.status} +
` ; + html = `
${html}
`; + cur_frm.dashboard.set_headline_alert(html); + } }; extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm })); diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index ed33d896f8..dc030fe819 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -486,7 +486,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2021-06-17 00:20:37.768449", + "modified": "2021-07-28 00:20:37.768449", "modified_by": "Administrator", "module": "CRM", "name": "Lead", From 47a651a80fe5be8ec9100b68f33097f2e349a086 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 28 Jul 2021 18:21:19 +0530 Subject: [PATCH 149/253] fix: removing depends_on for contact fields --- erpnext/crm/doctype/lead/lead.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index dc030fe819..f9a500fa97 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -145,7 +145,6 @@ "search_index": 1 }, { - "depends_on": "eval: doc.__islocal", "fieldname": "salutation", "fieldtype": "Link", "label": "Salutation", @@ -283,7 +282,6 @@ "options": "Country" }, { - "collapsible_depends_on": "eval: doc.__islocal", "fieldname": "pincode", "fieldtype": "Data", "label": "Postal Code" @@ -299,7 +297,6 @@ "read_only": 1 }, { - "depends_on": "eval: doc.__islocal", "fieldname": "phone", "fieldtype": "Data", "label": "Phone", @@ -308,7 +305,6 @@ "options": "Phone" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "mobile_no", "fieldtype": "Data", "label": "Mobile No.", @@ -317,7 +313,6 @@ "options": "Phone" }, { - "depends_on": "eval: doc.__islocal", "fieldname": "fax", "fieldtype": "Data", "label": "Fax", From 5dcd5e48e7f84ed9cdcfb2a01d57a7ad5b4b7e76 Mon Sep 17 00:00:00 2001 From: Anuja Date: Thu, 29 Jul 2021 00:54:48 +0530 Subject: [PATCH 150/253] fix: fixed zero tax rate issue by adding custom field --- .../vat_audit_report/vat_audit_report.py | 59 ++++++++++--------- erpnext/regional/south_africa/setup.py | 17 ++++++ 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index feb2a1620d..bce8586720 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -20,10 +20,10 @@ class VATAuditReport(object): def run(self): self.get_sa_vat_accounts() + self.get_columns() for doctype in self.doctypes: - self.get_columns(doctype) self.select_columns = """ - name as invoice_number, + name as voucher_no, posting_date, remarks""" columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \ else ", customer as party, debit_to as account" @@ -44,7 +44,7 @@ class VATAuditReport(object): if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) - def get_invoice_data(self,doctype): + def get_invoice_data(self, doctype): conditions = self.get_conditions() self.invoices = frappe._dict() @@ -62,9 +62,9 @@ class VATAuditReport(object): where_conditions=conditions), self.filters, as_dict=1) for d in invoice_data: - self.invoices.setdefault(d.invoice_number, d) + self.invoices.setdefault(d.voucher_no, d) - def get_invoice_items(self,doctype): + def get_invoice_items(self, doctype): self.invoice_items = frappe._dict() self.item_tax_rate = frappe._dict() @@ -82,7 +82,7 @@ class VATAuditReport(object): sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items if i.item_code == d.item_code and i.parent == d.parent)) - def get_items_based_on_tax_rate(self,doctype): + def get_items_based_on_tax_rate(self, doctype): self.items_based_on_tax_rate = frappe._dict() self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \ else "Sales Taxes and Charges" @@ -107,6 +107,9 @@ class VATAuditReport(object): else: continue for item_code, taxes in item_wise_tax_detail.items(): + is_zero_rated = frappe.get_value("Item", item_code, "is_zero_rated") + if taxes[0] == 0 and not is_zero_rated: + continue tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes) if tax_rate is not None: @@ -145,12 +148,12 @@ class VATAuditReport(object): return conditions def get_data(self, doctype): - consolidated_data = self.get_consolidated_data() + consolidated_data = self.get_consolidated_data(doctype) section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales") for rate, section in consolidated_data.items(): rate = int(rate) - label = frappe.bold(_("Standard Rate") + " " + section_name + " " + str(rate) + "%") + label = frappe.bold(section_name + "- " + "Rate" + " " + str(rate) + "%") section_head = {"posting_date": label} total_gross = total_tax = total_net = 0 self.data.append(section_head) @@ -170,7 +173,7 @@ class VATAuditReport(object): self.data.append(total) self.data.append({}) - def get_consolidated_data(self): + def get_consolidated_data(self, doctype): consolidated_data_map={} for inv, inv_data in self.invoices.items(): if self.items_based_on_tax_rate.get(inv): @@ -181,8 +184,8 @@ class VATAuditReport(object): item_details = self.item_tax_rate.get(inv).get(item) row["account"] = inv_data.get("account") row["posting_date"] = formatdate(inv_data.get("posting_date"), 'dd-mm-yyyy') - row["invoice_number"] = inv - row["party"] = inv_data.get("party") + row["voucher_type"] = doctype + row["voucher_no"] = inv row["remarks"] = inv_data.get("remarks") row["gross_amount"]= item_details[0].get("gross_amount") row["tax_amount"]= item_details[0].get("tax_amount") @@ -191,7 +194,7 @@ class VATAuditReport(object): return consolidated_data_map - def get_columns(self,doctype): + def get_columns(self): self.columns = [ { "fieldname": "posting_date", @@ -204,44 +207,44 @@ class VATAuditReport(object): "label": "Account", "fieldtype": "Link", "options": "Account", - "width": 140 + "width": 150 }, { - "fieldname": "invoice_number", + "fieldname": "voucher_type", + "label": "Voucher Type", + "fieldtype": "Data", + "width": 140, + "hidden": 1 + }, + { + "fieldname": "voucher_no", "label": "Reference", - "fieldtype": "Link", - "options": doctype, - "width": 140 - }, - { - "fieldname": "party", - "label": "Party", - "fieldtype": "Link", - "options": "Supplier" if doctype == "Purchase Invoice" else "Customer", - "width": 140 + "fieldtype": "Dynamic Link", + "options": "voucher_type", + "width": 150 }, { "fieldname": "remarks", "label": "Details", "fieldtype": "Data", - "width": 140 + "width": 150 }, { "fieldname": "net_amount", "label": "Net Amount", "fieldtype": "Currency", - "width": 140 + "width": 150 }, { "fieldname": "tax_amount", "label": "Tax Amount", "fieldtype": "Currency", - "width": 140 + "width": 150 }, { "fieldname": "gross_amount", "label": "Gross Amount", "fieldtype": "Currency", - "width": 140 + "width": 150 }, ] diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py index a4335bddef..ac783b8488 100644 --- a/erpnext/regional/south_africa/setup.py +++ b/erpnext/regional/south_africa/setup.py @@ -4,11 +4,28 @@ from __future__ import unicode_literals # import frappe, os, json +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property def setup(company=None, patch=True): add_permissions() +def make_custom_fields(update=True): + is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated', + fieldtype='Check', fetch_from='item_code.is_zero_rated', + insert_after='description', print_hide=1) + custom_fields = { + 'Item': [ + dict(fieldname='is_zero_rated', label='Is Zero Rated', + fieldtype='Check', insert_after='item_group', + print_hide=1) + ], + 'Sales Invoice Item': is_zero_rated, + 'Purchase Invoice Item': is_zero_rated + } + + create_custom_fields(custom_fields, update=update) + def add_permissions(): """Add Permissions for South Africa VAT Settings and South Africa VAT Account""" for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'): From a6ce1244a07279d601702ece31243d9d6e857557 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 29 Jul 2021 10:43:21 +0530 Subject: [PATCH 151/253] fix: sider issues --- erpnext/crm/doctype/lead/lead.js | 6 +++--- erpnext/crm/doctype/lead/lead.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index bd1639bb12..ad24a6e222 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -85,7 +85,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller render_basic_info_html() { let html=''; - if (cur_frm.doc.lead_owner){ + if (cur_frm.doc.lead_owner) { html += `
Lead Owner
@@ -94,7 +94,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
` ; } - if (cur_frm.doc.email_id){ + if (cur_frm.doc.email_id) { html += `
Email
@@ -103,7 +103,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
` ; } - if (cur_frm.doc.mobile_no){ + if (cur_frm.doc.mobile_no) { html += `
Mobile
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index ce36069052..f09a814540 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -95,7 +95,7 @@ class Lead(SellingController): if d.link_doctype == self.doctype and d.link_name == self.name: to_remove = d if to_remove: - link_doctype.remove(to_remove) + linked_doc.remove(to_remove) def has_customer(self): return frappe.db.get_value("Customer", {"lead_name": self.name}) From f8343890b9e065ced4ba87a065624d01f388b86e Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 29 Jul 2021 11:09:34 +0530 Subject: [PATCH 152/253] feat: don't recompute taxes (#26695) --- .../sales_taxes_and_charges.json | 14 ++++++++++++-- erpnext/controllers/taxes_and_totals.py | 7 ++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 1b7a0fe562..cfdb167bbc 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -27,7 +27,8 @@ "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", - "item_wise_tax_detail" + "item_wise_tax_detail", + "dont_recompute_tax" ], "fields": [ { @@ -200,13 +201,22 @@ "fieldname": "included_in_paid_amount", "fieldtype": "Check", "label": "Considered In Paid Amount" + }, + { + "default": "0", + "fieldname": "dont_recompute_tax", + "fieldtype": "Check", + "hidden": 1, + "label": "Dont Recompute tax", + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-06-14 01:44:36.899147", + "modified": "2021-07-27 12:40:59.051803", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 56da5b71da..099c7d4346 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -152,7 +152,7 @@ class calculate_taxes_and_totals(object): validate_taxes_and_charges(tax) validate_inclusive_tax(tax, self.doc) - if not self.doc.get('is_consolidated'): + if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")): tax.item_wise_tax_detail = {} tax_fields = ["total", "tax_amount_after_discount_amount", @@ -347,7 +347,7 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty - if not self.doc.get("is_consolidated"): + if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")): self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount @@ -455,7 +455,8 @@ class calculate_taxes_and_totals(object): def _cleanup(self): if not self.doc.get('is_consolidated'): for tax in self.doc.get("taxes"): - tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) + if not tax.get("dont_recompute_tax"): + tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':')) def set_discount_amount(self): if self.doc.additional_discount_percentage: From e906acdc49a5131680301eb056f16c7821b0b539 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 29 Jul 2021 13:56:21 +0530 Subject: [PATCH 153/253] chore: change location of backport action (#26705) --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index cc98f4544f..1d180f251e 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -12,7 +12,7 @@ jobs: - name: Checkout Actions uses: actions/checkout@v2 with: - repository: "ankush/backport" + repository: "frappe/backport" path: ./actions ref: develop - name: Install Actions From c3e739caf81c2eb63f3c17c045c6ec4ccf2f1f4a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 29 Jul 2021 15:34:18 +0530 Subject: [PATCH 154/253] fix: correct field for GLE against account in PR --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 41800e3715..48da78516e 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -436,7 +436,7 @@ class PurchaseReceipt(BuyingController): "cost_center": cost_center, "debit": debit, "credit": credit, - "against_account": against_account, + "against": against_account, "remarks": remarks, } From 5764b49767f7aa02699c407f9d00c01987e434f2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 29 Jul 2021 15:41:22 +0530 Subject: [PATCH 155/253] fix: remove incorrect field check from reposting --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1cdbd8d38a..9afe365f74 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -966,7 +966,7 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision): for e in existing_gle: if entry.account == e.account: account_existed = True - if (entry.account == e.account and entry.against_account == e.against_account + if (entry.account == e.account and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) and ( flt(entry.debit, precision) != flt(e.debit, precision) or flt(entry.credit, precision) != flt(e.credit, precision))): From 909995a441c3fe9d1c757bede91a66c021f1e451 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Thu, 29 Jul 2021 15:58:27 +0530 Subject: [PATCH 156/253] fix: issue with cache.setex --- erpnext/setup/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 33af28687c..27237bf2cb 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -107,7 +107,7 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No # expire in 6 hours response.raise_for_status() value = response.json()["result"] - cache.setex(key, value, 6 * 60 * 60) + cache.setex(name=key, time=21600, value=flt(value)) return flt(value) except: frappe.log_error(title="Get Exchange Rate") From 0bb60b37df4517c81cf684042bc8d42ea2a67364 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 29 Jul 2021 17:15:12 +0530 Subject: [PATCH 157/253] chore: add timeout to GHA workflows (#26714) --- .github/workflows/backport.yml | 1 + .github/workflows/docs-checker.yml | 1 + .github/workflows/patch.yml | 1 + .github/workflows/server-tests.yml | 1 + .github/workflows/ui-tests.yml | 1 + 5 files changed, 5 insertions(+) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 1d180f251e..bd622275d6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -8,6 +8,7 @@ on: jobs: main: runs-on: ubuntu-latest + timeout-minutes: 60 steps: - name: Checkout Actions uses: actions/checkout@v2 diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml index cdf676dd67..db46c5621b 100644 --- a/.github/workflows/docs-checker.yml +++ b/.github/workflows/docs-checker.yml @@ -6,6 +6,7 @@ on: 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 b96a3d6bbe..dc72987a41 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -5,6 +5,7 @@ on: [pull_request, workflow_dispatch] 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 69afa15187..606002e3cd 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -9,6 +9,7 @@ on: 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 412a05b0a1..9e29b6f1d2 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -7,6 +7,7 @@ on: jobs: test: runs-on: ubuntu-18.04 + timeout-minutes: 60 strategy: fail-fast: false From e99f68fd7ed9e9acacac86775ac6f8ed9030d459 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Thu, 29 Jul 2021 18:31:38 +0530 Subject: [PATCH 158/253] fix: expense claim semgrep issue (#26709) * fix: expense claim semgrep issue * fix: sider --- .../employee_advance/employee_advance.py | 7 +++--- .../hr/doctype/expense_claim/expense_claim.py | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index cb72f6b6d9..08e0b24a20 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -22,7 +22,6 @@ class EmployeeAdvance(Document): 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 @@ def make_return_entry(employee, company, employee_advance_name, return_amount, 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 @@ def get_voucher_type(mode_of_payment=None): 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/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 5010fc3f75..8cef143835 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -35,8 +35,8 @@ class ExpenseClaim(AccountsController): 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 @@ class ExpenseClaim(AccountsController): 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 @@ class ExpenseClaim(AccountsController): 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 @@ class ExpenseClaim(AccountsController): 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): From c1c5a4ae17aaba4781c4a0da29de5083c9d710d1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 29 Jul 2021 17:02:06 +0530 Subject: [PATCH 159/253] fix: remove cancelled entries from Stock and Account Value comparison report --- .../stock_and_account_value_comparison.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 14d543b174..bfc4471b9a 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -22,6 +22,7 @@ def get_data(report_filters): data = [] filters = { + "is_cancelled": 0, "company": report_filters.company, "posting_date": ("<=", report_filters.as_on_date) } @@ -34,7 +35,7 @@ def get_data(report_filters): key = (d.voucher_type, d.voucher_no) gl_data = voucher_wise_gl_data.get(key) or {} d.account_value = gl_data.get("account_value", 0) - d.difference_value = (d.stock_value - d.account_value) + d.difference_value = abs(d.stock_value - d.account_value) if abs(d.difference_value) > 0.1: data.append(d) From a6d276a06fe02fa4a16bdbad2ce31432701a9f3f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 29 Jul 2021 17:02:06 +0530 Subject: [PATCH 160/253] fix: remove cancelled entries from Stock and Account Value comparison report --- .../stock_and_account_value_comparison.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py index 14d543b174..bfc4471b9a 100644 --- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py +++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py @@ -22,6 +22,7 @@ def get_data(report_filters): data = [] filters = { + "is_cancelled": 0, "company": report_filters.company, "posting_date": ("<=", report_filters.as_on_date) } @@ -34,7 +35,7 @@ def get_data(report_filters): key = (d.voucher_type, d.voucher_no) gl_data = voucher_wise_gl_data.get(key) or {} d.account_value = gl_data.get("account_value", 0) - d.difference_value = (d.stock_value - d.account_value) + d.difference_value = abs(d.stock_value - d.account_value) if abs(d.difference_value) > 0.1: data.append(d) From c7c90244feb2381d343711e3d1d322b5e56ccecc Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 29 Jul 2021 19:18:35 +0530 Subject: [PATCH 161/253] fix: Check if Purchase Order has Payment Terms Template --- erpnext/controllers/accounts_controller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 2bf5c77e61..913e70b30c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1109,7 +1109,7 @@ class AccountsController(TransactionBase): elif self.doctype in ["Sales Invoice", "Purchase Invoice"]: po_or_so, doctype, fieldname = self.get_order_details() - if self.linked_order_has_payment_terms(po_or_so, fieldname): + if self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): self.fetch_payment_terms_from_order(po_or_so, doctype) elif self.doctype not in ["Purchase Receipt"]: @@ -1138,9 +1138,9 @@ class AccountsController(TransactionBase): return po_or_so, po_or_so_doctype, po_or_so_doctype_name - def linked_order_has_payment_terms(self, po_or_so, fieldname): + def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype): if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname): - if self.linked_order_has_payment_terms_template(po_or_so): + if self.linked_order_has_payment_terms_template(po_or_so, doctype): return True elif self.linked_order_has_payment_schedule(po_or_so): return True @@ -1154,8 +1154,8 @@ class AccountsController(TransactionBase): return True - def linked_order_has_payment_terms_template(self, po_or_so): - return frappe.get_value('Sales Order', po_or_so, 'payment_terms_template') + def linked_order_has_payment_terms_template(self, po_or_so, doctype): + return frappe.get_value(doctype, po_or_so, 'payment_terms_template') def linked_order_has_payment_schedule(self, po_or_so): return frappe.get_all('Payment Schedule', filters={'parent': po_or_so}) From 533ee9a401d45aa265445bffd1b5d42ab7992500 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 29 Jul 2021 19:39:32 +0530 Subject: [PATCH 162/253] feat: (Stock Reco) Ignore Empty Stock while fetching items from warehouse - Added checkbox to `Fetch Items from Warehouse` dialog to ignore empty stock - fix: Items fetched twice due to Item Defaults - Improved code readability --- .../stock_reconciliation.js | 72 +++++++++++-------- .../stock_reconciliation.py | 65 ++++++++++++----- 2 files changed, 89 insertions(+), 48 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 4540954489..84f65a077e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -56,25 +56,40 @@ frappe.ui.form.on("Stock Reconciliation", { }, get_items: function(frm) { - let fields = [{ - label: 'Warehouse', fieldname: 'warehouse', fieldtype: 'Link', options: 'Warehouse', reqd: 1, - "get_query": function() { - return { - "filters": { - "company": frm.doc.company, - } - }; + let fields = [ + { + label: 'Warehouse', + fieldname: 'warehouse', + fieldtype: 'Link', + options: 'Warehouse', + reqd: 1, + "get_query": function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + } + }, + { + label: "Item Code", + fieldname: "item_code", + fieldtype: "Link", + options: "Item", + "get_query": function() { + return { + "filters": { + "disabled": 0, + } + }; + } + }, + { + label: __("Ignore Empty Stock"), + fieldname: "ignore_empty_stock", + fieldtype: "Check" } - }, { - label: "Item Code", fieldname: "item_code", fieldtype: "Link", options: "Item", - "get_query": function() { - return { - "filters": { - "disabled": 0, - } - }; - } - }]; + ]; frappe.prompt(fields, function(data) { frappe.call({ @@ -84,22 +99,21 @@ frappe.ui.form.on("Stock Reconciliation", { posting_date: frm.doc.posting_date, posting_time: frm.doc.posting_time, company: frm.doc.company, - item_code: data.item_code + item_code: data.item_code, + ignore_empty_stock: data.ignore_empty_stock }, callback: function(r) { + if (r.exc || !r.message || !r.message.length) return; + frm.clear_table("items"); - for (var i=0; i { + let item = frm.add_child("items"); + $.extend(item, row); - if (!d.valuation_rate) { - d.valuation_rate = 0; - } - } + item.qty = item.qty || 0; + item.valuation_rate = item.valuation_rate || 0; + }); frm.refresh_field("items"); } }); diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 9875491593..0bae7cfe25 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -483,7 +483,8 @@ class StockReconciliation(StockController): self._cancel() @frappe.whitelist() -def get_items(warehouse, posting_date, posting_time, company, item_code=None): +def get_items(warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False): + ignore_empty_stock = cint(ignore_empty_stock) items = [frappe._dict({ 'item_code': item_code, 'warehouse': warehouse @@ -497,18 +498,24 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None): for d in items: if d.item_code in itemwise_batch_data: - stock_bal = get_stock_balance(d.item_code, d.warehouse, - posting_date, posting_time, with_valuation_rate=True) + valuation_rate = get_stock_balance(d.item_code, d.warehouse, + posting_date, posting_time, with_valuation_rate=True)[1] for row in itemwise_batch_data.get(d.item_code): - args = get_item_data(row, row.qty, stock_bal[1]) + if ignore_empty_stock and not row.qty: + continue + + args = get_item_data(row, row.qty, valuation_rate) res.append(args) else: stock_bal = get_stock_balance(d.item_code, d.warehouse, posting_date, posting_time, with_valuation_rate=True , with_serial_no=cint(d.has_serial_no)) + qty, valuation_rate, serial_no = stock_bal[0], stock_bal[1], stock_bal[2] if cint(d.has_serial_no) else '' - args = get_item_data(d, stock_bal[0], stock_bal[1], - stock_bal[2] if cint(d.has_serial_no) else '') + if ignore_empty_stock and not stock_bal[0]: + continue + + args = get_item_data(d, qty, valuation_rate, serial_no) res.append(args) @@ -516,24 +523,44 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None): def get_items_for_stock_reco(warehouse, company): lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"]) - items = frappe.db.sql(""" - select i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no - from tabBin bin, tabItem i - where i.name=bin.item_code and IFNULL(i.disabled, 0) = 0 and i.is_stock_item = 1 - and i.has_variants = 0 and exists( - select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse - ) - """, (lft, rgt), as_dict=1) + items = frappe.db.sql(f""" + select + i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no + from + tabBin bin, tabItem i + where + i.name = bin.item_code + and IFNULL(i.disabled, 0) = 0 + and i.is_stock_item = 1 + and i.has_variants = 0 + and exists( + select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse + ) + """, as_dict=1) items += frappe.db.sql(""" - select i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no - from tabItem i, `tabItem Default` id - where i.name = id.parent - and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse) - and i.is_stock_item = 1 and i.has_variants = 0 and IFNULL(i.disabled, 0) = 0 and id.company=%s + select + i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no + from + tabItem i, `tabItem Default` id + where + i.name = id.parent + and exists( + select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse + ) + and i.is_stock_item = 1 + and i.has_variants = 0 + and IFNULL(i.disabled, 0) = 0 + and id.company = %s group by i.name """, (lft, rgt, company), as_dict=1) + # remove duplicates + # check if item-warehouse key extracted from each entry exists in set iw_keys + # and update iw_keys + iw_keys = set() + items = [item for item in items if [(item.item_code, item.warehouse) not in iw_keys, iw_keys.add((item.item_code, item.warehouse))][0]] + return items def get_item_data(row, qty, valuation_rate, serial_no=None): From 57cd273f7c20510985162cde175ae4aa424f56b0 Mon Sep 17 00:00:00 2001 From: Ankush Date: Thu, 29 Jul 2021 19:49:36 +0530 Subject: [PATCH 163/253] fix: empty "against account" in Purchase Receipt GLE bp #26712 (#26719) * fix: correct field for GLE against account in PR * fix: remove incorrect field check from reposting --- erpnext/accounts/utils.py | 2 +- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 1cdbd8d38a..9afe365f74 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -966,7 +966,7 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle, precision): for e in existing_gle: if entry.account == e.account: account_existed = True - if (entry.account == e.account and entry.against_account == e.against_account + if (entry.account == e.account and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center) and ( flt(entry.debit, precision) != flt(e.debit, precision) or flt(entry.credit, precision) != flt(e.credit, precision))): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 82c87a83a5..26ea11e01d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -415,7 +415,7 @@ class PurchaseReceipt(BuyingController): "cost_center": cost_center, "debit": debit, "credit": credit, - "against_account": against_account, + "against": against_account, "remarks": remarks, } From 9c7a9f3a1374bbba0693295f444db3158c06e6bd Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 29 Jul 2021 18:47:16 +0530 Subject: [PATCH 164/253] fix: Parent condition in pricing rules --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index b54d0e73a8..94abf3b3c0 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -168,7 +168,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True): frappe.throw(_("Invalid {0}").format(args.get(field))) parent_groups = frappe.db.sql_list("""select name from `tab%s` - where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) + where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt)) if parenttype in ["Customer Group", "Item Group", "Territory"]: parent_field = "parent_{0}".format(frappe.scrub(parenttype)) From 821db5cce7052c663717132a0ab6149adcdd67c2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 30 Jul 2021 10:21:42 +0530 Subject: [PATCH 165/253] fix: employee status server-side validation (#26442) * fix: employee status server-side validation * fix: test * test: employee inactive status * fix: test --- erpnext/hr/doctype/appraisal/appraisal.py | 3 +- erpnext/hr/doctype/attendance/attendance.py | 2 ++ .../attendance_request/attendance_request.py | 3 +- .../compensatory_leave_request.py | 3 +- erpnext/hr/doctype/employee/employee.py | 8 +++-- erpnext/hr/doctype/employee/test_employee.py | 30 +++++++++++++++++-- .../employee_advance/employee_advance.py | 2 ++ .../employee_checkin/employee_checkin.py | 4 ++- .../employee_promotion/employee_promotion.py | 5 ++-- .../employee_referral/employee_referral.py | 2 ++ .../employee_transfer/employee_transfer.py | 4 --- .../hr/doctype/expense_claim/expense_claim.py | 3 +- .../leave_application/leave_application.py | 3 +- .../leave_encashment/leave_encashment.py | 3 +- .../shift_assignment/shift_assignment.py | 2 ++ .../hr/doctype/shift_request/shift_request.py | 3 +- .../doctype/travel_request/travel_request.py | 4 ++- erpnext/hr/utils.py | 10 +++++-- .../additional_salary/additional_salary.py | 2 ++ .../employee_benefit_application.py | 3 +- .../employee_benefit_claim.py | 3 +- .../employee_incentive/employee_incentive.py | 2 ++ .../employee_tax_exemption_declaration.py | 3 +- ...employee_tax_exemption_proof_submission.py | 3 +- .../retention_bonus/retention_bonus.py | 5 ++-- .../doctype/salary_slip/salary_slip.py | 2 ++ .../projects/doctype/timesheet/timesheet.py | 3 ++ 27 files changed, 88 insertions(+), 32 deletions(-) diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py index f7601870fa..c2ed457984 100644 --- a/erpnext/hr/doctype/appraisal/appraisal.py +++ b/erpnext/hr/doctype/appraisal/appraisal.py @@ -9,7 +9,7 @@ from frappe.utils import flt, getdate from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee class Appraisal(Document): def validate(self): @@ -19,6 +19,7 @@ class Appraisal(Document): if not self.goals: frappe.throw(_("Goals cannot be empty")) + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_existing_appraisal() diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 3412675d81..f79f0fe418 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -8,11 +8,13 @@ from frappe.utils import getdate, nowdate from frappe import _ from frappe.model.document import Document from frappe.utils import cstr, get_datetime, formatdate +from erpnext.hr.utils import validate_active_employee class Attendance(Document): def validate(self): from erpnext.controllers.status_updater import validate_status validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"]) + validate_active_employee(self.employee) self.validate_attendance_date() self.validate_duplicate_record() self.validate_employee_status() diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py index 090d53262c..7f88fed73a 100644 --- a/erpnext/hr/doctype/attendance_request/attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/attendance_request.py @@ -8,10 +8,11 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import date_diff, add_days, getdate from erpnext.hr.doctype.employee.employee import is_holiday -from erpnext.hr.utils import validate_dates +from erpnext.hr.utils import validate_dates, validate_active_employee class AttendanceRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.from_date, self.to_date) if self.half_day: if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date): diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py index a6fe429be1..0d7fded921 100644 --- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py +++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.utils import date_diff, add_days, getdate, cint, format_date from frappe.model.document import Document -from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \ +from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \ get_holidays_for_employee, create_additional_leave_ledger_entry class CompensatoryLeaveRequest(Document): def validate(self): + validate_active_employee(self.employee) validate_dates(self, self.work_from_date, self.work_end_date) if self.half_day: if not self.half_day_date: diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index fa017d9d4c..5ca47560b1 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -13,8 +13,10 @@ from frappe.model.document import Document from erpnext.utilities.transaction_base import delete_events from frappe.utils.nestedset import NestedSet -class EmployeeUserDisabledError(frappe.ValidationError): pass -class EmployeeLeftValidationError(frappe.ValidationError): pass +class EmployeeUserDisabledError(frappe.ValidationError): + pass +class InactiveEmployeeStatusError(frappe.ValidationError): + pass class Employee(NestedSet): nsm_parent_field = 'reports_to' @@ -196,7 +198,7 @@ class Employee(NestedSet): message += "

  • " + "
  • ".join(link_to_employees) message += "

" message += _("Please make sure the employees above report to another Active employee.") - throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee")) + throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee")) if not self.relieving_date: throw(_("Please enter relieving date.")) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 7d652a7366..8fc7cf1934 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -7,7 +7,7 @@ import frappe import erpnext import unittest import frappe.utils -from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError +from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError test_records = frappe.get_test_records('Employee') @@ -45,10 +45,33 @@ class TestEmployee(unittest.TestCase): employee2_doc.save() employee1_doc.reload() employee1_doc.status = 'Left' - self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) + self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save) + + def test_employee_status_inactive(self): + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip + from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list + + employee = make_employee("test_employee_status@company.com") + employee_doc = frappe.get_doc("Employee", employee) + employee_doc.status = "Inactive" + employee_doc.save() + employee_doc.reload() + + make_holiday_list() + frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") + + frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""") + salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly", + employee=employee_doc.name, company=employee_doc.company) + salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name) + + self.assertRaises(InactiveEmployeeStatusError, salary_slip.save) + + def tearDown(self): + frappe.db.rollback() def make_employee(user, company=None, **kwargs): - "" if not frappe.db.get_value("User", user): frappe.get_doc({ "doctype": "User", @@ -80,4 +103,5 @@ def make_employee(user, company=None, **kwargs): employee.insert() return employee.name else: + frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active") return frappe.get_value("Employee", {"employee_name":user}, "name") diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 08e0b24a20..cbb3cc813b 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import flt, nowdate from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account +from erpnext.hr.utils import validate_active_employee class EmployeeAdvanceOverPayment(frappe.ValidationError): pass @@ -18,6 +19,7 @@ class EmployeeAdvance(Document): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.set_status() def on_cancel(self): diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index 15fbd4e015..60ea0f9895 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -9,9 +9,11 @@ from frappe.model.document import Document from frappe import _ from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift +from erpnext.hr.utils import validate_active_employee class EmployeeCheckin(Document): def validate(self): + validate_active_employee(self.employee) self.validate_duplicate_log() self.fetch_shift() @@ -122,7 +124,7 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): """Given a set of logs in chronological order calculates the total working hours based on the parameters. Zero is returned for all invalid cases. - + :param logs: The List of 'Employee Checkin'. :param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin' :param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out' diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index 83fb235f92..a3a61834c8 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -7,12 +7,11 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee +from erpnext.hr.utils import update_employee, validate_active_employee class EmployeePromotion(Document): def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot promote Employee with status Left or Inactive")) + validate_active_employee(self.employee) def before_submit(self): if getdate(self.promotion_date) > getdate(): diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py index 45d68729ce..0493306166 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -7,9 +7,11 @@ import frappe from frappe import _ from frappe.utils import get_link_to_form from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeReferral(Document): def validate(self): + validate_active_employee(self.referrer) self.set_full_name() self.set_referral_bonus_payment_status() diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index 6eec9fa12a..c2007747fb 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -10,10 +10,6 @@ from frappe.utils import getdate from erpnext.hr.utils import update_employee class EmployeeTransfer(Document): - def validate(self): - if frappe.get_value("Employee", self.employee, "status") != "Active": - frappe.throw(_("Cannot transfer Employee with status Left or Inactive")) - def before_submit(self): if getdate(self.transfer_date) > getdate(): frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"), diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 8cef143835..95e2806aed 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -6,7 +6,7 @@ import frappe, erpnext from frappe import _ from frappe.utils import get_fullname, flt, cstr, get_link_to_form from frappe.model.document import Document -from erpnext.hr.utils import set_employee_name, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee from erpnext.accounts.party import get_party_account from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account @@ -23,6 +23,7 @@ class ExpenseClaim(AccountsController): 'make_payment_via_journal_entry') def validate(self): + validate_active_employee(self.employee) self.validate_advances() self.validate_sanctioned_amount() self.calculate_total_amount() diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index cee6f374fd..93fb19f4a1 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate -from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver +from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange @@ -22,6 +22,7 @@ class LeaveApplication(Document): return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type) def validate(self): + validate_active_employee(self.employee) set_employee_name(self) self.validate_dates() self.validate_balance_leaves() diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index e041b7fb8f..912bd8ad92 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import getdate, nowdate, flt -from erpnext.hr.utils import set_employee_name +from erpnext.hr.utils import set_employee_name, validate_active_employee from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves @@ -15,6 +15,7 @@ from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leav class LeaveEncashment(Document): def validate(self): set_employee_name(self) + validate_active_employee(self.employee) self.get_leave_details_for_encashment() self.validate_salary_structure() diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index ab65260c09..89ae4d535d 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -9,10 +9,12 @@ from frappe.model.document import Document from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday +from erpnext.hr.utils import validate_active_employee from datetime import timedelta, datetime class ShiftAssignment(Document): def validate(self): + validate_active_employee(self.employee) self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 177c45edc6..6461f07552 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,12 +7,13 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate -from erpnext.hr.utils import share_doc_with_approver +from erpnext.hr.utils import share_doc_with_approver, validate_active_employee class OverlapError(frappe.ValidationError): pass class ShiftRequest(Document): def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_shift_request_overlap_dates() self.validate_approver() diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py index 01d3f34706..60834d3f4a 100644 --- a/erpnext/hr/doctype/travel_request/travel_request.py +++ b/erpnext/hr/doctype/travel_request/travel_request.py @@ -5,6 +5,8 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class TravelRequest(Document): - pass + def validate(self): + validate_active_employee(self.employee) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 3cc1a014d7..992b18d37a 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -3,13 +3,12 @@ import erpnext import frappe -from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError from frappe import _ from frappe.desk.form import assign_to from frappe.model.document import Document from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, - get_datetime, getdate, nowdate, today, unique) - + get_datetime, getdate, nowdate, today, unique, get_link_to_form) class DuplicateDeclarationError(frappe.ValidationError): pass @@ -410,3 +409,8 @@ def share_doc_with_approver(doc, user): approver = approvers.get(doc.doctype) if doc_before_save.get(approver) != doc.get(approver): frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver)) + +def validate_active_employee(employee): + if frappe.db.get_value("Employee", employee, "status") == "Inactive": + frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format( + get_link_to_form("Employee", employee)), InactiveEmployeeStatusError) \ No newline at end of file diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index ebeddf97f9..381f399e9f 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -7,6 +7,7 @@ import frappe from frappe.model.document import Document from frappe import _, bold from frappe.utils import getdate, date_diff, comma_and, formatdate +from erpnext.hr.utils import validate_active_employee class AdditionalSalary(Document): def on_submit(self): @@ -19,6 +20,7 @@ class AdditionalSalary(Document): self.update_employee_referral(cancel=True) def validate(self): + validate_active_employee(self.employee) self.validate_dates() self.validate_salary_structure() self.validate_recurring_additional_salary_overlap() diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py index 27df30a459..5ebe514ac0 100644 --- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py +++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py @@ -9,10 +9,11 @@ from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt from frappe.model.document import Document from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure -from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount +from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee class EmployeeBenefitApplication(Document): def validate(self): + validate_active_employee(self.employee) self.validate_duplicate_on_payroll_period() if not self.max_benefits: self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period) diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py index d9937a7bb9..c6713f3aa4 100644 --- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py +++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py @@ -8,12 +8,13 @@ from frappe import _ from frappe.utils import flt from frappe.model.document import Document from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits -from erpnext.hr.utils import get_previous_claimed_amount +from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure class EmployeeBenefitClaim(Document): def validate(self): + validate_active_employee(self.employee) max_benefits = get_max_benefits(self.employee, self.claim_date) if not max_benefits or max_benefits <= 0: frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee)) diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py index ead3db126f..6b918ba76d 100644 --- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py +++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py @@ -6,9 +6,11 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from erpnext.hr.utils import validate_active_employee class EmployeeIncentive(Document): def validate(self): + validate_active_employee(self.employee) self.validate_salary_structure() def validate_salary_structure(self): diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index fb71a2877a..e11d60a464 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -8,11 +8,12 @@ from frappe.model.document import Document from frappe import _ from frappe.utils import flt from frappe.model.mapper import get_mapped_doc -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionDeclaration(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.declarations) validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee) self.set_total_declared_amount() diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py index 5bc33a65f2..8131ae0fa8 100644 --- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py +++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py @@ -7,11 +7,12 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import flt -from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \ +from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \ calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period class EmployeeTaxExemptionProofSubmission(Document): def validate(self): + validate_active_employee(self.employee) validate_tax_declaration(self.tax_exemption_proofs) self.set_total_actual_amount() self.set_total_exemption_amount() diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py index 049ea265cc..055bea7410 100644 --- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py +++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py @@ -7,11 +7,10 @@ import frappe from frappe.model.document import Document from frappe import _ from frappe.utils import getdate - +from erpnext.hr.utils import validate_active_employee class RetentionBonus(Document): def validate(self): - if frappe.get_value('Employee', self.employee, 'status') != 'Active': - frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees')) + validate_active_employee(self.employee) if getdate(self.bonus_payment_date) < getdate(): frappe.throw(_('Bonus Payment Date cannot be a past date')) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index f82b0d51bb..c321f6f60f 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 erpnext.hr.utils import validate_active_employee from six import iteritems class SalarySlip(TransactionBase): @@ -39,6 +40,7 @@ class SalarySlip(TransactionBase): def validate(self): self.status = self.get_status() + validate_active_employee(self.employee) self.validate_dates() self.check_existing() if not self.salary_slip_based_on_timesheet: diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index c8bd80fca0..ae38d4ca19 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -15,12 +15,15 @@ from erpnext.manufacturing.doctype.workstation.workstation import (check_if_with WorkstationHolidayError) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.setup.utils import get_exchange_rate +from erpnext.hr.utils import validate_active_employee class OverlapError(frappe.ValidationError): pass class OverWorkLoggedError(frappe.ValidationError): pass class Timesheet(Document): def validate(self): + if self.employee: + validate_active_employee(self.employee) self.set_employee_name() self.set_status() self.validate_dates() From d4ae1febe3b174469c8f86f281523974fff02ca3 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 30 Jul 2021 11:21:49 +0530 Subject: [PATCH 166/253] fix: gl entries for exchange gain loss (#26734) --- .../purchase_invoice/test_purchase_invoice.py | 8 ++++---- erpnext/controllers/accounts_controller.py | 17 +++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index ca4d009956..4bc22a544d 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -997,8 +997,8 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gle = [ ["_Test Account Cost for Goods Sold - _TC", 37500.0], - ["_Test Payable USD - _TC", -40000.0], - ["Exchange Gain/Loss - _TC", 2500.0] + ["_Test Payable USD - _TC", -35000.0], + ["Exchange Gain/Loss - _TC", -2500.0] ] gl_entries = frappe.db.sql(""" @@ -1028,8 +1028,8 @@ class TestPurchaseInvoice(unittest.TestCase): expected_gle = [ ["_Test Account Cost for Goods Sold - _TC", 36500.0], - ["_Test Payable USD - _TC", -38000.0], - ["Exchange Gain/Loss - _TC", 1500.0] + ["_Test Payable USD - _TC", -35000.0], + ["Exchange Gain/Loss - _TC", -1500.0] ] gl_entries = frappe.db.sql(""" diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cdd865ac4a..a9b7efbe98 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -674,19 +674,24 @@ class AccountsController(TransactionBase): if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']: for d in self.get("advances"): if d.exchange_gain_loss: - party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer - party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to - party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer" + is_purchase_invoice = self.get('doctype') == 'Purchase Invoice' + party = self.supplier if is_purchase_invoice else self.customer + party_account = self.credit_to if is_purchase_invoice else self.debit_to + party_type = "Supplier" if is_purchase_invoice else "Customer" gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account') + if not gain_loss_account: + frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}") + .format(self.get('company'))) account_currency = get_account_currency(gain_loss_account) if account_currency != self.company_currency: - frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency)) + frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency)) # for purchase dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit' - # just reverse for sales? - dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' + if not is_purchase_invoice: + # just reverse for sales? + dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit' gl_entries.append( self.get_gl_dict({ From 1a2332a81cd7999e1f9266337fb48462ebfcbcc4 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 30 Jul 2021 18:30:15 +0530 Subject: [PATCH 167/253] fix: student category mapping from the program enrollment tool (#26716) Co-authored-by: Jannat Patel <31363128+pateljannat@users.noreply.github.com> --- erpnext/education/api.py | 7 +- .../program_enrollment_tool_student.json | 245 +++++------------- 2 files changed, 64 insertions(+), 188 deletions(-) diff --git a/erpnext/education/api.py b/erpnext/education/api.py index afa0be9b9f..4493a3fef1 100644 --- a/erpnext/education/api.py +++ b/erpnext/education/api.py @@ -34,11 +34,14 @@ def enroll_student(source_name): } }}, ignore_permissions=True) student.save() + + student_applicant = frappe.db.get_value("Student Applicant", source_name, + ["student_category", "program"], as_dict=True) program_enrollment = frappe.new_doc("Program Enrollment") program_enrollment.student = student.name - program_enrollment.student_category = student.student_category + program_enrollment.student_category = student_applicant.student_category program_enrollment.student_name = student.title - program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program") + program_enrollment.program = student_applicant.program frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user) return program_enrollment diff --git a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json index 9be292b65e..1d7497387f 100644 --- a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json +++ b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json @@ -1,195 +1,68 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-06-10 03:29:02.539914", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-06-10 03:29:02.539914", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "student_applicant", + "student", + "student_name", + "column_break_3", + "student_batch_name", + "student_category" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "student_applicant", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student Applicant", - "length": 0, - "no_copy": 0, - "options": "Student Applicant", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "student_applicant", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Applicant", + "options": "Student Applicant" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "student", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student", - "length": 0, - "no_copy": 0, - "options": "Student", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "student", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student", + "options": "Student" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "student_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "student_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Student Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "student_batch_name", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Student Batch Name", - "length": 0, - "no_copy": 0, - "options": "Student Batch Name", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "student_batch_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Student Batch Name", + "options": "Student Batch Name" + }, + { + "fieldname": "student_category", + "fieldtype": "Link", + "label": "Student Category", + "options": "Student Category", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-01-02 12:03:53.890741", - "modified_by": "Administrator", - "module": "Education", - "name": "Program Enrollment Tool Student", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Education", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 + ], + "istable": 1, + "links": [], + "modified": "2021-07-29 18:19:54.471594", + "modified_by": "Administrator", + "module": "Education", + "name": "Program Enrollment Tool Student", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "restrict_to_domain": "Education", + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From 6eded547f5e6935c01ec24caf96ba03e70a860d9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 29 Jul 2021 15:26:19 +0530 Subject: [PATCH 168/253] fix: TDS calculation for first threshold breach for TDS category 194Q --- .../tax_withholding_category/tax_withholding_category.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 020de3c3f3..481ef285e7 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -148,6 +148,7 @@ def get_lower_deduction_certificate(fiscal_year, pan_no): def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None): fiscal_year = fiscal_year_details[0] + vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type) advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers @@ -267,7 +268,11 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)): if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount): - supp_credit_amt -= cumulative_threshold + # Get net total again as TDS is calculated on net total + # Grand is used to just check for threshold breach + net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0 + net_total += inv.net_total + supp_credit_amt = net_total - cumulative_threshold if ldc and is_valid_certificate( ldc.valid_from, ldc.valid_upto, From 2a14f255cfa8d85815cc22d0bcae76d7e4de8634 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 30 Jul 2021 12:36:35 +0530 Subject: [PATCH 169/253] fix: COGS account in purchase receipt --- .../doctype/pricing_rule/test_pricing_rule.py | 10 ++++++++++ erpnext/accounts/utils.py | 6 +++--- erpnext/controllers/accounts_controller.py | 4 ++-- .../doctype/purchase_receipt/purchase_receipt.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 15 +++++++++------ 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ffe8be1162..3173db13af 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -15,6 +15,7 @@ from erpnext.healthcare.doctype.lab_test_template.lab_test_template import make_ class TestPricingRule(unittest.TestCase): def setUp(self): delete_existing_pricing_rules() + setup_pricing_rule_data() def tearDown(self): delete_existing_pricing_rules() @@ -554,6 +555,8 @@ class TestPricingRule(unittest.TestCase): for doc in [si, si1]: doc.delete() +test_dependencies = ["Campaign"] + def make_pricing_rule(**args): args = frappe._dict(args) @@ -600,6 +603,13 @@ def make_pricing_rule(**args): if args.get(applicable_for): doc.db_set(applicable_for, args.get(applicable_for)) +def setup_pricing_rule_data(): + if not frappe.db.exists('Campaign', '_Test Campaign'): + frappe.get_doc({ + 'doctype': 'Campaign', + 'campaign_name': '_Test Campaign', + 'name': '_Test Campaign' + }).insert() def delete_existing_pricing_rules(): for doctype in ["Pricing Rule", "Pricing Rule Item Code", diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9afe365f74..9272bc4fce 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -566,10 +566,10 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no): frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe))) @frappe.whitelist() -def get_company_default(company, fieldname): - value = frappe.get_cached_value('Company', company, fieldname) +def get_company_default(company, fieldname, ignore_validation=False): + value = frappe.get_cached_value('Company', company, fieldname) - if not value: + if not ignore_validation and not value: throw(_("Please set default {0} in Company {1}") .format(frappe.get_meta("Company").get_label(fieldname), company)) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index cdd865ac4a..d5c5d42910 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -904,9 +904,9 @@ class AccountsController(TransactionBase): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) - def get_company_default(self, fieldname): + def get_company_default(self, fieldname, ignore_validation=False): from erpnext.accounts.utils import get_company_default - return get_company_default(self.company, fieldname) + return get_company_default(self.company, fieldname, ignore_validation=ignore_validation) def get_stock_items(self): stock_items = [] diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 48da78516e..899d7e8e66 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -352,7 +352,7 @@ class PurchaseReceipt(BuyingController): if self.is_return or flt(d.item_tax_amount): loss_account = expenses_included_in_valuation else: - loss_account = self.get_company_default("default_expense_account") + loss_account = self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index dbba21fde1..82461cb843 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -336,10 +336,13 @@ class TestPurchaseReceipt(unittest.TestCase): se3.cancel() po.reload() pr2.load_from_db() - pr2.cancel() - po.load_from_db() - po.cancel() + if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry', + {'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'): + pr2.cancel() + + po.load_from_db() + po.cancel() def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) @@ -1044,7 +1047,7 @@ class TestPurchaseReceipt(unittest.TestCase): 'account': srbnb_account, 'voucher_detail_no': pr.items[1].name }, pluck="name") - + # check if the entries are not merged into one # seperate entries should be made since voucher_detail_no is different self.assertEqual(len(item_one_gl_entry), 1) @@ -1055,13 +1058,13 @@ class TestPurchaseReceipt(unittest.TestCase): def test_purchase_receipt_with_exchange_rate_difference(self): from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt - + pi = create_purchase_invoice(company="_Test Company with perpetual inventory", cost_center = "Main - TCP1", warehouse = "Stores - TCP1", expense_account ="_Test Account Cost for Goods Sold - TCP1", currency = "USD", conversion_rate = 70) - + pr = create_purchase_receipt(pi.name) pr.conversion_rate = 80 pr.items[0].purchase_invoice = pi.name From 6fffc90b46b9b0dd59b78837e5bab7b0d5c2651c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 31 Jul 2021 16:05:41 +0530 Subject: [PATCH 170/253] chore: Added change log for v13.8.0 --- erpnext/change_log/v13/v13_8_0.md | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 erpnext/change_log/v13/v13_8_0.md diff --git a/erpnext/change_log/v13/v13_8_0.md b/erpnext/change_log/v13/v13_8_0.md new file mode 100644 index 0000000000..98ed95ae04 --- /dev/null +++ b/erpnext/change_log/v13/v13_8_0.md @@ -0,0 +1,39 @@ +# Version 13.8.0 Release Notes + +### Features & Enhancements +- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222)) +- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677)) +- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564)) + +### Fixes +- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600)) +- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651)) +- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460)) +- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438)) +- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206)) +- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487)) +- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502)) +- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644)) +- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431)) +- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508)) +- Errors on parallel requests creation of company for India ([#26420](https://github.com/frappe/erpnext/pull/26420)) +- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558)) +- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712)) +- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721)) +- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691)) +- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518)) +- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646)) +- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727)) +- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294)) +- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292)) +- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436)) +- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412)) +- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613)) +- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614)) +- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734)) +- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485)) +- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560)) +- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576)) +- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643)) +- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418)) +- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498)) From 8cb560c7534b9dc2f47333f4dcbea078b9090740 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 1 Aug 2021 14:55:09 +0550 Subject: [PATCH 171/253] bumped to version 13.8.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index a181c2d42c..c90e01cfbd 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '13.7.1' +__version__ = '13.8.0' def get_default_company(user=None): '''Get default company for user''' From 493029195c81dd5317de23649486dd61aa8e8765 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sun, 1 Aug 2021 19:47:42 +0530 Subject: [PATCH 172/253] fix: Additional salary processing --- .../doctype/salary_slip/salary_slip.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index b39cef8bbd..f0ca64fdf2 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -647,10 +647,13 @@ class SalarySlip(TransactionBase): continue if ( - (not d.additional_salary - and (not additional_salary or additional_salary.overwrite)) - or (additional_salary - and additional_salary.name == d.additional_salary) + ( + not d.additional_salary + and (not additional_salary or additional_salary.overwrite) + ) or ( + additional_salary + and additional_salary.name == d.additional_salary + ) ): component_row = d break @@ -679,8 +682,12 @@ class SalarySlip(TransactionBase): if additional_salary: component_row.is_recurring_additional_salary = is_recurring - component_row.default_amount = 0 - component_row.additional_amount = amount + if additional_salary.overwrite: + component_row.additional_amount = flt(flt(amount) - flt(component_row.get("default_amount", 0)), + component_row.precision("additional_amount")) + else: + component_row.default_amount = 0 + component_row.additional_amount = amount component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date From 31fe5f5b92d071d4b963a40bf46a58dff23e79d2 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 2 Aug 2021 11:01:30 +0530 Subject: [PATCH 173/253] fix: added progress bar in repost item valuation (#26680) --- .../repost_item_valuation.js | 37 +++++++++ .../repost_item_valuation.json | 34 +++++++- .../repost_item_valuation.py | 2 +- erpnext/stock/stock_ledger.py | 83 ++++++++++++++----- 4 files changed, 134 insertions(+), 22 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index b3e4286bcc..4cd40bf38e 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -29,13 +29,50 @@ frappe.ui.form.on('Repost Item Valuation', { }; }); } + + frm.trigger('setup_realtime_progress'); }, + + setup_realtime_progress: function(frm) { + frappe.realtime.on('item_reposting_progress', data => { + if (frm.doc.name !== data.name) { + return; + } + + if (frm.doc.status == 'In Progress') { + frm.doc.current_index = data.current_index; + frm.doc.items_to_be_repost = data.items_to_be_repost; + + frm.dashboard.reset(); + frm.trigger('show_reposting_progress'); + } + }); + }, + refresh: function(frm) { if (frm.doc.status == "Failed" && frm.doc.docstatus==1) { frm.add_custom_button(__('Restart'), function () { frm.trigger("restart_reposting"); }).addClass("btn-primary"); } + + frm.trigger('show_reposting_progress'); + }, + + show_reposting_progress: function(frm) { + var bars = []; + + let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0; + let progress = flt(cint(frm.doc.current_index) / total_count * 100, 2) || 0.5; + var title = __('Reposting Completed {0}%', [progress]); + + bars.push({ + 'title': title, + 'width': progress + '%', + 'progress_class': 'progress-bar-success' + }); + + frm.dashboard.add_progress(__('Reposting Progress'), bars); }, restart_reposting: function(frm) { diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json index 071fc86d9b..a800bf8701 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json @@ -21,7 +21,10 @@ "allow_zero_rate", "amended_from", "error_section", - "error_log" + "error_log", + "items_to_be_repost", + "distinct_item_and_warehouse", + "current_index" ], "fields": [ { @@ -142,12 +145,39 @@ "fieldname": "allow_zero_rate", "fieldtype": "Check", "label": "Allow Zero Rate" + }, + { + "fieldname": "items_to_be_repost", + "fieldtype": "Code", + "hidden": 1, + "label": "Items to Be Repost", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "distinct_item_and_warehouse", + "fieldtype": "Code", + "hidden": 1, + "label": "Distinct Item and Warehouse", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "current_index", + "fieldtype": "Int", + "hidden": 1, + "label": "Current Index", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-10 07:52:12.476589", + "modified": "2021-07-22 18:59:43.057878", "modified_by": "Administrator", "module": "Stock", "name": "Repost Item Valuation", diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 5f31d9caf0..b22759d3b7 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -81,7 +81,7 @@ def repost(doc): def repost_sl_entries(doc): if doc.based_on == 'Transaction': repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no, - allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher) + allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher, doc=doc) else: repost_future_sle(args=[frappe._dict({ "item_code": doc.item_code, diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c15d1eda7d..f990ce06be 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -127,30 +127,26 @@ def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): sle.submit() return sle -def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False): +def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False, doc=None): if not args and voucher_type and voucher_no: - args = get_args_for_voucher(voucher_type, voucher_no) + args = get_items_to_be_repost(voucher_type, voucher_no, doc) - distinct_item_warehouses = {} - for i, d in enumerate(args): - distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({ - "reposting_status": False, - "sle": d, - "args_idx": i - })) + distinct_item_warehouses = get_distinct_item_warehouse(args, doc) - i = 0 + i = get_current_index(doc) or 0 while i < len(args): + validate_item_warehouse(args[i]) + obj = update_entries_after({ - "item_code": args[i].item_code, - "warehouse": args[i].warehouse, - "posting_date": args[i].posting_date, - "posting_time": args[i].posting_time, - "creation": args[i].get("creation"), - "distinct_item_warehouses": distinct_item_warehouses + 'item_code': args[i].get('item_code'), + 'warehouse': args[i].get('warehouse'), + 'posting_date': args[i].get('posting_date'), + 'posting_time': args[i].get('posting_time'), + 'creation': args[i].get('creation'), + 'distinct_item_warehouses': distinct_item_warehouses }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True + distinct_item_warehouses[(args[i].get('item_code'), args[i].get('warehouse'))].reposting_status = True if obj.new_items_found: for item_wh, data in iteritems(distinct_item_warehouses): @@ -159,11 +155,41 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat args.append(data.sle) elif data.sle_changed and not data.reposting_status: args[data.args_idx] = data.sle - + data.sle_changed = False i += 1 -def get_args_for_voucher(voucher_type, voucher_no): + if doc and i % 2 == 0: + update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses) + + if doc and args: + update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses) + +def validate_item_warehouse(args): + for field in ['item_code', 'warehouse', 'posting_date', 'posting_time']: + if not args.get(field): + validation_msg = f'The field {frappe.unscrub(args.get(field))} is required for the reposting' + frappe.throw(_(validation_msg)) + +def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses): + frappe.db.set_value(doc.doctype, doc.name, { + 'items_to_be_repost': json.dumps(args, default=str), + 'distinct_item_and_warehouse': json.dumps({str(k): v for k,v in distinct_item_warehouses.items()}, default=str), + 'current_index': index + }) + + frappe.db.commit() + + frappe.publish_realtime('item_reposting_progress', { + 'name': doc.name, + 'items_to_be_repost': json.dumps(args, default=str), + 'current_index': index + }) + +def get_items_to_be_repost(voucher_type, voucher_no, doc=None): + if doc and doc.items_to_be_repost: + return json.loads(doc.items_to_be_repost) or [] + return frappe.db.get_all("Stock Ledger Entry", filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"], @@ -171,6 +197,25 @@ def get_args_for_voucher(voucher_type, voucher_no): group_by="item_code, warehouse" ) +def get_distinct_item_warehouse(args=None, doc=None): + distinct_item_warehouses = {} + if doc and doc.distinct_item_and_warehouse: + distinct_item_warehouses = json.loads(doc.distinct_item_and_warehouse) + distinct_item_warehouses = {frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items()} + else: + for i, d in enumerate(args): + distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({ + "reposting_status": False, + "sle": d, + "args_idx": i + })) + + return distinct_item_warehouses + +def get_current_index(doc=None): + if doc and doc.current_index: + return doc.current_index + class update_entries_after(object): """ update valution rate and qty after transaction From 58118d7830f16c72a95188a2ab29caea4ea21e2b Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 2 Aug 2021 11:34:43 +0530 Subject: [PATCH 174/253] fix: missing QR Code in auto email attachment (#26598) --- erpnext/regional/india/e_invoice/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 81c7a6b9a0..f8a230d0b8 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -969,7 +969,7 @@ class GSPConnector(): "attached_to_doctype": doctype, "attached_to_name": docname, "attached_to_field": "qrcode_image", - "is_private": 1, + "is_private": 0, "content": qr_image.getvalue()}) _file.save() frappe.db.commit() From 3b7615750aa780671e75fa637a360218184095b8 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 2 Aug 2021 11:37:38 +0530 Subject: [PATCH 175/253] fix: POS Item Cart non-stop scroll issue (#26692) --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 6e36d2809a..a4a4b0e0ed 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -564,7 +564,6 @@ erpnext.PointOfSale.ItemCart = class { ) set_dynamic_rate_header_width(); - this.scroll_to_item($item_to_update); function set_dynamic_rate_header_width() { const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount")); @@ -639,12 +638,6 @@ erpnext.PointOfSale.ItemCart = class { $($img).parent().replaceWith(`
${item_abbr}
`); } - scroll_to_item($item) { - if ($item.length === 0) return; - const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop(); - this.$cart_items_wrapper.animate({ scrollTop }); - } - update_selector_value_in_cart_item(selector, value, item) { const $item_to_update = this.get_cart_item(item); $item_to_update.attr(`data-${selector}`, escape(value)); From e99cd2e9daa659ff006b91f2b16f2bc3e9fc6100 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 2 Aug 2021 11:39:20 +0530 Subject: [PATCH 176/253] fix: POS Invoice consolidated Sales Invoice field set to no copy (#26706) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 7459c11d4d..33c3e0432b 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1545,6 +1545,7 @@ "fieldname": "consolidated_invoice", "fieldtype": "Link", "label": "Consolidated Sales Invoice", + "no_copy": 1, "options": "Sales Invoice", "read_only": 1 } @@ -1552,7 +1553,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2021-02-01 15:03:33.800707", + "modified": "2021-07-29 13:37:20.636171", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", From f9fc3bbfa83ac3d90ce3636cc2fae4f319c4a340 Mon Sep 17 00:00:00 2001 From: Anuja Date: Mon, 2 Aug 2021 12:54:30 +0530 Subject: [PATCH 177/253] fix: added patch for custom field and minor fixes --- erpnext/patches.txt | 1 + .../add_custom_field_for_south_africa.py | 13 +++++++++ .../vat_audit_report/vat_audit_report.py | 28 +++++++++++-------- 3 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 erpnext/patches/v13_0/add_custom_field_for_south_africa.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a029627ab1..942acd59a5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -297,3 +297,4 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v13_0.add_custom_field_for_south_africa diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py new file mode 100644 index 0000000000..73e9af9e4b --- /dev/null +++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py @@ -0,0 +1,13 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from erpnext.regional.south_africa.setup import make_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'South Africa'}) + if not company: + return + + make_custom_fields() diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index bce8586720..0c716cdab2 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -55,7 +55,7 @@ class VATAuditReport(object): `tab{doctype}` WHERE docstatus = 1 {where_conditions} - and is_opening = 'No' + and is_opening = "No" ORDER BY posting_date DESC """.format(select_columns=self.select_columns, doctype=doctype, @@ -66,26 +66,31 @@ class VATAuditReport(object): def get_invoice_items(self, doctype): self.invoice_items = frappe._dict() - self.item_tax_rate = frappe._dict() items = frappe.db.sql(""" SELECT - item_code, parent, taxable_value, base_net_amount, item_tax_rate + item_code, parent, taxable_value, base_net_amount, is_zero_rated FROM `tab%s Item` WHERE parent in (%s) - """ % (doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) + """ % (doctype, ", ".join(["%s"]*len(self.invoices))), tuple(self.invoices), as_dict=1) for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items - if i.item_code == d.item_code and i.parent == d.parent)) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {}) \ + .setdefault("net_amount", sum((i.get("taxable_value", 0) \ + or i.get("base_net_amount", 0)) for i in items \ + if (i.item_code == d.item_code and i.parent == d.parent))) + + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {}) \ + .setdefault("is_zero_rated", d.is_zero_rated) def get_items_based_on_tax_rate(self, doctype): self.items_based_on_tax_rate = frappe._dict() + self.item_tax_rate = frappe._dict() self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \ else "Sales Taxes and Charges" + self.tax_details = frappe.db.sql(""" SELECT parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount @@ -96,7 +101,7 @@ class VATAuditReport(object): and parent in (%s) ORDER BY account_head - """ % (self.tax_doctype, '%s', ', '.join(['%s']*len(self.invoices.keys()))), + """ % (self.tax_doctype, "%s", ", ".join(["%s"]*len(self.invoices.keys()))), tuple([doctype] + list(self.invoices.keys()))) for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: @@ -107,7 +112,8 @@ class VATAuditReport(object): else: continue for item_code, taxes in item_wise_tax_detail.items(): - is_zero_rated = frappe.get_value("Item", item_code, "is_zero_rated") + is_zero_rated = self.invoice_items.get(parent).get(item_code).get("is_zero_rated") + #to skip items with non-zero tax rate in multiple rows if taxes[0] == 0 and not is_zero_rated: continue tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes) @@ -121,7 +127,7 @@ class VATAuditReport(object): continue def get_item_amount_map(self, parent, item_code, taxes): - net_amount = abs(self.invoice_items.get(parent).get(item_code)) + net_amount = self.invoice_items.get(parent).get(item_code).get("net_amount") tax_rate = taxes[0] tax_amount = taxes[1] gross_amount = net_amount + tax_amount @@ -183,7 +189,7 @@ class VATAuditReport(object): row = {} item_details = self.item_tax_rate.get(inv).get(item) row["account"] = inv_data.get("account") - row["posting_date"] = formatdate(inv_data.get("posting_date"), 'dd-mm-yyyy') + row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy") row["voucher_type"] = doctype row["voucher_no"] = inv row["remarks"] = inv_data.get("remarks") From 7fe588e236051b9e03cd1b1934fa0a88379716b7 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Mon, 2 Aug 2021 20:07:55 +0530 Subject: [PATCH 178/253] fix: change format string to percent string interpolation (#26774) --- .../patches/v13_0/add_missing_fg_item_for_stock_entry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py index d7ad1fc696..0d8109c41a 100644 --- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py +++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py @@ -30,19 +30,20 @@ def execute(): return repost_stock_entries = [] + stock_entries = frappe.db.sql_list(''' SELECT se.name FROM `tabStock Entry` se WHERE - se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in {work_orders} + se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in %s and not exists( select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1 ) - Order BY + ORDER BY se.posting_date, se.posting_time - '''.format(work_orders=tuple(work_orders))) + ''', (work_orders,)) if stock_entries: print('Length of stock entries', len(stock_entries)) From b3740e9afc375624dc478c97a97f757e06de084f Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 2 Aug 2021 20:13:26 +0530 Subject: [PATCH 179/253] fix: remove limit from stock balance report (#26773) --- erpnext/stock/report/stock_balance/stock_balance.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index b6a8063189..9e56ad4130 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -16,8 +16,6 @@ def execute(filters=None): is_reposting_item_valuation_in_progress() if not filters: filters = {} - validate_filters(filters) - from_date = filters.get('from_date') to_date = filters.get('to_date') @@ -295,12 +293,6 @@ def get_item_reorder_details(items): return dict((d.parent + d.warehouse, d) for d in item_reorder_details) -def validate_filters(filters): - if not (filters.get("item_code") or filters.get("warehouse")): - sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0]) - if sle_count > 500000: - frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries.")) - def get_variants_attributes(): '''Return all item variant attributes.''' return [i.name for i in frappe.get_all('Item Attribute')] From 78762850ee5cd900ead9422453d49823163a01cb Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 2 Aug 2021 20:15:54 +0530 Subject: [PATCH 180/253] fix: gl entries for exchange gain loss (#26711) From efb037b566b68d26c269623bcd16069d72e7f2c9 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 2 Aug 2021 21:57:52 +0530 Subject: [PATCH 181/253] refactor!: drop shopify integration from ERPNext (#26700) BREAKING CHANGE: remove Shopify integration. The integration is moved to a separate app with additional enhancements. The app is still FOSS and licensed under the same license as ERPNext. Any data migration required is taken care of after installing the app and enabling it. New app: https://github.com/frappe/ecommerce_integrations --- .../connectors/shopify_connection.py | 353 ------------ .../doctype/shopify_log/__init__.py | 0 .../doctype/shopify_log/shopify_log.js | 22 - .../doctype/shopify_log/shopify_log.json | 268 --------- .../doctype/shopify_log/shopify_log.py | 68 --- .../doctype/shopify_log/shopify_log_list.js | 12 - .../doctype/shopify_log/test_shopify_log.js | 23 - .../doctype/shopify_log/test_shopify_log.py | 12 - .../doctype/shopify_settings/__init__.py | 0 .../shopify_settings/shopify_settings.js | 90 --- .../shopify_settings/shopify_settings.json | 353 ------------ .../shopify_settings/shopify_settings.py | 144 ----- .../doctype/shopify_settings/sync_customer.py | 71 --- .../doctype/shopify_settings/sync_product.py | 309 ---------- .../test_data/custom_field.json | 527 ------------------ .../test_data/shopify_customer.json | 59 -- .../test_data/shopify_item.json | 125 ----- .../test_data/shopify_order.json | 270 --------- .../shopify_settings/test_shopify_settings.js | 23 - .../shopify_settings/test_shopify_settings.py | 107 ---- .../doctype/shopify_tax_account/__init__.py | 0 .../shopify_tax_account.json | 133 ----- .../shopify_tax_account.py | 10 - .../shopify_webhook_detail/__init__.py | 0 .../shopify_webhook_detail.json | 103 ---- .../shopify_webhook_detail.py | 10 - .../erpnext_integrations.json | 12 +- .../erpnext_integrations_settings.json | 12 +- erpnext/hooks.py | 1 - erpnext/patches.txt | 4 +- .../patches/v11_0/refactor_erpnext_shopify.py | 43 -- .../v12_0/set_default_shopify_app_type.py | 6 - .../v13_0/shopify_deprecation_warning.py | 10 + .../v13_0/update_custom_fields_for_shopify.py | 10 - 34 files changed, 13 insertions(+), 3177 deletions(-) delete mode 100644 erpnext/erpnext_integrations/connectors/shopify_connection.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_log/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_tax_account/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json delete mode 100644 erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py delete mode 100644 erpnext/patches/v11_0/refactor_erpnext_shopify.py delete mode 100644 erpnext/patches/v12_0/set_default_shopify_app_type.py create mode 100644 erpnext/patches/v13_0/shopify_deprecation_warning.py delete mode 100644 erpnext/patches/v13_0/update_custom_fields_for_shopify.py diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py deleted file mode 100644 index 5d5b2e19ce..0000000000 --- a/erpnext/erpnext_integrations/connectors/shopify_connection.py +++ /dev/null @@ -1,353 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ -import json -from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime -from erpnext.erpnext_integrations.utils import validate_webhooks_request -from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice -from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify -from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer -from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data -from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header - -@frappe.whitelist(allow_guest=True) -@validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret') -def store_request_data(order=None, event=None): - if frappe.request: - order = json.loads(frappe.request.data) - event = frappe.request.headers.get('X-Shopify-Topic') - - dump_request_data(order, event) - -def sync_sales_order(order, request_id=None, old_order_sync=False): - frappe.set_user('Administrator') - shopify_settings = frappe.get_doc("Shopify Settings") - frappe.flags.request_id = request_id - - if not frappe.db.get_value("Sales Order", filters={"shopify_order_id": cstr(order['id'])}): - try: - validate_customer(order, shopify_settings) - validate_item(order, shopify_settings) - create_order(order, shopify_settings, old_order_sync=old_order_sync) - except Exception as e: - make_shopify_log(status="Error", exception=e) - - else: - make_shopify_log(status="Success") - -def prepare_sales_invoice(order, request_id=None): - frappe.set_user('Administrator') - shopify_settings = frappe.get_doc("Shopify Settings") - frappe.flags.request_id = request_id - - try: - sales_order = get_sales_order(cstr(order['id'])) - if sales_order: - create_sales_invoice(order, shopify_settings, sales_order) - make_shopify_log(status="Success") - except Exception as e: - make_shopify_log(status="Error", exception=e, rollback=True) - -def prepare_delivery_note(order, request_id=None): - frappe.set_user('Administrator') - shopify_settings = frappe.get_doc("Shopify Settings") - frappe.flags.request_id = request_id - - try: - sales_order = get_sales_order(cstr(order['id'])) - if sales_order: - create_delivery_note(order, shopify_settings, sales_order) - make_shopify_log(status="Success") - except Exception as e: - make_shopify_log(status="Error", exception=e, rollback=True) - -def get_sales_order(shopify_order_id): - sales_order = frappe.db.get_value("Sales Order", filters={"shopify_order_id": shopify_order_id}) - if sales_order: - so = frappe.get_doc("Sales Order", sales_order) - return so - -def validate_customer(order, shopify_settings): - customer_id = order.get("customer", {}).get("id") - if customer_id: - if not frappe.db.get_value("Customer", {"shopify_customer_id": customer_id}, "name"): - create_customer(order.get("customer"), shopify_settings) - -def validate_item(order, shopify_settings): - for item in order.get("line_items"): - if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"): - sync_item_from_shopify(shopify_settings, item) - -def create_order(order, shopify_settings, old_order_sync=False, company=None): - so = create_sales_order(order, shopify_settings, company) - if so: - if order.get("financial_status") == "paid": - create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync) - - if order.get("fulfillments") and not old_order_sync: - create_delivery_note(order, shopify_settings, so) - -def create_sales_order(shopify_order, shopify_settings, company=None): - product_not_exists = [] - customer = frappe.db.get_value("Customer", {"shopify_customer_id": shopify_order.get("customer", {}).get("id")}, "name") - so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name") - - if not so: - items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at'))) - - if not items: - message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master' - message += "\n" + ", ".join(product_not_exists) - - make_shopify_log(status="Error", exception=message, rollback=True) - - return '' - - so = frappe.get_doc({ - "doctype": "Sales Order", - "naming_series": shopify_settings.sales_order_series or "SO-Shopify-", - "shopify_order_id": shopify_order.get("id"), - "shopify_order_number": shopify_order.get("name"), - "customer": customer or shopify_settings.default_customer, - "transaction_date": getdate(shopify_order.get("created_at")) or nowdate(), - "delivery_date": getdate(shopify_order.get("created_at")) or nowdate(), - "company": shopify_settings.company, - "selling_price_list": shopify_settings.price_list, - "ignore_pricing_rule": 1, - "items": items, - "taxes": get_order_taxes(shopify_order, shopify_settings), - "apply_discount_on": "Grand Total", - "discount_amount": get_discounted_amount(shopify_order), - }) - - if company: - so.update({ - "company": company, - "status": "Draft" - }) - so.flags.ignore_mandatory = True - so.save(ignore_permissions=True) - so.submit() - - else: - so = frappe.get_doc("Sales Order", so) - - frappe.db.commit() - return so - -def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False): - if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\ - and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice): - - if old_order_sync: - posting_date = getdate(shopify_order.get('created_at')) - else: - posting_date = nowdate() - - si = make_sales_invoice(so.name, ignore_permissions=True) - si.shopify_order_id = shopify_order.get("id") - si.shopify_order_number = shopify_order.get("name") - si.set_posting_time = 1 - si.posting_date = posting_date - si.due_date = posting_date - si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-" - si.flags.ignore_mandatory = True - set_cost_center(si.items, shopify_settings.cost_center) - si.insert(ignore_mandatory=True) - si.submit() - make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date) - frappe.db.commit() - -def set_cost_center(items, cost_center): - for item in items: - item.cost_center = cost_center - -def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None): - from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry - payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account) - payment_entry.flags.ignore_mandatory = True - payment_entry.reference_no = doc.name - payment_entry.posting_date = posting_date or nowdate() - payment_entry.reference_date = posting_date or nowdate() - payment_entry.insert(ignore_permissions=True) - payment_entry.submit() - -def create_delivery_note(shopify_order, shopify_settings, so): - if not cint(shopify_settings.sync_delivery_note): - return - - for fulfillment in shopify_order.get("fulfillments"): - if not frappe.db.get_value("Delivery Note", {"shopify_fulfillment_id": fulfillment.get("id")}, "name")\ - and so.docstatus==1: - - dn = make_delivery_note(so.name) - dn.shopify_order_id = fulfillment.get("order_id") - dn.shopify_order_number = shopify_order.get("name") - dn.set_posting_time = 1 - dn.posting_date = getdate(fulfillment.get("created_at")) - dn.shopify_fulfillment_id = fulfillment.get("id") - dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-" - dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings) - dn.flags.ignore_mandatory = True - dn.save() - dn.submit() - frappe.db.commit() - -def get_fulfillment_items(dn_items, fulfillment_items, shopify_settings): - return [dn_item.update({"qty": item.get("quantity")}) for item in fulfillment_items for dn_item in dn_items\ - if get_item_code(item) == dn_item.item_code] - -def get_discounted_amount(order): - discounted_amount = 0.0 - for discount in order.get("discount_codes"): - discounted_amount += flt(discount.get("amount")) - return discounted_amount - -def get_order_items(order_items, shopify_settings, delivery_date): - items = [] - all_product_exists = True - product_not_exists = [] - - for shopify_item in order_items: - if not shopify_item.get('product_exists'): - all_product_exists = False - product_not_exists.append({'title':shopify_item.get('title'), - 'shopify_order_id': shopify_item.get('id')}) - continue - - if all_product_exists: - item_code = get_item_code(shopify_item) - items.append({ - "item_code": item_code, - "item_name": shopify_item.get("name"), - "rate": shopify_item.get("price"), - "delivery_date": delivery_date, - "qty": shopify_item.get("quantity"), - "stock_uom": shopify_item.get("uom") or _("Nos"), - "warehouse": shopify_settings.warehouse - }) - else: - items = [] - - return items - -def get_item_code(shopify_item): - item_code = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("variant_id")}, "item_code") - if not item_code: - item_code = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("product_id")}, "item_code") - if not item_code: - item_code = frappe.db.get_value("Item", {"item_name": shopify_item.get("title")}, "item_code") - - return item_code - -def get_order_taxes(shopify_order, shopify_settings): - taxes = [] - for tax in shopify_order.get("tax_lines"): - taxes.append({ - "charge_type": _("On Net Total"), - "account_head": get_tax_account_head(tax), - "description": "{0} - {1}%".format(tax.get("title"), tax.get("rate") * 100.0), - "rate": tax.get("rate") * 100.00, - "included_in_print_rate": 1 if shopify_order.get("taxes_included") else 0, - "cost_center": shopify_settings.cost_center - }) - - taxes = update_taxes_with_shipping_lines(taxes, shopify_order.get("shipping_lines"), shopify_settings) - - return taxes - -def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings): - """Shipping lines represents the shipping details, - each such shipping detail consists of a list of tax_lines""" - for shipping_charge in shipping_lines: - if shipping_charge.get("price"): - taxes.append({ - "charge_type": _("Actual"), - "account_head": get_tax_account_head(shipping_charge), - "description": shipping_charge["title"], - "tax_amount": shipping_charge["price"], - "cost_center": shopify_settings.cost_center - }) - - for tax in shipping_charge.get("tax_lines"): - taxes.append({ - "charge_type": _("Actual"), - "account_head": get_tax_account_head(tax), - "description": tax["title"], - "tax_amount": tax["price"], - "cost_center": shopify_settings.cost_center - }) - - return taxes - -def get_tax_account_head(tax): - tax_title = tax.get("title").encode("utf-8") - - tax_account = frappe.db.get_value("Shopify Tax Account", \ - {"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account") - - if not tax_account: - frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title"))) - - return tax_account - -@frappe.whitelist(allow_guest=True) -def sync_old_orders(): - frappe.set_user('Administrator') - shopify_settings = frappe.get_doc('Shopify Settings') - - if not shopify_settings.sync_missing_orders: - return - - url = get_url(shopify_settings) - session = get_request_session() - - try: - res = session.get(url, headers=get_header(shopify_settings)) - res.raise_for_status() - orders = res.json()["orders"] - - for order in orders: - if is_sync_complete(shopify_settings, order): - stop_sync(shopify_settings) - return - - sync_sales_order(order=order, old_order_sync=True) - last_order_id = order.get('id') - - if last_order_id: - shopify_settings.load_from_db() - shopify_settings.last_order_id = last_order_id - shopify_settings.save() - frappe.db.commit() - - except Exception as e: - raise e - -def stop_sync(shopify_settings): - shopify_settings.sync_missing_orders = 0 - shopify_settings.last_order_id = '' - shopify_settings.save() - frappe.db.commit() - -def get_url(shopify_settings): - last_order_id = shopify_settings.last_order_id - - if not last_order_id: - if shopify_settings.sync_based_on == 'Date': - url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format( - get_datetime(shopify_settings.from_date)), shopify_settings) - else: - url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format( - shopify_settings.from_order_id), shopify_settings) - else: - url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings) - - return url - -def is_sync_complete(shopify_settings, order): - if shopify_settings.sync_based_on == 'Date': - return getdate(shopify_settings.to_date) < getdate(order.get('created_at')) - else: - return cstr(order.get('id')) == cstr(shopify_settings.to_order_id) - diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js deleted file mode 100644 index d3fe7d2b4d..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Shopify Log', { - refresh: function(frm) { - if (frm.doc.request_data && frm.doc.status=='Error'){ - frm.add_custom_button('Resync', function() { - frappe.call({ - method:"erpnext.erpnext_integrations.doctype.shopify_log.shopify_log.resync", - args:{ - method:frm.doc.method, - name: frm.doc.name, - request_data: frm.doc.request_data - }, - callback: function(r){ - frappe.msgprint(__("Order rescheduled for sync")) - } - }) - }).addClass('btn-primary'); - } - } -}); diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json deleted file mode 100644 index ab373eedb4..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json +++ /dev/null @@ -1,268 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-03-14 10:02:06.227184", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Queued", - "fieldname": "status", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "method", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Method", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "message", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Message", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "traceback", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Traceback", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "request_data", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Request Data", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-04-20 16:23:36.862381", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Shopify Log", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py deleted file mode 100644 index a2b6af99b2..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -import json -from frappe.model.document import Document -from erpnext.erpnext_integrations.utils import get_webhook_address - -class ShopifyLog(Document): - pass - - -def make_shopify_log(status="Queued", exception=None, rollback=False): - # if name not provided by log calling method then fetch existing queued state log - make_new = False - - if not frappe.flags.request_id: - make_new = True - - if rollback: - frappe.db.rollback() - - if make_new: - log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True) - else: - log = log = frappe.get_doc("Shopify Log", frappe.flags.request_id) - - log.message = get_message(exception) - log.traceback = frappe.get_traceback() - log.status = status - log.save(ignore_permissions=True) - frappe.db.commit() - -def get_message(exception): - message = None - - if hasattr(exception, 'message'): - message = exception.message - elif hasattr(exception, '__str__'): - message = exception.__str__() - else: - message = "Something went wrong while syncing" - return message - -def dump_request_data(data, event="create/order"): - event_mapper = { - "orders/create": get_webhook_address(connector_name='shopify_connection', method="sync_sales_order", exclude_uri=True), - "orders/paid" : get_webhook_address(connector_name='shopify_connection', method="prepare_sales_invoice", exclude_uri=True), - "orders/fulfilled": get_webhook_address(connector_name='shopify_connection', method="prepare_delivery_note", exclude_uri=True) - } - - log = frappe.get_doc({ - "doctype": "Shopify Log", - "request_data": json.dumps(data, indent=1), - "method": event_mapper[event] - }).insert(ignore_permissions=True) - - frappe.db.commit() - frappe.enqueue(method=event_mapper[event], queue='short', timeout=300, is_async=True, - **{"order": data, "request_id": log.name}) - -@frappe.whitelist() -def resync(method, name, request_data): - frappe.db.set_value("Shopify Log", name, "status", "Queued", update_modified=False) - frappe.enqueue(method=method, queue='short', timeout=300, is_async=True, - **{"order": json.loads(request_data), "request_id": name}) diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js deleted file mode 100644 index 0913ce4ef3..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js +++ /dev/null @@ -1,12 +0,0 @@ -frappe.listview_settings['Shopify Log'] = { - add_fields: ["status"], - get_indicator: function(doc) { - if(doc.status==="Success"){ - return [__("Success"), "green", "status,=,Success"]; - } else if(doc.status ==="Error"){ - return [__("Error"), "red", "status,=,Error"]; - } else if(doc.status ==="Queued"){ - return [__("Queued"), "orange", "status,=,Queued"]; - } - } -} diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js deleted file mode 100644 index d22b6d5240..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shopify Log", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shopify Log - () => frappe.tests.make('Shopify Log', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py deleted file mode 100644 index 5892e1d6c4..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -# test_records = frappe.get_test_records('Shopify Log') - -class TestShopifyLog(unittest.TestCase): - pass diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/__init__.py b/erpnext/erpnext_integrations/doctype/shopify_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js deleted file mode 100644 index 1574795dfa..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.provide("erpnext_integrations.shopify_settings"); - -frappe.ui.form.on("Shopify Settings", "onload", function(frm){ - frappe.call({ - method:"erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings.get_series", - callback:function(r){ - $.each(r.message, function(key, value){ - set_field_options(key, value); - }); - } - }); - erpnext_integrations.shopify_settings.setup_queries(frm); -}) - -frappe.ui.form.on("Shopify Settings", "app_type", function(frm) { - frm.toggle_reqd("api_key", (frm.doc.app_type == "Private")); - frm.toggle_reqd("password", (frm.doc.app_type == "Private")); -}) - -frappe.ui.form.on("Shopify Settings", "refresh", function(frm){ - if(!frm.doc.__islocal && frm.doc.enable_shopify === 1){ - frm.toggle_reqd("price_list", true); - frm.toggle_reqd("warehouse", true); - frm.toggle_reqd("taxes", true); - frm.toggle_reqd("company", true); - frm.toggle_reqd("cost_center", true); - frm.toggle_reqd("cash_bank_account", true); - frm.toggle_reqd("sales_order_series", true); - frm.toggle_reqd("customer_group", true); - frm.toggle_reqd("shared_secret", true); - - frm.toggle_reqd("sales_invoice_series", frm.doc.sync_sales_invoice); - frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note); - - } -}) - -$.extend(erpnext_integrations.shopify_settings, { - setup_queries: function(frm) { - frm.fields_dict["warehouse"].get_query = function(doc) { - return { - filters:{ - "company": doc.company, - "is_group": "No" - } - } - } - - frm.fields_dict["taxes"].grid.get_field("tax_account").get_query = function(doc){ - return { - "query": "erpnext.controllers.queries.tax_account_query", - "filters": { - "account_type": ["Tax", "Chargeable", "Expense Account"], - "company": doc.company - } - } - } - - frm.fields_dict["cash_bank_account"].get_query = function(doc) { - return { - filters: [ - ["Account", "account_type", "in", ["Cash", "Bank"]], - ["Account", "root_type", "=", "Asset"], - ["Account", "is_group", "=",0], - ["Account", "company", "=", doc.company] - ] - } - } - - frm.fields_dict["cost_center"].get_query = function(doc) { - return { - filters:{ - "company": doc.company, - "is_group": "No" - } - } - } - - frm.fields_dict["price_list"].get_query = function() { - return { - filters:{ - "selling": 1 - } - } - } - } -}) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json deleted file mode 100644 index 308e7d163f..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json +++ /dev/null @@ -1,353 +0,0 @@ -{ - "actions": [], - "creation": "2015-05-18 05:21:07.270859", - "doctype": "DocType", - "document_type": "System", - "engine": "InnoDB", - "field_order": [ - "status_html", - "enable_shopify", - "app_type", - "column_break_4", - "last_sync_datetime", - "section_break_2", - "shopify_url", - "api_key", - "column_break_3", - "password", - "shared_secret", - "access_token", - "section_break_38", - "webhooks", - "section_break_15", - "default_customer", - "column_break_19", - "customer_group", - "company_dependent_settings", - "company", - "cash_bank_account", - "column_break_20", - "cost_center", - "erp_settings", - "price_list", - "update_price_in_erpnext_price_list", - "column_break_26", - "warehouse", - "section_break_25", - "sales_order_series", - "column_break_27", - "sync_delivery_note", - "delivery_note_series", - "sync_sales_invoice", - "sales_invoice_series", - "section_break_22", - "html_16", - "taxes", - "syncing_details_section", - "sync_missing_orders", - "sync_based_on", - "column_break_41", - "from_date", - "to_date", - "from_order_id", - "to_order_id", - "last_order_id" - ], - "fields": [ - { - "fieldname": "status_html", - "fieldtype": "HTML", - "label": "status html", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "enable_shopify", - "fieldtype": "Check", - "label": "Enable Shopify" - }, - { - "default": "Private", - "fieldname": "app_type", - "fieldtype": "Data", - "in_list_view": 1, - "label": "App Type", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "last_sync_datetime", - "fieldtype": "Datetime", - "label": "Last Sync Datetime", - "read_only": 1 - }, - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, - { - "description": "eg: frappe.myshopify.com", - "fieldname": "shopify_url", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Shop URL", - "reqd": 1 - }, - { - "depends_on": "eval:doc.app_type==\"Private\"", - "fieldname": "api_key", - "fieldtype": "Data", - "label": "API Key" - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.app_type==\"Private\"", - "fieldname": "password", - "fieldtype": "Password", - "label": "Password" - }, - { - "fieldname": "shared_secret", - "fieldtype": "Data", - "label": "Shared secret" - }, - { - "fieldname": "access_token", - "fieldtype": "Data", - "hidden": 1, - "label": "Access Token", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_38", - "fieldtype": "Section Break", - "label": "Webhooks Details" - }, - { - "fieldname": "webhooks", - "fieldtype": "Table", - "label": "Webhooks", - "options": "Shopify Webhook Detail", - "read_only": 1 - }, - { - "fieldname": "section_break_15", - "fieldtype": "Section Break", - "label": "Customer Settings" - }, - { - "description": "If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order", - "fieldname": "default_customer", - "fieldtype": "Link", - "label": "Default Customer", - "options": "Customer" - }, - { - "fieldname": "column_break_19", - "fieldtype": "Column Break" - }, - { - "description": "Customer Group will set to selected group while syncing customers from Shopify", - "fieldname": "customer_group", - "fieldtype": "Link", - "label": "Customer Group", - "options": "Customer Group" - }, - { - "fieldname": "company_dependent_settings", - "fieldtype": "Section Break" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "For Company", - "options": "Company" - }, - { - "description": "Cash Account will used for Sales Invoice creation", - "fieldname": "cash_bank_account", - "fieldtype": "Link", - "label": "Cash/Bank Account", - "options": "Account" - }, - { - "fieldname": "column_break_20", - "fieldtype": "Column Break" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "erp_settings", - "fieldtype": "Section Break" - }, - { - "fieldname": "price_list", - "fieldtype": "Link", - "label": "Price List", - "options": "Price List" - }, - { - "default": "0", - "fieldname": "update_price_in_erpnext_price_list", - "fieldtype": "Check", - "label": "Update Price from Shopify To ERPNext Price List" - }, - { - "fieldname": "column_break_26", - "fieldtype": "Column Break" - }, - { - "description": "Default Warehouse to to create Sales Order and Delivery Note", - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "section_break_25", - "fieldtype": "Section Break" - }, - { - "fieldname": "sales_order_series", - "fieldtype": "Select", - "label": "Sales Order Series" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "sync_delivery_note", - "fieldtype": "Check", - "label": "Import Delivery Notes from Shopify on Shipment" - }, - { - "depends_on": "eval:doc.sync_delivery_note==1", - "fieldname": "delivery_note_series", - "fieldtype": "Select", - "label": "Delivery Note Series" - }, - { - "default": "0", - "fieldname": "sync_sales_invoice", - "fieldtype": "Check", - "label": "Import Sales Invoice from Shopify if Payment is marked" - }, - { - "depends_on": "eval:doc.sync_sales_invoice==1", - "fieldname": "sales_invoice_series", - "fieldtype": "Select", - "label": "Sales Invoice Series" - }, - { - "fieldname": "section_break_22", - "fieldtype": "Section Break" - }, - { - "fieldname": "html_16", - "fieldtype": "HTML", - "options": "Map Shopify Taxes / Shipping Charges to ERPNext Account" - }, - { - "fieldname": "taxes", - "fieldtype": "Table", - "label": "Shopify Tax Account", - "options": "Shopify Tax Account" - }, - { - "collapsible": 1, - "fieldname": "syncing_details_section", - "fieldtype": "Section Break", - "label": "Syncing Missing Orders" - }, - { - "depends_on": "eval:doc.sync_missing_orders", - "fieldname": "last_order_id", - "fieldtype": "Data", - "label": "Last Order Id", - "read_only": 1 - }, - { - "fieldname": "column_break_41", - "fieldtype": "Column Break" - }, - { - "default": "0", - "description": "On checking this Order from the ", - "fieldname": "sync_missing_orders", - "fieldtype": "Check", - "label": "Sync Missing Old Shopify Orders" - }, - { - "depends_on": "eval:doc.sync_missing_orders", - "fieldname": "sync_based_on", - "fieldtype": "Select", - "label": "Sync Based On", - "mandatory_depends_on": "eval:doc.sync_missing_orders", - "options": "\nDate\nShopify Order Id" - }, - { - "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders", - "fieldname": "from_date", - "fieldtype": "Date", - "label": "From Date", - "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders" - }, - { - "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders", - "fieldname": "to_date", - "fieldtype": "Date", - "label": "To Date", - "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders" - }, - { - "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders", - "fieldname": "from_order_id", - "fieldtype": "Data", - "label": "From Order Id", - "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders" - }, - { - "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders", - "fieldname": "to_order_id", - "fieldtype": "Data", - "label": "To Order Id", - "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders" - } - ], - "issingle": 1, - "links": [], - "modified": "2021-03-02 17:35:41.953317", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Shopify Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py deleted file mode 100644 index 381c5e5dec..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ /dev/null @@ -1,144 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -import json -from frappe import _ -from frappe.model.document import Document -from frappe.utils import get_request_session -from requests.exceptions import HTTPError -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from erpnext.erpnext_integrations.utils import get_webhook_address -from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log - -class ShopifySettings(Document): - def validate(self): - if self.enable_shopify == 1: - setup_custom_fields() - self.validate_access_credentials() - self.register_webhooks() - else: - self.unregister_webhooks() - - def validate_access_credentials(self): - if not (self.get_password(raise_exception=False) and self.api_key and self.shopify_url): - frappe.msgprint(_("Missing value for Password, API Key or Shopify URL"), raise_exception=frappe.ValidationError) - - def register_webhooks(self): - webhooks = ["orders/create", "orders/paid", "orders/fulfilled"] - # url = get_shopify_url('admin/webhooks.json', self) - created_webhooks = [d.method for d in self.webhooks] - url = get_shopify_url('admin/api/2021-04/webhooks.json', self) - for method in webhooks: - session = get_request_session() - try: - res = session.post(url, data=json.dumps({ - "webhook": { - "topic": method, - "address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True), - "format": "json" - } - }), headers=get_header(self)) - res.raise_for_status() - self.update_webhook_table(method, res.json()) - - except HTTPError as e: - error_message = res.json().get('errors', e) - make_shopify_log(status="Warning", exception=error_message, rollback=True) - - except Exception as e: - make_shopify_log(status="Warning", exception=e, rollback=True) - - def unregister_webhooks(self): - session = get_request_session() - deleted_webhooks = [] - - for d in self.webhooks: - url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self) - try: - res = session.delete(url, headers=get_header(self)) - res.raise_for_status() - deleted_webhooks.append(d) - - except HTTPError as e: - error_message = res.json().get('errors', e) - make_shopify_log(status="Warning", exception=error_message, rollback=True) - - except Exception as e: - frappe.log_error(message=e, title='Shopify Webhooks Issue') - - for d in deleted_webhooks: - self.remove(d) - - def update_webhook_table(self, method, res): - self.append("webhooks", { - "webhook_id": res['webhook']['id'], - "method": method - }) - -def get_shopify_url(path, settings): - if settings.app_type == "Private": - return 'https://{}:{}@{}/{}'.format(settings.api_key, settings.get_password('password'), settings.shopify_url, path) - else: - return 'https://{}/{}'.format(settings.shopify_url, path) - -def get_header(settings): - header = {'Content-Type': 'application/json'} - - return header - -@frappe.whitelist() -def get_series(): - return { - "sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-Shopify-", - "sales_invoice_series" : frappe.get_meta("Sales Invoice").get_options("naming_series") or "SI-Shopify-", - "delivery_note_series" : frappe.get_meta("Delivery Note").get_options("naming_series") or "DN-Shopify-" - } - -def setup_custom_fields(): - custom_fields = { - "Customer": [ - dict(fieldname='shopify_customer_id', label='Shopify Customer Id', - fieldtype='Data', insert_after='series', read_only=1, print_hide=1) - ], - "Supplier": [ - dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id', - fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1) - ], - "Address": [ - dict(fieldname='shopify_address_id', label='Shopify Address Id', - fieldtype='Data', insert_after='fax', read_only=1, print_hide=1) - ], - "Item": [ - dict(fieldname='shopify_variant_id', label='Shopify Variant Id', - fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1), - dict(fieldname='shopify_product_id', label='Shopify Product Id', - fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1), - dict(fieldname='shopify_description', label='Shopify Description', - fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1) - ], - "Sales Order": [ - dict(fieldname='shopify_order_id', label='Shopify Order Id', - fieldtype='Data', insert_after='title', read_only=1, print_hide=1), - dict(fieldname='shopify_order_number', label='Shopify Order Number', - fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1) - ], - "Delivery Note":[ - dict(fieldname='shopify_order_id', label='Shopify Order Id', - fieldtype='Data', insert_after='title', read_only=1, print_hide=1), - dict(fieldname='shopify_order_number', label='Shopify Order Number', - fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1), - dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id', - fieldtype='Data', insert_after='title', read_only=1, print_hide=1) - ], - "Sales Invoice": [ - dict(fieldname='shopify_order_id', label='Shopify Order Id', - fieldtype='Data', insert_after='title', read_only=1, print_hide=1), - dict(fieldname='shopify_order_number', label='Shopify Order Number', - fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1) - ] - } - - create_custom_fields(custom_fields) diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py deleted file mode 100644 index 2af57f4c89..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ - -def create_customer(shopify_customer, shopify_settings): - import frappe.utils.nestedset - - cust_name = (shopify_customer.get("first_name") + " " + (shopify_customer.get("last_name") \ - and shopify_customer.get("last_name") or "")) if shopify_customer.get("first_name")\ - else shopify_customer.get("email") - - try: - customer = frappe.get_doc({ - "doctype": "Customer", - "name": shopify_customer.get("id"), - "customer_name" : cust_name, - "shopify_customer_id": shopify_customer.get("id"), - "sync_with_shopify": 1, - "customer_group": shopify_settings.customer_group, - "territory": frappe.utils.nestedset.get_root_of("Territory"), - "customer_type": _("Individual") - }) - customer.flags.ignore_mandatory = True - customer.insert(ignore_permissions=True) - - if customer: - create_customer_address(customer, shopify_customer) - - frappe.db.commit() - - except Exception as e: - raise e - -def create_customer_address(customer, shopify_customer): - addresses = shopify_customer.get("addresses", []) - - if not addresses and "default_address" in shopify_customer: - addresses.append(shopify_customer["default_address"]) - - for i, address in enumerate(addresses): - address_title, address_type = get_address_title_and_type(customer.customer_name, i) - try : - frappe.get_doc({ - "doctype": "Address", - "shopify_address_id": address.get("id"), - "address_title": address_title, - "address_type": address_type, - "address_line1": address.get("address1") or "Address 1", - "address_line2": address.get("address2"), - "city": address.get("city") or "City", - "state": address.get("province"), - "pincode": address.get("zip"), - "country": address.get("country"), - "phone": address.get("phone"), - "email_id": shopify_customer.get("email"), - "links": [{ - "link_doctype": "Customer", - "link_name": customer.name - }] - }).insert(ignore_mandatory=True) - - except Exception as e: - raise e - -def get_address_title_and_type(customer_name, index): - address_type = _("Billing") - address_title = customer_name - if frappe.db.get_value("Address", "{0}-{1}".format(customer_name.strip(), address_type)): - address_title = "{0}-{1}".format(customer_name.strip(), index) - - return address_title, address_type diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py deleted file mode 100644 index 16efb6caee..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py +++ /dev/null @@ -1,309 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ -from erpnext import get_default_company -from frappe.utils import cstr, cint, get_request_session -from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header - -shopify_variants_attr_list = ["option1", "option2", "option3"] - -def sync_item_from_shopify(shopify_settings, item): - url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings) - session = get_request_session() - - try: - res = session.get(url, headers=get_header(shopify_settings)) - res.raise_for_status() - - shopify_item = res.json()["product"] - make_item(shopify_settings.warehouse, shopify_item) - except Exception as e: - raise e - -def make_item(warehouse, shopify_item): - add_item_weight(shopify_item) - - if has_variants(shopify_item): - attributes = create_attribute(shopify_item) - create_item(shopify_item, warehouse, 1, attributes) - create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list) - - else: - shopify_item["variant_id"] = shopify_item['variants'][0]["id"] - create_item(shopify_item, warehouse) - -def add_item_weight(shopify_item): - shopify_item["weight"] = shopify_item['variants'][0]["weight"] - shopify_item["weight_unit"] = shopify_item['variants'][0]["weight_unit"] - -def has_variants(shopify_item): - if len(shopify_item.get("options")) >= 1 and "Default Title" not in shopify_item.get("options")[0]["values"]: - return True - return False - -def create_attribute(shopify_item): - attribute = [] - # shopify item dict - for attr in shopify_item.get('options'): - if not frappe.db.get_value("Item Attribute", attr.get("name"), "name"): - frappe.get_doc({ - "doctype": "Item Attribute", - "attribute_name": attr.get("name"), - "item_attribute_values": [ - { - "attribute_value": attr_value, - "abbr":attr_value - } - for attr_value in attr.get("values") - ] - }).insert() - attribute.append({"attribute": attr.get("name")}) - - else: - # check for attribute values - item_attr = frappe.get_doc("Item Attribute", attr.get("name")) - if not item_attr.numeric_values: - set_new_attribute_values(item_attr, attr.get("values")) - item_attr.save() - attribute.append({"attribute": attr.get("name")}) - - else: - attribute.append({ - "attribute": attr.get("name"), - "from_range": item_attr.get("from_range"), - "to_range": item_attr.get("to_range"), - "increment": item_attr.get("increment"), - "numeric_values": item_attr.get("numeric_values") - }) - - return attribute - -def set_new_attribute_values(item_attr, values): - for attr_value in values: - if not any((d.abbr.lower() == attr_value.lower() or d.attribute_value.lower() == attr_value.lower())\ - for d in item_attr.item_attribute_values): - item_attr.append("item_attribute_values", { - "attribute_value": attr_value, - "abbr": attr_value - }) - -def create_item(shopify_item, warehouse, has_variant=0, attributes=None,variant_of=None): - item_dict = { - "doctype": "Item", - "shopify_product_id": shopify_item.get("id"), - "shopify_variant_id": shopify_item.get("variant_id"), - "variant_of": variant_of, - "sync_with_shopify": 1, - "is_stock_item": 1, - "item_code": cstr(shopify_item.get("item_code")) or cstr(shopify_item.get("id")), - "item_name": shopify_item.get("title", '').strip(), - "description": shopify_item.get("body_html") or shopify_item.get("title"), - "shopify_description": shopify_item.get("body_html") or shopify_item.get("title"), - "item_group": get_item_group(shopify_item.get("product_type")), - "has_variants": has_variant, - "attributes":attributes or [], - "stock_uom": shopify_item.get("uom") or _("Nos"), - "stock_keeping_unit": shopify_item.get("sku") or get_sku(shopify_item), - "default_warehouse": warehouse, - "image": get_item_image(shopify_item), - "weight_uom": shopify_item.get("weight_unit"), - "weight_per_unit": shopify_item.get("weight"), - "default_supplier": get_supplier(shopify_item), - "item_defaults": [ - { - "company": get_default_company() - } - ] - } - - if not is_item_exists(item_dict, attributes, variant_of=variant_of): - item_details = get_item_details(shopify_item) - name = '' - - if not item_details: - new_item = frappe.get_doc(item_dict) - new_item.insert(ignore_permissions=True, ignore_mandatory=True) - name = new_item.name - - if not name: - name = item_details.name - - if not has_variant: - add_to_price_list(shopify_item, name) - - frappe.db.commit() - -def create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list): - template_item = frappe.db.get_value("Item", filters={"shopify_product_id": shopify_item.get("id")}, - fieldname=["name", "stock_uom"], as_dict=True) - - if template_item: - for variant in shopify_item.get("variants"): - shopify_item_variant = { - "id" : variant.get("id"), - "item_code": variant.get("id"), - "title": variant.get("title"), - "product_type": shopify_item.get("product_type"), - "sku": variant.get("sku"), - "uom": template_item.stock_uom or _("Nos"), - "item_price": variant.get("price"), - "variant_id": variant.get("id"), - "weight_unit": variant.get("weight_unit"), - "weight": variant.get("weight") - } - - for i, variant_attr in enumerate(shopify_variants_attr_list): - if variant.get(variant_attr): - attributes[i].update({"attribute_value": get_attribute_value(variant.get(variant_attr), attributes[i])}) - create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name) - -def get_attribute_value(variant_attr_val, attribute): - attribute_value = frappe.db.sql("""select attribute_value from `tabItem Attribute Value` - where parent = %s and (abbr = %s or attribute_value = %s)""", (attribute["attribute"], variant_attr_val, - variant_attr_val), as_list=1) - return attribute_value[0][0] if len(attribute_value)>0 else cint(variant_attr_val) - -def get_item_group(product_type=None): - import frappe.utils.nestedset - parent_item_group = frappe.utils.nestedset.get_root_of("Item Group") - - if product_type: - if not frappe.db.get_value("Item Group", product_type, "name"): - item_group = frappe.get_doc({ - "doctype": "Item Group", - "item_group_name": product_type, - "parent_item_group": parent_item_group, - "is_group": "No" - }).insert() - return item_group.name - else: - return product_type - else: - return parent_item_group - - -def get_sku(item): - if item.get("variants"): - return item.get("variants")[0].get("sku") - return "" - -def add_to_price_list(item, name): - shopify_settings = frappe.db.get_value("Shopify Settings", None, ["price_list", "update_price_in_erpnext_price_list"], as_dict=1) - if not shopify_settings.update_price_in_erpnext_price_list: - return - - item_price_name = frappe.db.get_value("Item Price", - {"item_code": name, "price_list": shopify_settings.price_list}, "name") - - if not item_price_name: - frappe.get_doc({ - "doctype": "Item Price", - "price_list": shopify_settings.price_list, - "item_code": name, - "price_list_rate": item.get("item_price") or item.get("variants")[0].get("price") - }).insert() - else: - item_rate = frappe.get_doc("Item Price", item_price_name) - item_rate.price_list_rate = item.get("item_price") or item.get("variants")[0].get("price") - item_rate.save() - -def get_item_image(shopify_item): - if shopify_item.get("image"): - return shopify_item.get("image").get("src") - return None - -def get_supplier(shopify_item): - if shopify_item.get("vendor"): - supplier = frappe.db.sql("""select name from tabSupplier - where name = %s or shopify_supplier_id = %s """, (shopify_item.get("vendor"), - shopify_item.get("vendor").lower()), as_list=1) - - if not supplier: - supplier = frappe.get_doc({ - "doctype": "Supplier", - "supplier_name": shopify_item.get("vendor"), - "shopify_supplier_id": shopify_item.get("vendor").lower(), - "supplier_group": get_supplier_group() - }).insert() - return supplier.name - else: - return shopify_item.get("vendor") - else: - return "" - -def get_supplier_group(): - supplier_group = frappe.db.get_value("Supplier Group", _("Shopify Supplier")) - if not supplier_group: - supplier_group = frappe.get_doc({ - "doctype": "Supplier Group", - "supplier_group_name": _("Shopify Supplier") - }).insert() - return supplier_group.name - return supplier_group - -def get_item_details(shopify_item): - item_details = {} - - item_details = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("id")}, - ["name", "stock_uom", "item_name"], as_dict=1) - - if item_details: - return item_details - - else: - item_details = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("id")}, - ["name", "stock_uom", "item_name"], as_dict=1) - return item_details - -def is_item_exists(shopify_item, attributes=None, variant_of=None): - if variant_of: - name = variant_of - else: - name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")}) - - if name: - item = frappe.get_doc("Item", name) - item.flags.ignore_mandatory=True - - if not variant_of and not item.shopify_product_id: - item.shopify_product_id = shopify_item.get("shopify_product_id") - item.shopify_variant_id = shopify_item.get("shopify_variant_id") - item.save() - return True - - if item.shopify_product_id and attributes and attributes[0].get("attribute_value"): - if not variant_of: - variant_of = frappe.db.get_value("Item", - {"shopify_product_id": item.shopify_product_id}, "variant_of") - - # create conditions for all item attributes, - # as we are putting condition basis on OR it will fetch all items matching either of conditions - # thus comparing matching conditions with len(attributes) - # which will give exact matching variant item. - - conditions = ["(iv.attribute='{0}' and iv.attribute_value = '{1}')"\ - .format(attr.get("attribute"), attr.get("attribute_value")) for attr in attributes] - - conditions = "( {0} ) and iv.parent = it.name ) = {1}".format(" or ".join(conditions), len(attributes)) - - parent = frappe.db.sql(""" select * from tabItem it where - ( select count(*) from `tabItem Variant Attribute` iv - where {conditions} and it.variant_of = %s """.format(conditions=conditions) , - variant_of, as_list=1) - - if parent: - variant = frappe.get_doc("Item", parent[0][0]) - variant.flags.ignore_mandatory = True - - variant.shopify_product_id = shopify_item.get("shopify_product_id") - variant.shopify_variant_id = shopify_item.get("shopify_variant_id") - variant.save() - return False - - if item.shopify_product_id and item.shopify_product_id != shopify_item.get("shopify_product_id"): - return False - - return True - - else: - return False diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json deleted file mode 100644 index db6c3d5aa3..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json +++ /dev/null @@ -1,527 +0,0 @@ -[ - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Print Settings", - "fieldname": "compact_item_print", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "with_letterhead", - "label": "Compact Item Print", - "modified": "2016-06-06 15:18:17.025602", - "name": "Print Settings-compact_item_print", - "no_copy": 0, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Customer", - "fieldname": "shopify_customer_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "naming_series", - "label": "Shopify Customer Id", - "modified": "2016-01-15 17:25:28.991818", - "name": "Customer-shopify_customer_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Address", - "fieldname": "shopify_address_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "fax", - "label": "Shopify Address Id", - "modified": "2016-01-15 17:50:52.213743", - "name": "Address-shopify_address_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Sales Order", - "fieldname": "shopify_order_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "title", - "label": "Shopify Order Id", - "modified": "2016-01-18 09:55:50.764524", - "name": "Sales Order-shopify_order_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "shopify_product_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "item_code", - "label": "Shopify Product Id", - "modified": "2016-01-19 15:44:16.132952", - "name": "Item-shopify_product_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Sales Invoice", - "fieldname": "shopify_order_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "naming_series", - "label": "Shopify Order Id", - "modified": "2016-01-19 16:30:12.261797", - "name": "Sales Invoice-shopify_order_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Delivery Note", - "fieldname": "shopify_order_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "title", - "label": "Shopify Order Id", - "modified": "2016-01-19 16:30:31.201198", - "name": "Delivery Note-shopify_order_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "stock_keeping_unit", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "stock_uom", - "label": "Stock Keeping Unit", - "modified": "2015-11-10 09:29:10.854943", - "name": "Item-stock_keeping_unit", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": "0", - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "sync_with_shopify", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "is_stock_item", - "label": "Sync With Shopify", - "modified": "2015-10-12 15:54:31.997714", - "name": "Item-sync_with_shopify", - "no_copy": 0, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Customer", - "fieldname": "sync_with_shopify", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "is_frozen", - "label": "Sync With Shopify", - "modified": "2015-10-01 17:31:55.758826", - "name": "Customer-sync_with_shopify", - "no_copy": 0, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "shopify_variant_id", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "item_code", - "label": "Variant Id", - "modified": "2015-11-09 18:26:50.825858", - "name": "Item-shopify_variant_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "sync_qty_with_shopify", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "item_code", - "label": "Sync Quantity With Shopify", - "modified": "2015-12-29 08:37:46.183295", - "name": "Item-sync_qty_with_shopify", - "no_copy": 0, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Delivery Note", - "fieldname": "shopify_fulfillment_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "title", - "label": "Shopify Fulfillment Id", - "modified": "2016-01-20 23:50:35.609543", - "name": "Delivery Note-shopify_fulfillment_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Supplier", - "fieldname": "shopify_supplier_id", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "supplier_name", - "label": "Shopify Supplier Id", - "modified": "2016-02-01 15:41:25.818306", - "name": "Supplier-shopify_supplier_id", - "no_copy": 1, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "shopify_description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "section_break_11", - "label": "shopify_description", - "modified": "2016-06-15 12:15:36.325581", - "name": "Item-shopify_description", - "no_copy": 0, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - } -] \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json deleted file mode 100644 index e91ce9abf8..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "customer": { - "id": 2324518599, - "email": "andrew@wyatt.co.in", - "accepts_marketing": false, - "created_at": "2016-01-20T17:18:35+05:30", - "updated_at": "2016-01-20T17:22:23+05:30", - "first_name": "Andrew", - "last_name": "Wyatt", - "orders_count": 0, - "state": "disabled", - "total_spent": "0.00", - "last_order_id": null, - "note": "", - "verified_email": true, - "multipass_identifier": null, - "tax_exempt": false, - "tags": "", - "last_order_name": null, - "default_address": { - "id": 2476804295, - "first_name": "Andrew", - "last_name": "Wyatt", - "company": "Wyatt Inc.", - "address1": "B-11, Betahouse", - "address2": "Street 11, Sector 52", - "city": "Manhattan", - "province": "New York", - "country": "United States", - "zip": "10027", - "phone": "145-112211", - "name": "Andrew Wyatt", - "province_code": "NY", - "country_code": "US", - "country_name": "United States", - "default": true - }, - "addresses": [ - { - "id": 2476804295, - "first_name": "Andrew", - "last_name": "Wyatt", - "company": "Wyatt Inc.", - "address1": "B-11, Betahouse", - "address2": "Street 11, Sector 52", - "city": "Manhattan", - "province": "New York", - "country": "United States", - "zip": "10027", - "phone": "145-112211", - "name": "Andrew Wyatt", - "province_code": "NY", - "country_code": "US", - "country_name": "United States", - "default": true - } - ] - } -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json deleted file mode 100644 index 296dede786..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "product": { - "id": 4059739520, - "title": "Shopify Test Item", - "body_html": "
Hold back Spin Medallion-Set of 2
\n
\n
Finish: Plated/ Powder Coated
\n
Material: Iron
\n
Color Finish: Satin Silver, Brown Oil Rubbed, Roman Bronze
\n
Qty: 1 Set
", - "vendor": "Boa casa", - "product_type": "Curtain Accessories", - "created_at": "2016-01-18T17:16:37+05:30", - "handle": "1001624-01", - "updated_at": "2016-01-20T17:26:44+05:30", - "published_at": "2016-01-18T17:16:37+05:30", - "template_suffix": null, - "published_scope": "global", - "tags": "Category_Curtain Accessories, Type_Holdback", - "variants": [{ - "id": 13917612359, - "product_id": 4059739520, - "title": "Test BALCK Item", - "price": "499.00", - "sku": "", - "position": 1, - "grams": 0, - "inventory_policy": "continue", - "compare_at_price": null, - "fulfillment_service": "manual", - "inventory_management": "shopify", - "option1": "BLACK", - "option2": null, - "option3": null, - "created_at": "2016-01-18T17:16:37+05:30", - "updated_at": "2016-01-20T17:26:44+05:30", - "requires_shipping": true, - "taxable": true, - "barcode": "", - "inventory_quantity": -1, - "old_inventory_quantity": -1, - "image_id": 8539321735, - "weight": 0, - "weight_unit": "kg" - }, { - "id": 13917612423, - "product_id": 4059739520, - "title": "Test BLUE Item", - "price": "499.00", - "sku": "", - "position": 2, - "grams": 0, - "inventory_policy": "continue", - "compare_at_price": null, - "fulfillment_service": "manual", - "inventory_management": "shopify", - "option1": "BLUE", - "option2": null, - "option3": null, - "created_at": "2016-01-18T17:16:37+05:30", - "updated_at": "2016-01-20T17:26:44+05:30", - "requires_shipping": true, - "taxable": true, - "barcode": "", - "inventory_quantity": -1, - "old_inventory_quantity": -1, - "image_id": null, - "weight": 0, - "weight_unit": "kg" - }, { - "id": 13917612487, - "product_id": 4059739520, - "title": "Test White Item", - "price": "499.00", - "sku": "", - "position": 3, - "grams": 0, - "inventory_policy": "continue", - "compare_at_price": null, - "fulfillment_service": "manual", - "inventory_management": "shopify", - "option1": "White", - "option2": null, - "option3": null, - "created_at": "2016-01-18T17:16:37+05:30", - "updated_at": "2016-01-18T17:16:37+05:30", - "requires_shipping": true, - "taxable": true, - "barcode": "", - "inventory_quantity": 0, - "old_inventory_quantity": 0, - "image_id": null, - "weight": 0, - "weight_unit": "kg" - }], - "options": [{ - "id": 4985027399, - "product_id": 4059739520, - "name": "Colour", - "position": 1, - "values": [ - "BLACK", - "BLUE", - "White" - ] - }], - "images": [{ - "id": 8539321735, - "product_id": 4059739520, - "position": 1, - "created_at": "2016-01-18T17:16:37+05:30", - "updated_at": "2016-01-18T17:16:37+05:30", - "src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597", - "variant_ids": [ - 13917612359 - ] - }], - "image": { - "id": 8539321735, - "product_id": 4059739520, - "position": 1, - "created_at": "2016-01-18T17:16:37+05:30", - "updated_at": "2016-01-18T17:16:37+05:30", - "src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597", - "variant_ids": [ - 13917612359 - ] - } - } -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json deleted file mode 100644 index 988a2f0423..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json +++ /dev/null @@ -1,270 +0,0 @@ -{ - "order": { - "id": 2414345735, - "email": "andrew@wyatt.co.in", - "closed_at": null, - "created_at": "2016-01-20T17:26:39+05:30", - "updated_at": "2016-01-20T17:27:15+05:30", - "number": 5, - "note": "", - "token": "660fed25987517b733644a8c9ec7c8e0", - "gateway": "manual", - "test": false, - "total_price": "1018.00", - "subtotal_price": "998.00", - "total_weight": 0, - "total_tax": "0.00", - "taxes_included": false, - "currency": "INR", - "financial_status": "paid", - "confirmed": true, - "total_discounts": "0.00", - "total_line_items_price": "998.00", - "cart_token": null, - "buyer_accepts_marketing": false, - "name": "#1005", - "referring_site": null, - "landing_site": null, - "cancelled_at": null, - "cancel_reason": null, - "total_price_usd": "15.02", - "checkout_token": null, - "reference": null, - "user_id": 55391175, - "location_id": null, - "source_identifier": null, - "source_url": null, - "processed_at": "2016-01-20T17:26:39+05:30", - "device_id": null, - "browser_ip": null, - "landing_site_ref": null, - "order_number": 1005, - "discount_codes": [], - "note_attributes": [], - "payment_gateway_names": [ - "manual" - ], - "processing_method": "manual", - "checkout_id": null, - "source_name": "shopify_draft_order", - "fulfillment_status": "fulfilled", - "tax_lines": [], - "tags": "", - "contact_email": "andrew@wyatt.co.in", - "line_items": [ - { - "id": 4125768135, - "variant_id": 13917612359, - "title": "Shopify Test Item", - "quantity": 1, - "price": "499.00", - "grams": 0, - "sku": "", - "variant_title": "Roman BALCK 1", - "vendor": "Boa casa", - "fulfillment_service": "manual", - "product_id": 4059739527, - "requires_shipping": true, - "taxable": true, - "gift_card": false, - "name": "Roman BALCK 1", - "variant_inventory_management": "shopify", - "properties": [], - "product_exists": true, - "fulfillable_quantity": 0, - "total_discount": "0.00", - "fulfillment_status": "fulfilled", - "tax_lines": [] - }, - { - "id": 4125768199, - "variant_id": 13917612423, - "title": "Shopify Test Item", - "quantity": 1, - "price": "499.00", - "grams": 0, - "sku": "", - "variant_title": "Satin BLUE 1", - "vendor": "Boa casa", - "fulfillment_service": "manual", - "product_id": 4059739527, - "requires_shipping": true, - "taxable": true, - "gift_card": false, - "name": "Satin BLUE 1", - "variant_inventory_management": "shopify", - "properties": [], - "product_exists": true, - "fulfillable_quantity": 0, - "total_discount": "0.00", - "fulfillment_status": "fulfilled", - "tax_lines": [] - } - ], - "shipping_lines": [ - { - "id": 2108906247, - "title": "International Shipping", - "price": "20.00", - "code": "International Shipping", - "source": "shopify", - "phone": null, - "tax_lines": [] - } - ], - "billing_address": { - "first_name": "Andrew", - "address1": "B-11, Betahouse", - "phone": "145-112211", - "city": "Manhattan", - "zip": "10027", - "province": "New York", - "country": "United States", - "last_name": "Wyatt", - "address2": "Street 11, Sector 52", - "company": "Wyatt Inc.", - "latitude": 40.8138912, - "longitude": -73.96243270000001, - "name": "Andrew Wyatt", - "country_code": "US", - "province_code": "NY" - }, - "shipping_address": { - "first_name": "Andrew", - "address1": "B-11, Betahouse", - "phone": "145-112211", - "city": "Manhattan", - "zip": "10027", - "province": "New York", - "country": "United States", - "last_name": "Wyatt", - "address2": "Street 11, Sector 52", - "company": "Wyatt Inc.", - "latitude": 40.8138912, - "longitude": -73.96243270000001, - "name": "Andrew Wyatt", - "country_code": "US", - "province_code": "NY" - }, - "fulfillments": [ - { - "id": 1849629255, - "order_id": 2414345735, - "status": "success", - "created_at": "2016-01-20T17:27:15+05:30", - "service": "manual", - "updated_at": "2016-01-20T17:27:15+05:30", - "tracking_company": null, - "tracking_number": null, - "tracking_numbers": [], - "tracking_url": null, - "tracking_urls": [], - "receipt": {}, - "line_items": [ - { - "id": 4125768199, - "variant_id": 13917612423, - "title": "1001624/01", - "quantity": 1, - "price": "499.00", - "grams": 0, - "sku": "", - "variant_title": "Satin Silver", - "vendor": "Boa casa", - "fulfillment_service": "manual", - "product_id": 4059739527, - "requires_shipping": true, - "taxable": true, - "gift_card": false, - "name": "1001624/01 - Satin Silver", - "variant_inventory_management": "shopify", - "properties": [], - "product_exists": true, - "fulfillable_quantity": 0, - "total_discount": "0.00", - "fulfillment_status": "fulfilled", - "tax_lines": [] - } - ] - }, - { - "id": 1849628167, - "order_id": 2414345735, - "status": "success", - "created_at": "2016-01-20T17:26:58+05:30", - "service": "manual", - "updated_at": "2016-01-20T17:26:58+05:30", - "tracking_company": null, - "tracking_number": null, - "tracking_numbers": [], - "tracking_url": null, - "tracking_urls": [], - "receipt": {}, - "line_items": [ - { - "id": 4125768135, - "variant_id": 13917612359, - "title": "1001624/01", - "quantity": 1, - "price": "499.00", - "grams": 0, - "sku": "", - "variant_title": "Roman Bronze", - "vendor": "Boa casa", - "fulfillment_service": "manual", - "product_id": 4059739527, - "requires_shipping": true, - "taxable": true, - "gift_card": false, - "name": "1001624/01 - Roman Bronze", - "variant_inventory_management": "shopify", - "properties": [], - "product_exists": true, - "fulfillable_quantity": 0, - "total_discount": "0.00", - "fulfillment_status": "fulfilled", - "tax_lines": [] - } - ] - } - ], - "refunds": [], - "customer": { - "id": 2324518599, - "email": "andrew@wyatt.co.in", - "accepts_marketing": false, - "created_at": "2016-01-20T17:18:35+05:30", - "updated_at": "2016-01-20T17:26:39+05:30", - "first_name": "Andrew", - "last_name": "Wyatt", - "orders_count": 1, - "state": "disabled", - "total_spent": "1018.00", - "last_order_id": 2414345735, - "note": "", - "verified_email": true, - "multipass_identifier": null, - "tax_exempt": false, - "tags": "", - "last_order_name": "#1005", - "default_address": { - "id": 2476804295, - "first_name": "Andrew", - "last_name": "Wyatt", - "company": "Wyatt Inc.", - "address1": "B-11, Betahouse", - "address2": "Street 11, Sector 52", - "city": "Manhattan", - "province": "New York", - "country": "United States", - "zip": "10027", - "phone": "145-112211", - "name": "Andrew Wyatt", - "province_code": "NY", - "country_code": "US", - "country_name": "United States", - "default": true - } - } - } -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js deleted file mode 100644 index b2f82d5a27..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Shopify Settings", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Shopify Settings - () => frappe.tests.make('Shopify Settings', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py deleted file mode 100644 index 6bec301b8e..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals -import frappe - -import unittest, os, json -from frappe.utils import cstr, cint -from erpnext.erpnext_integrations.connectors.shopify_connection import create_order -from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item -from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer -from frappe.core.doctype.data_import.data_import import import_doc - - -class ShopifySettings(unittest.TestCase): - @classmethod - def setUpClass(cls): - frappe.set_user("Administrator") - - cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')) - if not cls.allow_negative_stock: - frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1) - - # use the fixture data - import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json")) - - frappe.reload_doctype("Customer") - frappe.reload_doctype("Sales Order") - frappe.reload_doctype("Delivery Note") - frappe.reload_doctype("Sales Invoice") - - cls.setup_shopify() - - @classmethod - def tearDownClass(cls): - if not cls.allow_negative_stock: - frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0) - - @classmethod - def setup_shopify(cls): - shopify_settings = frappe.get_doc("Shopify Settings") - shopify_settings.taxes = [] - - shopify_settings.update({ - "app_type": "Private", - "shopify_url": "test.myshopify.com", - "api_key": "17702c7c4452b9c5d235240b6e7a39da", - "password": "17702c7c4452b9c5d235240b6e7a39da", - "shared_secret": "17702c7c4452b9c5d235240b6e7a39da", - "price_list": "_Test Price List", - "warehouse": "_Test Warehouse - _TC", - "cash_bank_account": "Cash - _TC", - "account": "Cash - _TC", - "customer_group": "_Test Customer Group", - "cost_center": "Main - _TC", - "taxes": [ - { - "shopify_tax": "International Shipping", - "tax_account":"Legal Expenses - _TC" - } - ], - "enable_shopify": 0, - "sales_order_series": "SO-", - "sync_sales_invoice": 1, - "sales_invoice_series": "SINV-", - "sync_delivery_note": 1, - "delivery_note_series": "DN-" - }).save(ignore_permissions=True) - - cls.shopify_settings = shopify_settings - - def test_order(self): - # Create Customer - with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer: - shopify_customer = json.load(shopify_customer) - create_customer(shopify_customer.get("customer"), self.shopify_settings) - - # Create Item - with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item: - shopify_item = json.load(shopify_item) - make_item("_Test Warehouse - _TC", shopify_item.get("product")) - - # Create Order - with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order: - shopify_order = json.load(shopify_order) - - create_order(shopify_order.get("order"), self.shopify_settings, False, company="_Test Company") - - sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))}) - - self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id) - - # Check for customer - shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id")) - sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id") - - self.assertEqual(shopify_order_customer_id, sales_order_customer_id) - - # Check sales invoice - sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id}) - self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total) - - # Check delivery note - delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note` - where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0] - - self.assertEqual(delivery_note_count, len(shopify_order.get("order").get("fulfillments"))) diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/__init__.py b/erpnext/erpnext_integrations/doctype/shopify_tax_account/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json b/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json deleted file mode 100644 index 63c674c5c4..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json +++ /dev/null @@ -1,133 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2015-10-05 16:55:20.455371", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shopify_tax", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Shopify Tax/Shipping Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "ERPNext Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-04-09 11:36:49.272815", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Shopify Tax Account", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py b/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py deleted file mode 100644 index 74c13c0f6c..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, 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 ShopifyTaxAccount(Document): - pass diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json deleted file mode 100644 index e47ecdcc50..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-04-10 17:06:22.697427", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "webhook_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Webhook ID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "method", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Method", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-04-11 12:43:09.456449", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Shopify Webhook Detail", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py deleted file mode 100644 index e127989ce3..0000000000 --- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, 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 ShopifyWebhookDetail(Document): - pass diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json index 4a5e54edd2..24b8e48ed6 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -40,16 +40,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Shopify Settings", - "link_to": "Shopify Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -113,4 +103,4 @@ "pin_to_bottom": 0, "pin_to_top": 0, "shortcuts": [] -} \ No newline at end of file +} diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json index d258d57131..d656b3c4fe 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json @@ -30,16 +30,6 @@ "onboard": 0, "type": "Link" }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Shopify Settings", - "link_to": "Shopify Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "dependencies": "", "hidden": 0, @@ -79,4 +69,4 @@ "pin_to_bottom": 0, "pin_to_top": 0, "shortcuts": [] -} \ No newline at end of file +} diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 59b011d1a9..8e98675dab 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -341,7 +341,6 @@ scheduler_events = { "erpnext.projects.doctype.project.project.hourly_reminder", "erpnext.projects.doctype.project.project.collect_project_status", "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", - "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", "erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance" ], "hourly_long": [ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 744196a7c6..da96d95e4e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -45,7 +45,6 @@ erpnext.patches.v11_0.make_location_from_warehouse erpnext.patches.v11_0.make_asset_finance_book_against_old_entries erpnext.patches.v11_0.check_buying_selling_in_currency_exchange erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019 -erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07 erpnext.patches.v11_0.rename_overproduction_percent_field erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom erpnext.patches.v11_0.inter_state_field_for_gst @@ -143,7 +142,6 @@ erpnext.patches.v12_0.add_variant_of_in_item_attribute_table erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account erpnext.patches.v12_0.create_default_energy_point_rules erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order -erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.set_cwip_and_delete_asset_settings erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings @@ -244,7 +242,6 @@ erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.update_reason_for_resignation_in_employee execute:frappe.delete_doc("Report", "Quoted Item Comparison") erpnext.patches.v13_0.update_member_email_address -erpnext.patches.v13_0.update_custom_fields_for_shopify erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log @@ -299,3 +296,4 @@ erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_tds_check_field #3 +erpnext.patches.v13_0.shopify_deprecation_warning \ No newline at end of file diff --git a/erpnext/patches/v11_0/refactor_erpnext_shopify.py b/erpnext/patches/v11_0/refactor_erpnext_shopify.py deleted file mode 100644 index 340e9fc8bf..0000000000 --- a/erpnext/patches/v11_0/refactor_erpnext_shopify.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.installer import remove_from_installed_apps - -def execute(): - frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings') - frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_tax_account') - frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_log') - frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_webhook_detail') - - if 'erpnext_shopify' in frappe.get_installed_apps(): - remove_from_installed_apps('erpnext_shopify') - - frappe.delete_doc("Module Def", 'erpnext_shopify') - - frappe.db.commit() - - frappe.db.sql("truncate `tabShopify Log`") - - setup_app_type() - else: - disable_shopify() - -def setup_app_type(): - try: - shopify_settings = frappe.get_doc("Shopify Settings") - shopify_settings.app_type = 'Private' - shopify_settings.update_price_in_erpnext_price_list = 0 if getattr(shopify_settings, 'push_prices_to_shopify', None) else 1 - shopify_settings.flags.ignore_mandatory = True - shopify_settings.ignore_permissions = True - shopify_settings.save() - except Exception: - frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0) - frappe.log_error(frappe.get_traceback()) - -def disable_shopify(): - # due to frappe.db.set_value wrongly written and enable_shopify being default 1 - # Shopify Settings isn't properly configured and leads to error - shopify = frappe.get_doc('Shopify Settings') - - if shopify.app_type == "Public" or shopify.app_type == None or \ - (shopify.enable_shopify and not (shopify.shopify_url or shopify.api_key)): - frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0) diff --git a/erpnext/patches/v12_0/set_default_shopify_app_type.py b/erpnext/patches/v12_0/set_default_shopify_app_type.py deleted file mode 100644 index d040ea7f71..0000000000 --- a/erpnext/patches/v12_0/set_default_shopify_app_type.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings') - frappe.db.set_value('Shopify Settings', None, 'app_type', 'Private') \ No newline at end of file diff --git a/erpnext/patches/v13_0/shopify_deprecation_warning.py b/erpnext/patches/v13_0/shopify_deprecation_warning.py new file mode 100644 index 0000000000..245d1a9625 --- /dev/null +++ b/erpnext/patches/v13_0/shopify_deprecation_warning.py @@ -0,0 +1,10 @@ +import click + + +def execute(): + + click.secho( + "Shopify Integration is moved to a separate app and will be removed from ERPNext in version-14.\n" + "Please install the app to continue using the integration: https://github.com/frappe/ecommerce_integrations", + fg="yellow", + ) diff --git a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py b/erpnext/patches/v13_0/update_custom_fields_for_shopify.py deleted file mode 100644 index f1d2ea2d74..0000000000 --- a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import setup_custom_fields - -def execute(): - if frappe.db.get_single_value('Shopify Settings', 'enable_shopify'): - setup_custom_fields() From f2ee1155c0e398ec85094c1c7e27c047ba9e4a95 Mon Sep 17 00:00:00 2001 From: Anuja Date: Mon, 2 Aug 2021 23:13:21 +0530 Subject: [PATCH 182/253] fix: sider fixes --- erpnext/patches/v13_0/add_custom_field_for_south_africa.py | 2 +- .../regional/report/vat_audit_report/vat_audit_report.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py index 73e9af9e4b..f882fdedf3 100644 --- a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py +++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from erpnext.regional.south_africa.setup import make_custom_fields +from erpnext.regional.south_africa.setup import make_custom_fields def execute(): company = frappe.get_all('Company', filters = {'country': 'South Africa'}) diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index 0c716cdab2..a9c3858bb1 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -78,9 +78,9 @@ class VATAuditReport(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {}) \ - .setdefault("net_amount", sum((i.get("taxable_value", 0) \ - or i.get("base_net_amount", 0)) for i in items \ - if (i.item_code == d.item_code and i.parent == d.parent))) + .setdefault("net_amount", sum((i.get("taxable_value", 0) + or i.get("base_net_amount", 0)) for i in items + if(i.item_code == d.item_code and i.parent == d.parent))) self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {}) \ .setdefault("is_zero_rated", d.is_zero_rated) From 471f48f64db0f8da9d4703f4b82b6f9517fbacae Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 3 Aug 2021 14:39:38 +0530 Subject: [PATCH 183/253] fix: Reset weight_per_unit on replacing Item (#26619) * fix: Assign Item's default weight_per_unit as its weight_per_unit in get_item_details * fix: Set weight_uom in get_item_details as Item's default weight_uom --- erpnext/stock/get_item_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cf52803fca..2ed7a04ba8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -312,8 +312,8 @@ def get_basic_details(args, item, overwrite_warehouse=True): "transaction_date": args.get("transaction_date"), "against_blanket_order": args.get("against_blanket_order"), "bom_no": item.get("default_bom"), - "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), - "weight_uom": args.get("weight_uom") or item.get("weight_uom") + "weight_per_unit": item.get("weight_per_unit"), + "weight_uom": item.get("weight_uom") }) if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): From 4e1a205c119012752e2eba60a7550b2ed36f4e95 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 3 Aug 2021 14:58:45 +0530 Subject: [PATCH 184/253] fix: Optimize item updation --- .../report/vat_audit_report/vat_audit_report.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index a9c3858bb1..f45ba01dea 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -77,13 +77,10 @@ class VATAuditReport(object): """ % (doctype, ", ".join(["%s"]*len(self.invoices))), tuple(self.invoices), as_dict=1) for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {}) \ - .setdefault("net_amount", sum((i.get("taxable_value", 0) - or i.get("base_net_amount", 0)) for i in items - if(i.item_code == d.item_code and i.parent == d.parent))) - - self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {}) \ - .setdefault("is_zero_rated", d.is_zero_rated) + self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, { + 'net_amount': 0.0}) + self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0) + self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated def get_items_based_on_tax_rate(self, doctype): self.items_based_on_tax_rate = frappe._dict() From 3a50490c04398c30663cab77f722153ec0b220e1 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 3 Aug 2021 15:57:11 +0530 Subject: [PATCH 185/253] fix: fetching of item tax from hsn code (#26736) * fix: fetching of item tax from hsn code --- erpnext/hooks.py | 3 ++- erpnext/regional/india/utils.py | 13 ++++++++++++- erpnext/stock/doctype/item/item.js | 14 -------------- erpnext/stock/doctype/item/item.py | 5 +++++ erpnext/stock/doctype/item/regional/india.js | 15 +++++++++++++++ 5 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 erpnext/stock/doctype/item/regional/india.js diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 8e98675dab..8f7c7db208 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -437,7 +437,8 @@ regional_overrides = { 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', - 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount' + 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount', + 'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data', diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index fbe47d0532..88c350ac89 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -859,4 +859,15 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) - return depreciation_amount \ No newline at end of file + return depreciation_amount + +def set_item_tax_from_hsn_code(item): + if not item.taxes and item.gst_hsn_code: + hsn_doc = frappe.get_doc("GST HSN Code", item.gst_hsn_code) + + for tax in hsn_doc.taxes: + item.append('taxes', { + 'item_tax_template': tax.item_tax_template, + 'tax_category': tax.tax_category, + 'valid_from': tax.valid_from + }) \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 87bd9e61fe..fd080fddd9 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -138,20 +138,6 @@ frappe.ui.form.on("Item", { frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0); }, - gst_hsn_code: function(frm) { - if((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) { - frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => { - $.each(hsn_doc.taxes || [], function(i, tax) { - let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes'); - a.item_tax_template = tax.item_tax_template; - a.tax_category = tax.tax_category; - a.valid_from = tax.valid_from; - frm.refresh_field('taxes'); - }); - }); - } - }, - is_fixed_asset: function(frm) { // set serial no to false & toggles its visibility frm.set_value('has_serial_no', 0); diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 42cc67c5cd..614c53abb5 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -123,6 +123,7 @@ class Item(WebsiteGenerator): self.cant_change() self.update_show_in_website() self.validate_item_tax_net_rate_range() + set_item_tax_from_hsn_code(self) if not self.is_new(): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -1305,3 +1306,7 @@ def update_variants(variants, template, publish_progress=True): def on_doctype_update(): # since route is a Text column, it needs a length for indexing frappe.db.add_index("Item", ["route(500)"]) + +@erpnext.allow_regional +def set_item_tax_from_hsn_code(item): + pass \ No newline at end of file diff --git a/erpnext/stock/doctype/item/regional/india.js b/erpnext/stock/doctype/item/regional/india.js new file mode 100644 index 0000000000..77ae51fa34 --- /dev/null +++ b/erpnext/stock/doctype/item/regional/india.js @@ -0,0 +1,15 @@ +frappe.ui.form.on('Item', { + gst_hsn_code: function(frm) { + if ((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) { + frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => { + $.each(hsn_doc.taxes || [], function(i, tax) { + let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes'); + a.item_tax_template = tax.item_tax_template; + a.tax_category = tax.tax_category; + a.valid_from = tax.valid_from; + frm.refresh_field('taxes'); + }); + }); + } + }, +}); \ No newline at end of file From 035ce304fe3b62aaccf060be39baf45c5cbaadf6 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Wed, 4 Aug 2021 13:57:55 +0530 Subject: [PATCH 186/253] fix: POS payment modes displayed wrong total (#26708) --- erpnext/selling/page/point_of_sale/pos_payment.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index f1a166b523..63306adc6f 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -198,6 +198,7 @@ erpnext.PointOfSale.Payment = class { const is_cash_shortcuts_invisible = !this.$payment_modes.find('.cash-shortcuts').is(':visible'); this.attach_cash_shortcuts(frm.doc); !is_cash_shortcuts_invisible && this.$payment_modes.find('.cash-shortcuts').css('display', 'grid'); + this.render_payment_mode_dom(); }); frappe.ui.form.on('POS Invoice', 'loyalty_amount', (frm) => { From e5d8ba65ca1c982ff4a8db0352e81939ae64665f Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Wed, 4 Aug 2021 16:35:38 +0530 Subject: [PATCH 187/253] fix: trigger lost reason dialog when status is changed to lost (#26811) --- erpnext/crm/doctype/opportunity/opportunity.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js index 43e1b99f3a..e9a7a95fc7 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.js +++ b/erpnext/crm/doctype/opportunity/opportunity.js @@ -53,6 +53,13 @@ frappe.ui.form.on("Opportunity", { frm.get_field("items").grid.set_multiple_add("item_code", "qty"); }, + status:function(frm){ + if (frm.doc.status == "Lost"){ + frm.trigger('set_as_lost_dialog'); + } + + }, + customer_address: function(frm, cdt, cdn) { erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false); }, @@ -91,11 +98,6 @@ frappe.ui.form.on("Opportunity", { frm.add_custom_button(__('Quotation'), cur_frm.cscript.create_quotation, __('Create')); - if(doc.status!=="Quotation") { - frm.add_custom_button(__('Lost'), () => { - frm.trigger('set_as_lost_dialog'); - }); - } } if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) { From 005291e6dd4088fca23fa36b85f1755510939823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20de=20Ryckel?= Date: Wed, 4 Aug 2021 19:29:18 +0300 Subject: [PATCH 188/253] fix: typo in error message (#26816) --- erpnext/accounts/doctype/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 1be2fbf5c8..f763df0852 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -230,7 +230,7 @@ class Account(NestedSet): if self.check_gle_exists(): throw(_("Account with existing transaction can not be converted to group.")) elif self.account_type and not self.flags.exclude_account_type_check: - throw(_("Cannot covert to Group because Account Type is selected.")) + throw(_("Cannot convert to Group because Account Type is selected.")) else: self.is_group = 1 self.save() From 2732490cbefd0e8af56ae4e473e6a2fb9c72b1a3 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 5 Aug 2021 00:30:01 +0530 Subject: [PATCH 189/253] fix: review changes --- erpnext/crm/doctype/lead/lead.js | 47 +++++--------------- erpnext/crm/doctype/lead/lead.json | 70 +++++++++++++----------------- erpnext/crm/doctype/lead/lead.py | 18 +++++--- 3 files changed, 54 insertions(+), 81 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index ad24a6e222..10e3f7da6b 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -12,7 +12,8 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller 'Opportunity': this.make_opportunity }; - this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead); + // For avoiding integration issues. + this.frm.set_df_property('first_name', 'reqd', true); } onload () { @@ -84,42 +85,16 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } render_basic_info_html() { - let html=''; - if (cur_frm.doc.lead_owner) { - html += `
- Lead Owner -
-
- ${cur_frm.doc.lead_owner} -
` ; + if (cur_frm.doc.contact_date) { + let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date) + let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today()); + let color = diff_days > 0 ? "orange" : "green"; + let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date"); + let html = `
+ ${message} : ${contact_date} +
` ; + cur_frm.dashboard.set_headline_alert(html); } - - if (cur_frm.doc.email_id) { - html += `
- Email -
-
- ${cur_frm.doc.email_id} -
` ; - } - - if (cur_frm.doc.mobile_no) { - html += `
- Mobile -
-
- ${cur_frm.doc.mobile_no} -
` ; - } - - html += `
- Status -
-
- ${cur_frm.doc.status} -
` ; - html = `
${html}
`; - cur_frm.dashboard.set_headline_alert(html); } }; diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index f9a500fa97..542977e689 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -16,37 +16,36 @@ "middle_name", "last_name", "lead_name", - "email_id", - "mobile_no", - "phone", "col_break123", "status", "company_name", "designation", "gender", + "contact_details_section", + "email_id", + "mobile_no", + "whatsapp_no", + "column_break_16", + "phone", + "phone_ext", "additional_information_section", "no_of_employees", "industry", "market_segment", - "type", - "request_type", "column_break_22", - "whatsapp_no", "fax", "website", + "type", + "request_type", "address_section", "address_html", - "address_type", - "address_title", - "address_line1", - "address_line2", "city", + "pincode", "county", "column_break2", "contact_html", "state", "country", - "pincode", "section_break_12", "lead_owner", "ends_on", @@ -91,9 +90,9 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Full Name", - "mandatory_depends_on": "eval: !(doc.company_name)", "oldfieldname": "lead_name", "oldfieldtype": "Data", + "read_only": 1, "search_index": 1 }, { @@ -102,7 +101,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Organization Name", - "mandatory_depends_on": "eval: !(doc.lead_name)", + "mandatory_depends_on": "eval: !(doc.first_name)", "oldfieldname": "company_name", "oldfieldtype": "Data" }, @@ -241,23 +240,6 @@ "label": "Address HTML", "read_only": 1 }, - { - "description": "Home, Work, etc.", - "fieldname": "address_title", - "fieldtype": "Data", - "label": "Address Title" - }, - { - "fieldname": "address_line1", - "fieldtype": "Data", - "label": "Address Line 1", - "mandatory_depends_on": "eval: doc.address_title && doc.address_type" - }, - { - "fieldname": "address_line2", - "fieldtype": "Data", - "label": "Address Line 2" - }, { "fieldname": "city", "fieldtype": "Data", @@ -406,13 +388,6 @@ "label": "Designation", "options": "Designation" }, - { - "default": "Billing", - "fieldname": "address_type", - "fieldtype": "Select", - "label": "Address Type", - "options": "Billing\nShipping\nOffice\nPersonal\nPlant\nPostal\nShop\nSubsidiary\nWarehouse\nCurrent\nPermanent\nOther" - }, { "fieldname": "language", "fieldtype": "Link", @@ -422,7 +397,8 @@ { "fieldname": "first_name", "fieldtype": "Data", - "label": "First Name" + "label": "First Name", + "mandatory_depends_on": "eval: !(doc.company_name)" }, { "fieldname": "middle_name", @@ -457,7 +433,7 @@ }, { "collapsible": 1, - "depends_on": "eval:!doc.__islocal", + "depends_on": "eval: !doc.__islocal", "fieldname": "address_section", "fieldtype": "Section Break", "label": "Address" @@ -475,13 +451,27 @@ "fieldname": "other_information_section", "fieldtype": "Section Break", "label": "Other Information" + }, + { + "fieldname": "contact_details_section", + "fieldtype": "Section Break", + "label": "Contact Details" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "phone_ext", + "fieldtype": "Data", + "label": "Phone Ext." } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", "links": [], - "modified": "2021-07-28 00:20:37.768449", + "modified": "2021-08-04 00:24:57.208590", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index f09a814540..33fda89f28 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -21,18 +21,24 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) + def set_full_name(self): + self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) + def validate(self): + self.set_full_name() self.set_lead_name() self.set_title() + self.set_status() + self.check_email_id_is_unique() + self.validate_email_id() + self.validate_contact_date() self._prev = frappe._dict({ "contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None, "ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None, "contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None, }) - - self.set_status() - self.check_email_id_is_unique() - + + def validate_email_id(self): if self.email_id: if not self.flags.ignore_email_validation: validate_email_address(self.email_id, throw=True) @@ -46,6 +52,7 @@ class Lead(SellingController): if self.is_new() or not self.image: self.image = has_gravatar(self.email_id) + def validate_contact_date(self): if self.contact_date and getdate(self.contact_date) < getdate(nowdate()): frappe.throw(_("Next Contact Date cannot be in the past")) @@ -88,7 +95,7 @@ class Lead(SellingController): linked_doc = frappe.get_doc(link['parenttype'], link['parent']) if len(linked_doc.get('links')) == 1: - linked_doc.delete() + linked_doc.delete(ignore_permissions=True) else: to_remove = None for d in linked_doc.get('links'): @@ -96,6 +103,7 @@ class Lead(SellingController): to_remove = d if to_remove: linked_doc.remove(to_remove) + linked_doc.save(ignore_permissions=True) def has_customer(self): return frappe.db.get_value("Customer", {"lead_name": self.name}) From 884d8cf0653794af68b377e2f02ac60968a7ead1 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Thu, 5 Aug 2021 11:14:46 +0530 Subject: [PATCH 190/253] fix: Let all System Managers be able to delete Company transactions (#26815) --- .../transaction_deletion_record.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 9313f95516..23e59472a6 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -54,7 +54,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-05-08 23:13:48.049879", + "modified": "2021-08-04 20:15:59.071493", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", @@ -70,6 +70,7 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, "write": 1 } ], From c35a526dd826e91e764194ad45a1b6ac52b98b2a Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 5 Aug 2021 14:42:15 +0530 Subject: [PATCH 191/253] fix: adding test cases --- erpnext/crm/doctype/lead/lead.js | 6 ++-- erpnext/crm/doctype/lead/lead.py | 6 ++-- erpnext/crm/doctype/lead/test_lead.py | 49 +++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 10e3f7da6b..3363d8c023 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -43,7 +43,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller if (!this.frm.is_new()) { frappe.contacts.render_address_and_contact(this.frm); - cur_frm.trigger('render_basic_info_html'); + cur_frm.trigger('render_contact_day_html'); } else { frappe.contacts.clear_address_and_contact(this.frm); } @@ -84,14 +84,14 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } } - render_basic_info_html() { + render_contact_day_html() { if (cur_frm.doc.contact_date) { let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date) let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today()); let color = diff_days > 0 ? "orange" : "green"; let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date"); let html = `
- ${message} : ${contact_date} + ${message} : ${frappe.datetime.global_date_format(contact_date)}
` ; cur_frm.dashboard.set_headline_alert(html); } diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 33fda89f28..49b682c12f 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -21,9 +21,6 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) - def set_full_name(self): - self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) - def validate(self): self.set_full_name() self.set_lead_name() @@ -38,6 +35,9 @@ class Lead(SellingController): "contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None, }) + def set_full_name(self): + self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) + def validate_email_id(self): if self.email_id: if not self.flags.ignore_email_validation: diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index d428a453f9..174b1c98bd 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils import random_string import unittest test_records = frappe.get_test_records('Lead') @@ -32,3 +33,51 @@ class TestLead(unittest.TestCase): customer.company = "_Test Company" customer.customer_group = "_Test Customer Group" customer.insert() + + def test_create_lead_and_unlinking_dynamic_links(self): + lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum") + lead_doc_1 = make_lead() + address = frappe.get_doc({ + "doctype": "Address", + "address_type": "Billing", + "city": "Mumbai", + "address_line1": "Vidya Vihar West", + "country": "India", + "links": [{ + "link_doctype": "Lead", + "link_name": lead_doc.name + }] + }).insert() + + address_1 = frappe.get_doc({ + "doctype": "Address", + "address_type": "Billing", + "address_line1": "Baner", + "city": "Pune", + "country": "India", + "links": [{ + "link_doctype": "Lead", + "link_name": lead_doc.name + }, + { + "link_doctype": "Lead", + "link_name": lead_doc_1.name + }] + }).insert() + + lead_doc.delete() + address_1.reload() + self.assertEqual(frappe.db.exists("Lead",lead_doc.name), None) + self.assertEqual(len(address_1.get('links')), 1) + +def make_lead(**args): + args = frappe._dict(args) + + lead_doc = frappe.get_doc({ + "doctype": "Lead", + "first_name": args.first_name or "Test", + "last_name": args.last_name or "Lead", + "email_id": args.email_id or "new_lead{}@example.com".format(random_string(5)), + }).insert() + + return lead_doc \ No newline at end of file From 1d90f7684e0fce8227fb566fa4110b96003d9ee5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 5 Aug 2021 19:17:32 +0530 Subject: [PATCH 192/253] fix: Do not fetch fully return issued purchase receipts (#26809) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 7562418fd2..bda9f419a9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. }, get_query_filters: { docstatus: 1, - status: ["not in", ["Closed", "Completed"]], + status: ["not in", ["Closed", "Completed", "Return Issued"]], company: me.frm.doc.company, is_return: 0 } From e7fa2e582649edc141800645bc364bf3d74edf13 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 6 Aug 2021 11:03:57 +0530 Subject: [PATCH 193/253] fix(e-invoicing): cannot generate IRNs for standalone credit notes (#26824) --- erpnext/regional/india/e_invoice/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index f8a230d0b8..f4976138ac 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -316,10 +316,6 @@ def get_payment_details(invoice): )) def get_return_doc_reference(invoice): - if not invoice.return_against: - frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.') - .format(frappe.bold('Return Against')), title=_('Missing Field')) - invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') return frappe._dict(dict( invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') @@ -438,7 +434,7 @@ def make_einvoice(invoice): if invoice.is_pos and invoice.base_paid_amount: payment_details = get_payment_details(invoice) - if invoice.is_return: + if invoice.is_return and invoice.return_against: prev_doc_details = get_return_doc_reference(invoice) if invoice.transporter and not invoice.is_return: From 4723e18f9e3dec0846fd13240dd7594095b7cbc4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 5 Aug 2021 19:44:00 +0530 Subject: [PATCH 194/253] fix: sider issue --- erpnext/crm/doctype/lead/lead.js | 2 +- erpnext/crm/doctype/lead/test_lead.py | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 3363d8c023..97e6315eff 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -86,7 +86,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller render_contact_day_html() { if (cur_frm.doc.contact_date) { - let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date) + let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date); let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today()); let color = diff_days > 0 ? "orange" : "green"; let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date"); diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index 174b1c98bd..18e0692c5e 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -37,7 +37,7 @@ class TestLead(unittest.TestCase): def test_create_lead_and_unlinking_dynamic_links(self): lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum") lead_doc_1 = make_lead() - address = frappe.get_doc({ + frappe.get_doc({ "doctype": "Address", "address_type": "Billing", "city": "Mumbai", @@ -55,14 +55,16 @@ class TestLead(unittest.TestCase): "address_line1": "Baner", "city": "Pune", "country": "India", - "links": [{ - "link_doctype": "Lead", - "link_name": lead_doc.name - }, - { - "link_doctype": "Lead", - "link_name": lead_doc_1.name - }] + "links": [ + { + "link_doctype": "Lead", + "link_name": lead_doc.name + }, + { + "link_doctype": "Lead", + "link_name": lead_doc_1.name + } + ] }).insert() lead_doc.delete() From 01a0585ba0bbfe68b650a1090edb20f6071bfaf0 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Aug 2021 11:47:07 +0530 Subject: [PATCH 195/253] fix: removing organization_lead traceback --- erpnext/crm/doctype/lead/lead.js | 2 +- erpnext/crm/doctype/lead/test_lead.py | 4 ++-- erpnext/crm/doctype/lead/test_records.json | 1 - erpnext/crm/doctype/lead/tests/test_lead_organization.js | 1 - erpnext/selling/doctype/customer/customer.py | 4 ++-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 97e6315eff..75af937990 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -71,7 +71,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } company_name () { - if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) { + if (!this.frm.doc.lead_name) { this.frm.set_value("lead_name", this.frm.doc.company_name); } } diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index 18e0692c5e..cadc1a28a0 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -35,7 +35,7 @@ class TestLead(unittest.TestCase): customer.insert() def test_create_lead_and_unlinking_dynamic_links(self): - lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum") + lead_doc = make_lead(first_name = "Lorem", last_name="Ipsum", email_id="lorem_ipsum@example.com") lead_doc_1 = make_lead() frappe.get_doc({ "doctype": "Address", @@ -79,7 +79,7 @@ def make_lead(**args): "doctype": "Lead", "first_name": args.first_name or "Test", "last_name": args.last_name or "Lead", - "email_id": args.email_id or "new_lead{}@example.com".format(random_string(5)), + "email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)), }).insert() return lead_doc \ No newline at end of file diff --git a/erpnext/crm/doctype/lead/test_records.json b/erpnext/crm/doctype/lead/test_records.json index 39864e2e3e..3158add0f2 100644 --- a/erpnext/crm/doctype/lead/test_records.json +++ b/erpnext/crm/doctype/lead/test_records.json @@ -27,7 +27,6 @@ { "doctype": "Lead", "email_id": "test_lead4@example.com", - "organization_lead": 1, "lead_name": "_Test Lead 4", "company_name": "_Test Lead 4", "status": "Open" diff --git a/erpnext/crm/doctype/lead/tests/test_lead_organization.js b/erpnext/crm/doctype/lead/tests/test_lead_organization.js index 43959356b1..7fb957370b 100644 --- a/erpnext/crm/doctype/lead/tests/test_lead_organization.js +++ b/erpnext/crm/doctype/lead/tests/test_lead_organization.js @@ -9,7 +9,6 @@ QUnit.test("test: lead", function (assert) { () => frappe.set_route("List", "Lead"), () => frappe.new_doc("Lead"), () => frappe.timeout(1), - () => cur_frm.set_value("organization_lead", "1"), () => cur_frm.set_value("company_name", lead_name), () => cur_frm.save(), () => frappe.timeout(1), diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 3b62081e24..66edcd0188 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -176,12 +176,12 @@ class Customer(TransactionBase): address.append('links', dict(link_doctype='Customer', link_name=self.name)) address.save(ignore_permissions=self.flags.ignore_permissions) - lead = frappe.db.get_value("Lead", self.lead_name, ["organization_lead", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) + lead = frappe.db.get_value("Lead", self.lead_name, ["company_name", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True) if not lead.lead_name: frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name)) - if lead.organization_lead: + if lead.company_name: contact_names = frappe.get_all('Dynamic Link', filters={ "parenttype":"Contact", "link_doctype":"Lead", From 59c971015abf1e90452da2e8752d47b34ed76ee4 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Aug 2021 14:02:57 +0530 Subject: [PATCH 196/253] fix: test case --- erpnext/crm/doctype/lead/test_lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py index cadc1a28a0..d4886d3506 100644 --- a/erpnext/crm/doctype/lead/test_lead.py +++ b/erpnext/crm/doctype/lead/test_lead.py @@ -77,7 +77,7 @@ def make_lead(**args): lead_doc = frappe.get_doc({ "doctype": "Lead", - "first_name": args.first_name or "Test", + "first_name": args.first_name or "_Test", "last_name": args.last_name or "Lead", "email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)), }).insert() From b4e720f8ecd8ad5966fb8c7f399c1347f11a84d2 Mon Sep 17 00:00:00 2001 From: Anupam Date: Fri, 6 Aug 2021 14:39:13 +0530 Subject: [PATCH 197/253] fix: test case --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index 50c98c59de..bcab7bd708 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -9,7 +9,7 @@ import datetime def create_test_lead(): - test_lead = frappe.db.exists({'doctype': 'Lead', 'lead_name': 'Test Lead'}) + test_lead = frappe.db.exists({'doctype': 'Lead', 'email':'test@example.com'}) if test_lead: return frappe.get_doc('Lead', test_lead[0][0]) test_lead = frappe.get_doc({ From f1141e775642e501f8547d01050001d2e020aac9 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 6 Aug 2021 17:37:17 +0530 Subject: [PATCH 198/253] fix: failing budget test due to project naming (#26834) --- erpnext/accounts/doctype/budget/test_budget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 603e21ea24..6c25f0024d 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -249,7 +249,7 @@ class TestBudget(unittest.TestCase): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): if budget_against_field == "project": - budget_against = "_Test Project" + budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"}) else: budget_against = budget_against_CC or "_Test Cost Center - _TC" @@ -275,7 +275,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True) elif budget_against_field == "project": make_journal_entry("_Test Account Cost for Goods Sold - _TC", - "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate()) + "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate()) def make_budget(**args): args = frappe._dict(args) From 614336fe1d2f2d136809e29d394800994e7ae4c9 Mon Sep 17 00:00:00 2001 From: Ankush Date: Fri, 6 Aug 2021 19:36:21 +0530 Subject: [PATCH 199/253] test: use item that allows fractional UOM in test (#26837) --- .../stock/doctype/purchase_receipt/test_purchase_receipt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 82461cb843..bb4a710a04 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -23,9 +23,7 @@ class TestPurchaseReceipt(unittest.TestCase): def test_reverse_purchase_receipt_sle(self): - frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 0) - - pr = make_purchase_receipt(qty=0.5) + pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200") sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", "voucher_no": pr.name}, ['actual_qty']) @@ -41,8 +39,6 @@ class TestPurchaseReceipt(unittest.TestCase): self.assertEqual(len(sl_entry_cancelled), 2) self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5) - frappe.db.set_value('UOM', '_Test UOM', 'must_be_whole_number', 1) - def test_make_purchase_invoice(self): if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'): frappe.get_doc({ From 27a29eb6bc67fc8dce482c0b38239929cf3fc6fb Mon Sep 17 00:00:00 2001 From: Ankush Date: Fri, 6 Aug 2021 21:34:44 +0530 Subject: [PATCH 200/253] test: fix pricelist tests (#26839) problem: exchange rate API is returning exchange rates for "_Test currency". These tests were relying on failure of that function. --- erpnext/stock/doctype/batch/test_batch.py | 9 ++++++--- erpnext/stock/doctype/item/test_item.py | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index cbd272df4b..a85a0222b5 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -269,11 +269,14 @@ class TestBatch(unittest.TestCase): batch2 = create_batch('_Test Batch Price Item', 300, 1) batch3 = create_batch('_Test Batch Price Item', 400, 0) + company = "_Test Company with perpetual inventory" + currency = frappe.get_cached_value("Company", company, "default_currency") + args = frappe._dict({ "item_code": "_Test Batch Price Item", - "company": "_Test Company with perpetual inventory", + "company": company, "price_list": "_Test Price List", - "currency": "_Test Currency", + "currency": currency, "doctype": "Sales Invoice", "conversion_rate": 1, "price_list_currency": "_Test Currency", @@ -333,4 +336,4 @@ def make_new_batch(**args): except frappe.DuplicateEntryError: batch = frappe.get_doc("Batch", args.batch_id) - return batch \ No newline at end of file + return batch diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 922049f144..7a9985d7f0 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -83,14 +83,17 @@ class TestItem(unittest.TestCase): make_test_objects("Item Price") + company = "_Test Company" + currency = frappe.get_cached_value("Company", company, "default_currency") + details = get_item_details({ "item_code": "_Test Item", - "company": "_Test Company", + "company": company, "price_list": "_Test Price List", - "currency": "_Test Currency", + "currency": currency, "doctype": "Sales Order", "conversion_rate": 1, - "price_list_currency": "_Test Currency", + "price_list_currency": currency, "plc_conversion_rate": 1, "order_type": "Sales", "customer": "_Test Customer", From 07e65ab5895f166b6dceb38dfbea1cb90f6015b9 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Mon, 9 Aug 2021 07:02:31 +0200 Subject: [PATCH 201/253] feat: add french address template (#26316) * add french address template Co-authored-by: Ankush --- erpnext/regional/address_template/templates/france.html | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 erpnext/regional/address_template/templates/france.html diff --git a/erpnext/regional/address_template/templates/france.html b/erpnext/regional/address_template/templates/france.html new file mode 100644 index 0000000000..752331eeec --- /dev/null +++ b/erpnext/regional/address_template/templates/france.html @@ -0,0 +1,5 @@ +{% if address_line1 %}{{ address_line1 }}{% endif -%} +{% if address_line2 %}
{{ address_line2 }}{% endif -%} +{% if pincode %}
{{ pincode }}{% endif -%} +{% if city %} {{ city }}{% endif -%} +{% if country %}
{{ country }}{% endif -%} From 16d4de5130097bc2dfdc7e073f1e13f0a22481d1 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 10:41:24 +0530 Subject: [PATCH 202/253] fix: price list with 0 value are ignored (#26655) * fix: price list with 0 value are ignored Steps to reproduce: 1. Create 2 item price for two different supplier. One of them should be zero. 2. Create PO 3. Add supplier with non-zero price and add item. 4. change supplier. Price won't change. If price was non-zero it would've changed. Root cause: falsiness check instead of null value check is used for checking if price list value exists. 0 is evaluated as false. * refactor: make get_price_list_rate function pure --- erpnext/manufacturing/doctype/bom/bom.py | 5 ++--- erpnext/stock/get_item_details.py | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 4e93fc6799..0ba85078ea 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -717,9 +717,8 @@ def get_bom_item_rate(args, bom_doc): "ignore_conversion_rate": True }) item_doc = frappe.get_cached_doc("Item", args.get("item_code")) - out = frappe._dict() - get_price_list_rate(bom_args, item_doc, out) - rate = out.price_list_rate + price_list_data = get_price_list_rate(bom_args, item_doc) + rate = price_list_data.price_list_rate return rate diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 2ed7a04ba8..be8508a000 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -74,8 +74,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru update_party_blanket_order(args, out) - - get_price_list_rate(args, item, out) + out.update(get_price_list_rate(args, item)) if args.customer and cint(args.is_pos): out.update(get_pos_profile_item_details(args.company, args, update_data=True)) @@ -638,7 +637,10 @@ def get_default_supplier(args, item, item_group, brand): or item_group.get("default_supplier") or brand.get("default_supplier")) -def get_price_list_rate(args, item_doc, out): +def get_price_list_rate(args, item_doc, out=None): + if out is None: + out = frappe._dict() + meta = frappe.get_meta(args.parenttype or args.doctype) if meta.get_field("currency") or args.get('currency'): @@ -651,17 +653,17 @@ def get_price_list_rate(args, item_doc, out): if meta.get_field("currency"): validate_conversion_rate(args, meta) - price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0 + price_list_rate = get_price_list_rate_for(args, item_doc.name) # variant - if not price_list_rate and item_doc.variant_of: + if price_list_rate is None and item_doc.variant_of: price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) # insert in database - if not price_list_rate: + if price_list_rate is None: if args.price_list and args.rate: insert_item_price(args) - return {} + return out out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \ / flt(args.conversion_rate) @@ -671,6 +673,8 @@ def get_price_list_rate(args, item_doc, out): out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate)) + return out + def insert_item_price(args): """Insert Item Price if Price List and Price List Rate are specified and currency is the same""" if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \ @@ -1073,9 +1077,8 @@ def apply_price_list(args, as_doc=False): } def apply_price_list_on_item(args): - item_details = frappe._dict() item_doc = frappe.get_doc("Item", args.item_code) - get_price_list_rate(args, item_doc, item_details) + item_details = get_price_list_rate(args, item_doc) item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate)) From 7e0c57fa3fe62417ad3be75412e0c031d6486bb8 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 10:46:29 +0530 Subject: [PATCH 203/253] fix: allow alternative items when using job card (#26724) --- erpnext/manufacturing/doctype/job_card/job_card.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 69c7f5c614..66e2394b84 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -608,6 +608,11 @@ def make_stock_entry(source_name, target_doc=None): target.set_missing_values() target.set_stock_entry_type() + wo_allows_alternate_item = frappe.db.get_value("Work Order", target.work_order, "allow_alternative_item") + for item in target.items: + item.allow_alternative_item = int(wo_allows_alternate_item and + frappe.get_cached_value("Item", item.item_code, "allow_alternative_item")) + doclist = get_mapped_doc("Job Card", source_name, { "Job Card": { "doctype": "Stock Entry", @@ -698,4 +703,4 @@ def make_corrective_job_card(source_name, operation=None, for_operation=None, ta } }, target_doc, set_missing_values) - return doclist \ No newline at end of file + return doclist From ab8f0cab4d0a934c200665a6916ecb7de032519b Mon Sep 17 00:00:00 2001 From: Marica Date: Mon, 9 Aug 2021 12:24:04 +0530 Subject: [PATCH 204/253] fix: Faulty Gl Entry for Asset LCVs (#26803) * fix: Faulty Gl Entry for Asset LCVs - Both Gl entries were crediting in their respective accounts - Asset Account must be debited into * fix: Use keyword arguments instead of positional for better readability * chore: Test for LCV for draft asset created via Purchase Receipt --- .../test_landed_cost_voucher.py | 35 +++- .../purchase_receipt/purchase_receipt.py | 179 ++++++++++++++---- 2 files changed, 181 insertions(+), 33 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 32b08f60c4..128a2ab62f 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -11,6 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import create_account +from erpnext.assets.doctype.asset.test_asset import create_asset_category, create_fixed_asset_item class TestLandedCostVoucher(unittest.TestCase): def test_landed_cost_voucher(self): @@ -250,6 +251,38 @@ class TestLandedCostVoucher(unittest.TestCase): self.assertEqual(entry.credit, amounts[0]) self.assertEqual(entry.credit_in_account_currency, amounts[1]) + def test_asset_lcv(self): + "Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly." + if not frappe.db.exists("Asset Category", "Computers"): + create_asset_category() + + if not frappe.db.exists("Item", "Macbook Pro"): + create_fixed_asset_item() + + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=50000) + + # check if draft asset was created + assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) + self.assertEqual(len(assets), 1) + + frappe.db.set_value("Company", pr.company, "capital_work_in_progress_account", "CWIP Account - _TC") + lcv = make_landed_cost_voucher( + company = pr.company, + receipt_document_type = "Purchase Receipt", + receipt_document=pr.name, + charges=80, + expense_account="Expenses Included In Valuation - _TC") + + lcv.save() + lcv.submit() + + # lcv updates amount in draft asset + self.assertEqual(frappe.db.get_value("Asset", assets[0].name, "gross_purchase_amount"), 50080) + + # tear down + lcv.cancel() + pr.cancel() + def make_landed_cost_voucher(** args): args = frappe._dict(args) ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document) @@ -268,7 +301,7 @@ def make_landed_cost_voucher(** args): lcv.set("taxes", [{ "description": "Shipping Charges", - "expense_account": "Expenses Included In Valuation - TCP1", + "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1", "amount": args.charges }]) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 899d7e8e66..bcf605288f 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -290,8 +290,16 @@ class PurchaseReceipt(BuyingController): and warehouse_account_name == supplier_warehouse_account: continue - self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks, - stock_rbnb, account_currency=warehouse_account_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=warehouse_account_name, + cost_center=d.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_rbnb, + account_currency=warehouse_account_currency, + item=d) # GL Entry for from warehouse or Stock Received but not billed # Intentionally passed negative debit amount to avoid incorrect GL Entry validation @@ -304,9 +312,17 @@ class PurchaseReceipt(BuyingController): account = warehouse_account[d.from_warehouse]['account'] \ if d.from_warehouse else stock_rbnb - self.add_gl_entry(gl_entries, account, d.cost_center, - -1 * flt(d.base_net_amount, d.precision("base_net_amount")), 0.0, remarks, warehouse_account_name, - debit_in_account_currency=-1 * credit_amount, account_currency=credit_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")), + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + debit_in_account_currency=-1 * credit_amount, + account_currency=credit_currency, + item=d) # check if the exchange rate has changed if d.get('purchase_invoice'): @@ -317,13 +333,29 @@ class PurchaseReceipt(BuyingController): discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * \ (exchange_rate_map[d.purchase_invoice] - self.conversion_rate) - self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference, - remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=0.0, + credit=discrepancy_caused_by_exchange_rate_difference, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, + item=d) - self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0, - remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, - account_currency=credit_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=self.get_company_default("exchange_gain_loss_account"), + cost_center=d.cost_center, + debit=discrepancy_caused_by_exchange_rate_difference, + credit=0.0, + remarks=remarks, + against_account=self.supplier, + debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference, + account_currency=credit_currency, + item=d) # Amount added through landed-cos-voucher if d.landed_cost_voucher_amount and landed_cost_entries: @@ -332,14 +364,31 @@ class PurchaseReceipt(BuyingController): credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or account_currency!=self.company_currency) else flt(amount["amount"])) - self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, credit_amount, remarks, - warehouse_account_name, credit_in_account_currency=flt(amount["amount"]), - account_currency=account_currency, project=d.project, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=d.cost_center, + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=warehouse_account_name, + credit_in_account_currency=flt(amount["amount"]), + account_currency=account_currency, + project=d.project, + item=d) # sub-contracting warehouse if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): - self.add_gl_entry(gl_entries, supplier_warehouse_account, d.cost_center, 0.0, flt(d.rm_supp_cost), - remarks, warehouse_account_name, account_currency=supplier_warehouse_account_currency, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=supplier_warehouse_account, + cost_center=d.cost_center, + debit=0.0, + credit=flt(d.rm_supp_cost), + remarks=remarks, + against_account=warehouse_account_name, + account_currency=supplier_warehouse_account_currency, + item=d) # divisional loss adjustment valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \ @@ -356,8 +405,17 @@ class PurchaseReceipt(BuyingController): cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") - self.add_gl_entry(gl_entries, loss_account, cost_center, divisional_loss, 0.0, remarks, - warehouse_account_name, account_currency=credit_currency, project=d.project, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + account_currency=credit_currency, + project=d.project, + item=d) elif d.warehouse not in warehouse_with_no_account or \ d.rejected_warehouse not in warehouse_with_no_account: @@ -368,12 +426,30 @@ class PurchaseReceipt(BuyingController): debit_currency = get_account_currency(d.expense_account) remarks = self.get("remarks") or _("Accounting Entry for Service") - self.add_gl_entry(gl_entries, service_received_but_not_billed_account, d.cost_center, 0.0, d.amount, - remarks, d.expense_account, account_currency=credit_currency, project=d.project, + self.add_gl_entry( + gl_entries=gl_entries, + account=service_received_but_not_billed_account, + cost_center=d.cost_center, + debit=0.0, + credit=d.amount, + remarks=remarks, + against_account=d.expense_account, + account_currency=credit_currency, + project=d.project, voucher_detail_no=d.name, item=d) - self.add_gl_entry(gl_entries, d.expense_account, d.cost_center, d.amount, 0.0, remarks, service_received_but_not_billed_account, - account_currency = debit_currency, project=d.project, voucher_detail_no=d.name, item=d) + self.add_gl_entry( + gl_entries=gl_entries, + account=d.expense_account, + cost_center=d.cost_center, + debit=d.amount, + credit=0.0, + remarks=remarks, + against_account=service_received_but_not_billed_account, + account_currency = debit_currency, + project=d.project, + voucher_detail_no=d.name, + item=d) if warehouse_with_no_account: frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" + @@ -423,8 +499,15 @@ class PurchaseReceipt(BuyingController): applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount) amount_including_divisional_loss -= applicable_amount - self.add_gl_entry(gl_entries, account, tax.cost_center, 0.0, applicable_amount, self.remarks or _("Accounting Entry for Stock"), - against_account, item=tax) + self.add_gl_entry( + gl_entries=gl_entries, + account=account, + cost_center=tax.cost_center, + debit=0.0, + credit=applicable_amount, + remarks=self.remarks or _("Accounting Entry for Stock"), + against_account=against_account, + item=tax) i += 1 @@ -477,15 +560,31 @@ class PurchaseReceipt(BuyingController): # debit cwip account debit_in_account_currency = (base_asset_amount if cwip_account_currency == self.company_currency else asset_amount) - self.add_gl_entry(gl_entries, cwip_account, item.cost_center, base_asset_amount, 0.0, remarks, - arbnb_account, debit_in_account_currency=debit_in_account_currency, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=cwip_account, + cost_center=item.cost_center, + debit=base_asset_amount, + credit=0.0, + remarks=remarks, + against_account=arbnb_account, + debit_in_account_currency=debit_in_account_currency, + item=item) asset_rbnb_currency = get_account_currency(arbnb_account) # credit arbnb account credit_in_account_currency = (base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount) - self.add_gl_entry(gl_entries, arbnb_account, item.cost_center, 0.0, base_asset_amount, remarks, - cwip_account, credit_in_account_currency=credit_in_account_currency, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=arbnb_account, + cost_center=item.cost_center, + debit=0.0, + credit=base_asset_amount, + remarks=remarks, + against_account=cwip_account, + credit_in_account_currency=credit_in_account_currency, + item=item) def add_lcv_gl_entries(self, item, gl_entries): expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation") @@ -498,11 +597,27 @@ class PurchaseReceipt(BuyingController): remarks = self.get("remarks") or _("Accounting Entry for Stock") - self.add_gl_entry(gl_entries, expenses_included_in_asset_valuation, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), - remarks, asset_account, project=item.project, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=expenses_included_in_asset_valuation, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.landed_cost_voucher_amount), + remarks=remarks, + against_account=asset_account, + project=item.project, + item=item) - self.add_gl_entry(gl_entries, asset_account, item.cost_center, 0.0, flt(item.landed_cost_voucher_amount), - remarks, expenses_included_in_asset_valuation, project=item.project, item=item) + self.add_gl_entry( + gl_entries=gl_entries, + account=asset_account, + cost_center=item.cost_center, + debit=flt(item.landed_cost_voucher_amount), + credit=0.0, + remarks=remarks, + against_account=expenses_included_in_asset_valuation, + project=item.project, + item=item) def update_assets(self, item, valuation_rate): assets = frappe.db.get_all('Asset', From ef8539fd606b1fc00b0d5b3ea124b11df765e32e Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 9 Aug 2021 12:38:14 +0530 Subject: [PATCH 205/253] refactor: Selling Settings form cleanup (#26841) --- .../selling_settings/selling_settings.json | 61 +++++++++++++++---- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index f01934b7e6..717fd9b92e 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -6,24 +6,31 @@ "document_type": "Other", "engine": "InnoDB", "field_order": [ + "customer_defaults_section", "cust_master_name", - "campaign_naming_by", "customer_group", + "column_break_4", "territory", - "selling_price_list", - "close_opportunity_after_days", + "crm_settings_section", + "campaign_naming_by", "default_valid_till", - "column_break_5", + "column_break_9", + "close_opportunity_after_days", + "item_price_settings_section", + "selling_price_list", + "column_break_15", + "maintain_same_sales_rate", + "maintain_same_rate_action", + "editable_price_list_rate", + "validate_selling_price", + "sales_transactions_settings_section", "so_required", "dn_required", "sales_update_frequency", - "maintain_same_sales_rate", - "maintain_same_rate_action", + "column_break_5", "role_to_override_stop_action", - "editable_price_list_rate", "allow_multiple_items", "allow_against_multiple_purchase_orders", - "validate_selling_price", "hide_tax_id" ], "fields": [ @@ -116,7 +123,7 @@ "default": "0", "fieldname": "allow_multiple_items", "fieldtype": "Check", - "label": "Allow Item to Be Added Multiple Times in a Transaction" + "label": "Allow Item to be Added Multiple Times in a Transaction" }, { "default": "0", @@ -142,7 +149,7 @@ "description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.", "fieldname": "maintain_same_rate_action", "fieldtype": "Select", - "label": "Action If Same Rate is Not Maintained", + "label": "Action if Same Rate is Not Maintained", "mandatory_depends_on": "maintain_same_sales_rate", "options": "Stop\nWarn" }, @@ -152,6 +159,38 @@ "fieldtype": "Link", "label": "Role Allowed to Override Stop Action", "options": "Role" + }, + { + "fieldname": "customer_defaults_section", + "fieldtype": "Section Break", + "label": "Customer Defaults" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "crm_settings_section", + "fieldtype": "Section Break", + "label": "CRM Settings" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "item_price_settings_section", + "fieldtype": "Section Break", + "label": "Item Price Settings" + }, + { + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, + { + "fieldname": "sales_transactions_settings_section", + "fieldtype": "Section Break", + "label": "Transaction Settings" } ], "icon": "fa fa-cog", @@ -159,7 +198,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-04 20:18:12.814624", + "modified": "2021-08-06 22:25:50.119458", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From 07337d5c78c14119ae9fc5d010080a1db61d0bdd Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 12:38:40 +0530 Subject: [PATCH 206/253] fix: validate python expressions (#26835) --- erpnext/accounts/doctype/pricing_rule/pricing_rule.json | 5 +++-- .../item_quality_inspection_parameter.json | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 428989aa96..0be41b4063 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -558,7 +558,8 @@ "description": "Simple Python Expression, Example: territory != 'All Territories'", "fieldname": "condition", "fieldtype": "Code", - "label": "Condition" + "label": "Condition", + "options": "PythonExpression" }, { "fieldname": "column_break_42", @@ -575,7 +576,7 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2021-03-06 22:01:24.840422", + "modified": "2021-08-06 15:10:04.219321", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json index 9b1a47eed6..5de45cbcad 100644 --- a/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json +++ b/erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json @@ -47,7 +47,8 @@ "description": "Simple Python formula applied on Reading fields.
Numeric eg. 1: reading_1 > 0.2 and reading_1 < 0.5
\nNumeric eg. 2: mean > 3.5 (mean of populated fields)
\nValue based eg.: reading_value in (\"A\", \"B\", \"C\")", "fieldname": "acceptance_formula", "fieldtype": "Code", - "label": "Acceptance Criteria Formula" + "label": "Acceptance Criteria Formula", + "options": "PythonExpression" }, { "default": "0", @@ -89,7 +90,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-02-04 18:50:02.056173", + "modified": "2021-08-06 15:08:20.911338", "modified_by": "Administrator", "module": "Stock", "name": "Item Quality Inspection Parameter", From b0f3c0f3fd86ed6d75e0234075402362b1941681 Mon Sep 17 00:00:00 2001 From: harshpwctech <84438948+harshpwctech@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:42:05 +0530 Subject: [PATCH 207/253] fix: JWT decoding error (#26624) --- erpnext/regional/india/e_invoice/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index f4976138ac..4276946ebb 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -928,7 +928,7 @@ class GSPConnector(): def set_einvoice_data(self, res): enc_signed_invoice = res.get('SignedInvoice') - dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data'] + dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data'] self.invoice.irn = res.get('Irn') self.invoice.ewaybill = res.get('EwbNo') @@ -1126,4 +1126,4 @@ def check_scheduler_status(): def job_already_enqueued(job_name): enqueued_jobs = [d.get("job_name") for d in get_info()] if job_name in enqueued_jobs: - return True \ No newline at end of file + return True From 9c04c297b8906978c5931223f1f56c75246e7709 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 9 Aug 2021 14:01:02 +0530 Subject: [PATCH 208/253] refactor: Accounts Settings form cleanup (#26842) --- .../doctype/accounts_settings/accounts_settings.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 703e93c075..7bcc6ee53b 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -10,6 +10,7 @@ "accounts_transactions_settings_section", "over_billing_allowance", "role_allowed_to_over_bill", + "credit_controller", "make_payment_via_journal_entry", "column_break_11", "check_supplier_invoice_uniqueness", @@ -27,7 +28,6 @@ "acc_frozen_upto", "frozen_accounts_modifier", "column_break_4", - "credit_controller", "deferred_accounting_settings_section", "book_deferred_entries_based_on", "column_break_18", @@ -73,11 +73,10 @@ "fieldtype": "Column Break" }, { - "description": "This role is allowed to submit transactions that exceed credit limits", "fieldname": "credit_controller", "fieldtype": "Link", "in_list_view": 1, - "label": "Credit Controller", + "label": "Role allowed to bypass Credit Limit", "options": "Role" }, { @@ -268,7 +267,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-17 20:26:03.721202", + "modified": "2021-08-09 13:08:01.335416", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From b3bbebd27c73827d5a88ff47d0d16fb73dcf6de2 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Mon, 9 Aug 2021 14:39:32 +0530 Subject: [PATCH 209/253] fix: add parameter for db insert while adding item tax (#26855) --- erpnext/controllers/accounts_controller.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bf4ab1a848..9e2e5968c6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1507,7 +1507,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc): if child_item.get("item_tax_template"): child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True) -def add_taxes_from_tax_template(child_item, parent_doc): +def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True): add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template") if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template: @@ -1530,7 +1530,8 @@ def add_taxes_from_tax_template(child_item, parent_doc): "category" : "Total", "add_deduct_tax" : "Add" }) - tax_row.db_insert() + if db_insert: + tax_row.db_insert() def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item): """ From 42bb77bf80a494e6ed4fae35e1e283dbadf2c581 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 9 Aug 2021 15:11:13 +0530 Subject: [PATCH 210/253] fix: creating contact on creation of lead --- erpnext/crm/doctype/lead/lead.py | 58 +++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 49b682c12f..7f028cb316 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -63,6 +63,22 @@ class Lead(SellingController): def on_update(self): self.add_calendar_event() + def before_insert(self): + self.contact_doc = self.create_contact() + + def after_insert(self): + self.update_links() + + def update_links(self): + # update contact links + if self.contact_doc: + self.contact_doc.append("links", { + "link_doctype": "Lead", + "link_name": self.name, + "link_title": self.lead_name + }) + self.contact_doc.save() + def add_calendar_event(self, opts=None, force=False): super(Lead, self).add_calendar_event({ "owner": self.lead_owner, @@ -116,7 +132,6 @@ class Lead(SellingController): "party_name": self.name, "docstatus": 1, "status": ["!=", "Lost"] - }) def has_lost_quotation(self): @@ -137,10 +152,43 @@ class Lead(SellingController): self.lead_name = self.email_id.split("@")[0] def set_title(self): - if self.company_name: - self.title = self.company_name - else: - self.title = self.lead_name + self.title = self.company_name or self.lead_name + + def create_contact(self): + if not self.lead_name: + self.set_full_name() + self.set_lead_name() + + contact = frappe.new_doc("Contact") + contact.update({ + "first_name": self.first_name or self.lead_name, + "last_name": self.last_name, + "salutation": self.salutation, + "gender": self.gender, + "designation": self.designation, + }) + + if self.email_id: + contact.append("email_ids", { + "email_id": self.email_id, + "is_primary": 1 + }) + + if self.phone: + contact.append("phone_nos", { + "phone": self.phone, + "is_primary_phone": 1 + }) + + if self.mobile_no: + contact.append("phone_nos", { + "phone": self.mobile_no, + "is_primary_mobile_no":1 + }) + + contact.insert(ignore_permissions=True) + + return contact @frappe.whitelist() def make_customer(source_name, target_doc=None): From 7ba8c821a1659185df524c466fd8906d44649876 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 15:14:26 +0530 Subject: [PATCH 211/253] test: fix flaky purchase receipt test (#26859) --- .../purchase_receipt/test_purchase_receipt.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index bb4a710a04..d40d78184d 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -324,21 +324,8 @@ class TestPurchaseReceipt(unittest.TestCase): pr1.submit() self.assertRaises(frappe.ValidationError, pr2.submit) + frappe.db.rollback() - pr1.cancel() - se.cancel() - se1.cancel() - se2.cancel() - se3.cancel() - po.reload() - pr2.load_from_db() - - if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry', - {'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'): - pr2.cancel() - - po.load_from_db() - po.cancel() def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) From db3ee314138ee40653c562d89d5aff0764a03ad7 Mon Sep 17 00:00:00 2001 From: Anupam Date: Mon, 9 Aug 2021 15:34:50 +0530 Subject: [PATCH 212/253] fix: test case --- erpnext/crm/doctype/appointment/test_appointment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py index bcab7bd708..c7563e9d15 100644 --- a/erpnext/crm/doctype/appointment/test_appointment.py +++ b/erpnext/crm/doctype/appointment/test_appointment.py @@ -9,7 +9,7 @@ import datetime def create_test_lead(): - test_lead = frappe.db.exists({'doctype': 'Lead', 'email':'test@example.com'}) + test_lead = frappe.db.exists({'doctype': 'Lead', 'email_id':'test@example.com'}) if test_lead: return frappe.get_doc('Lead', test_lead[0][0]) test_lead = frappe.get_doc({ From 458f7f119416d380a63fe69e8a2d74b25234c468 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Mon, 9 Aug 2021 17:16:48 +0530 Subject: [PATCH 213/253] patch: delete all orphaned tables docs (#26743) --- erpnext/patches.txt | 1 + .../patches/v13_0/delete_orphaned_tables.py | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 erpnext/patches/v13_0/delete_orphaned_tables.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 04f05eda13..30486242c1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -294,6 +294,7 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_tds_check_field #3 erpnext.patches.v13_0.add_custom_field_for_south_africa diff --git a/erpnext/patches/v13_0/delete_orphaned_tables.py b/erpnext/patches/v13_0/delete_orphaned_tables.py new file mode 100644 index 0000000000..1d6eebe039 --- /dev/null +++ b/erpnext/patches/v13_0/delete_orphaned_tables.py @@ -0,0 +1,69 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from frappe.utils import getdate + +def execute(): + frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record') + + if has_deleted_company_transactions(): + child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected() + + for doctype in child_doctypes: + docs = frappe.get_all(doctype, fields=['name', 'parent', 'parenttype', 'creation']) + + for doc in docs: + if not frappe.db.exists(doc['parenttype'], doc['parent']): + frappe.db.delete(doctype, {'name': doc['name']}) + + elif check_for_new_doc_with_same_name_as_deleted_parent(doc): + frappe.db.delete(doctype, {'name': doc['name']}) + +def has_deleted_company_transactions(): + return frappe.get_all('Transaction Deletion Record') + +def get_child_doctypes_whose_parent_doctypes_were_affected(): + parent_doctypes = get_affected_doctypes() + child_doctypes = frappe.get_all( + 'DocField', + filters={ + 'fieldtype': 'Table', + 'parent':['in', parent_doctypes] + }, pluck='options') + + return child_doctypes + +def get_affected_doctypes(): + affected_doctypes = [] + tdr_docs = frappe.get_all('Transaction Deletion Record', pluck="name") + + for tdr in tdr_docs: + tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr) + + for doctype in tdr_doc.doctypes: + if is_not_child_table(doctype.doctype_name): + affected_doctypes.append(doctype.doctype_name) + + affected_doctypes = remove_duplicate_items(affected_doctypes) + return affected_doctypes + +def is_not_child_table(doctype): + return not bool(frappe.get_value('DocType', doctype, 'istable')) + +def remove_duplicate_items(affected_doctypes): + return list(set(affected_doctypes)) + +def check_for_new_doc_with_same_name_as_deleted_parent(doc): + """ + Compares creation times of parent and child docs. + Since Transaction Deletion Record resets the naming series after deletion, + it allows the creation of new docs with the same names as the deleted ones. + """ + + parent_creation_time = frappe.db.get_value(doc['parenttype'], doc['parent'], 'creation') + child_creation_time = doc['creation'] + + return getdate(parent_creation_time) > getdate(child_creation_time) \ No newline at end of file From 08ae49cd118bf24beff9908c162ae2abcb544337 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Mon, 9 Aug 2021 17:58:52 +0530 Subject: [PATCH 214/253] fix(regional): add permissions for VAT Audit report (#26851) fix(regional): add permissions for VAT Audit report --- erpnext/patches.txt | 2 +- .../add_custom_field_for_south_africa.py | 3 ++- .../vat_audit_report/vat_audit_report.json | 12 +--------- .../vat_audit_report/vat_audit_report.py | 22 ++++++++++++++++--- erpnext/regional/south_africa/setup.py | 20 ++++++++++++++--- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 30486242c1..35b248c08e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -297,5 +297,5 @@ erpnext.patches.v13_0.update_amt_in_work_order_required_items erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_tds_check_field #3 -erpnext.patches.v13_0.add_custom_field_for_south_africa +erpnext.patches.v13_0.add_custom_field_for_south_africa #2 erpnext.patches.v13_0.shopify_deprecation_warning diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py index f882fdedf3..73ff1cad5b 100644 --- a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py +++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from erpnext.regional.south_africa.setup import make_custom_fields +from erpnext.regional.south_africa.setup import make_custom_fields, add_permissions def execute(): company = frappe.get_all('Company', filters = {'country': 'South Africa'}) @@ -11,3 +11,4 @@ def execute(): return make_custom_fields() + add_permissions() diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.json b/erpnext/regional/report/vat_audit_report/vat_audit_report.json index 8917e8f3c7..a8be7bf64c 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.json +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.json @@ -18,15 +18,5 @@ "ref_doctype": "GL Entry", "report_name": "VAT Audit Report", "report_type": "Script Report", - "roles": [ - { - "role": "Accounts User" - }, - { - "role": "Accounts Manager" - }, - { - "role": "Auditor" - } - ] + "roles": [] } \ No newline at end of file diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py index f45ba01dea..292605ef13 100644 --- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -189,6 +189,8 @@ class VATAuditReport(object): row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy") row["voucher_type"] = doctype row["voucher_no"] = inv + row["party_type"] = "Customer" if doctype == "Sales Invoice" else "Supplier" + row["party"] = inv_data.get("party") row["remarks"] = inv_data.get("remarks") row["gross_amount"]= item_details[0].get("gross_amount") row["tax_amount"]= item_details[0].get("tax_amount") @@ -226,6 +228,20 @@ class VATAuditReport(object): "options": "voucher_type", "width": 150 }, + { + "fieldname": "party_type", + "label": "Party Type", + "fieldtype": "Data", + "width": 140, + "hidden": 1 + }, + { + "fieldname": "party", + "label": "Party", + "fieldtype": "Dynamic Link", + "options": "party_type", + "width": 150 + }, { "fieldname": "remarks", "label": "Details", @@ -236,18 +252,18 @@ class VATAuditReport(object): "fieldname": "net_amount", "label": "Net Amount", "fieldtype": "Currency", - "width": 150 + "width": 130 }, { "fieldname": "tax_amount", "label": "Tax Amount", "fieldtype": "Currency", - "width": 150 + "width": 130 }, { "fieldname": "gross_amount", "label": "Gross Amount", "fieldtype": "Currency", - "width": 150 + "width": 130 }, ] diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py index ac783b8488..4657ff833d 100644 --- a/erpnext/regional/south_africa/setup.py +++ b/erpnext/regional/south_africa/setup.py @@ -3,11 +3,12 @@ from __future__ import unicode_literals -# import frappe, os, json +import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.permissions import add_permission, update_permission_property def setup(company=None, patch=True): + make_custom_fields() add_permissions() def make_custom_fields(update=True): @@ -27,10 +28,23 @@ def make_custom_fields(update=True): create_custom_fields(custom_fields, update=update) def add_permissions(): - """Add Permissions for South Africa VAT Settings and South Africa VAT Account""" + """Add Permissions for South Africa VAT Settings and South Africa VAT Account + and VAT Audit Report""" for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): add_permission(doctype, role, 0) update_permission_property(doctype, role, 0, 'write', 1) - update_permission_property(doctype, role, 0, 'create', 1) \ No newline at end of file + update_permission_property(doctype, role, 0, 'create', 1) + + + if not frappe.db.get_value('Custom Role', dict(report="VAT Audit Report")): + frappe.get_doc(dict( + doctype='Custom Role', + report="VAT Audit Report", + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager'), + dict(role='Auditor') + ] + )).insert() \ No newline at end of file From 54d1336d113de3962db6e6e55e0fa4225c84d924 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 2 Aug 2021 23:15:44 +0530 Subject: [PATCH 215/253] fix: Condition for fetching Payment Terms from Sales/Purchase Orders --- erpnext/controllers/accounts_controller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c793c19a92..5f3d3ce1e6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1106,16 +1106,16 @@ class AccountsController(TransactionBase): base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): + if self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.get("payment_terms_template"): + po_or_so, doctype, fieldname = self.get_order_details() + if self.get("payment_terms_template"): data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) - elif self.doctype in ["Sales Invoice", "Purchase Invoice"]: - po_or_so, doctype, fieldname = self.get_order_details() - - if self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): - self.fetch_payment_terms_from_order(po_or_so, doctype) + elif self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): + self.fetch_payment_terms_from_order(po_or_so, doctype) elif self.doctype not in ["Purchase Receipt"]: data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) From 7244afe129b836b54af2a2376694bdbd705ac83a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 4 Aug 2021 03:15:13 +0530 Subject: [PATCH 216/253] fix: Rename test to reflect changes in code --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 474c9cf3df..d7db27cb54 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -632,7 +632,7 @@ class TestPurchaseOrder(unittest.TestCase): else: raise Exception - def test_terms_does_not_copy(self): + def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): po = create_purchase_order() self.assertTrue(po.get('payment_schedule')) From 1200872c7e83bbe2c518d2642447539837534c69 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 5 Aug 2021 00:35:45 +0530 Subject: [PATCH 217/253] fix: Disable automcatically_fetch_payment_terms after running its associated tests --- erpnext/buying/doctype/purchase_order/test_purchase_order.py | 2 ++ erpnext/selling/doctype/sales_order/test_sales_order.py | 2 ++ erpnext/stock/doctype/delivery_note/test_delivery_note.py | 2 ++ erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index d7db27cb54..0db54e4206 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -988,6 +988,8 @@ class TestPurchaseOrder(unittest.TestCase): # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) compare_payment_schedules(self, po, pi) + automatically_fetch_payment_terms(enable=0) + def make_pr_against_po(po, received_qty=0): pr = make_purchase_receipt(po) pr.get("items")[0].qty = received_qty or 5 diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index f4a089bcef..5639ee8069 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1248,6 +1248,8 @@ class TestSalesOrder(unittest.TestCase): self.assertEqual(so.payment_terms_template, si.payment_terms_template) compare_payment_schedules(self, so, si) + automatically_fetch_payment_terms(enable=0) + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings.automatically_fetch_payment_terms = enable diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index ca8d8b99d3..756825e826 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -785,6 +785,8 @@ class TestDeliveryNote(unittest.TestCase): self.assertEqual(so.payment_terms_template, si.payment_terms_template) compare_payment_schedules(self, so, si) + automatically_fetch_payment_terms(enable=0) + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") args = frappe._dict(args) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index ee7fe4c9bd..0ae7e125e4 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1107,6 +1107,8 @@ class TestPurchaseReceipt(unittest.TestCase): # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) compare_payment_schedules(self, po, pi) + automatically_fetch_payment_terms(enable=0) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s From 55cb82c74d4f536891e862f452971f9269e0af15 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 5 Aug 2021 00:52:55 +0530 Subject: [PATCH 218/253] fix: Compare Payment Schedules --- .../doctype/sales_order/test_sales_order.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 5639ee8069..a226da75cd 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -5,7 +5,7 @@ import json import unittest import frappe import frappe.permissions -from frappe.utils import flt, add_days, nowdate +from frappe.utils import flt, add_days, nowdate, getdate from frappe.core.doctype.user_permission.test_user_permission import create_user from erpnext.selling.doctype.sales_order.sales_order \ import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired @@ -1256,17 +1256,11 @@ def automatically_fetch_payment_terms(enable=1): accounts_settings.save() def compare_payment_schedules(doc, doc1, doc2): - payment_schedule1 = frappe.db.sql("""select payment_term, description, due_date, mode_of_payment, invoice_portion, payment_amount - from `tabPayment Schedule` - where parenttype=%s and parent=%s - order by payment_term asc""", (doc1.doctype, doc1.name), as_dict=1) - - payment_schedule2 = frappe.db.sql("""select payment_term, description, due_date, mode_of_payment, invoice_portion, payment_amount - from `tabPayment Schedule` - where parenttype=%s and parent=%s - order by payment_term asc""", (doc2.doctype, doc2.name), as_dict=1) - - doc.assertEqual(payment_schedule1, payment_schedule2) + for index, schedule in enumerate(doc1.get('payment_schedule')): + doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term) + doc.assertEqual(getdate(schedule.due_date), doc2.payment_schedule[index].due_date) + doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion) + doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount) def make_sales_order(**args): so = frappe.new_doc("Sales Order") From 0588382c38642bf5240deca16ef82dca351608a2 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 5 Aug 2021 21:42:09 +0530 Subject: [PATCH 219/253] fix: Stop fetching amount while fetching Payment Terms --- erpnext/controllers/accounts_controller.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5f3d3ce1e6..35ebebc84c 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1180,10 +1180,7 @@ class AccountsController(TransactionBase): 'due_date': schedule.due_date, 'invoice_portion': schedule.invoice_portion, 'discount_type': schedule.discount_type, - 'discount': schedule.discount, - 'base_payment_amount': schedule.base_payment_amount, - 'payment_amount': schedule.payment_amount, - 'outstanding': schedule.outstanding + 'discount': schedule.discount } self.append("payment_schedule", payment_schedule) From 5b33e75c6564f7afe02a44628b6f44f478af2aac Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 5 Aug 2021 21:50:09 +0530 Subject: [PATCH 220/253] fix: Fetch discount details from Payment Terms only if Discount Type = Percentage --- erpnext/controllers/accounts_controller.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 35ebebc84c..b0e24606c6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1179,9 +1179,14 @@ class AccountsController(TransactionBase): 'payment_term': schedule.payment_term, 'due_date': schedule.due_date, 'invoice_portion': schedule.invoice_portion, - 'discount_type': schedule.discount_type, - 'discount': schedule.discount + 'mode_of_payment': schedule.mode_of_payment, + 'description': schedule.description } + + if schedule.discount_type == 'Percentage': + payment_schedule['discount_type'] = schedule.discount_type + payment_schedule['discount'] = schedule.discount + self.append("payment_schedule", payment_schedule) def set_due_date(self): From e247e3a4b282f5dab116276827be60dcc0092b2d Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 5 Aug 2021 22:04:11 +0530 Subject: [PATCH 221/253] fix: Only fetch default Payment Terms Template if present --- erpnext/accounts/party.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index b97dc401e6..19a394f743 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +from re import template import frappe, erpnext from frappe import _, msgprint, scrub @@ -59,7 +60,10 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= billing_address=party_address, shipping_address=shipping_address) if fetch_payment_terms_template: - party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company) + payment_terms_template = get_payment_terms_template(party.name, party_type, company) + + if payment_terms_template: + party_details["payment_terms_template"] = payment_terms_template if not party_details.get("currency"): party_details["currency"] = currency From 072f63b32434672ef97dbd6ba7f2e3b3a3138933 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 5 Aug 2021 23:04:58 +0530 Subject: [PATCH 222/253] Revert "fix: Only fetch default Payment Terms Template if present" This reverts commit fb80ca9e06ae57dbb61e1a3907b2cfc19b1fd925. --- erpnext/accounts/party.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 19a394f743..b97dc401e6 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -2,7 +2,6 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -from re import template import frappe, erpnext from frappe import _, msgprint, scrub @@ -60,10 +59,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= billing_address=party_address, shipping_address=shipping_address) if fetch_payment_terms_template: - payment_terms_template = get_payment_terms_template(party.name, party_type, company) - - if payment_terms_template: - party_details["payment_terms_template"] = payment_terms_template + party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company) if not party_details.get("currency"): party_details["currency"] = currency From bcf56e64ba41af8000a2371cb37bff12c3dc702e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 6 Aug 2021 23:53:16 +0530 Subject: [PATCH 223/253] fix: Ignore default payment term templates when coping payment terms from orders --- .../purchase_invoice/purchase_invoice.js | 5 +- .../purchase_invoice/purchase_invoice.json | 635 +++++++++++---- .../doctype/sales_invoice/sales_invoice.json | 727 +++++++++++++----- erpnext/accounts/party.py | 4 +- .../doctype/purchase_order/purchase_order.py | 7 +- erpnext/controllers/accounts_controller.py | 31 +- erpnext/controllers/buying_controller.py | 3 +- erpnext/public/js/utils/party.js | 1 + .../purchase_receipt/purchase_receipt.py | 5 +- 9 files changed, 1057 insertions(+), 361 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 87ab31f0d5..c953d5ce0c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -275,7 +275,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. // Do not update if inter company reference is there as the details will already be updated if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference) return; - + erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details", { posting_date: this.frm.doc.posting_date, @@ -283,7 +283,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying. party: this.frm.doc.supplier, party_type: "Supplier", account: this.frm.doc.credit_to, - price_list: this.frm.doc.buying_price_list + price_list: this.frm.doc.buying_price_list, + fetch_payment_terms_template: cint(!this.frm.doc.ignore_default_payment_terms_template) }, function() { me.apply_pricing_rule(); me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0; diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 00ef7d5c18..f1bf595a39 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -131,6 +131,7 @@ "advances", "payment_schedule_section", "payment_terms_template", + "ignore_default_payment_terms_template", "payment_schedule", "terms_section_break", "tc_name", @@ -175,7 +176,9 @@ "hidden": 1, "label": "Title", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "naming_series", @@ -187,7 +190,9 @@ "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier", @@ -199,7 +204,9 @@ "options": "Supplier", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -211,7 +218,9 @@ "label": "Supplier Name", "oldfieldname": "supplier_name", "oldfieldtype": "Data", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "supplier.tax_id", @@ -219,21 +228,27 @@ "fieldtype": "Read Only", "label": "Tax Id", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "due_date", "fieldtype": "Date", "label": "Due Date", "oldfieldname": "due_date", - "oldfieldtype": "Date" + "oldfieldtype": "Date", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "is_paid", "fieldtype": "Check", "label": "Is Paid", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -241,19 +256,25 @@ "fieldtype": "Check", "label": "Is Return (Debit Note)", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "apply_tds", "fieldtype": "Check", "label": "Apply Tax Withholding Amount", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "oldfieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -263,13 +284,17 @@ "label": "Company", "options": "Company", "print_hide": 1, - "remember_last_selected_value": 1 + "remember_last_selected_value": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cost_center", "fieldtype": "Link", "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "default": "Today", @@ -281,7 +306,9 @@ "oldfieldtype": "Date", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "posting_time", @@ -290,6 +317,8 @@ "no_copy": 1, "print_hide": 1, "print_width": "100px", + "show_days": 1, + "show_seconds": 1, "width": "100px" }, { @@ -298,7 +327,9 @@ "fieldname": "set_posting_time", "fieldtype": "Check", "label": "Edit Posting Date and Time", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amended_from", @@ -310,44 +341,58 @@ "oldfieldtype": "Link", "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:doc.on_hold", "fieldname": "sb_14", "fieldtype": "Section Break", - "label": "Hold Invoice" + "label": "Hold Invoice", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "on_hold", "fieldtype": "Check", - "label": "Hold Invoice" + "label": "Hold Invoice", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.on_hold", "description": "Once set, this invoice will be on hold till the set date", "fieldname": "release_date", "fieldtype": "Date", - "label": "Release Date" + "label": "Release Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cb_17", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.on_hold", "fieldname": "hold_comment", "fieldtype": "Small Text", - "label": "Reason For Putting On Hold" + "label": "Reason For Putting On Hold", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "bill_no", "fieldname": "supplier_invoice_details", "fieldtype": "Section Break", - "label": "Supplier Invoice Details" + "label": "Supplier Invoice Details", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "bill_no", @@ -355,11 +400,15 @@ "label": "Supplier Invoice No", "oldfieldname": "bill_no", "oldfieldtype": "Data", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_15", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "bill_date", @@ -368,13 +417,17 @@ "no_copy": 1, "oldfieldname": "bill_date", "oldfieldtype": "Date", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "return_against", "fieldname": "returns", "fieldtype": "Section Break", - "label": "Returns" + "label": "Returns", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "return_against", @@ -384,26 +437,34 @@ "no_copy": 1, "options": "Purchase Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "section_addresses", "fieldtype": "Section Break", - "label": "Address and Contact" + "label": "Address and Contact", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "supplier_address", "fieldtype": "Link", "label": "Select Supplier Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "address_display", "fieldtype": "Small Text", "label": "Address", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_person", @@ -411,51 +472,67 @@ "in_global_search": 1, "label": "Contact Person", "options": "Contact", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_display", "fieldtype": "Small Text", "label": "Contact", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_mobile", "fieldtype": "Small Text", "label": "Mobile No", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_email", "fieldtype": "Small Text", "label": "Contact Email", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_address", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address", "fieldtype": "Link", "label": "Select Shipping Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address_display", "fieldtype": "Small Text", "label": "Shipping Address", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "currency_and_price_list", "fieldtype": "Section Break", "label": "Currency and Price List", - "options": "fa fa-tag" + "options": "fa fa-tag", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "currency", @@ -464,7 +541,9 @@ "oldfieldname": "currency", "oldfieldtype": "Select", "options": "Currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "conversion_rate", @@ -473,18 +552,24 @@ "oldfieldname": "conversion_rate", "oldfieldtype": "Currency", "precision": "9", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break2", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "buying_price_list", "fieldtype": "Link", "label": "Price List", "options": "Price List", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "price_list_currency", @@ -492,14 +577,18 @@ "label": "Price List Currency", "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "plc_conversion_rate", "fieldtype": "Float", "label": "Price List Exchange Rate", "precision": "9", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -508,11 +597,15 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sec_warehouse", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -521,7 +614,9 @@ "fieldtype": "Link", "label": "Set Accepted Warehouse", "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -531,11 +626,15 @@ "label": "Rejected Warehouse", "no_copy": 1, "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_warehouse", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -543,25 +642,33 @@ "fieldtype": "Select", "label": "Raw Materials Supplied", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "items_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart" + "options": "fa fa-shopping-cart", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "update_stock", "fieldtype": "Check", "label": "Update Stock", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "scan_barcode", "fieldtype": "Data", - "label": "Scan Barcode" + "label": "Scan Barcode", + "show_days": 1, + "show_seconds": 1 }, { "allow_bulk_edit": 1, @@ -571,25 +678,33 @@ "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Purchase Invoice Item", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rule_details", "fieldtype": "Section Break", - "label": "Pricing Rules" + "label": "Pricing Rules", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rules", "fieldtype": "Table", "label": "Pricing Rule Detail", "options": "Pricing Rule Detail", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible_depends_on": "supplied_items", "fieldname": "raw_materials_supplied", "fieldtype": "Section Break", - "label": "Raw Materials Supplied" + "label": "Raw Materials Supplied", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -597,17 +712,23 @@ "fieldtype": "Table", "label": "Supplied Items", "no_copy": 1, - "options": "Purchase Receipt Item Supplied" + "options": "Purchase Receipt Item Supplied", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_26", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_qty", "fieldtype": "Float", "label": "Total Quantity", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total", @@ -615,7 +736,9 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_net_total", @@ -625,18 +748,24 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_28", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total", "fieldtype": "Currency", "label": "Total", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "net_total", @@ -646,42 +775,56 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_net_weight", "fieldtype": "Float", "label": "Total Net Weight", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_section", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_category", "fieldtype": "Link", "label": "Tax Category", "options": "Tax Category", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_49", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_rule", "fieldtype": "Link", "label": "Shipping Rule", "options": "Shipping Rule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_51", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges", @@ -690,7 +833,9 @@ "oldfieldname": "purchase_other_charges", "oldfieldtype": "Link", "options": "Purchase Taxes and Charges Template", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes", @@ -698,13 +843,17 @@ "label": "Purchase Taxes and Charges", "oldfieldname": "purchase_tax_details", "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges" + "options": "Purchase Taxes and Charges", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "sec_tax_breakup", "fieldtype": "Section Break", - "label": "Tax Breakup" + "label": "Tax Breakup", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "other_charges_calculation", @@ -713,13 +862,17 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "totals", "fieldtype": "Section Break", "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_taxes_and_charges_added", @@ -729,7 +882,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_taxes_and_charges_deducted", @@ -739,7 +894,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total_taxes_and_charges", @@ -749,11 +906,15 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_40", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges_added", @@ -763,7 +924,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges_deducted", @@ -773,7 +936,9 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_taxes_and_charges", @@ -781,14 +946,18 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "discount_amount", "fieldname": "section_break_44", "fieldtype": "Section Break", - "label": "Additional Discount" + "label": "Additional Discount", + "show_days": 1, + "show_seconds": 1 }, { "default": "Grand Total", @@ -796,7 +965,9 @@ "fieldtype": "Select", "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_discount_amount", @@ -804,28 +975,38 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_46", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "additional_discount_percentage", "fieldtype": "Float", "label": "Additional Discount Percentage", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "discount_amount", "fieldtype": "Currency", "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_49", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_grand_total", @@ -835,7 +1016,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -845,7 +1028,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -855,7 +1040,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_in_words", @@ -865,13 +1052,17 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break8", "fieldtype": "Column Break", "oldfieldtype": "Column Break", "print_hide": 1, + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -882,7 +1073,9 @@ "oldfieldname": "grand_total_import", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -892,7 +1085,9 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -902,7 +1097,9 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "in_words", @@ -912,7 +1109,9 @@ "oldfieldname": "in_words_import", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_advance", @@ -923,7 +1122,9 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "outstanding_amount", @@ -934,14 +1135,18 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "depends_on": "grand_total", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total" + "label": "Disable Rounded Total", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -949,20 +1154,26 @@ "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)", "fieldname": "payments_section", "fieldtype": "Section Break", - "label": "Payments" + "label": "Payments", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "mode_of_payment", "fieldtype": "Link", "label": "Mode of Payment", "options": "Mode of Payment", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cash_bank_account", "fieldtype": "Link", "label": "Cash/Bank Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "clearance_date", @@ -970,11 +1181,15 @@ "label": "Clearance Date", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_br_payments", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_paid", @@ -983,7 +1198,9 @@ "label": "Paid Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_paid_amount", @@ -992,7 +1209,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1000,7 +1219,9 @@ "depends_on": "grand_total", "fieldname": "write_off", "fieldtype": "Section Break", - "label": "Write Off" + "label": "Write Off", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "write_off_amount", @@ -1008,7 +1229,9 @@ "label": "Write Off Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_write_off_amount", @@ -1017,11 +1240,15 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_61", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1029,7 +1256,9 @@ "fieldtype": "Link", "label": "Write Off Account", "options": "Account", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:flt(doc.write_off_amount)!=0", @@ -1037,7 +1266,9 @@ "fieldtype": "Link", "label": "Write Off Cost Center", "options": "Cost Center", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1047,13 +1278,17 @@ "label": "Advance Payments", "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "allocate_advances_automatically", "fieldtype": "Check", - "label": "Set Advances and Allocate (FIFO)" + "label": "Set Advances and Allocate (FIFO)", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.allocate_advances_automatically", @@ -1061,7 +1296,9 @@ "fieldtype": "Button", "label": "Get Advances Paid", "oldfieldtype": "Button", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "advances", @@ -1071,20 +1308,26 @@ "oldfieldname": "advance_allocation_details", "oldfieldtype": "Table", "options": "Purchase Invoice Advance", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "eval:(!doc.is_return)", "fieldname": "payment_schedule_section", "fieldtype": "Section Break", - "label": "Payment Terms" + "label": "Payment Terms", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_terms_template", "fieldtype": "Link", "label": "Payment Terms Template", - "options": "Payment Terms Template" + "options": "Payment Terms Template", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payment_schedule", @@ -1092,7 +1335,9 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1100,25 +1345,33 @@ "fieldname": "terms_section_break", "fieldtype": "Section Break", "label": "Terms and Conditions", - "options": "fa fa-legal" + "options": "fa fa-legal", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tc_name", "fieldtype": "Link", "label": "Terms", "options": "Terms and Conditions", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "terms", "fieldtype": "Text Editor", - "label": "Terms and Conditions1" + "label": "Terms and Conditions1", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "printing_settings", "fieldtype": "Section Break", - "label": "Printing Settings" + "label": "Printing Settings", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1126,7 +1379,9 @@ "fieldtype": "Link", "label": "Letter Head", "options": "Letter Head", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1134,11 +1389,15 @@ "fieldname": "group_same_items", "fieldtype": "Check", "label": "Group same items", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_112", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1150,14 +1409,18 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1 + "report_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "language", "fieldtype": "Data", "label": "Print Language", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1166,7 +1429,9 @@ "label": "More Information", "oldfieldtype": "Section Break", "options": "fa fa-file-text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "credit_to", @@ -1177,7 +1442,9 @@ "options": "Account", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "party_account_currency", @@ -1187,7 +1454,9 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -1197,7 +1466,9 @@ "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "against_expense_account", @@ -1207,11 +1478,15 @@ "no_copy": 1, "oldfieldname": "against_expense_account", "oldfieldtype": "Small Text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_63", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "Draft", @@ -1220,7 +1495,9 @@ "in_standard_filter": 1, "label": "Status", "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "inter_company_invoice_reference", @@ -1229,7 +1506,9 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "remarks", @@ -1238,14 +1517,18 @@ "no_copy": 1, "oldfieldname": "remarks", "oldfieldtype": "Text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1254,7 +1537,9 @@ "fieldtype": "Date", "label": "From Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1263,11 +1548,15 @@ "fieldtype": "Date", "label": "To Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_114", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "auto_repeat", @@ -1276,24 +1565,32 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "depends_on": "eval: doc.auto_repeat", "fieldname": "update_auto_repeat_reference", "fieldtype": "Button", - "label": "Update Auto Repeat Reference" + "label": "Update Auto Repeat Reference", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", "fieldtype": "Section Break", - "label": "Accounting Dimensions " + "label": "Accounting Dimensions ", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1301,7 +1598,9 @@ "fieldname": "is_internal_supplier", "fieldtype": "Check", "label": "Is Internal Supplier", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_withholding_category", @@ -1309,25 +1608,33 @@ "hidden": 1, "label": "Tax Withholding Category", "options": "Tax Withholding Category", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "billing_address", "fieldtype": "Link", "label": "Select Billing Address", - "options": "Address" + "options": "Address", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "billing_address_display", "fieldtype": "Small Text", "label": "Billing Address", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "project", "fieldtype": "Link", "label": "Project", - "options": "Project" + "options": "Project", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1335,7 +1642,9 @@ "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_internal_supplier", @@ -1344,7 +1653,9 @@ "fieldname": "represents_company", "fieldtype": "Link", "label": "Represents Company", - "options": "Company" + "options": "Company", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.update_stock && doc.is_internal_supplier", @@ -1356,6 +1667,8 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", + "show_days": 1, + "show_seconds": 1, "width": "50px" }, { @@ -1367,6 +1680,8 @@ "options": "Warehouse", "print_hide": 1, "print_width": "50px", + "show_days": 1, + "show_seconds": 1, "width": "50px" }, { @@ -1376,14 +1691,26 @@ "label": "Per Received", "no_copy": 1, "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "0", + "fieldname": "ignore_default_payment_terms_template", + "fieldtype": "Check", + "hidden": 1, + "label": "Ignore Default Payment Terms Template", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-06-15 18:20:56.806195", + "modified": "2021-08-07 17:53:14.351439", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 0a9a105b7c..f5ed87d02a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -127,6 +127,7 @@ "get_advances", "advances", "payment_schedule_section", + "ignore_default_payment_terms_template", "payment_terms_template", "payment_schedule", "payments_section", @@ -197,7 +198,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "options": "fa fa-user" + "options": "fa fa-user", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -209,7 +212,9 @@ "hide_seconds": 1, "label": "Title", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -224,7 +229,9 @@ "options": "ACC-SINV-.YYYY.-\nACC-SINV-RET-.YYYY.-", "print_hide": 1, "reqd": 1, - "set_only_once": 1 + "set_only_once": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -238,7 +245,9 @@ "oldfieldtype": "Link", "options": "Customer", "print_hide": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -252,7 +261,9 @@ "label": "Customer Name", "oldfieldname": "customer_name", "oldfieldtype": "Data", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_id", @@ -261,7 +272,9 @@ "hide_seconds": 1, "label": "Tax Id", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "project", @@ -273,7 +286,9 @@ "oldfieldname": "project_name", "oldfieldtype": "Link", "options": "Project", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -284,7 +299,9 @@ "label": "Include Payment (POS)", "oldfieldname": "is_pos", "oldfieldtype": "Check", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_pos", @@ -294,7 +311,9 @@ "hide_seconds": 1, "label": "POS Profile", "options": "POS Profile", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -304,14 +323,18 @@ "hide_seconds": 1, "label": "Is Return (Credit Note)", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break1", "fieldtype": "Column Break", "hide_days": 1, "hide_seconds": 1, - "oldfieldtype": "Column Break" + "oldfieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", @@ -325,7 +348,9 @@ "options": "Company", "print_hide": 1, "remember_last_selected_value": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "cost_center", @@ -333,7 +358,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -347,7 +374,9 @@ "oldfieldname": "posting_date", "oldfieldtype": "Date", "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "posting_time", @@ -358,7 +387,9 @@ "no_copy": 1, "oldfieldname": "posting_time", "oldfieldtype": "Time", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -368,7 +399,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Edit Posting Date and Time", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "due_date", @@ -378,7 +411,9 @@ "label": "Payment Due Date", "no_copy": 1, "oldfieldname": "due_date", - "oldfieldtype": "Date" + "oldfieldtype": "Date", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "amended_from", @@ -392,7 +427,9 @@ "oldfieldtype": "Link", "options": "Sales Invoice", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.return_against || doc.is_debit_note", @@ -405,7 +442,9 @@ "options": "Sales Invoice", "print_hide": 1, "read_only_depends_on": "eval:doc.is_return", - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -414,7 +453,9 @@ "fieldtype": "Check", "hide_days": 1, "hide_seconds": 1, - "label": "Update Billed Amount in Sales Order" + "label": "Update Billed Amount in Sales Order", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -423,7 +464,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Customer PO Details" + "label": "Customer PO Details", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -433,13 +476,17 @@ "hide_seconds": 1, "label": "Customer's Purchase Order", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_23", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -447,7 +494,9 @@ "fieldtype": "Date", "hide_days": 1, "hide_seconds": 1, - "label": "Customer's Purchase Order Date" + "label": "Customer's Purchase Order Date", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -455,7 +504,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Address and Contact" + "label": "Address and Contact", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "customer_address", @@ -464,7 +515,9 @@ "hide_seconds": 1, "label": "Customer Address", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "address_display", @@ -472,7 +525,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Address", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_person", @@ -482,7 +537,9 @@ "in_global_search": 1, "label": "Contact Person", "options": "Contact", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_display", @@ -490,7 +547,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Contact", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_mobile", @@ -499,7 +558,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Mobile No", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "contact_email", @@ -510,7 +571,9 @@ "label": "Contact Email", "options": "Email", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "territory", @@ -519,13 +582,17 @@ "hide_seconds": 1, "label": "Territory", "options": "Territory", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break4", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address_name", @@ -534,7 +601,9 @@ "hide_seconds": 1, "label": "Shipping Address Name", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_address", @@ -543,7 +612,9 @@ "hide_seconds": 1, "label": "Shipping Address", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company_address", @@ -552,7 +623,9 @@ "hide_seconds": 1, "label": "Company Address Name", "options": "Address", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company_address_display", @@ -562,7 +635,9 @@ "hide_seconds": 1, "label": "Company Address", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -571,7 +646,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Currency and Price List" + "label": "Currency and Price List", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "currency", @@ -583,7 +660,9 @@ "oldfieldtype": "Select", "options": "Currency", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "description": "Rate at which Customer Currency is converted to customer's base currency", @@ -596,13 +675,17 @@ "oldfieldtype": "Currency", "precision": "9", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break2", "fieldtype": "Column Break", "hide_days": 1, "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -615,7 +698,9 @@ "oldfieldtype": "Select", "options": "Price List", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "price_list_currency", @@ -626,7 +711,9 @@ "options": "Currency", "print_hide": 1, "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "description": "Rate at which Price list currency is converted to customer's base currency", @@ -637,7 +724,9 @@ "label": "Price List Exchange Rate", "precision": "9", "print_hide": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -648,14 +737,18 @@ "label": "Ignore Pricing Rule", "no_copy": 1, "permlevel": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sec_warehouse", "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Warehouse" + "label": "Warehouse", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "update_stock", @@ -665,7 +758,9 @@ "hide_seconds": 1, "label": "Source Warehouse", "options": "Warehouse", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "items_section", @@ -674,7 +769,9 @@ "hide_seconds": 1, "label": "Items", "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart" + "options": "fa fa-shopping-cart", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -685,14 +782,18 @@ "label": "Update Stock", "oldfieldname": "update_stock", "oldfieldtype": "Check", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "scan_barcode", "fieldtype": "Data", "hide_days": 1, "hide_seconds": 1, - "label": "Scan Barcode" + "label": "Scan Barcode", + "show_days": 1, + "show_seconds": 1 }, { "allow_bulk_edit": 1, @@ -704,14 +805,18 @@ "oldfieldname": "entries", "oldfieldtype": "Table", "options": "Sales Invoice Item", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rule_details", "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Pricing Rules" + "label": "Pricing Rules", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pricing_rules", @@ -720,7 +825,9 @@ "hide_seconds": 1, "label": "Pricing Rule Detail", "options": "Pricing Rule Detail", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "packing_list", @@ -729,7 +836,9 @@ "hide_seconds": 1, "label": "Packing List", "options": "fa fa-suitcase", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "packed_items", @@ -738,7 +847,9 @@ "hide_seconds": 1, "label": "Packed Items", "options": "Packed Item", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "product_bundle_help", @@ -746,7 +857,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Product Bundle Help", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -756,7 +869,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Time Sheet List" + "label": "Time Sheet List", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "timesheets", @@ -765,7 +880,9 @@ "hide_seconds": 1, "label": "Time Sheets", "options": "Sales Invoice Timesheet", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -776,13 +893,17 @@ "label": "Total Billing Amount", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_30", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_qty", @@ -790,7 +911,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Total Quantity", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total", @@ -800,7 +923,9 @@ "label": "Total (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_net_total", @@ -813,13 +938,17 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_32", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total", @@ -828,7 +957,9 @@ "hide_seconds": 1, "label": "Total", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "net_total", @@ -838,7 +969,9 @@ "label": "Net Total", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_net_weight", @@ -847,7 +980,9 @@ "hide_seconds": 1, "label": "Total Net Weight", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_section", @@ -855,7 +990,9 @@ "hide_days": 1, "hide_seconds": 1, "oldfieldtype": "Section Break", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes_and_charges", @@ -866,13 +1003,17 @@ "oldfieldname": "charge", "oldfieldtype": "Link", "options": "Sales Taxes and Charges Template", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_38", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "shipping_rule", @@ -882,7 +1023,9 @@ "label": "Shipping Rule", "oldfieldtype": "Button", "options": "Shipping Rule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tax_category", @@ -891,13 +1034,17 @@ "hide_seconds": 1, "label": "Tax Category", "options": "Tax Category", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_40", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "taxes", @@ -907,7 +1054,9 @@ "label": "Sales Taxes and Charges", "oldfieldname": "other_charges", "oldfieldtype": "Table", - "options": "Sales Taxes and Charges" + "options": "Sales Taxes and Charges", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -915,7 +1064,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Tax Breakup" + "label": "Tax Breakup", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "other_charges_calculation", @@ -926,13 +1077,17 @@ "no_copy": 1, "oldfieldtype": "HTML", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_43", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_total_taxes_and_charges", @@ -944,13 +1099,17 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_47", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_taxes_and_charges", @@ -960,7 +1119,9 @@ "label": "Total Taxes and Charges", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -968,7 +1129,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Loyalty Points Redemption" + "label": "Loyalty Points Redemption", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "redeem_loyalty_points", @@ -978,7 +1141,9 @@ "hide_seconds": 1, "label": "Loyalty Points", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "redeem_loyalty_points", @@ -990,7 +1155,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1000,13 +1167,17 @@ "hide_seconds": 1, "label": "Redeem Loyalty Points", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_77", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "customer.loyalty_program", @@ -1018,7 +1189,9 @@ "no_copy": 1, "options": "Loyalty Program", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "redeem_loyalty_points", @@ -1028,7 +1201,9 @@ "hide_seconds": 1, "label": "Redemption Account", "no_copy": 1, - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "redeem_loyalty_points", @@ -1038,7 +1213,9 @@ "hide_seconds": 1, "label": "Redemption Cost Center", "no_copy": 1, - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1047,7 +1224,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Additional Discount" + "label": "Additional Discount", + "show_days": 1, + "show_seconds": 1 }, { "default": "Grand Total", @@ -1057,7 +1236,9 @@ "hide_seconds": 1, "label": "Apply Additional Discount On", "options": "\nGrand Total\nNet Total", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_discount_amount", @@ -1067,13 +1248,17 @@ "label": "Additional Discount Amount (Company Currency)", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_51", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "additional_discount_percentage", @@ -1081,7 +1266,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Additional Discount Percentage", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "discount_amount", @@ -1090,7 +1277,9 @@ "hide_seconds": 1, "label": "Additional Discount Amount", "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "totals", @@ -1099,7 +1288,9 @@ "hide_seconds": 1, "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_grand_total", @@ -1112,7 +1303,9 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1124,7 +1317,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1137,7 +1332,9 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "description": "In Words will be visible once you save the Sales Invoice.", @@ -1150,7 +1347,9 @@ "oldfieldname": "in_words", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break5", @@ -1159,6 +1358,8 @@ "hide_seconds": 1, "oldfieldtype": "Column Break", "print_hide": 1, + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -1173,7 +1374,9 @@ "oldfieldtype": "Currency", "options": "currency", "read_only": 1, - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.disable_rounded_total", @@ -1185,7 +1388,9 @@ "no_copy": 1, "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -1198,7 +1403,9 @@ "oldfieldname": "rounded_total_export", "oldfieldtype": "Currency", "options": "currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "in_words", @@ -1210,7 +1417,9 @@ "oldfieldname": "in_words_export", "oldfieldtype": "Data", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_advance", @@ -1222,7 +1431,9 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "outstanding_amount", @@ -1235,7 +1446,9 @@ "oldfieldtype": "Currency", "options": "party_account_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1247,7 +1460,9 @@ "label": "Advance Payments", "oldfieldtype": "Section Break", "options": "fa fa-money", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1255,7 +1470,9 @@ "fieldtype": "Check", "hide_days": 1, "hide_seconds": 1, - "label": "Allocate Advances Automatically (FIFO)" + "label": "Allocate Advances Automatically (FIFO)", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:!doc.allocate_advances_automatically", @@ -1264,7 +1481,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Get Advances Received", - "options": "set_advances" + "options": "set_advances", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "advances", @@ -1275,7 +1494,9 @@ "oldfieldname": "advance_adjustment_details", "oldfieldtype": "Table", "options": "Sales Invoice Advance", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1284,7 +1505,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Payment Terms" + "label": "Payment Terms", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:(!doc.is_pos && !doc.is_return)", @@ -1295,7 +1518,9 @@ "label": "Payment Terms Template", "no_copy": 1, "options": "Payment Terms Template", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:(!doc.is_pos && !doc.is_return)", @@ -1306,7 +1531,9 @@ "label": "Payment Schedule", "no_copy": 1, "options": "Payment Schedule", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_pos===1||(doc.advances && doc.advances.length>0)", @@ -1315,7 +1542,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Payments", - "options": "fa fa-money" + "options": "fa fa-money", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_pos", @@ -1328,7 +1557,9 @@ "oldfieldname": "cash_bank_account", "oldfieldtype": "Link", "options": "Account", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_pos===1", @@ -1338,13 +1569,17 @@ "hide_seconds": 1, "label": "Sales Invoice Payment", "options": "Sales Invoice Payment", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_84", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_paid_amount", @@ -1355,13 +1590,17 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_86", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval: doc.is_pos || doc.redeem_loyalty_points", @@ -1375,13 +1614,17 @@ "oldfieldtype": "Currency", "options": "currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_88", "fieldtype": "Section Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_pos", @@ -1393,13 +1636,17 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_90", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_pos", @@ -1410,7 +1657,9 @@ "label": "Change Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_pos", @@ -1420,7 +1669,9 @@ "hide_seconds": 1, "label": "Account for Change Amount", "options": "Account", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1431,6 +1682,8 @@ "hide_days": 1, "hide_seconds": 1, "label": "Write Off", + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -1441,7 +1694,9 @@ "label": "Write Off Amount", "no_copy": 1, "options": "currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "base_write_off_amount", @@ -1452,7 +1707,9 @@ "no_copy": 1, "options": "Company:company:default_currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1462,13 +1719,17 @@ "hide_days": 1, "hide_seconds": 1, "label": "Write Off Outstanding Amount", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_74", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "write_off_account", @@ -1477,7 +1738,9 @@ "hide_seconds": 1, "label": "Write Off Account", "options": "Account", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "write_off_cost_center", @@ -1486,7 +1749,9 @@ "hide_seconds": 1, "label": "Write Off Cost Center", "options": "Cost Center", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1496,7 +1761,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Terms and Conditions", - "oldfieldtype": "Section Break" + "oldfieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "tc_name", @@ -1507,7 +1774,9 @@ "oldfieldname": "tc_name", "oldfieldtype": "Link", "options": "Terms and Conditions", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "terms", @@ -1516,7 +1785,9 @@ "hide_seconds": 1, "label": "Terms and Conditions Details", "oldfieldname": "terms", - "oldfieldtype": "Text Editor" + "oldfieldtype": "Text Editor", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1524,7 +1795,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Printing Settings" + "label": "Printing Settings", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1536,7 +1809,9 @@ "oldfieldname": "letter_head", "oldfieldtype": "Select", "options": "Letter Head", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1546,7 +1821,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Group same items", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "language", @@ -1555,13 +1832,17 @@ "hide_seconds": 1, "label": "Print Language", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_84", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1575,7 +1856,9 @@ "oldfieldtype": "Link", "options": "Print Heading", "print_hide": 1, - "report_hide": 1 + "report_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1584,7 +1867,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "More Information" + "label": "More Information", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "inter_company_invoice_reference", @@ -1593,7 +1878,9 @@ "hide_seconds": 1, "label": "Inter Company Invoice Reference", "options": "Purchase Invoice", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "customer_group", @@ -1603,7 +1890,9 @@ "hide_seconds": 1, "label": "Customer Group", "options": "Customer Group", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "campaign", @@ -1614,7 +1903,9 @@ "oldfieldname": "campaign", "oldfieldtype": "Link", "options": "Campaign", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1624,13 +1915,17 @@ "hide_seconds": 1, "label": "Is Discounted", "no_copy": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break23", "fieldtype": "Column Break", "hide_days": 1, "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -1644,7 +1939,9 @@ "no_copy": 1, "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "source", @@ -1655,7 +1952,9 @@ "oldfieldname": "source", "oldfieldtype": "Select", "options": "Lead Source", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1666,7 +1965,9 @@ "label": "Accounting Details", "oldfieldtype": "Section Break", "options": "fa fa-file-text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "debit_to", @@ -1679,7 +1980,9 @@ "options": "Account", "print_hide": 1, "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "party_account_currency", @@ -1691,7 +1994,9 @@ "no_copy": 1, "options": "Currency", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "No", @@ -1703,7 +2008,9 @@ "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "c_form_applicable", @@ -1713,7 +2020,9 @@ "label": "C-Form Applicable", "no_copy": 1, "options": "No\nYes", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "c_form_no", @@ -1724,7 +2033,9 @@ "no_copy": 1, "options": "C-Form", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break8", @@ -1732,7 +2043,9 @@ "hide_days": 1, "hide_seconds": 1, "oldfieldtype": "Column Break", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "remarks", @@ -1743,7 +2056,9 @@ "no_copy": 1, "oldfieldname": "remarks", "oldfieldtype": "Text", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1755,7 +2070,9 @@ "label": "Commission", "oldfieldtype": "Section Break", "options": "fa fa-group", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sales_partner", @@ -1766,7 +2083,9 @@ "oldfieldname": "sales_partner", "oldfieldtype": "Link", "options": "Sales Partner", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break10", @@ -1775,6 +2094,8 @@ "hide_seconds": 1, "oldfieldtype": "Column Break", "print_hide": 1, + "show_days": 1, + "show_seconds": 1, "width": "50%" }, { @@ -1785,7 +2106,9 @@ "label": "Commission Rate (%)", "oldfieldname": "commission_rate", "oldfieldtype": "Currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_commission", @@ -1796,7 +2119,9 @@ "oldfieldname": "total_commission", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1806,7 +2131,9 @@ "hide_days": 1, "hide_seconds": 1, "label": "Sales Team", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1818,7 +2145,9 @@ "oldfieldname": "sales_team", "oldfieldtype": "Table", "options": "Sales Team", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1826,7 +2155,9 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Subscription Section" + "label": "Subscription Section", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1836,7 +2167,9 @@ "hide_seconds": 1, "label": "From Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1846,13 +2179,17 @@ "hide_seconds": 1, "label": "To Date", "no_copy": 1, - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_140", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1864,7 +2201,9 @@ "no_copy": 1, "options": "Auto Repeat", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, @@ -1873,7 +2212,9 @@ "fieldtype": "Button", "hide_days": 1, "hide_seconds": 1, - "label": "Update Auto Repeat Reference" + "label": "Update Auto Repeat Reference", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "against_income_account", @@ -1886,7 +2227,9 @@ "oldfieldname": "against_income_account", "oldfieldtype": "Small Text", "print_hide": 1, - "report_hide": 1 + "report_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, @@ -1894,13 +2237,17 @@ "fieldtype": "Section Break", "hide_days": 1, "hide_seconds": 1, - "label": "Accounting Dimensions" + "label": "Accounting Dimensions", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "dimension_col_break", "fieldtype": "Column Break", "hide_days": 1, - "hide_seconds": 1 + "hide_seconds": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1908,7 +2255,9 @@ "fieldname": "is_consolidated", "fieldtype": "Check", "label": "Is Consolidated", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1918,14 +2267,18 @@ "hide_days": 1, "hide_seconds": 1, "label": "Is Internal Customer", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "company.tax_id", "fieldname": "company_tax_id", "fieldtype": "Data", "label": "Company Tax ID", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_internal_customer", @@ -1933,7 +2286,9 @@ "fieldname": "unrealized_profit_loss_account", "fieldtype": "Link", "label": "Unrealized Profit / Loss Account", - "options": "Account" + "options": "Account", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.is_internal_customer", @@ -1943,24 +2298,32 @@ "fieldtype": "Link", "label": "Represents Company", "options": "Company", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_55", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval: doc.is_internal_customer && doc.update_stock", "fieldname": "set_target_warehouse", "fieldtype": "Link", "label": "Set Target Warehouse", - "options": "Warehouse" + "options": "Warehouse", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "is_debit_note", "fieldtype": "Check", - "label": "Is Debit Note" + "label": "Is Debit Note", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -1983,6 +2346,16 @@ "fieldtype": "Small Text", "label": "Dispatch Address", "read_only": 1 + }, + { + "default": "0", + "fieldname": "ignore_default_payment_terms_template", + "fieldtype": "Check", + "hidden": 1, + "label": "Ignore Default Payment Terms Template", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-file-text", @@ -1995,7 +2368,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-07-08 14:03:55.502522", + "modified": "2021-08-06 23:02:20.445127", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index b97dc401e6..329f9a97b8 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -8,7 +8,7 @@ from frappe import _, msgprint, scrub from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values from frappe.utils import (add_days, getdate, formatdate, date_diff, - add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day) + add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day, cint) from frappe.contacts.doctype.address.address import (get_address_display, get_default_address, get_company_address) from frappe.contacts.doctype.contact.contact import get_contact_details @@ -58,7 +58,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category, billing_address=party_address, shipping_address=shipping_address) - if fetch_payment_terms_template: + if cint(fetch_payment_terms_template): party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company) if not party_details.get("currency"): diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index f68d81909a..94684bff1c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -447,10 +447,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions target.flags.ignore_permissions = ignore_permissions set_missing_values(source, target) #Get the advance paid Journal Entries in Purchase Invoice Advance - if target.get("allocate_advances_automatically"): target.set_advances() + target.set_payment_schedule() + def update_item(obj, target, source_parent): target.amount = flt(obj.amount) - flt(obj.billed_amt) target.base_amount = target.amount * flt(source_parent.conversion_rate) @@ -492,10 +493,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions doc = get_mapped_doc("Purchase Order", source_name, fields, target_doc, postprocess, ignore_permissions=ignore_permissions) - automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) - if automatically_fetch_payment_terms: - doc.set_payment_schedule() - return doc @frappe.whitelist() diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b0e24606c6..ae76dea384 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1096,6 +1096,8 @@ class AccountsController(TransactionBase): if self.doctype in ("Sales Invoice", "Purchase Invoice"): base_grand_total = base_grand_total - flt(self.base_write_off_amount) grand_total = grand_total - flt(self.write_off_amount) + po_or_so, doctype, fieldname = self.get_order_details() + automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) if self.get("total_advance"): if party_account_currency == self.company_currency: @@ -1106,28 +1108,25 @@ class AccountsController(TransactionBase): base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): - if self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.get("payment_terms_template"): - po_or_so, doctype, fieldname = self.get_order_details() - - if self.get("payment_terms_template"): + if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): + self.fetch_payment_terms_from_order(po_or_so, doctype) + if self.get('payment_terms_template'): + self.ignore_default_payment_terms_template = 1 + elif self.get("payment_terms_template"): data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total) for item in data: self.append("payment_schedule", item) - - elif self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): - self.fetch_payment_terms_from_order(po_or_so, doctype) - elif self.doctype not in ["Purchase Receipt"]: data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total) self.append("payment_schedule", data) - else: - for d in self.get("payment_schedule"): - if d.invoice_portion: - d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) - d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount')) - d.outstanding = d.payment_amount - elif not d.invoice_portion: - d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount')) + + for d in self.get("payment_schedule"): + if d.invoice_portion: + d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount')) + d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount')) + d.outstanding = d.payment_amount + elif not d.invoice_portion: + d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount')) def get_order_details(self): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6a550e0e97..974ade3584 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -72,7 +72,8 @@ class BuyingController(StockController, Subcontracting): # set contact and address details for supplier, if they are not mentioned if getattr(self, "supplier", None): self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions, - doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'))) + doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'), + fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'))) self.set_missing_item_details(for_validate) diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index a79eadc761..54df0d63cb 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -76,6 +76,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { if (args) { args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; + args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template) } } if (!args || !args.party) return; diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 6b8a410f58..21a6e16a45 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -619,6 +619,7 @@ def make_purchase_invoice(source_name, target_doc=None): doc.run_method("onload") doc.run_method("set_missing_values") doc.run_method("calculate_taxes_and_totals") + doc.set_payment_schedule() def update_item(source_doc, target_doc, source_parent): target_doc.qty, returned_qty = get_pending_qty(source_doc) @@ -675,10 +676,6 @@ def make_purchase_invoice(source_name, target_doc=None): } }, target_doc, set_missing_values) - automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms')) - if automatically_fetch_payment_terms: - doc.set_payment_schedule() - return doclist def get_invoiced_qty_map(purchase_receipt): From 802c6b37385546f666a03874cc0d420a6f4142d8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 7 Aug 2021 17:39:40 +0530 Subject: [PATCH 224/253] test: Improve test case for not coping payment terms --- .../buying/doctype/purchase_order/purchase_order.py | 1 + .../doctype/purchase_order/test_purchase_order.py | 12 ++++++++---- erpnext/selling/doctype/sales_order/sales_order.py | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 94684bff1c..a0b1e073cc 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -471,6 +471,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions "party_account_currency": "party_account_currency", "supplier_warehouse":"supplier_warehouse" }, + "field_no_map" : ["payment_terms_template"], "validation": { "docstatus": ["=", 1], } diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 0db54e4206..d06f9d2027 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -633,13 +633,17 @@ class TestPurchaseOrder(unittest.TestCase): raise Exception def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self): - po = create_purchase_order() - - self.assertTrue(po.get('payment_schedule')) + po = create_purchase_order(do_not_save=1) + po.payment_terms_template = '_Test Payment Term Template' + po.save() + po.submit() + company = frappe.get_doc('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1') pi = make_pi_from_po(po.name) + pi.save() - self.assertFalse(pi.get('payment_schedule')) + self.assertEqual(pi.get('payment_terms_template'), '_Test Payment Term Template 1') + frappe.db.set_value('Company', '_Test Company', 'payment_terms', '') def test_terms_copied(self): po = create_purchase_order(do_not_save=1) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 2b9d516e21..bba54018ae 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -670,6 +670,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): "party_account_currency": "party_account_currency", "payment_terms_template": "payment_terms_template" }, + "field_no_map": ["payment_terms_template"], "validation": { "docstatus": ["=", 1] } From 8b9c04aae8eb89af21f5f7680966c092271d58b9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 8 Aug 2021 19:17:38 +0530 Subject: [PATCH 225/253] test: Fix test cases for payment terms fetch --- .../buying/doctype/purchase_order/test_purchase_order.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index d06f9d2027..d668c76b6b 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -484,6 +484,9 @@ class TestPurchaseOrder(unittest.TestCase): def test_make_purchase_invoice_with_terms(self): + from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules + + automatically_fetch_payment_terms() po = create_purchase_order(do_not_save=True) self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name) @@ -509,6 +512,7 @@ class TestPurchaseOrder(unittest.TestCase): self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date)) self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0) self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30)) + automatically_fetch_payment_terms(enable=0) def test_subcontracting(self): po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") @@ -638,7 +642,7 @@ class TestPurchaseOrder(unittest.TestCase): po.save() po.submit() - company = frappe.get_doc('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1') + frappe.db.set_value('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1') pi = make_pi_from_po(po.name) pi.save() @@ -992,7 +996,7 @@ class TestPurchaseOrder(unittest.TestCase): # self.assertEqual(po.payment_terms_template, pi.payment_terms_template) compare_payment_schedules(self, po, pi) - automatically_fetch_payment_terms(enable=0) + automatically_fetch_payment_terms(enable=0) def make_pr_against_po(po, received_qty=0): pr = make_purchase_receipt(po) From 7e3dd9e8ee05611f5192a674a8ed6f87545ad389 Mon Sep 17 00:00:00 2001 From: Francisco Roldan Date: Mon, 9 Aug 2021 11:21:41 -0300 Subject: [PATCH 226/253] fix: depends_on in price list field --- .../accounts/doctype/subscription_plan/subscription_plan.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json index 46ce0939e4..771611a786 100644 --- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json +++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json @@ -78,7 +78,7 @@ "label": "Cost" }, { - "depends_on": "eval:doc.price_determination==\"Based on price list\"", + "depends_on": "eval:doc.price_determination==\"Based On Price List\"", "fieldname": "price_list", "fieldtype": "Link", "label": "Price List", @@ -147,7 +147,7 @@ } ], "links": [], - "modified": "2020-06-25 10:53:44.205774", + "modified": "2021-08-09 10:53:44.205774", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription Plan", From 5e428f0447e2b0de930efe0f7924090e54543045 Mon Sep 17 00:00:00 2001 From: Ankush Date: Mon, 9 Aug 2021 22:15:49 +0530 Subject: [PATCH 227/253] perf(cache): fix active SLA doctype caching (#26861) If no SLA is configured then this query runs on EVERY `validate` call. Root cause: if not active SLA doctypes exist then `not []` evalutes to true and causes query to run again. --- .../service_level_agreement/service_level_agreement.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index 8739cb2364..cfa264feb5 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -281,15 +281,18 @@ def get_repeated(values): def get_documents_with_active_service_level_agreement(): - if not frappe.cache().hget("service_level_agreement", "active"): - set_documents_with_active_service_level_agreement() + sla_doctypes = frappe.cache().hget("service_level_agreement", "active") - return frappe.cache().hget("service_level_agreement", "active") + if sla_doctypes is None: + return set_documents_with_active_service_level_agreement() + + return sla_doctypes def set_documents_with_active_service_level_agreement(): active = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])] frappe.cache().hset("service_level_agreement", "active", active) + return active def apply(doc, method=None): From 8db0f07115676c63c40c38d092ca352a794d0f67 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Tue, 10 Aug 2021 11:43:43 +0530 Subject: [PATCH 228/253] fix: Missing method reset_issue_metrics added back to Issue doctype (#26573) --- erpnext/support/doctype/issue/issue.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index b9a65b6749..24dadd5a8c 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -116,6 +116,10 @@ class Issue(Document): }).insert(ignore_permissions=True) return replicated_issue.name + + def reset_issue_metrics(self): + self.db_set("resolution_time", None) + self.db_set("user_resolution_time", None) def get_list_context(context=None): return { From 9ca2febd880c62be7f009b4c29ff456155636ca9 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 10 Aug 2021 13:10:46 +0530 Subject: [PATCH 229/253] fix: Set CWIP Account in company at the start to avoid flaky test --- .../doctype/landed_cost_voucher/test_landed_cost_voucher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 128a2ab62f..cb09d93380 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -253,6 +253,8 @@ class TestLandedCostVoucher(unittest.TestCase): def test_asset_lcv(self): "Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly." + frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC") + if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() @@ -265,7 +267,6 @@ class TestLandedCostVoucher(unittest.TestCase): assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name}) self.assertEqual(len(assets), 1) - frappe.db.set_value("Company", pr.company, "capital_work_in_progress_account", "CWIP Account - _TC") lcv = make_landed_cost_voucher( company = pr.company, receipt_document_type = "Purchase Receipt", From 94beda65ca01724fb3d0d2b57190672083740c61 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:16:46 +0530 Subject: [PATCH 230/253] fix: updating lead status while customer creation (#26606) * fix: updating lead status while customer creation * fix: changes requested --- erpnext/selling/doctype/customer/customer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 66edcd0188..abf146c43f 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -157,9 +157,7 @@ class Customer(TransactionBase): '''If Customer created from Lead, update lead status to "Converted" update Customer link in Quotation, Opportunity''' if self.lead_name: - lead = frappe.get_doc('Lead', self.lead_name) - lead.status = 'Converted' - lead.save() + frappe.db.set_value("Lead", self.lead_name, "status", "Converted") def create_lead_address_contact(self): if self.lead_name: From 948386d85db8fea500443f055d89b75c837b8285 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 10 Aug 2021 14:00:55 +0530 Subject: [PATCH 231/253] test: Serial no sanitation --- .../stock/doctype/serial_no/test_serial_no.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index cde7fe07c6..b9a58cf43e 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -174,5 +174,23 @@ class TestSerialNo(unittest.TestCase): self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC") self.assertEqual(sn_doc.purchase_document_no, se.name) + def test_serial_no_sanitation(self): + "Test if Serial No input is sanitised before entering the DB." + item_code = "_Test Serialized Item" + test_records = frappe.get_test_records('Stock Entry') + + se = frappe.copy_doc(test_records[0]) + se.get("items")[0].item_code = item_code + se.get("items")[0].qty = 3 + se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 " + se.get("items")[0].transfer_qty = 3 + se.set_stock_entry_type() + se.insert() + se.submit() + + self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3") + + frappe.db.rollback() + def tearDown(self): frappe.db.rollback() \ No newline at end of file From af2f5277d5352ab2fc710200e152f4f9c468439c Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:16:16 +0530 Subject: [PATCH 232/253] fix: pos profile not mandatory for Sales Invoice (#26853) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 88899130a2..4e4e5b5aa7 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -480,7 +480,7 @@ class SalesInvoice(SellingController): if not self.pos_profile: pos_profile = get_pos_profile(self.company) or {} if not pos_profile: - frappe.throw(_("No POS Profile found. Please create a New POS Profile first")) + return self.pos_profile = pos_profile.get('name') pos = {} From 2ae2580706774135813a158db989ca74d7dc69de Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 10 Aug 2021 16:37:48 +0530 Subject: [PATCH 233/253] fix(asset): incorrect date difference calculation (#26793) --- erpnext/assets/doctype/asset/test_asset.py | 10 +++++----- erpnext/regional/india/utils.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 59fbe3b030..e23a715452 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -639,7 +639,7 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-12' + asset.available_for_use_date = '2030-07-12' asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 1000, @@ -653,10 +653,10 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 1106.85, 1106.85], - ["2031-12-31", 3446.58, 4553.43], - ["2032-12-31", 1723.29, 6276.72], - ["2033-06-12", 723.28, 7000.00] + ["2030-12-31", 942.47, 942.47], + ["2031-12-31", 3528.77, 4471.24], + ["2032-12-31", 1764.38, 6235.62], + ["2033-07-12", 764.38, 7000.00] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 88c350ac89..a152797a5d 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -851,7 +851,7 @@ def get_depreciation_amount(asset, depreciable_value, row): # if its the first depreciation if depreciable_value == asset.gross_purchase_amount: # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 - diff = date_diff(asset.available_for_use_date, row.depreciation_start_date) + diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) if diff <= 180: rate_of_depreciation = rate_of_depreciation / 2 frappe.msgprint( From 84c4161c001b446dda25b8070d8da16203eec07d Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 10 Aug 2021 16:46:44 +0530 Subject: [PATCH 234/253] fix(e-invoicing): cannot cancel invoice if IRN cancelled on portal (#26638) --- erpnext/patches.txt | 1 + .../v12_0/show_einvoice_irn_cancelled_field.py | 12 ++++++++++++ erpnext/regional/india/setup.py | 4 ++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 35b248c08e..86356e3026 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -294,6 +294,7 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v12_0.show_einvoice_irn_cancelled_field erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst erpnext.patches.v13_0.update_tds_check_field #3 diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py new file mode 100644 index 0000000000..2319c17b34 --- /dev/null +++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'}) + if irn_cancelled_field: + frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn') + frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index e9372f9b8f..b4f146ce57 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -457,7 +457,7 @@ def make_custom_fields(update=True): depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, - depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'), dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), @@ -985,4 +985,4 @@ def create_gratuity_rule(): def update_accounts_settings_for_taxes(): if frappe.db.count('Company') == 1: - frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) \ No newline at end of file + frappe.db.set_value('Accounts Settings', None, "add_taxes_from_item_tax_template", 0) From 1cba77cfbd1e3684603795eabd52d6af4284167a Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 10 Aug 2021 17:25:12 +0530 Subject: [PATCH 235/253] fix: Sales Return cancellation if linked with Payment Entry (#26551) --- .../purchase_invoice/purchase_invoice.py | 4 +- .../doctype/sales_invoice/sales_invoice.py | 46 +++++++++++++++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 863c104dff..25f42bce84 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -22,7 +22,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accoun from frappe.model.mapper import get_mapped_doc from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\ - unlink_inter_company_doc + unlink_inter_company_doc, check_if_return_invoice_linked_with_payment_entry from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost @@ -1014,6 +1014,8 @@ class PurchaseInvoice(BuyingController): }, item=self)) def on_cancel(self): + check_if_return_invoice_linked_with_payment_entry(self) + super(PurchaseInvoice, self).on_cancel() self.check_on_hold_or_closed_status() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 4e4e5b5aa7..cecc1a18df 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -290,6 +290,8 @@ class SalesInvoice(SellingController): self.update_time_sheet(None) def on_cancel(self): + check_if_return_invoice_linked_with_payment_entry(self) + super(SalesInvoice, self).on_cancel() self.check_sales_order_on_hold_or_close("sales_order") @@ -922,7 +924,7 @@ class SalesInvoice(SellingController): asset = frappe.get_doc("Asset", item.asset) else: frappe.throw(_( - "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), + "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), title=_("Missing Asset") ) if (len(asset.finance_books) > 1 and not item.finance_book @@ -944,7 +946,7 @@ class SalesInvoice(SellingController): gl_entries.append(self.get_gl_dict(gle, item=item)) self.set_asset_status(asset) - + else: # Do not book income for transfer within same company if not self.is_internal_transfer(): @@ -973,7 +975,7 @@ class SalesInvoice(SellingController): def set_asset_status(self, asset): if self.is_return: asset.set_status() - else: + else: asset.set_status("Sold" if self.docstatus==1 else None) def make_loyalty_point_redemption_gle(self, gl_entries): @@ -1941,3 +1943,41 @@ def create_dunning(source_name, target_doc=None): } }, target_doc, set_missing_values) return doclist + +def check_if_return_invoice_linked_with_payment_entry(self): + # If a Return invoice is linked with payment entry along with other invoices, + # the cancellation of the Return causes allocated amount to be greater than paid + + if not frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): + return + + payment_entries = [] + if self.is_return and self.return_against: + invoice = self.return_against + else: + invoice = self.name + + payment_entries = frappe.db.sql_list(""" + SELECT + t1.name + FROM + `tabPayment Entry` t1, `tabPayment Entry Reference` t2 + WHERE + t1.name = t2.parent + and t1.docstatus = 1 + and t2.reference_name = %s + and t2.allocated_amount < 0 + """, invoice) + + links_to_pe = [] + if payment_entries: + for payment in payment_entries: + payment_entry = frappe.get_doc("Payment Entry", payment) + if len(payment_entry.references) > 1: + links_to_pe.append(payment_entry.name) + if links_to_pe: + payment_entries_link = [get_link_to_form('Payment Entry', name, label=name) for name in links_to_pe] + message = _("Please cancel and amend the Payment Entry") + message += " " + ", ".join(payment_entries_link) + " " + message += _("to unallocate the amount of this Return Invoice before cancelling it.") + frappe.throw(message) From 0e337be06573d40ad1f239a38e95953761fe497d Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 10 Aug 2021 17:26:35 +0530 Subject: [PATCH 236/253] fix: cost center & account validation in Sales/Purchase Taxes and Charges (#25929) --- .../sales_taxes_and_charges_template.py | 4 +++- .../test_records.json | 8 +++++++ erpnext/controllers/accounts_controller.py | 21 +++++++++++++++++++ erpnext/public/js/controllers/accounts.js | 8 +++++++ erpnext/setup/doctype/company/company.py | 6 +++--- .../setup_wizard/operations/taxes_setup.py | 3 ++- 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py index 52d19d54a8..8f9eb6577b 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.utils import flt from frappe.model.document import Document -from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax +from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax, validate_cost_center, validate_account_head class SalesTaxesandChargesTemplate(Document): def validate(self): @@ -39,6 +39,8 @@ def valdiate_taxes_and_charges_template(doc): for tax in doc.get("taxes"): validate_taxes_and_charges(tax) + validate_account_head(tax, doc) + validate_cost_center(tax, doc) validate_inclusive_tax(tax, doc) def validate_disabled(doc): diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json index 2b737b9804..74db08d5b8 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/test_records.json @@ -8,6 +8,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 6 }, @@ -16,6 +17,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 6.36 } @@ -114,6 +116,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -122,6 +125,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } @@ -137,6 +141,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -145,6 +150,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } @@ -160,6 +166,7 @@ "charge_type": "On Net Total", "description": "VAT", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 12 }, @@ -168,6 +175,7 @@ "charge_type": "On Net Total", "description": "Service Tax", "doctype": "Sales Taxes and Charges", + "cost_center": "Main - _TC", "parentfield": "taxes", "rate": 4 } diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9e2e5968c6..bc8e4cea2d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1288,6 +1288,27 @@ def validate_taxes_and_charges(tax): tax.rate = None +def validate_account_head(tax, doc): + company = frappe.get_cached_value('Account', + tax.account_head, 'company') + + if company != doc.company: + frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}') + .format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account')) + + +def validate_cost_center(tax, doc): + if not tax.cost_center: + return + + company = frappe.get_cached_value('Cost Center', + tax.cost_center, 'company') + + if company != doc.company: + frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}') + .format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center')) + + def validate_inclusive_tax(tax, doc): def _on_previous_row_error(row_range): throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range)) diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js index 7b997a1153..84c717676c 100644 --- a/erpnext/public/js/controllers/accounts.js +++ b/erpnext/public/js/controllers/accounts.js @@ -31,6 +31,14 @@ frappe.ui.form.on(cur_frm.doctype, { } } }); + frm.set_query("cost_center", "taxes", function(doc) { + return { + filters: { + "company": doc.company, + "is_group": 0 + } + }; + }); } }, validate: function(frm) { diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 8755125c81..95cbf5150c 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -108,6 +108,9 @@ class Company(NestedSet): frappe.flags.country_change = True self.create_default_accounts() self.create_default_warehouses() + + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): + self.create_default_cost_center() if frappe.flags.country_change: install_country_fixtures(self.name, self.country) @@ -117,9 +120,6 @@ class Company(NestedSet): from erpnext.setup.setup_wizard.operations.install_fixtures import install_post_company_fixtures install_post_company_fixtures(frappe._dict({'company_name': self.name})) - if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): - self.create_default_cost_center() - if not frappe.local.flags.ignore_chart_of_accounts: self.set_default_accounts() if self.default_cash_account: diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index cbb3dc881f..bacada9f5c 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -124,7 +124,8 @@ def make_taxes_and_charges_template(company_name, doctype, template): account_data = tax_row.get('account_head') tax_row_defaults = { 'category': 'Total', - 'charge_type': 'On Net Total' + 'charge_type': 'On Net Total', + 'cost_center': frappe.db.get_value('Company', company_name, 'cost_center') } if doctype == 'Purchase Taxes and Charges Template': From 55d8aaf0b66f8cf3178a28c65455aabf186ab96b Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 27 Jul 2021 16:53:55 +0530 Subject: [PATCH 237/253] fix: Stock Analytics Report must consider warehouse during calculation --- .../report/stock_analytics/stock_analytics.py | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py index 0cc8ca48aa..d44685060c 100644 --- a/erpnext/stock/report/stock_analytics/stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/stock_analytics.py @@ -114,14 +114,41 @@ def get_period(posting_date, filters): def get_periodic_data(entry, filters): + """Structured as: + Item 1 + - Balance (updated and carried forward): + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jun 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jul 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + Item 2 + - Balance (updated and carried forward): + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jun 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + - Jul 2021 (sum of warehouse quantities used in report) + - Warehouse A : bal_qty/value + - Warehouse B : bal_qty/value + """ periodic_data = {} for d in entry: period = get_period(d.posting_date, filters) bal_qty = 0 + # if period against item does not exist yet, instantiate it + # insert existing balance dict against period, and add/subtract to it + if periodic_data.get(d.item_code) and not periodic_data.get(d.item_code).get(period): + periodic_data[d.item_code][period] = periodic_data[d.item_code]['balance'] + if d.voucher_type == "Stock Reconciliation": - if periodic_data.get(d.item_code): - bal_qty = periodic_data[d.item_code]["balance"] + if periodic_data.get(d.item_code) and periodic_data.get(d.item_code).get('balance').get(d.warehouse): + bal_qty = periodic_data[d.item_code]['balance'][d.warehouse] qty_diff = d.qty_after_transaction - bal_qty else: @@ -132,12 +159,12 @@ def get_periodic_data(entry, filters): else: value = d.stock_value_difference - periodic_data.setdefault(d.item_code, {}).setdefault(period, 0.0) - periodic_data.setdefault(d.item_code, {}).setdefault("balance", 0.0) - - periodic_data[d.item_code]["balance"] += value - periodic_data[d.item_code][period] = periodic_data[d.item_code]["balance"] + # period-warehouse wise balance + periodic_data.setdefault(d.item_code, {}).setdefault('balance', {}).setdefault(d.warehouse, 0.0) + periodic_data.setdefault(d.item_code, {}).setdefault(period, {}).setdefault(d.warehouse, 0.0) + periodic_data[d.item_code]['balance'][d.warehouse] += value + periodic_data[d.item_code][period][d.warehouse] = periodic_data[d.item_code]['balance'][d.warehouse] return periodic_data @@ -160,7 +187,8 @@ def get_data(filters): total = 0 for dummy, end_date in ranges: period = get_period(end_date, filters) - amount = flt(periodic_data.get(item_data.name, {}).get(period)) + period_data = periodic_data.get(item_data.name, {}).get(period) + amount = sum(period_data.values()) if period_data else 0 row[scrub(period)] = amount total += amount row["total"] = total From eb2050b40731131a3c578c50edc74f193a7ebf3b Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 10 Aug 2021 20:37:09 +0530 Subject: [PATCH 238/253] fix: Brand filter in Stock Analytics --- erpnext/stock/report/stock_balance/stock_balance.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 9e56ad4130..fc3d719a78 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -235,12 +235,15 @@ def filter_items_with_no_transactions(iwb_map, float_precision): return iwb_map def get_items(filters): + "Get items based on item code, item group or brand." conditions = [] if filters.get("item_code"): conditions.append("item.name=%(item_code)s") else: if filters.get("item_group"): conditions.append(get_item_group_condition(filters.get("item_group"))) + if filters.get("brand"): # used in stock analytics report + conditions.append("item.brand=%(brand)s") items = [] if conditions: From b614834efedbef572e0567828f0d9d82e81331ee Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 10 Aug 2021 21:33:58 +0530 Subject: [PATCH 239/253] fix: unseting of payment if no pos profile found (#26884) --- erpnext/controllers/taxes_and_totals.py | 6 +----- erpnext/public/js/controllers/taxes_and_totals.js | 2 -- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 099c7d4346..05edb2530c 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -679,17 +679,13 @@ class calculate_taxes_and_totals(object): default_mode_of_payment = frappe.db.get_value('POS Payment Method', {'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1) - self.doc.payments = [] - if default_mode_of_payment: + self.doc.payments = [] self.doc.append('payments', { 'mode_of_payment': default_mode_of_payment.mode_of_payment, 'amount': total_amount_to_pay, 'default': 1 }) - else: - self.doc.is_pos = 0 - self.doc.pos_profile = '' self.calculate_paid_amount() diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index a495a9b0c1..84697e0f00 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -751,8 +751,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { this.frm.doc.payments.find(pay => { if (pay.default) { pay.amount = total_amount_to_pay; - } else { - pay.amount = 0.0 } }); this.frm.refresh_fields(); From bff3b0962a903dddf13f80eb1e8b8dc028c35559 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 7 Aug 2021 00:12:57 +0530 Subject: [PATCH 240/253] fix: Override template only if setting is enabled --- erpnext/controllers/accounts_controller.py | 3 ++- erpnext/public/js/utils/party.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bc22ff7495..e10a9e7bfd 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1108,7 +1108,8 @@ class AccountsController(TransactionBase): base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) if not self.get("payment_schedule"): - if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): + if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \ + and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype): self.fetch_payment_terms_from_order(po_or_so, doctype) if self.get('payment_terms_template'): self.ignore_default_payment_terms_template = 1 diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index 54df0d63cb..4d432e3d5c 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -76,7 +76,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { if (args) { args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; - args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template) + args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template); } } if (!args || !args.party) return; From 9152715f9049585e8d59967dd5c4fef15f04428c Mon Sep 17 00:00:00 2001 From: Ankush Date: Wed, 11 Aug 2021 11:17:50 +0530 Subject: [PATCH 241/253] perf: various minor perf fixes for ledger postings (#26775) * perf: only validate if voucher is journal entry * perf: optimize merge GLE - Order fields such that comparison will fail faster - Break out of loops if not matched * perf: don't try to match SLE if count mismatch * refactor: simplify initialize_previous_data * perf: use cache for fetching valuation_method These are set only once fields * refactor: simplify get_future_stock_vouchers * refactor: simplify get_voucherwise_gl_entries * perf: fetch only required fields for GL comparison `select *` fetches all fields, output of this function is only used for comparing. * perf: reorder conditions in PL cost center check * perf: reduce query while validating new gle * perf: use cache for validating warehouse props These properties don't change often, no need to query everytime. * perf: use cached stock settings to validate SLE * docs: update misleading docstring Co-authored-by: Marica --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 22 ++++++----- erpnext/accounts/general_ledger.py | 25 ++++++++----- erpnext/accounts/utils.py | 37 +++++++++++++------ .../stock_ledger_entry/stock_ledger_entry.py | 4 +- erpnext/stock/stock_ledger.py | 12 +++--- erpnext/stock/utils.py | 8 ++-- 6 files changed, 65 insertions(+), 43 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 11465b711e..0844995f29 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -58,8 +58,8 @@ class GLEntry(Document): if not self.get(k): frappe.throw(_("{0} is required").format(_(self.meta.get_label(k)))) - account_type = frappe.get_cached_value("Account", self.account, "account_type") if not (self.party_type and self.party): + account_type = frappe.get_cached_value("Account", self.account, "account_type") if account_type == "Receivable": frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}") .format(self.voucher_type, self.voucher_no, self.account)) @@ -73,15 +73,19 @@ class GLEntry(Document): .format(self.voucher_type, self.voucher_no, self.account)) def pl_must_have_cost_center(self): - if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": - if not self.cost_center and self.voucher_type != 'Period Closing Voucher': - msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( - self.voucher_type, self.voucher_no, self.account) - msg += " " - msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( - self.voucher_type) + """Validate that profit and loss type account GL entries have a cost center.""" - frappe.throw(msg, title=_("Missing Cost Center")) + if self.cost_center or self.voucher_type == 'Period Closing Voucher': + return + + if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss": + msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format( + self.voucher_type, self.voucher_no, self.account) + msg += " " + msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format( + self.voucher_type) + + frappe.throw(msg, title=_("Missing Cost Center")) def validate_dimensions_for_pl_and_bs(self): account_type = frappe.db.get_value("Account", self.account, "report_type") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 25d2cf10bd..4c7c567b42 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -100,8 +100,8 @@ def merge_similar_entries(gl_map, precision=None): return merged_gl_map def check_if_in_list(gle, gl_map, dimensions=None): - account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type', - 'cost_center', 'project', 'voucher_detail_no'] + account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher', + 'cost_center', 'against_voucher_type', 'party_type', 'project'] if dimensions: account_head_fieldnames = account_head_fieldnames + dimensions @@ -110,10 +110,12 @@ def check_if_in_list(gle, gl_map, dimensions=None): same_head = True if e.account != gle.account: same_head = False + continue for fieldname in account_head_fieldnames: if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)): same_head = False + break if same_head: return e @@ -143,16 +145,19 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): validate_expense_against_budget(args) def validate_cwip_accounts(gl_map): - cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")) + """Validate that CWIP account are not used in Journal Entry""" + if gl_map and gl_map[0].voucher_type != "Journal Entry": + return - if cwip_enabled and gl_map[0].voucher_type == "Journal Entry": - cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount - where account_type = 'Capital Work in Progress' and is_group=0""")] + cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting")) + if cwip_enabled: + cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount + where account_type = 'Capital Work in Progress' and is_group=0""")] - for entry in gl_map: - if entry.account in cwip_accounts: - frappe.throw( - _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) + for entry in gl_map: + if entry.account in cwip_accounts: + frappe.throw( + _("Account: {0} is capital Work in progress and can not be updated by Journal Entry").format(entry.account)) def round_off_debit_credit(gl_map): precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9272bc4fce..5b58e874fe 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -920,7 +920,6 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa _delete_gl_entries(voucher_type, voucher_no) def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None): - future_stock_vouchers = [] values = [] condition = "" @@ -936,30 +935,46 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f condition += " and company = %s" values.append(company) - for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no + future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no from `tabStock Ledger Entry` sle where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) and is_cancelled = 0 {condition} order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), - tuple([posting_date, posting_time] + values), as_dict=True): - future_stock_vouchers.append([d.voucher_type, d.voucher_no]) + tuple([posting_date, posting_time] + values), as_dict=True) - return future_stock_vouchers + return [(d.voucher_type, d.voucher_no) for d in future_stock_vouchers] def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): + """ Get voucherwise list of GL entries. + + Only fetches GLE fields required for comparing with new GLE. + Check compare_existing_and_expected_gle function below. + """ gl_entries = {} - if future_stock_vouchers: - for d in frappe.db.sql("""select * from `tabGL Entry` - where posting_date >= %s and voucher_no in (%s)""" % - ('%s', ', '.join(['%s']*len(future_stock_vouchers))), - tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): - gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) + if not future_stock_vouchers: + return gl_entries + + voucher_nos = [d[1] for d in future_stock_vouchers] + + gles = frappe.db.sql(""" + select name, account, credit, debit, cost_center, project + from `tabGL Entry` + where + posting_date >= %s and voucher_no in (%s)""" % + ('%s', ', '.join(['%s'] * len(voucher_nos))), + tuple([posting_date] + voucher_nos), as_dict=1) + + for d in gles: + gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) return gl_entries def compare_existing_and_expected_gle(existing_gle, expected_gle, precision): + if len(existing_gle) != len(expected_gle): + return False + matched = True for entry in expected_gle: account_existed = False diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index b4f458388b..be1f00e37f 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -55,8 +55,8 @@ class StockLedgerEntry(Document): "sum(actual_qty)") or 0 frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty) - #check for item quantity available in stock def actual_amt_check(self): + """Validate that qty at warehouse for selected batch is >=0""" if self.batch_no and not self.get("allow_negative_stock"): batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty) from `tabStock Ledger Entry` @@ -107,7 +107,7 @@ class StockLedgerEntry(Document): self.stock_uom = item_det.stock_uom def check_stock_frozen_date(self): - stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings') + stock_settings = frappe.get_cached_doc('Stock Settings') if stock_settings.stock_frozen_upto: if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index f990ce06be..eddd048c74 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -279,15 +279,13 @@ class update_entries_after(object): } """ - self.data.setdefault(args.warehouse, frappe._dict()) - warehouse_dict = self.data[args.warehouse] previous_sle = get_previous_sle_of_current_voucher(args) - warehouse_dict.previous_sle = previous_sle - for key in ("qty_after_transaction", "valuation_rate", "stock_value"): - setattr(warehouse_dict, key, flt(previous_sle.get(key))) - - warehouse_dict.update({ + self.data[args.warehouse] = frappe._dict({ + "previous_sle": previous_sle, + "qty_after_transaction": flt(previous_sle.qty_after_transaction), + "valuation_rate": flt(previous_sle.valuation_rate), + "stock_value": flt(previous_sle.stock_value), "prev_stock_value": previous_sle.stock_value or 0.0, "stock_queue": json.loads(previous_sle.stock_queue or "[]"), "stock_value_difference": 0.0 diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index b57b2aa6b8..9f6d0a8add 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -224,7 +224,7 @@ def get_avg_purchase_rate(serial_nos): def get_valuation_method(item_code): """get valuation method from item or default""" - val_method = frappe.db.get_value('Item', item_code, 'valuation_method') + val_method = frappe.db.get_value('Item', item_code, 'valuation_method', cache=True) if not val_method: val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO" return val_method @@ -275,17 +275,17 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''): return valid_serial_nos def validate_warehouse_company(warehouse, company): - warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company") + warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company", cache=True) if warehouse_company and warehouse_company != company: frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company), InvalidWarehouseCompany) def is_group_warehouse(warehouse): - if frappe.db.get_value("Warehouse", warehouse, "is_group"): + if frappe.db.get_value("Warehouse", warehouse, "is_group", cache=True): frappe.throw(_("Group node warehouse is not allowed to select for transactions")) def validate_disabled_warehouse(warehouse): - if frappe.db.get_value("Warehouse", warehouse, "disabled"): + if frappe.db.get_value("Warehouse", warehouse, "disabled", cache=True): frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse))) def update_included_uom_in_report(columns, result, include_uom, conversion_factors): From e5b02216933a71c06457920e5f3a58a06ffa5bbc Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 13:02:49 +0530 Subject: [PATCH 242/253] test: fix attendance request tests - Use `frappe.db.get_value` instead of `get_doc` for asserting values - Get values after cancellation as reloading attendance doc breaks due to stale doc (primary key changed after cancel of attendance request) - rollback everything on tearDown --- .../test_attendance_request.py | 68 ++++++++++++++----- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py index 3c42bd9fc3..0898476edf 100644 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py @@ -15,6 +15,9 @@ class TestAttendanceRequest(unittest.TestCase): for doctype in ["Attendance Request", "Attendance"]: frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) + def tearDown(self): + frappe.db.rollback() + def test_on_duty_attendance_request(self): today = nowdate() employee = get_employee() @@ -26,15 +29,33 @@ class TestAttendanceRequest(unittest.TestCase): attendance_request.company = "_Test Company" attendance_request.insert() attendance_request.submit() - attendance = frappe.get_doc('Attendance', { - 'employee': employee.name, - 'attendance_date': date(date.today().year, 1, 1), - 'docstatus': 1 - }) - self.assertEqual(attendance.status, 'Present') + + attendance = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname=["status", "docstatus"], + as_dict=True + ) + self.assertEqual(attendance.status, "Present") + self.assertEqual(attendance.docstatus, 1) + + # cancelling attendance request cancels linked attendances attendance_request.cancel() - attendance.reload() - self.assertEqual(attendance.docstatus, 2) + + # cancellation alters docname + # fetch attendance value again to avoid stale docname + attendance_docstatus = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="docstatus" + ) + self.assertEqual(attendance_docstatus, 2) def test_work_from_home_attendance_request(self): today = nowdate() @@ -47,15 +68,30 @@ class TestAttendanceRequest(unittest.TestCase): attendance_request.company = "_Test Company" attendance_request.insert() attendance_request.submit() - attendance = frappe.get_doc('Attendance', { - 'employee': employee.name, - 'attendance_date': date(date.today().year, 1, 1), - 'docstatus': 1 - }) - self.assertEqual(attendance.status, 'Work From Home') + + attendance_status = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="status" + ) + self.assertEqual(attendance_status, 'Work From Home') + attendance_request.cancel() - attendance.reload() - self.assertEqual(attendance.docstatus, 2) + + # cancellation alters docname + # fetch attendance value again to avoid stale docname + attendance_docstatus = frappe.db.get_value( + "Attendance", + filters={ + "attendance_request": attendance_request.name, + "attendance_date": date(date.today().year, 1, 1) + }, + fieldname="docstatus" + ) + self.assertEqual(attendance_docstatus, 2) def get_employee(): return frappe.get_doc("Employee", "_T-Employee-00001") From bca30d6101f539b4f68c536e83a82c1cd29b1bb7 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 13:29:44 +0530 Subject: [PATCH 243/253] test: fix Shift Request test - Use `get_value` instead of `get_doc` - Remove unnecessary loop, only one shift assignment is made against a shift request - Get value after cancel again. Get doc is not reliable since primary key changed after cancel --- .../test_attendance_request.py | 2 ++ .../shift_request/test_shift_request.py | 33 ++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py index 0898476edf..9e668aa72f 100644 --- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py +++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py @@ -19,6 +19,7 @@ class TestAttendanceRequest(unittest.TestCase): frappe.db.rollback() def test_on_duty_attendance_request(self): + "Test creation/updation of Attendace from Attendance Request, on duty." today = nowdate() employee = get_employee() attendance_request = frappe.new_doc("Attendance Request") @@ -58,6 +59,7 @@ class TestAttendanceRequest(unittest.TestCase): self.assertEqual(attendance_docstatus, 2) def test_work_from_home_attendance_request(self): + "Test creation/updation of Attendace from Attendance Request, work from home." today = nowdate() employee = get_employee() attendance_request = frappe.new_doc("Attendance Request") diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 9c0d8e3198..3525540cdf 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -15,24 +15,35 @@ class TestShiftRequest(unittest.TestCase): for doctype in ["Shift Request", "Shift Assignment"]: frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) + def tearDown(self): + frappe.db.rollback() + def test_make_shift_request(self): + "Test creation/updation of Shift Assignment from Shift Request." department = frappe.get_value("Employee", "_T-Employee-00001", 'department') set_shift_approver(department) approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0] shift_request = make_shift_request(approver) - shift_assignments = frappe.db.sql(''' - SELECT shift_request, employee - FROM `tabShift Assignment` - WHERE shift_request = '{0}' - '''.format(shift_request.name), as_dict=1) - for d in shift_assignments: - employee = d.get('employee') - self.assertEqual(shift_request.employee, employee) - shift_request.cancel() - shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')}) - self.assertEqual(shift_assignment_doc.docstatus, 2) + # Only one shift assignment is created against a shift request + shift_assignment = frappe.db.get_value( + "Shift Assignment", + filters={"shift_request": shift_request.name}, + fieldname=["employee", "docstatus"], + as_dict=True + ) + self.assertEqual(shift_request.employee, shift_assignment.employee) + self.assertEqual(shift_assignment.docstatus, 1) + + shift_request.cancel() + + shift_assignment_docstatus = frappe.db.get_value( + "Shift Assignment", + filters={"shift_request": shift_request.name}, + fieldname="docstatus" + ) + self.assertEqual(shift_assignment_docstatus, 2) def test_shift_request_approver_perms(self): employee = frappe.get_doc("Employee", "_T-Employee-00001") From 8f1a3aef2e3f14e179f4ac91718e9f376b34198c Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 15:17:06 +0530 Subject: [PATCH 244/253] test: fix POS Closing Entry Test - Separated into two tests, one checks if SI cancelling is blocked, the other checks PCE cancel impact - This is done because after cancel via assertRaises, damage done by cancel still exists or is partially comitted - Dont use this partially cancelled doc for any assertions further, end test at exception assertion - Use `get_value` to check SI docstatus, as its primary key changes after cancel --- .../test_pos_closing_entry.py | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index b596c0cf25..0265f43476 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -19,6 +19,7 @@ class TestPOSClosingEntry(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.rollback() def test_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() @@ -50,7 +51,8 @@ class TestPOSClosingEntry(unittest.TestCase): self.assertEqual(pcv_doc.total_quantity, 2) self.assertEqual(pcv_doc.net_total, 6700) - def test_cancelling_of_pos_closing_entry(self): + def test_cancelling_of_consolidated_sales_invoice(self): + "Check if cancelling consolidated Sales Invoice with submitted POS Closing Entry is blocked." test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -83,13 +85,46 @@ class TestPOSClosingEntry(unittest.TestCase): si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice) self.assertRaises(frappe.ValidationError, si_doc.cancel) + def test_cancelling_of_pos_closing_entry(self): + "Check impact of cancelling POS Closing Entry." + test_user, pos_profile = init_user_and_profile() + opening_entry = create_opening_entry(pos_profile, test_user.name) + + pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) + pos_inv1.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500 + }) + pos_inv1.submit() + + pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) + pos_inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 + }) + pos_inv2.submit() + + pcv_doc = make_closing_entry_from_opening(opening_entry) + payment = pcv_doc.payment_reconciliation[0] + + for d in pcv_doc.payment_reconciliation: + if d.mode_of_payment == 'Cash': + d.closing_amount = 6700 + + pcv_doc.submit() + + pos_inv1.load_from_db() + si_name = pos_inv1.consolidated_invoice + pcv_doc.load_from_db() pcv_doc.cancel() - si_doc.load_from_db() pos_inv1.load_from_db() - self.assertEqual(si_doc.docstatus, 2) - self.assertEqual(pos_inv1.status, 'Paid') + # After POS Closing Entry cancel, SI doc gets cancelled, unlinked and renamed + # There's no reference doc to fetch SI with new cancelled name + # which is why we are hardcoding suffix + si_docstatus = frappe.db.get_value("Sales Invoice", si_name+"-CANC-0", "docstatus") + self.assertEqual(si_docstatus, 2) + self.assertEqual(pos_inv1.status, 'Paid') + self.assertIsNone(pos_inv1.consolidated_invoice) def init_user_and_profile(**args): user = 'test@example.com' From 867939fcae12c689c145c0d8610a469508f1b900 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 16:40:07 +0530 Subject: [PATCH 245/253] test: fixed asset movement tests - set cwip account in company to avoid value missing - removed unused statement - removed trailing spaces --- .../test_pos_closing_entry.py | 1 - .../asset_movement/test_asset_movement.py | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index 0265f43476..c585f310b1 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -103,7 +103,6 @@ class TestPOSClosingEntry(unittest.TestCase): pos_inv2.submit() pcv_doc = make_closing_entry_from_opening(opening_entry) - payment = pcv_doc.payment_reconciliation[0] for d in pcv_doc.payment_reconciliation: if d.mode_of_payment == 'Cash': diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index cddee5fa0f..985bca9d0d 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -15,6 +15,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu class TestAssetMovement(unittest.TestCase): def setUp(self): + frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC") create_asset_data() make_location() @@ -45,12 +46,12 @@ class TestAssetMovement(unittest.TestCase): 'location_name': 'Test Location 2' }).insert() - movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") @@ -59,18 +60,18 @@ class TestAssetMovement(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") employee = make_employee("testassetmovemp@example.com", company="_Test Company") - movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, + movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) - + # after issuing asset should belong to an employee not at a location self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None) self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee) - + def test_last_movement_cancellation(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") - + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 @@ -85,17 +86,17 @@ class TestAssetMovement(unittest.TestCase): }) if asset.docstatus == 0: asset.submit() - + if not frappe.db.exists("Location", "Test Location 2"): frappe.get_doc({ 'doctype': 'Location', 'location_name': 'Test Location 2' }).insert() - + movement = frappe.get_doc({'doctype': 'Asset Movement', 'reference_name': pr.name }) self.assertRaises(frappe.ValidationError, movement.cancel) - movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, + movement1 = create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'target_location': 'Test Location 2'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") From 455d300fca044643eb37a78568e123a62a94ef38 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Aug 2021 17:00:46 +0530 Subject: [PATCH 246/253] Revert "test: fix POS Closing Entry Test" This reverts commit 8f1a3aef2e3f14e179f4ac91718e9f376b34198c. --- .../test_pos_closing_entry.py | 42 ++----------------- .../asset_movement/test_asset_movement.py | 4 +- 2 files changed, 6 insertions(+), 40 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index c585f310b1..b596c0cf25 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -19,7 +19,6 @@ class TestPOSClosingEntry(unittest.TestCase): def tearDown(self): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") - frappe.db.rollback() def test_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() @@ -51,8 +50,7 @@ class TestPOSClosingEntry(unittest.TestCase): self.assertEqual(pcv_doc.total_quantity, 2) self.assertEqual(pcv_doc.net_total, 6700) - def test_cancelling_of_consolidated_sales_invoice(self): - "Check if cancelling consolidated Sales Invoice with submitted POS Closing Entry is blocked." + def test_cancelling_of_pos_closing_entry(self): test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name) @@ -85,45 +83,13 @@ class TestPOSClosingEntry(unittest.TestCase): si_doc = frappe.get_doc("Sales Invoice", pos_inv1.consolidated_invoice) self.assertRaises(frappe.ValidationError, si_doc.cancel) - def test_cancelling_of_pos_closing_entry(self): - "Check impact of cancelling POS Closing Entry." - test_user, pos_profile = init_user_and_profile() - opening_entry = create_opening_entry(pos_profile, test_user.name) - - pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1) - pos_inv1.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3500 - }) - pos_inv1.submit() - - pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) - pos_inv2.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200 - }) - pos_inv2.submit() - - pcv_doc = make_closing_entry_from_opening(opening_entry) - - for d in pcv_doc.payment_reconciliation: - if d.mode_of_payment == 'Cash': - d.closing_amount = 6700 - - pcv_doc.submit() - - pos_inv1.load_from_db() - si_name = pos_inv1.consolidated_invoice - pcv_doc.load_from_db() pcv_doc.cancel() + si_doc.load_from_db() pos_inv1.load_from_db() - - # After POS Closing Entry cancel, SI doc gets cancelled, unlinked and renamed - # There's no reference doc to fetch SI with new cancelled name - # which is why we are hardcoding suffix - si_docstatus = frappe.db.get_value("Sales Invoice", si_name+"-CANC-0", "docstatus") - self.assertEqual(si_docstatus, 2) + self.assertEqual(si_doc.docstatus, 2) self.assertEqual(pos_inv1.status, 'Paid') - self.assertIsNone(pos_inv1.consolidated_invoice) + def init_user_and_profile(**args): user = 'test@example.com' diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.py b/erpnext/assets/doctype/asset_movement/test_asset_movement.py index 985bca9d0d..2b2d2b4400 100644 --- a/erpnext/assets/doctype/asset_movement/test_asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/test_asset_movement.py @@ -51,7 +51,7 @@ class TestAssetMovement(unittest.TestCase): reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2") - movement2 = create_asset_movement(purpose = 'Transfer', company = asset.company, + create_asset_movement(purpose = 'Transfer', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location 2', 'target_location': 'Test Location'}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") @@ -60,7 +60,7 @@ class TestAssetMovement(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location") employee = make_employee("testassetmovemp@example.com", company="_Test Company") - movement3 = create_asset_movement(purpose = 'Issue', company = asset.company, + create_asset_movement(purpose = 'Issue', company = asset.company, assets = [{ 'asset': asset.name , 'source_location': 'Test Location', 'to_employee': employee}], reference_doctype = 'Purchase Receipt', reference_name = pr.name) From 54c31ed33c6112cb9040470dc40cbb78c489d418 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Thu, 12 Aug 2021 13:42:56 +0530 Subject: [PATCH 247/253] feat: depreciate asset after sale (#26543) --- .../doctype/sales_invoice/sales_invoice.py | 106 +++++++++++++++--- .../sales_invoice/test_sales_invoice.py | 25 +++++ erpnext/assets/doctype/asset/asset.py | 44 ++++++-- 3 files changed, 155 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index cecc1a18df..51548e9a5b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext import frappe.defaults -from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, get_link_to_form, formatdate +from frappe.utils import cint, flt, getdate, add_days, add_months, cstr, nowdate, get_link_to_form, formatdate from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date, get_party_details from frappe.model.mapper import get_mapped_doc @@ -13,7 +13,7 @@ from erpnext.accounts.utils import get_account_currency from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.assets.doctype.asset.depreciation \ - import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain + import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, post_depreciation_entries from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no from erpnext.setup.doctype.company.company import update_company_current_month_sales @@ -920,27 +920,24 @@ class SalesInvoice(SellingController): for item in self.get("items"): if flt(item.base_net_amount, item.precision("base_net_amount")): if item.is_fixed_asset: - if item.get('asset'): - asset = frappe.get_doc("Asset", item.asset) - else: - frappe.throw(_( - "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), - title=_("Missing Asset") - ) - if (len(asset.finance_books) > 1 and not item.finance_book - and asset.finance_books[0].finance_book): - frappe.throw(_("Select finance book for the item {0} at row {1}") - .format(item.item_code, item.idx)) + asset = self.get_asset(item) if self.is_return: fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset, item.base_net_amount, item.finance_book) asset.db_set("disposal_date", None) + + if asset.calculate_depreciation: + self.reset_depreciation_schedule(asset) + else: fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset, item.base_net_amount, item.finance_book) asset.db_set("disposal_date", self.posting_date) + if asset.calculate_depreciation: + self.depreciate_asset(asset) + for gle in fixed_asset_gl_entries: gle["against"] = self.customer gl_entries.append(self.get_gl_dict(gle, item=item)) @@ -972,6 +969,89 @@ class SalesInvoice(SellingController): erpnext.is_perpetual_inventory_enabled(self.company): gl_entries += super(SalesInvoice, self).get_gl_entries() + def get_asset(self, item): + if item.get('asset'): + asset = frappe.get_doc("Asset", item.asset) + else: + frappe.throw(_( + "Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name), + title=_("Missing Asset") + ) + + self.check_finance_books(item, asset) + return asset + + def check_finance_books(self, item, asset): + if (len(asset.finance_books) > 1 and not item.finance_book + and asset.finance_books[0].finance_book): + frappe.throw(_("Select finance book for the item {0} at row {1}") + .format(item.item_code, item.idx)) + + def depreciate_asset(self, asset): + asset.flags.ignore_validate_update_after_submit = True + asset.prepare_depreciation_data(self.posting_date) + asset.save() + + post_depreciation_entries(self.posting_date) + + def reset_depreciation_schedule(self, asset): + asset.flags.ignore_validate_update_after_submit = True + + # recreate original depreciation schedule of the asset + asset.prepare_depreciation_data() + + self.modify_depreciation_schedule_for_asset_repairs(asset) + asset.save() + + self.delete_depreciation_entry_made_after_sale(asset) + + def modify_depreciation_schedule_for_asset_repairs(self, asset): + asset_repairs = frappe.get_all( + 'Asset Repair', + filters = {'asset': asset.name}, + fields = ['name', 'increase_in_asset_life'] + ) + + for repair in asset_repairs: + if repair.increase_in_asset_life: + asset_repair = frappe.get_doc('Asset Repair', repair.name) + asset_repair.modify_depreciation_schedule() + asset.prepare_depreciation_data() + + def delete_depreciation_entry_made_after_sale(self, asset): + from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry + + posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice() + + row = -1 + finance_book = asset.get('schedules')[0].get('finance_book') + for schedule in asset.get('schedules'): + if schedule.finance_book != finance_book: + row = 0 + finance_book = schedule.finance_book + else: + row += 1 + + if schedule.schedule_date == posting_date_of_original_invoice: + if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice): + reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) + reverse_journal_entry.posting_date = nowdate() + reverse_journal_entry.submit() + + def get_posting_date_of_sales_invoice(self): + return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date') + + # if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone + def sale_was_made_on_original_schedule_date(self, asset, schedule, row, posting_date_of_original_invoice): + for finance_book in asset.get('finance_books'): + if schedule.finance_book == finance_book.finance_book: + orginal_schedule_date = add_months(finance_book.depreciation_start_date, + row * cint(finance_book.frequency_of_depreciation)) + + if orginal_schedule_date == posting_date_of_original_invoice: + return True + return False + def set_asset_status(self, asset): if self.is_return: asset.set_status() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index be20b18bea..f98275bcc1 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -12,6 +12,7 @@ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unli from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import WarehouseMissingError from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data +from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from frappe.model.naming import make_autoname @@ -2101,6 +2102,30 @@ class TestSalesInvoice(unittest.TestCase): sales_invoice.save() self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + def test_asset_depreciation_on_sale(self): + """ + Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on Sept 30. + """ + + create_asset_data() + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) + post_depreciation_entries(getdate("2021-09-30")) + + create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30")) + asset.load_from_db() + + expected_values = [ + ["2020-06-30", 1311.48, 1311.48], + ["2021-06-30", 20000.0, 21311.48], + ["2021-09-30", 3966.76, 25278.24] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + self.assertTrue(schedule.journal_entry) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 66f0bdcd58..b7ca693331 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -56,12 +56,12 @@ class Asset(AccountsController): if self.is_existing_asset and self.purchase_invoice: frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)) - def prepare_depreciation_data(self): + def prepare_depreciation_data(self, date_of_sale=None): if self.calculate_depreciation: self.value_after_depreciation = 0 self.set_depreciation_rate() - self.make_depreciation_schedule() - self.set_accumulated_depreciation() + self.make_depreciation_schedule(date_of_sale) + self.set_accumulated_depreciation(date_of_sale) else: self.finance_books = [] self.value_after_depreciation = (flt(self.gross_purchase_amount) - @@ -167,7 +167,7 @@ class Asset(AccountsController): d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")) - def make_depreciation_schedule(self): + def make_depreciation_schedule(self, date_of_sale): if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules: self.schedules = [] @@ -212,6 +212,21 @@ class Asset(AccountsController): # so monthly schedule date is calculated by removing 11 months from it monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1) + # if asset is being sold + if date_of_sale: + from_date = self.get_from_date(d.finance_book) + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, + from_date, date_of_sale) + + self.append("schedules", { + "schedule_date": date_of_sale, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + break + # For first row if has_pro_rata and n==0: depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, @@ -303,6 +318,21 @@ class Asset(AccountsController): break return start + def get_from_date(self, finance_book): + if not self.get('schedules'): + return self.available_for_use_date + + if len(self.finance_books) == 1: + return self.schedules[-1].schedule_date + + from_date = "" + for schedule in self.get('schedules'): + if schedule.finance_book == finance_book: + from_date = schedule.schedule_date + + if from_date: + return from_date + return self.available_for_use_date # if it returns True, depreciation_amount will not be equal for the first and last rows def check_is_pro_rata(self, row): @@ -357,7 +387,7 @@ class Asset(AccountsController): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date") .format(row.idx)) - def set_accumulated_depreciation(self, ignore_booked_entry = False): + def set_accumulated_depreciation(self, date_of_sale=None, ignore_booked_entry = False): straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line'] finance_books = [] @@ -365,7 +395,7 @@ class Asset(AccountsController): if ignore_booked_entry and d.journal_entry: continue - if d.finance_book_id not in finance_books: + if int(d.finance_book_id) not in finance_books: accumulated_depreciation = flt(self.opening_accumulated_depreciation) value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id)) finance_books.append(int(d.finance_book_id)) @@ -374,7 +404,7 @@ class Asset(AccountsController): value_after_depreciation -= flt(depreciation_amount) # for the last row, if depreciation method = Straight Line - if straight_line_idx and i == max(straight_line_idx) - 1: + if straight_line_idx and i == max(straight_line_idx) - 1 and not date_of_sale: book = self.get('finance_books')[cint(d.finance_book_id) - 1] depreciation_amount += flt(value_after_depreciation - flt(book.expected_value_after_useful_life), d.precision("depreciation_amount")) From 88f13fef81b668547a9c66a68bb2fa9aea6973b2 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 12 Aug 2021 16:26:51 +0530 Subject: [PATCH 248/253] fix: ZeroDivisionError on creating e-invoice for credit note (#26915) --- erpnext/regional/india/e_invoice/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 4276946ebb..fa7e88d3a1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -190,8 +190,10 @@ def get_item_list(invoice): item.description = sanitize_for_json(d.item_name) item.qty = abs(item.qty) - - item.unit_rate = abs(item.taxable_value / item.qty) + if flt(item.qty) != 0.0: + item.unit_rate = abs(item.taxable_value / item.qty) + else: + item.unit_rate = abs(item.taxable_value) item.gross_amount = abs(item.taxable_value) item.taxable_value = abs(item.taxable_value) item.discount_amount = 0 From b8658d003f0364bb195d641d3024859471e09334 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 12 Aug 2021 20:03:28 +0530 Subject: [PATCH 249/253] fix: from_warehouse getting set to None (#26920) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 82933199d5..7b31d2fdf2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1185,7 +1185,7 @@ class StockEntry(StockController): wo = frappe.get_doc("Work Order", self.work_order) wo_items = frappe.get_all('Work Order Item', filters={'parent': self.work_order}, - fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"] + fields=["item_code", "source_warehouse", "required_qty", "consumed_qty", "transferred_qty"] ) work_order_qty = wo.material_transferred_for_manufacturing or wo.qty @@ -1205,7 +1205,7 @@ class StockEntry(StockController): if qty > 0: self.add_to_stock_entry_detail({ item.item_code: { - "from_warehouse": wo.wip_warehouse, + "from_warehouse": wo.wip_warehouse or item.source_warehouse, "to_warehouse": "", "qty": qty, "item_name": item.item_name, @@ -1857,4 +1857,4 @@ def get_supplied_items(purchase_order): supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty) - return supplied_item_details \ No newline at end of file + return supplied_item_details From 1de4c01942e32eff83bce98805a803ea3c26f721 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Thu, 12 Aug 2021 23:06:34 +0530 Subject: [PATCH 250/253] fix: Deferred Revenue Section should be collapsible only if its not enabled (#26928) --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 3 ++- .../doctype/sales_invoice_item/sales_invoice_item.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index b39022dd75..528c931903 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -501,6 +501,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "enable_deferred_expense", "fieldname": "deferred_expense_section", "fieldtype": "Section Break", "label": "Deferred Expense" @@ -854,7 +855,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-16 19:43:51.099386", + "modified": "2021-08-12 20:14:45.506639", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 6690bdafc3..dad29c0956 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -473,6 +473,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "enable_deferred_revenue", "fieldname": "deferred_revenue", "fieldtype": "Section Break", "label": "Deferred Revenue" @@ -825,7 +826,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-21 23:03:11.599901", + "modified": "2021-08-12 20:15:42.668399", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", From 587d2db6a9048c61414e00ab9d048be7efaa9db2 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Fri, 13 Aug 2021 10:59:06 +0530 Subject: [PATCH 251/253] fix: show proper currency symbol in taxes and charges table (#26827) Co-authored-by: Saqib Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../purchase_taxes_and_charges.json | 20 +++++++++---------- .../sales_taxes_and_charges.json | 20 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index 1fa68e0a8a..d86abade92 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -22,7 +22,7 @@ "cost_center", "dimension_col_break", "section_break_9", - "currency", + "account_currency", "tax_amount", "tax_amount_after_discount_amount", "total", @@ -208,14 +208,6 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, - { - "fetch_from": "account_head.account_currency", - "fieldname": "currency", - "fieldtype": "Link", - "label": "Account Currency", - "options": "Currency", - "read_only": 1 - }, { "default": "0", "depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", @@ -223,12 +215,20 @@ "fieldname": "included_in_paid_amount", "fieldtype": "Check", "label": "Considered In Paid Amount" + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "account_currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2021-06-14 01:43:50.750455", + "modified": "2021-08-05 20:04:36.618240", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index cfdb167bbc..3a871bfced 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -19,7 +19,7 @@ "section_break_8", "rate", "section_break_9", - "currency", + "account_currency", "tax_amount", "total", "tax_amount_after_discount_amount", @@ -186,14 +186,6 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, - { - "fetch_from": "account_head.account_currency", - "fieldname": "currency", - "fieldtype": "Link", - "label": "Account Currency", - "options": "Currency", - "read_only": 1 - }, { "default": "0", "depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)", @@ -210,13 +202,21 @@ "label": "Dont Recompute tax", "print_hide": 1, "read_only": 1 + }, + { + "fetch_from": "account_head.account_currency", + "fieldname": "account_currency", + "fieldtype": "Link", + "label": "Account Currency", + "options": "Currency", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-07-27 12:40:59.051803", + "modified": "2021-08-05 20:04:01.726867", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", From b32c2fa56190b9d745ac9e967e0927bf675bce5f Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 13 Aug 2021 12:19:16 +0530 Subject: [PATCH 252/253] fix: updated erpnext wspace json files (#26380) * fix: updated erpnext wspace json files * fix: updated wspace json files * fix: updated wspace json files * fix: removed padding code from wspace json files * fix: Updated restrict_to_domain in wspace json Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../workspace/accounting/accounting.json | 126 +++++++++++++++++- .../workspace/agriculture/agriculture.json | 32 ++++- erpnext/assets/workspace/assets/assets.json | 33 ++++- erpnext/buying/workspace/buying/buying.json | 65 ++++++++- erpnext/crm/workspace/crm/crm.json | 54 +++++++- .../workspace/education/education.json | 82 +++++++++++- .../erpnext_integrations.json | 45 ++++++- .../erpnext_integrations_settings.json | 39 +++++- .../workspace/healthcare/healthcare.json | 66 ++++++++- erpnext/hr/workspace/hr/hr.json | 104 ++++++++++++++- .../loan_management/loan_management.json | 40 +++++- .../manufacturing/manufacturing.json | 44 +++++- .../workspace/non_profit/non_profit.json | 39 +++++- .../payroll/workspace/payroll/payroll.json | 46 ++++++- .../projects/workspace/projects/projects.json | 35 ++++- .../workspace/quality/quality.json | 32 ++++- erpnext/selling/workspace/retail/retail.json | 27 +++- .../selling/workspace/selling/selling.json | 71 +++++++++- .../erpnext_settings/erpnext_settings.json | 25 ++-- erpnext/setup/workspace/home/home.json | 45 ++++++- erpnext/stock/workspace/stock/stock.json | 87 +++++++++++- .../support/workspace/support/support.json | 35 ++++- .../workspace/utilities/utilities.json | 24 +++- 23 files changed, 1092 insertions(+), 104 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 821fa4d2af..b5bd14d137 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -1,28 +1,32 @@ { - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Profit and Loss", "label": "Profit and Loss" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Accounts\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Profit and Loss\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chart of Accounts\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Journal Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payment Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Trial Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounting Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Payable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Financial Statements\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Multi Currency\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bank Statement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Subscription Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goods and Services Tax (GST India)\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Share Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Cost Center and Budgeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening and Closing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Profitability\", \"col\": 4}}]", "creation": "2020-03-02 15:41:59.515192", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "accounting", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Accounting", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Accounting Masters", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -31,6 +35,7 @@ "hidden": 0, "is_query_report": 0, "label": "Company", + "link_count": 0, "link_to": "Company", "link_type": "DocType", "onboard": 1, @@ -41,6 +46,7 @@ "hidden": 0, "is_query_report": 0, "label": "Chart of Accounts", + "link_count": 0, "link_to": "Account", "link_type": "DocType", "onboard": 1, @@ -51,6 +57,7 @@ "hidden": 0, "is_query_report": 0, "label": "Accounts Settings", + "link_count": 0, "link_to": "Accounts Settings", "link_type": "DocType", "onboard": 0, @@ -61,6 +68,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fiscal Year", + "link_count": 0, "link_to": "Fiscal Year", "link_type": "DocType", "onboard": 0, @@ -71,6 +79,7 @@ "hidden": 0, "is_query_report": 0, "label": "Accounting Dimension", + "link_count": 0, "link_to": "Accounting Dimension", "link_type": "DocType", "onboard": 0, @@ -81,6 +90,7 @@ "hidden": 0, "is_query_report": 0, "label": "Finance Book", + "link_count": 0, "link_to": "Finance Book", "link_type": "DocType", "onboard": 0, @@ -91,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Accounting Period", + "link_count": 0, "link_to": "Accounting Period", "link_type": "DocType", "onboard": 0, @@ -101,6 +112,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payment Term", + "link_count": 0, "link_to": "Payment Term", "link_type": "DocType", "onboard": 0, @@ -110,6 +122,7 @@ "hidden": 0, "is_query_report": 0, "label": "General Ledger", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -118,6 +131,7 @@ "hidden": 0, "is_query_report": 0, "label": "Journal Entry", + "link_count": 0, "link_to": "Journal Entry", "link_type": "DocType", "onboard": 0, @@ -128,6 +142,7 @@ "hidden": 0, "is_query_report": 0, "label": "Journal Entry Template", + "link_count": 0, "link_to": "Journal Entry Template", "link_type": "DocType", "onboard": 0, @@ -138,6 +153,7 @@ "hidden": 0, "is_query_report": 1, "label": "General Ledger", + "link_count": 0, "link_to": "General Ledger", "link_type": "Report", "onboard": 0, @@ -148,6 +164,7 @@ "hidden": 0, "is_query_report": 1, "label": "Customer Ledger Summary", + "link_count": 0, "link_to": "Customer Ledger Summary", "link_type": "Report", "onboard": 0, @@ -158,6 +175,7 @@ "hidden": 0, "is_query_report": 1, "label": "Supplier Ledger Summary", + "link_count": 0, "link_to": "Supplier Ledger Summary", "link_type": "Report", "onboard": 0, @@ -167,6 +185,7 @@ "hidden": 0, "is_query_report": 0, "label": "Accounts Receivable", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -175,6 +194,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Invoice", + "link_count": 0, "link_to": "Sales Invoice", "link_type": "DocType", "onboard": 1, @@ -185,6 +205,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customer", + "link_count": 0, "link_to": "Customer", "link_type": "DocType", "onboard": 1, @@ -195,6 +216,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payment Entry", + "link_count": 0, "link_to": "Payment Entry", "link_type": "DocType", "onboard": 0, @@ -205,6 +227,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payment Request", + "link_count": 0, "link_to": "Payment Request", "link_type": "DocType", "onboard": 0, @@ -215,6 +238,7 @@ "hidden": 0, "is_query_report": 1, "label": "Accounts Receivable", + "link_count": 0, "link_to": "Accounts Receivable", "link_type": "Report", "onboard": 0, @@ -225,6 +249,7 @@ "hidden": 0, "is_query_report": 1, "label": "Accounts Receivable Summary", + "link_count": 0, "link_to": "Accounts Receivable Summary", "link_type": "Report", "onboard": 0, @@ -235,6 +260,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Register", + "link_count": 0, "link_to": "Sales Register", "link_type": "Report", "onboard": 0, @@ -245,6 +271,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item-wise Sales Register", + "link_count": 0, "link_to": "Item-wise Sales Register", "link_type": "Report", "onboard": 0, @@ -255,6 +282,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Order Analysis", + "link_count": 0, "link_to": "Sales Order Analysis", "link_type": "Report", "onboard": 0, @@ -265,6 +293,7 @@ "hidden": 0, "is_query_report": 1, "label": "Delivered Items To Be Billed", + "link_count": 0, "link_to": "Delivered Items To Be Billed", "link_type": "Report", "onboard": 0, @@ -274,6 +303,7 @@ "hidden": 0, "is_query_report": 0, "label": "Accounts Payable", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -282,6 +312,7 @@ "hidden": 0, "is_query_report": 0, "label": "Purchase Invoice", + "link_count": 0, "link_to": "Purchase Invoice", "link_type": "DocType", "onboard": 1, @@ -292,6 +323,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier", + "link_count": 0, "link_to": "Supplier", "link_type": "DocType", "onboard": 1, @@ -302,6 +334,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payment Entry", + "link_count": 0, "link_to": "Payment Entry", "link_type": "DocType", "onboard": 0, @@ -312,6 +345,7 @@ "hidden": 0, "is_query_report": 1, "label": "Accounts Payable", + "link_count": 0, "link_to": "Accounts Payable", "link_type": "Report", "onboard": 0, @@ -322,6 +356,7 @@ "hidden": 0, "is_query_report": 1, "label": "Accounts Payable Summary", + "link_count": 0, "link_to": "Accounts Payable Summary", "link_type": "Report", "onboard": 0, @@ -332,6 +367,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Register", + "link_count": 0, "link_to": "Purchase Register", "link_type": "Report", "onboard": 0, @@ -342,6 +378,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item-wise Purchase Register", + "link_count": 0, "link_to": "Item-wise Purchase Register", "link_type": "Report", "onboard": 0, @@ -352,6 +389,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Order Analysis", + "link_count": 0, "link_to": "Purchase Order Analysis", "link_type": "Report", "onboard": 0, @@ -362,6 +400,7 @@ "hidden": 0, "is_query_report": 1, "label": "Received Items To Be Billed", + "link_count": 0, "link_to": "Received Items To Be Billed", "link_type": "Report", "onboard": 0, @@ -371,6 +410,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -379,6 +419,7 @@ "hidden": 0, "is_query_report": 1, "label": "Trial Balance for Party", + "link_count": 0, "link_to": "Trial Balance for Party", "link_type": "Report", "onboard": 0, @@ -389,6 +430,7 @@ "hidden": 0, "is_query_report": 1, "label": "Payment Period Based On Invoice Date", + "link_count": 0, "link_to": "Payment Period Based On Invoice Date", "link_type": "Report", "onboard": 0, @@ -399,6 +441,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Partners Commission", + "link_count": 0, "link_to": "Sales Partners Commission", "link_type": "Report", "onboard": 0, @@ -409,6 +452,7 @@ "hidden": 0, "is_query_report": 1, "label": "Customer Credit Balance", + "link_count": 0, "link_to": "Customer Credit Balance", "link_type": "Report", "onboard": 0, @@ -419,6 +463,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Payment Summary", + "link_count": 0, "link_to": "Sales Payment Summary", "link_type": "Report", "onboard": 0, @@ -429,6 +474,7 @@ "hidden": 0, "is_query_report": 1, "label": "Address And Contacts", + "link_count": 0, "link_to": "Address And Contacts", "link_type": "Report", "onboard": 0, @@ -439,6 +485,7 @@ "hidden": 0, "is_query_report": 1, "label": "Tax Detail", + "link_count": 0, "link_to": "Tax Detail", "link_type": "Report", "onboard": 0, @@ -449,6 +496,7 @@ "hidden": 0, "is_query_report": 1, "label": "DATEV Export", + "link_count": 0, "link_to": "DATEV", "link_type": "Report", "onboard": 0, @@ -460,6 +508,7 @@ "hidden": 0, "is_query_report": 1, "label": "UAE VAT 201", + "link_count": 0, "link_to": "UAE VAT 201", "link_type": "Report", "onboard": 0, @@ -470,6 +519,7 @@ "hidden": 0, "is_query_report": 0, "label": "Financial Statements", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -478,6 +528,7 @@ "hidden": 0, "is_query_report": 1, "label": "Trial Balance", + "link_count": 0, "link_to": "Trial Balance", "link_type": "Report", "onboard": 0, @@ -488,6 +539,7 @@ "hidden": 0, "is_query_report": 1, "label": "Profit and Loss Statement", + "link_count": 0, "link_to": "Profit and Loss Statement", "link_type": "Report", "onboard": 0, @@ -498,6 +550,7 @@ "hidden": 0, "is_query_report": 1, "label": "Balance Sheet", + "link_count": 0, "link_to": "Balance Sheet", "link_type": "Report", "onboard": 0, @@ -508,6 +561,7 @@ "hidden": 0, "is_query_report": 1, "label": "Cash Flow", + "link_count": 0, "link_to": "Cash Flow", "link_type": "Report", "onboard": 0, @@ -518,6 +572,7 @@ "hidden": 0, "is_query_report": 1, "label": "Consolidated Financial Statement", + "link_count": 0, "link_to": "Consolidated Financial Statement", "link_type": "Report", "onboard": 0, @@ -527,6 +582,7 @@ "hidden": 0, "is_query_report": 0, "label": "Multi Currency", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -535,6 +591,7 @@ "hidden": 0, "is_query_report": 0, "label": "Currency", + "link_count": 0, "link_to": "Currency", "link_type": "DocType", "onboard": 0, @@ -545,6 +602,7 @@ "hidden": 0, "is_query_report": 0, "label": "Currency Exchange", + "link_count": 0, "link_to": "Currency Exchange", "link_type": "DocType", "onboard": 0, @@ -555,6 +613,7 @@ "hidden": 0, "is_query_report": 0, "label": "Exchange Rate Revaluation", + "link_count": 0, "link_to": "Exchange Rate Revaluation", "link_type": "DocType", "onboard": 0, @@ -564,6 +623,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -572,6 +632,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payment Gateway Account", + "link_count": 0, "link_to": "Payment Gateway Account", "link_type": "DocType", "onboard": 0, @@ -582,6 +643,7 @@ "hidden": 0, "is_query_report": 0, "label": "Terms and Conditions Template", + "link_count": 0, "link_to": "Terms and Conditions", "link_type": "DocType", "onboard": 0, @@ -592,6 +654,7 @@ "hidden": 0, "is_query_report": 0, "label": "Mode of Payment", + "link_count": 0, "link_to": "Mode of Payment", "link_type": "DocType", "onboard": 0, @@ -601,6 +664,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bank Statement", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -609,6 +673,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bank", + "link_count": 0, "link_to": "Bank", "link_type": "DocType", "onboard": 0, @@ -619,6 +684,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bank Account", + "link_count": 0, "link_to": "Bank Account", "link_type": "DocType", "onboard": 0, @@ -629,6 +695,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bank Clearance", + "link_count": 0, "link_to": "Bank Clearance", "link_type": "DocType", "onboard": 0, @@ -639,6 +706,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bank Reconciliation Tool", + "link_count": 0, "link_to": "Bank Reconciliation Tool", "link_type": "DocType", "onboard": 0, @@ -649,6 +717,7 @@ "hidden": 0, "is_query_report": 1, "label": "Bank Reconciliation Statement", + "link_count": 0, "link_to": "Bank Reconciliation Statement", "link_type": "Report", "onboard": 0, @@ -658,6 +727,7 @@ "hidden": 0, "is_query_report": 0, "label": "Subscription Management", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -666,6 +736,7 @@ "hidden": 0, "is_query_report": 0, "label": "Subscription Plan", + "link_count": 0, "link_to": "Subscription Plan", "link_type": "DocType", "onboard": 0, @@ -676,6 +747,7 @@ "hidden": 0, "is_query_report": 0, "label": "Subscription", + "link_count": 0, "link_to": "Subscription", "link_type": "DocType", "onboard": 0, @@ -686,6 +758,7 @@ "hidden": 0, "is_query_report": 0, "label": "Subscription Settings", + "link_count": 0, "link_to": "Subscription Settings", "link_type": "DocType", "onboard": 0, @@ -695,6 +768,7 @@ "hidden": 0, "is_query_report": 0, "label": "Goods and Services Tax (GST India)", + "link_count": 0, "onboard": 0, "only_for": "India", "type": "Card Break" @@ -704,6 +778,7 @@ "hidden": 0, "is_query_report": 0, "label": "GST Settings", + "link_count": 0, "link_to": "GST Settings", "link_type": "DocType", "onboard": 0, @@ -715,6 +790,7 @@ "hidden": 0, "is_query_report": 0, "label": "GST HSN Code", + "link_count": 0, "link_to": "GST HSN Code", "link_type": "DocType", "onboard": 0, @@ -726,6 +802,7 @@ "hidden": 0, "is_query_report": 1, "label": "GSTR-1", + "link_count": 0, "link_to": "GSTR-1", "link_type": "Report", "onboard": 0, @@ -737,6 +814,7 @@ "hidden": 0, "is_query_report": 1, "label": "GSTR-2", + "link_count": 0, "link_to": "GSTR-2", "link_type": "Report", "onboard": 0, @@ -748,6 +826,7 @@ "hidden": 0, "is_query_report": 0, "label": "GSTR 3B Report", + "link_count": 0, "link_to": "GSTR 3B Report", "link_type": "DocType", "onboard": 0, @@ -759,6 +838,7 @@ "hidden": 0, "is_query_report": 1, "label": "GST Sales Register", + "link_count": 0, "link_to": "GST Sales Register", "link_type": "Report", "onboard": 0, @@ -770,6 +850,7 @@ "hidden": 0, "is_query_report": 1, "label": "GST Purchase Register", + "link_count": 0, "link_to": "GST Purchase Register", "link_type": "Report", "onboard": 0, @@ -781,6 +862,7 @@ "hidden": 0, "is_query_report": 1, "label": "GST Itemised Sales Register", + "link_count": 0, "link_to": "GST Itemised Sales Register", "link_type": "Report", "onboard": 0, @@ -792,6 +874,7 @@ "hidden": 0, "is_query_report": 1, "label": "GST Itemised Purchase Register", + "link_count": 0, "link_to": "GST Itemised Purchase Register", "link_type": "Report", "onboard": 0, @@ -803,6 +886,7 @@ "hidden": 0, "is_query_report": 0, "label": "C-Form", + "link_count": 0, "link_to": "C-Form", "link_type": "DocType", "onboard": 0, @@ -814,6 +898,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lower Deduction Certificate", + "link_count": 0, "link_to": "Lower Deduction Certificate", "link_type": "DocType", "onboard": 0, @@ -824,6 +909,7 @@ "hidden": 0, "is_query_report": 0, "label": "Share Management", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -832,6 +918,7 @@ "hidden": 0, "is_query_report": 0, "label": "Shareholder", + "link_count": 0, "link_to": "Shareholder", "link_type": "DocType", "onboard": 0, @@ -842,6 +929,7 @@ "hidden": 0, "is_query_report": 0, "label": "Share Transfer", + "link_count": 0, "link_to": "Share Transfer", "link_type": "DocType", "onboard": 0, @@ -852,6 +940,7 @@ "hidden": 0, "is_query_report": 1, "label": "Share Ledger", + "link_count": 0, "link_to": "Share Ledger", "link_type": "Report", "onboard": 0, @@ -862,6 +951,7 @@ "hidden": 0, "is_query_report": 1, "label": "Share Balance", + "link_count": 0, "link_to": "Share Balance", "link_type": "Report", "onboard": 0, @@ -871,6 +961,7 @@ "hidden": 0, "is_query_report": 0, "label": "Cost Center and Budgeting", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -879,6 +970,7 @@ "hidden": 0, "is_query_report": 0, "label": "Chart of Cost Centers", + "link_count": 0, "link_to": "Cost Center", "link_type": "DocType", "onboard": 0, @@ -889,6 +981,7 @@ "hidden": 0, "is_query_report": 0, "label": "Budget", + "link_count": 0, "link_to": "Budget", "link_type": "DocType", "onboard": 0, @@ -899,6 +992,7 @@ "hidden": 0, "is_query_report": 0, "label": "Accounting Dimension", + "link_count": 0, "link_to": "Accounting Dimension", "link_type": "DocType", "onboard": 0, @@ -909,6 +1003,7 @@ "hidden": 0, "is_query_report": 1, "label": "Budget Variance Report", + "link_count": 0, "link_to": "Budget Variance Report", "link_type": "Report", "onboard": 0, @@ -919,6 +1014,7 @@ "hidden": 0, "is_query_report": 0, "label": "Monthly Distribution", + "link_count": 0, "link_to": "Monthly Distribution", "link_type": "DocType", "onboard": 0, @@ -928,6 +1024,7 @@ "hidden": 0, "is_query_report": 0, "label": "Opening and Closing", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -936,6 +1033,7 @@ "hidden": 0, "is_query_report": 0, "label": "Opening Invoice Creation Tool", + "link_count": 0, "link_to": "Opening Invoice Creation Tool", "link_type": "DocType", "onboard": 0, @@ -946,6 +1044,7 @@ "hidden": 0, "is_query_report": 0, "label": "Chart of Accounts Importer", + "link_count": 0, "link_to": "Chart of Accounts Importer", "link_type": "DocType", "onboard": 0, @@ -956,6 +1055,7 @@ "hidden": 0, "is_query_report": 0, "label": "Period Closing Voucher", + "link_count": 0, "link_to": "Period Closing Voucher", "link_type": "DocType", "onboard": 0, @@ -965,6 +1065,7 @@ "hidden": 0, "is_query_report": 0, "label": "Taxes", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -973,6 +1074,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Taxes and Charges Template", + "link_count": 0, "link_to": "Sales Taxes and Charges Template", "link_type": "DocType", "onboard": 0, @@ -983,6 +1085,7 @@ "hidden": 0, "is_query_report": 0, "label": "Purchase Taxes and Charges Template", + "link_count": 0, "link_to": "Purchase Taxes and Charges Template", "link_type": "DocType", "onboard": 0, @@ -993,6 +1096,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Tax Template", + "link_count": 0, "link_to": "Item Tax Template", "link_type": "DocType", "onboard": 0, @@ -1003,6 +1107,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tax Category", + "link_count": 0, "link_to": "Tax Category", "link_type": "DocType", "onboard": 0, @@ -1013,6 +1118,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tax Rule", + "link_count": 0, "link_to": "Tax Rule", "link_type": "DocType", "onboard": 0, @@ -1023,6 +1129,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tax Withholding Category", + "link_count": 0, "link_to": "Tax Withholding Category", "link_type": "DocType", "onboard": 0, @@ -1032,6 +1139,7 @@ "hidden": 0, "is_query_report": 0, "label": "Profitability", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -1040,6 +1148,7 @@ "hidden": 0, "is_query_report": 1, "label": "Gross Profit", + "link_count": 0, "link_to": "Gross Profit", "link_type": "Report", "onboard": 0, @@ -1050,6 +1159,7 @@ "hidden": 0, "is_query_report": 1, "label": "Profitability Analysis", + "link_count": 0, "link_to": "Profitability Analysis", "link_type": "Report", "onboard": 0, @@ -1060,6 +1170,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Invoice Trends", + "link_count": 0, "link_to": "Sales Invoice Trends", "link_type": "Report", "onboard": 0, @@ -1070,20 +1181,26 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Invoice Trends", + "link_count": 0, "link_to": "Purchase Invoice Trends", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2021-06-10 03:17:31.427945", + "modified": "2021-08-05 12:15:52.872470", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", "onboarding": "Accounts", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 2, "shortcuts": [ { "label": "Chart of Accounts", @@ -1130,5 +1247,6 @@ "link_to": "Accounts", "type": "Dashboard" } - ] + ], + "title": "Accounting" } \ No newline at end of file diff --git a/erpnext/agriculture/workspace/agriculture/agriculture.json b/erpnext/agriculture/workspace/agriculture/agriculture.json index 2cc252491d..633777eeb7 100644 --- a/erpnext/agriculture/workspace/agriculture/agriculture.json +++ b/erpnext/agriculture/workspace/agriculture/agriculture.json @@ -1,22 +1,27 @@ { - "category": "Domains", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Crops & Lands\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Analytics\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Diseases & Fertilizers\", \"col\": 4}}]", "creation": "2020-03-02 17:23:34.339274", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "agriculture", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Agriculture", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Crops & Lands", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Crop", + "link_count": 0, "link_to": "Crop", "link_type": "DocType", "onboard": 1, @@ -35,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Crop Cycle", + "link_count": 0, "link_to": "Crop Cycle", "link_type": "DocType", "onboard": 1, @@ -45,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Location", + "link_count": 0, "link_to": "Location", "link_type": "DocType", "onboard": 1, @@ -54,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Analytics", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -62,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Plant Analysis", + "link_count": 0, "link_to": "Plant Analysis", "link_type": "DocType", "onboard": 0, @@ -72,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Soil Analysis", + "link_count": 0, "link_to": "Soil Analysis", "link_type": "DocType", "onboard": 0, @@ -82,6 +93,7 @@ "hidden": 0, "is_query_report": 0, "label": "Water Analysis", + "link_count": 0, "link_to": "Water Analysis", "link_type": "DocType", "onboard": 0, @@ -92,6 +104,7 @@ "hidden": 0, "is_query_report": 0, "label": "Soil Texture", + "link_count": 0, "link_to": "Soil Texture", "link_type": "DocType", "onboard": 0, @@ -102,6 +115,7 @@ "hidden": 0, "is_query_report": 0, "label": "Weather", + "link_count": 0, "link_to": "Weather", "link_type": "DocType", "onboard": 0, @@ -112,6 +126,7 @@ "hidden": 0, "is_query_report": 0, "label": "Agriculture Analysis Criteria", + "link_count": 0, "link_to": "Agriculture Analysis Criteria", "link_type": "DocType", "onboard": 0, @@ -121,6 +136,7 @@ "hidden": 0, "is_query_report": 0, "label": "Diseases & Fertilizers", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -129,6 +145,7 @@ "hidden": 0, "is_query_report": 0, "label": "Disease", + "link_count": 0, "link_to": "Disease", "link_type": "DocType", "onboard": 1, @@ -139,19 +156,26 @@ "hidden": 0, "is_query_report": 0, "label": "Fertilizer", + "link_count": 0, "link_to": "Fertilizer", "link_type": "DocType", "onboard": 1, "type": "Link" } ], - "modified": "2020-12-01 13:38:38.477493", + "modified": "2021-08-05 12:15:54.595197", "modified_by": "Administrator", "module": "Agriculture", "name": "Agriculture", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, "restrict_to_domain": "Agriculture", - "shortcuts": [] + "roles": [], + "sequence_id": 3, + "shortcuts": [], + "title": "Agriculture" } \ No newline at end of file diff --git a/erpnext/assets/workspace/assets/assets.json b/erpnext/assets/workspace/assets/assets.json index c401581758..dfbf1a378e 100644 --- a/erpnext/assets/workspace/assets/assets.json +++ b/erpnext/assets/workspace/assets/assets.json @@ -1,27 +1,32 @@ { - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Asset Value Analytics", "label": "Asset Value Analytics" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Assets\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Asset Value Analytics\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset Category\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fixed Asset Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assets\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:43:27.634865", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "assets", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Assets", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Assets", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -30,6 +35,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset", + "link_count": 0, "link_to": "Asset", "link_type": "DocType", "onboard": 1, @@ -40,6 +46,7 @@ "hidden": 0, "is_query_report": 0, "label": "Location", + "link_count": 0, "link_to": "Location", "link_type": "DocType", "onboard": 1, @@ -50,6 +57,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Category", + "link_count": 0, "link_to": "Asset Category", "link_type": "DocType", "onboard": 1, @@ -60,6 +68,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Movement", + "link_count": 0, "link_to": "Asset Movement", "link_type": "DocType", "onboard": 0, @@ -69,6 +78,7 @@ "hidden": 0, "is_query_report": 0, "label": "Maintenance", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -77,6 +87,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Maintenance Team", + "link_count": 0, "link_to": "Asset Maintenance Team", "link_type": "DocType", "onboard": 1, @@ -87,6 +98,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Maintenance", + "link_count": 0, "link_to": "Asset Maintenance", "link_type": "DocType", "onboard": 1, @@ -97,6 +109,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Maintenance Log", + "link_count": 0, "link_to": "Asset Maintenance Log", "link_type": "DocType", "onboard": 0, @@ -107,6 +120,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Value Adjustment", + "link_count": 0, "link_to": "Asset Value Adjustment", "link_type": "DocType", "onboard": 0, @@ -117,6 +131,7 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Repair", + "link_count": 0, "link_to": "Asset Repair", "link_type": "DocType", "onboard": 0, @@ -126,6 +141,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -134,6 +150,7 @@ "hidden": 0, "is_query_report": 1, "label": "Asset Depreciation Ledger", + "link_count": 0, "link_to": "Asset Depreciation Ledger", "link_type": "Report", "onboard": 0, @@ -144,6 +161,7 @@ "hidden": 0, "is_query_report": 1, "label": "Asset Depreciations and Balances", + "link_count": 0, "link_to": "Asset Depreciations and Balances", "link_type": "Report", "onboard": 0, @@ -154,20 +172,26 @@ "hidden": 0, "is_query_report": 0, "label": "Asset Maintenance", + "link_count": 0, "link_to": "Asset Maintenance", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:37.977119", + "modified": "2021-08-05 12:15:54.839452", "modified_by": "Administrator", "module": "Assets", "name": "Assets", "onboarding": "Assets", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 4, "shortcuts": [ { "label": "Asset", @@ -189,5 +213,6 @@ "link_to": "Asset", "type": "Dashboard" } - ] + ], + "title": "Assets" } \ No newline at end of file diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json index 6c9c0f3011..6c91e81695 100644 --- a/erpnext/buying/workspace/buying/buying.json +++ b/erpnext/buying/workspace/buying/buying.json @@ -1,6 +1,6 @@ { "cards_label": "", - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Purchase Order Trends", @@ -8,22 +8,27 @@ } ], "charts_label": "", + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Buying\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Purchase Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Buying\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items & Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier Scorecard\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Regional\", \"col\": 4}}]", "creation": "2020-01-28 11:50:26.195467", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "buying", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Buying", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Buying", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -32,6 +37,7 @@ "hidden": 0, "is_query_report": 0, "label": "Material Request", + "link_count": 0, "link_to": "Material Request", "link_type": "DocType", "onboard": 1, @@ -42,6 +48,7 @@ "hidden": 0, "is_query_report": 0, "label": "Purchase Order", + "link_count": 0, "link_to": "Purchase Order", "link_type": "DocType", "onboard": 1, @@ -52,6 +59,7 @@ "hidden": 0, "is_query_report": 0, "label": "Purchase Invoice", + "link_count": 0, "link_to": "Purchase Invoice", "link_type": "DocType", "onboard": 1, @@ -62,6 +70,7 @@ "hidden": 0, "is_query_report": 0, "label": "Request for Quotation", + "link_count": 0, "link_to": "Request for Quotation", "link_type": "DocType", "onboard": 1, @@ -72,6 +81,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier Quotation", + "link_count": 0, "link_to": "Supplier Quotation", "link_type": "DocType", "onboard": 0, @@ -81,6 +91,7 @@ "hidden": 0, "is_query_report": 0, "label": "Items & Pricing", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -89,6 +100,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item", + "link_count": 0, "link_to": "Item", "link_type": "DocType", "onboard": 1, @@ -99,6 +111,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Price", + "link_count": 0, "link_to": "Item Price", "link_type": "DocType", "onboard": 1, @@ -109,6 +122,7 @@ "hidden": 0, "is_query_report": 0, "label": "Price List", + "link_count": 0, "link_to": "Price List", "link_type": "DocType", "onboard": 1, @@ -119,6 +133,7 @@ "hidden": 0, "is_query_report": 0, "label": "Product Bundle", + "link_count": 0, "link_to": "Product Bundle", "link_type": "DocType", "onboard": 0, @@ -129,6 +144,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Group", + "link_count": 0, "link_to": "Item Group", "link_type": "DocType", "onboard": 0, @@ -139,6 +155,7 @@ "hidden": 0, "is_query_report": 0, "label": "Promotional Scheme", + "link_count": 0, "link_to": "Promotional Scheme", "link_type": "DocType", "onboard": 0, @@ -149,6 +166,7 @@ "hidden": 0, "is_query_report": 0, "label": "Pricing Rule", + "link_count": 0, "link_to": "Pricing Rule", "link_type": "DocType", "onboard": 0, @@ -158,6 +176,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -166,6 +185,7 @@ "hidden": 0, "is_query_report": 0, "label": "Buying Settings", + "link_count": 0, "link_to": "Buying Settings", "link_type": "DocType", "onboard": 0, @@ -176,6 +196,7 @@ "hidden": 0, "is_query_report": 0, "label": "Purchase Taxes and Charges Template", + "link_count": 0, "link_to": "Purchase Taxes and Charges Template", "link_type": "DocType", "onboard": 0, @@ -186,6 +207,7 @@ "hidden": 0, "is_query_report": 0, "label": "Terms and Conditions Template", + "link_count": 0, "link_to": "Terms and Conditions", "link_type": "DocType", "onboard": 0, @@ -195,6 +217,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -203,6 +226,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier", + "link_count": 0, "link_to": "Supplier", "link_type": "DocType", "onboard": 1, @@ -213,6 +237,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier Group", + "link_count": 0, "link_to": "Supplier Group", "link_type": "DocType", "onboard": 0, @@ -223,6 +248,7 @@ "hidden": 0, "is_query_report": 0, "label": "Contact", + "link_count": 0, "link_to": "Contact", "link_type": "DocType", "onboard": 0, @@ -233,6 +259,7 @@ "hidden": 0, "is_query_report": 0, "label": "Address", + "link_count": 0, "link_to": "Address", "link_type": "DocType", "onboard": 0, @@ -242,6 +269,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier Scorecard", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -250,6 +278,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier Scorecard", + "link_count": 0, "link_to": "Supplier Scorecard", "link_type": "DocType", "onboard": 0, @@ -260,6 +289,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier Scorecard Variable", + "link_count": 0, "link_to": "Supplier Scorecard Variable", "link_type": "DocType", "onboard": 0, @@ -270,6 +300,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier Scorecard Criteria", + "link_count": 0, "link_to": "Supplier Scorecard Criteria", "link_type": "DocType", "onboard": 0, @@ -280,6 +311,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier Scorecard Standing", + "link_count": 0, "link_to": "Supplier Scorecard Standing", "link_type": "DocType", "onboard": 0, @@ -289,6 +321,7 @@ "hidden": 0, "is_query_report": 0, "label": "Key Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -297,6 +330,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Analytics", + "link_count": 0, "link_to": "Purchase Analytics", "link_type": "Report", "onboard": 1, @@ -307,6 +341,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Order Analysis", + "link_count": 0, "link_to": "Purchase Order Analysis", "link_type": "Report", "onboard": 1, @@ -317,6 +352,7 @@ "hidden": 0, "is_query_report": 1, "label": "Supplier-Wise Sales Analytics", + "link_count": 0, "link_to": "Supplier-Wise Sales Analytics", "link_type": "Report", "onboard": 1, @@ -327,6 +363,7 @@ "hidden": 0, "is_query_report": 1, "label": "Items to Order and Receive", + "link_count": 0, "link_to": "Requested Items to Order and Receive", "link_type": "Report", "onboard": 1, @@ -337,6 +374,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Order Trends", + "link_count": 0, "link_to": "Purchase Order Trends", "link_type": "Report", "onboard": 1, @@ -347,6 +385,7 @@ "hidden": 0, "is_query_report": 1, "label": "Procurement Tracker", + "link_count": 0, "link_to": "Procurement Tracker", "link_type": "Report", "onboard": 1, @@ -356,6 +395,7 @@ "hidden": 0, "is_query_report": 0, "label": "Other Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -364,6 +404,7 @@ "hidden": 0, "is_query_report": 1, "label": "Items To Be Requested", + "link_count": 0, "link_to": "Items To Be Requested", "link_type": "Report", "onboard": 1, @@ -374,6 +415,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item-wise Purchase History", + "link_count": 0, "link_to": "Item-wise Purchase History", "link_type": "Report", "onboard": 1, @@ -384,6 +426,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Receipt Trends", + "link_count": 0, "link_to": "Purchase Receipt Trends", "link_type": "Report", "onboard": 0, @@ -394,6 +437,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Invoice Trends", + "link_count": 0, "link_to": "Purchase Invoice Trends", "link_type": "Report", "onboard": 0, @@ -404,6 +448,7 @@ "hidden": 0, "is_query_report": 1, "label": "Subcontracted Raw Materials To Be Transferred", + "link_count": 0, "link_to": "Subcontracted Raw Materials To Be Transferred", "link_type": "Report", "onboard": 0, @@ -414,6 +459,7 @@ "hidden": 0, "is_query_report": 1, "label": "Subcontracted Item To Be Received", + "link_count": 0, "link_to": "Subcontracted Item To Be Received", "link_type": "Report", "onboard": 0, @@ -424,6 +470,7 @@ "hidden": 0, "is_query_report": 1, "label": "Supplier Quotation Comparison", + "link_count": 0, "link_to": "Supplier Quotation Comparison", "link_type": "Report", "onboard": 1, @@ -434,6 +481,7 @@ "hidden": 0, "is_query_report": 1, "label": "Material Requests for which Supplier Quotations are not created", + "link_count": 0, "link_to": "Material Requests for which Supplier Quotations are not created", "link_type": "Report", "onboard": 0, @@ -444,6 +492,7 @@ "hidden": 0, "is_query_report": 1, "label": "Supplier Addresses And Contacts", + "link_count": 0, "link_to": "Address And Contacts", "link_type": "Report", "onboard": 0, @@ -453,6 +502,7 @@ "hidden": 0, "is_query_report": 0, "label": "Regional", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -461,20 +511,26 @@ "hidden": 0, "is_query_report": 0, "label": "Import Supplier Invoice", + "link_count": 0, "link_to": "Import Supplier Invoice", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:38.615167", + "modified": "2021-08-05 12:15:56.218427", "modified_by": "Administrator", "module": "Buying", "name": "Buying", "onboarding": "Buying", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 6, "shortcuts": [ { "color": "Green", @@ -516,5 +572,6 @@ "type": "Dashboard" } ], - "shortcuts_label": "" + "shortcuts_label": "", + "title": "Buying" } \ No newline at end of file diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json index b4fb7d8abe..c363395452 100644 --- a/erpnext/crm/workspace/crm/crm.json +++ b/erpnext/crm/workspace/crm/crm.json @@ -1,26 +1,31 @@ { - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Territory Wise Sales" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"CRM\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Lead\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Opportunity\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Sales Pipeline\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Campaign\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", "creation": "2020-01-23 14:48:30.183272", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "crm", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "CRM", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Sales Pipeline", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -29,6 +34,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lead", + "link_count": 0, "link_to": "Lead", "link_type": "DocType", "onboard": 1, @@ -39,6 +45,7 @@ "hidden": 0, "is_query_report": 0, "label": "Opportunity", + "link_count": 0, "link_to": "Opportunity", "link_type": "DocType", "onboard": 1, @@ -49,6 +56,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customer", + "link_count": 0, "link_to": "Customer", "link_type": "DocType", "onboard": 1, @@ -59,6 +67,7 @@ "hidden": 0, "is_query_report": 0, "label": "Contact", + "link_count": 0, "link_to": "Contact", "link_type": "DocType", "onboard": 1, @@ -69,6 +78,7 @@ "hidden": 0, "is_query_report": 0, "label": "Communication", + "link_count": 0, "link_to": "Communication", "link_type": "DocType", "onboard": 0, @@ -79,6 +89,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lead Source", + "link_count": 0, "link_to": "Lead Source", "link_type": "DocType", "onboard": 0, @@ -89,6 +100,7 @@ "hidden": 0, "is_query_report": 0, "label": "Contract", + "link_count": 0, "link_to": "Contract", "link_type": "DocType", "onboard": 0, @@ -99,6 +111,7 @@ "hidden": 0, "is_query_report": 0, "label": "Appointment", + "link_count": 0, "link_to": "Appointment", "link_type": "DocType", "onboard": 0, @@ -109,6 +122,7 @@ "hidden": 0, "is_query_report": 0, "label": "Newsletter", + "link_count": 0, "link_to": "Newsletter", "link_type": "DocType", "onboard": 0, @@ -118,6 +132,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -126,6 +141,7 @@ "hidden": 0, "is_query_report": 1, "label": "Lead Details", + "link_count": 0, "link_to": "Lead Details", "link_type": "Report", "onboard": 1, @@ -136,6 +152,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Funnel", + "link_count": 0, "link_to": "sales-funnel", "link_type": "Page", "onboard": 1, @@ -146,6 +163,7 @@ "hidden": 0, "is_query_report": 1, "label": "Prospects Engaged But Not Converted", + "link_count": 0, "link_to": "Prospects Engaged But Not Converted", "link_type": "Report", "onboard": 1, @@ -156,6 +174,7 @@ "hidden": 0, "is_query_report": 1, "label": "First Response Time for Opportunity", + "link_count": 0, "link_to": "First Response Time for Opportunity", "link_type": "Report", "onboard": 0, @@ -166,6 +185,7 @@ "hidden": 0, "is_query_report": 1, "label": "Inactive Customers", + "link_count": 0, "link_to": "Inactive Customers", "link_type": "Report", "onboard": 0, @@ -176,6 +196,7 @@ "hidden": 0, "is_query_report": 1, "label": "Campaign Efficiency", + "link_count": 0, "link_to": "Campaign Efficiency", "link_type": "Report", "onboard": 0, @@ -186,6 +207,7 @@ "hidden": 0, "is_query_report": 1, "label": "Lead Owner Efficiency", + "link_count": 0, "link_to": "Lead Owner Efficiency", "link_type": "Report", "onboard": 0, @@ -195,6 +217,7 @@ "hidden": 0, "is_query_report": 0, "label": "Maintenance", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -203,6 +226,7 @@ "hidden": 0, "is_query_report": 0, "label": "Maintenance Schedule", + "link_count": 0, "link_to": "Maintenance Schedule", "link_type": "DocType", "onboard": 1, @@ -213,6 +237,7 @@ "hidden": 0, "is_query_report": 0, "label": "Maintenance Visit", + "link_count": 0, "link_to": "Maintenance Visit", "link_type": "DocType", "onboard": 0, @@ -223,6 +248,7 @@ "hidden": 0, "is_query_report": 0, "label": "Warranty Claim", + "link_count": 0, "link_to": "Warranty Claim", "link_type": "DocType", "onboard": 0, @@ -232,6 +258,7 @@ "hidden": 0, "is_query_report": 0, "label": "Campaign", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -240,6 +267,7 @@ "hidden": 0, "is_query_report": 0, "label": "Campaign", + "link_count": 0, "link_to": "Campaign", "link_type": "DocType", "onboard": 0, @@ -250,6 +278,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email Campaign", + "link_count": 0, "link_to": "Email Campaign", "link_type": "DocType", "onboard": 0, @@ -260,6 +289,7 @@ "hidden": 0, "is_query_report": 0, "label": "Social Media Post", + "link_count": 0, "link_to": "Social Media Post", "link_type": "DocType", "onboard": 0, @@ -269,6 +299,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -277,6 +308,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customer Group", + "link_count": 0, "link_to": "Customer Group", "link_type": "DocType", "onboard": 1, @@ -287,6 +319,7 @@ "hidden": 0, "is_query_report": 0, "label": "Territory", + "link_count": 0, "link_to": "Territory", "link_type": "DocType", "onboard": 1, @@ -297,6 +330,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Person", + "link_count": 0, "link_to": "Sales Person", "link_type": "DocType", "onboard": 1, @@ -307,6 +341,7 @@ "hidden": 0, "is_query_report": 0, "label": "SMS Center", + "link_count": 0, "link_to": "SMS Center", "link_type": "DocType", "onboard": 0, @@ -317,6 +352,7 @@ "hidden": 0, "is_query_report": 0, "label": "SMS Log", + "link_count": 0, "link_to": "SMS Log", "link_type": "DocType", "onboard": 0, @@ -327,6 +363,7 @@ "hidden": 0, "is_query_report": 0, "label": "SMS Settings", + "link_count": 0, "link_to": "SMS Settings", "link_type": "DocType", "onboard": 0, @@ -337,6 +374,7 @@ "hidden": 0, "is_query_report": 0, "label": "Email Group", + "link_count": 0, "link_to": "Email Group", "link_type": "DocType", "onboard": 0, @@ -347,6 +385,7 @@ "hidden": 0, "is_query_report": 0, "label": "Twitter Settings", + "link_count": 0, "link_to": "Twitter Settings", "link_type": "DocType", "onboard": 0, @@ -357,20 +396,26 @@ "hidden": 0, "is_query_report": 0, "label": "LinkedIn Settings", + "link_count": 0, "link_to": "LinkedIn Settings", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:36.871352", + "modified": "2021-08-05 12:15:56.913091", "modified_by": "Administrator", "module": "CRM", "name": "CRM", "onboarding": "CRM", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 7, "shortcuts": [ { "color": "Blue", @@ -403,5 +448,6 @@ "link_to": "CRM", "type": "Dashboard" } - ] + ], + "title": "CRM" } \ No newline at end of file diff --git a/erpnext/education/workspace/education/education.json b/erpnext/education/workspace/education/education.json index bf7496146d..c58ddd63cf 100644 --- a/erpnext/education/workspace/education/education.json +++ b/erpnext/education/workspace/education/education.json @@ -1,27 +1,32 @@ { - "category": "Domains", + "category": "", "charts": [ { "chart_name": "Program Enrollments", "label": "Program Enrollments" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Education\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Program Enrollments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Instructor\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Program\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fees\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course Scheduling Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Attendance Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Student and Instructor\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Content Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Admission\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fees\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Schedule\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"LMS Activity\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-03-02 17:22:57.066401", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "education", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Education", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Student and Instructor", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -30,6 +35,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student", + "link_count": 0, "link_to": "Student", "link_type": "DocType", "onboard": 1, @@ -40,6 +46,7 @@ "hidden": 0, "is_query_report": 0, "label": "Instructor", + "link_count": 0, "link_to": "Instructor", "link_type": "DocType", "onboard": 1, @@ -50,6 +57,7 @@ "hidden": 0, "is_query_report": 0, "label": "Guardian", + "link_count": 0, "link_to": "Guardian", "link_type": "DocType", "onboard": 0, @@ -60,6 +68,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Group", + "link_count": 0, "link_to": "Student Group", "link_type": "DocType", "onboard": 0, @@ -70,6 +79,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Log", + "link_count": 0, "link_to": "Student Log", "link_type": "DocType", "onboard": 0, @@ -79,6 +89,7 @@ "hidden": 0, "is_query_report": 0, "label": "Masters", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -87,6 +98,7 @@ "hidden": 0, "is_query_report": 0, "label": "Program", + "link_count": 0, "link_to": "Program", "link_type": "DocType", "onboard": 0, @@ -97,6 +109,7 @@ "hidden": 0, "is_query_report": 0, "label": "Course", + "link_count": 0, "link_to": "Course", "link_type": "DocType", "onboard": 1, @@ -107,6 +120,7 @@ "hidden": 0, "is_query_report": 0, "label": "Topic", + "link_count": 0, "link_to": "Topic", "link_type": "DocType", "onboard": 0, @@ -117,6 +131,7 @@ "hidden": 0, "is_query_report": 0, "label": "Room", + "link_count": 0, "link_to": "Room", "link_type": "DocType", "onboard": 1, @@ -126,6 +141,7 @@ "hidden": 0, "is_query_report": 0, "label": "Content Masters", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -134,6 +150,7 @@ "hidden": 0, "is_query_report": 0, "label": "Article", + "link_count": 0, "link_to": "Article", "link_type": "DocType", "onboard": 0, @@ -144,6 +161,7 @@ "hidden": 0, "is_query_report": 0, "label": "Video", + "link_count": 0, "link_to": "Video", "link_type": "DocType", "onboard": 0, @@ -154,6 +172,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quiz", + "link_count": 0, "link_to": "Quiz", "link_type": "DocType", "onboard": 0, @@ -163,6 +182,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -171,6 +191,7 @@ "hidden": 0, "is_query_report": 0, "label": "Education Settings", + "link_count": 0, "link_to": "Education Settings", "link_type": "DocType", "onboard": 0, @@ -181,6 +202,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Category", + "link_count": 0, "link_to": "Student Category", "link_type": "DocType", "onboard": 0, @@ -191,6 +213,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Batch Name", + "link_count": 0, "link_to": "Student Batch Name", "link_type": "DocType", "onboard": 0, @@ -201,6 +224,7 @@ "hidden": 0, "is_query_report": 0, "label": "Grading Scale", + "link_count": 0, "link_to": "Grading Scale", "link_type": "DocType", "onboard": 1, @@ -211,6 +235,7 @@ "hidden": 0, "is_query_report": 0, "label": "Academic Term", + "link_count": 0, "link_to": "Academic Term", "link_type": "DocType", "onboard": 0, @@ -221,6 +246,7 @@ "hidden": 0, "is_query_report": 0, "label": "Academic Year", + "link_count": 0, "link_to": "Academic Year", "link_type": "DocType", "onboard": 0, @@ -230,6 +256,7 @@ "hidden": 0, "is_query_report": 0, "label": "Admission", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -238,6 +265,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Applicant", + "link_count": 0, "link_to": "Student Applicant", "link_type": "DocType", "onboard": 0, @@ -248,6 +276,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Admission", + "link_count": 0, "link_to": "Student Admission", "link_type": "DocType", "onboard": 0, @@ -258,6 +287,7 @@ "hidden": 0, "is_query_report": 0, "label": "Program Enrollment", + "link_count": 0, "link_to": "Program Enrollment", "link_type": "DocType", "onboard": 0, @@ -268,6 +298,7 @@ "hidden": 0, "is_query_report": 0, "label": "Course Enrollment", + "link_count": 0, "link_to": "Course Enrollment", "link_type": "DocType", "onboard": 0, @@ -277,6 +308,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fees", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -285,6 +317,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fee Structure", + "link_count": 0, "link_to": "Fee Structure", "link_type": "DocType", "onboard": 0, @@ -295,6 +328,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fee Category", + "link_count": 0, "link_to": "Fee Category", "link_type": "DocType", "onboard": 0, @@ -305,6 +339,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fee Schedule", + "link_count": 0, "link_to": "Fee Schedule", "link_type": "DocType", "onboard": 0, @@ -315,6 +350,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fees", + "link_count": 0, "link_to": "Fees", "link_type": "DocType", "onboard": 0, @@ -325,6 +361,7 @@ "hidden": 0, "is_query_report": 1, "label": "Student Fee Collection Report", + "link_count": 0, "link_to": "Student Fee Collection", "link_type": "Report", "onboard": 0, @@ -335,6 +372,7 @@ "hidden": 0, "is_query_report": 1, "label": "Program wise Fee Collection Report", + "link_count": 0, "link_to": "Program wise Fee Collection", "link_type": "Report", "onboard": 0, @@ -344,6 +382,7 @@ "hidden": 0, "is_query_report": 0, "label": "Schedule", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -352,6 +391,7 @@ "hidden": 0, "is_query_report": 0, "label": "Course Schedule", + "link_count": 0, "link_to": "Course Schedule", "link_type": "DocType", "onboard": 0, @@ -362,6 +402,7 @@ "hidden": 0, "is_query_report": 0, "label": "Course Scheduling Tool", + "link_count": 0, "link_to": "Course Scheduling Tool", "link_type": "DocType", "onboard": 0, @@ -371,6 +412,7 @@ "hidden": 0, "is_query_report": 0, "label": "Attendance", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -379,6 +421,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Attendance", + "link_count": 0, "link_to": "Student Attendance", "link_type": "DocType", "onboard": 0, @@ -389,6 +432,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Leave Application", + "link_count": 0, "link_to": "Student Leave Application", "link_type": "DocType", "onboard": 0, @@ -399,6 +443,7 @@ "hidden": 0, "is_query_report": 1, "label": "Student Monthly Attendance Sheet", + "link_count": 0, "link_to": "Student Monthly Attendance Sheet", "link_type": "Report", "onboard": 0, @@ -409,6 +454,7 @@ "hidden": 0, "is_query_report": 1, "label": "Absent Student Report", + "link_count": 0, "link_to": "Absent Student Report", "link_type": "Report", "onboard": 0, @@ -419,6 +465,7 @@ "hidden": 0, "is_query_report": 1, "label": "Student Batch-Wise Attendance", + "link_count": 0, "link_to": "Student Batch-Wise Attendance", "link_type": "Report", "onboard": 0, @@ -428,6 +475,7 @@ "hidden": 0, "is_query_report": 0, "label": "LMS Activity", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -436,6 +484,7 @@ "hidden": 0, "is_query_report": 0, "label": "Course Enrollment", + "link_count": 0, "link_to": "Course Enrollment", "link_type": "DocType", "onboard": 0, @@ -446,6 +495,7 @@ "hidden": 0, "is_query_report": 0, "label": "Course Activity", + "link_count": 0, "link_to": "Course Activity", "link_type": "DocType", "onboard": 0, @@ -456,6 +506,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quiz Activity", + "link_count": 0, "link_to": "Quiz Activity", "link_type": "DocType", "onboard": 0, @@ -465,6 +516,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assessment", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -473,6 +525,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assessment Plan", + "link_count": 0, "link_to": "Assessment Plan", "link_type": "DocType", "onboard": 0, @@ -483,6 +536,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assessment Group", + "link_count": 0, "link_to": "Assessment Group", "link_type": "DocType", "onboard": 0, @@ -493,6 +547,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assessment Result", + "link_count": 0, "link_to": "Assessment Result", "link_type": "DocType", "onboard": 0, @@ -503,6 +558,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assessment Criteria", + "link_count": 0, "link_to": "Assessment Criteria", "link_type": "DocType", "onboard": 0, @@ -512,6 +568,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assessment Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -520,6 +577,7 @@ "hidden": 0, "is_query_report": 1, "label": "Course wise Assessment Report", + "link_count": 0, "link_to": "Course wise Assessment Report", "link_type": "Report", "onboard": 0, @@ -530,6 +588,7 @@ "hidden": 0, "is_query_report": 1, "label": "Final Assessment Grades", + "link_count": 0, "link_to": "Final Assessment Grades", "link_type": "Report", "onboard": 0, @@ -540,6 +599,7 @@ "hidden": 0, "is_query_report": 1, "label": "Assessment Plan Status", + "link_count": 0, "link_to": "Assessment Plan Status", "link_type": "Report", "onboard": 0, @@ -550,6 +610,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Report Generation Tool", + "link_count": 0, "link_to": "Student Report Generation Tool", "link_type": "DocType", "onboard": 0, @@ -559,6 +620,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tools", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -567,6 +629,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Attendance Tool", + "link_count": 0, "link_to": "Student Attendance Tool", "link_type": "DocType", "onboard": 0, @@ -577,6 +640,7 @@ "hidden": 0, "is_query_report": 0, "label": "Assessment Result Tool", + "link_count": 0, "link_to": "Assessment Result Tool", "link_type": "DocType", "onboard": 0, @@ -587,6 +651,7 @@ "hidden": 0, "is_query_report": 0, "label": "Student Group Creation Tool", + "link_count": 0, "link_to": "Student Group Creation Tool", "link_type": "DocType", "onboard": 0, @@ -597,6 +662,7 @@ "hidden": 0, "is_query_report": 0, "label": "Program Enrollment Tool", + "link_count": 0, "link_to": "Program Enrollment Tool", "link_type": "DocType", "onboard": 0, @@ -607,6 +673,7 @@ "hidden": 0, "is_query_report": 0, "label": "Course Scheduling Tool", + "link_count": 0, "link_to": "Course Scheduling Tool", "link_type": "DocType", "onboard": 0, @@ -616,6 +683,7 @@ "hidden": 0, "is_query_report": 0, "label": "Other Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -624,21 +692,26 @@ "hidden": 0, "is_query_report": 1, "label": "Student and Guardian Contact Details", + "link_count": 0, "link_to": "Student and Guardian Contact Details", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:37.448989", + "modified": "2021-08-05 12:15:57.929275", "modified_by": "Administrator", "module": "Education", "name": "Education", "onboarding": "Education", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, "restrict_to_domain": "Education", + "roles": [], + "sequence_id": 9, "shortcuts": [ { "color": "Grey", @@ -697,5 +770,6 @@ "link_to": "Education", "type": "Dashboard" } - ] + ], + "title": "Education" } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json index 24b8e48ed6..9f9204a78d 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -1,22 +1,27 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Marketplace\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payments\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", "creation": "2020-08-20 19:30:48.138801", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "Integrations", - "extends_another_page": 1, - "hide_custom": 1, + "extends": "", + "extends_another_page": 0, + "for_user": "", + "hide_custom": 0, + "icon": "integration", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "ERPNext Integrations", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Marketplace", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Woocommerce Settings", + "link_count": 0, "link_to": "Woocommerce Settings", "link_type": "DocType", "onboard": 0, @@ -35,15 +41,28 @@ "hidden": 0, "is_query_report": 0, "label": "Amazon MWS Settings", + "link_count": 0, "link_to": "Amazon MWS Settings", "link_type": "DocType", "onboard": 0, "type": "Link" }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shopify Settings", + "link_count": 0, + "link_to": "Shopify Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, "label": "Payments", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -52,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "GoCardless Settings", + "link_count": 0, "link_to": "GoCardless Settings", "link_type": "DocType", "onboard": 0, @@ -62,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "M-Pesa Settings", + "link_count": 0, "link_to": "Mpesa Settings", "link_type": "DocType", "onboard": 0, @@ -71,6 +92,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -79,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Plaid Settings", + "link_count": 0, "link_to": "Plaid Settings", "link_type": "DocType", "onboard": 0, @@ -89,18 +112,26 @@ "hidden": 0, "is_query_report": 0, "label": "Exotel Settings", + "link_count": 0, "link_to": "Exotel Settings", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:35.846528", + "modified": "2021-08-05 12:15:58.740246", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, - "shortcuts": [] + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 10, + "shortcuts": [], + "title": "ERPNext Integrations" } diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json index d656b3c4fe..fd4afb85fd 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json @@ -1,22 +1,27 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Integrations Settings\", \"col\": 4}}]", "creation": "2020-07-31 10:38:54.021237", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "Settings", - "extends_another_page": 1, + "extends": "", + "extends_another_page": 0, + "for_user": "", "hide_custom": 0, + "icon": "setting", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "ERPNext Integrations Settings", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Integrations Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,16 +30,29 @@ "hidden": 0, "is_query_report": 0, "label": "Woocommerce Settings", + "link_count": 0, "link_to": "Woocommerce Settings", "link_type": "DocType", "onboard": 0, "type": "Link" }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Shopify Settings", + "link_count": 0, + "link_to": "Shopify Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, "is_query_report": 0, "label": "Amazon MWS Settings", + "link_count": 0, "link_to": "Amazon MWS Settings", "link_type": "DocType", "onboard": 0, @@ -45,6 +63,7 @@ "hidden": 0, "is_query_report": 0, "label": "Plaid Settings", + "link_count": 0, "link_to": "Plaid Settings", "link_type": "DocType", "onboard": 0, @@ -55,18 +74,26 @@ "hidden": 0, "is_query_report": 0, "label": "Exotel Settings", + "link_count": 0, "link_to": "Exotel Settings", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:34.732552", + "modified": "2021-08-05 12:15:58.951704", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations Settings", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, - "shortcuts": [] + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 11, + "shortcuts": [], + "title": "ERPNext Integrations Settings" } diff --git a/erpnext/healthcare/workspace/healthcare/healthcare.json b/erpnext/healthcare/workspace/healthcare/healthcare.json index b93dda2e87..55132f3695 100644 --- a/erpnext/healthcare/workspace/healthcare/healthcare.json +++ b/erpnext/healthcare/workspace/healthcare/healthcare.json @@ -1,5 +1,5 @@ { - "category": "Domains", + "category": "", "charts": [ { "chart_name": "Patient Appointments", @@ -7,22 +7,27 @@ } ], "charts_label": "", + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Healthcare\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Patient Appointments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient Appointment\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Service Unit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Practitioner\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient History\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Rehabilitation and Physiotherapy\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Records and History\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 17:23:17.919682", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "healthcare", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Healthcare", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Masters", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -31,6 +36,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient", + "link_count": 0, "link_to": "Patient", "link_type": "DocType", "onboard": 1, @@ -41,6 +47,7 @@ "hidden": 0, "is_query_report": 0, "label": "Healthcare Practitioner", + "link_count": 0, "link_to": "Healthcare Practitioner", "link_type": "DocType", "onboard": 1, @@ -51,6 +58,7 @@ "hidden": 0, "is_query_report": 0, "label": "Practitioner Schedule", + "link_count": 0, "link_to": "Practitioner Schedule", "link_type": "DocType", "onboard": 1, @@ -61,6 +69,7 @@ "hidden": 0, "is_query_report": 0, "label": "Medical Department", + "link_count": 0, "link_to": "Medical Department", "link_type": "DocType", "onboard": 0, @@ -71,6 +80,7 @@ "hidden": 0, "is_query_report": 0, "label": "Healthcare Service Unit Type", + "link_count": 0, "link_to": "Healthcare Service Unit Type", "link_type": "DocType", "onboard": 0, @@ -81,6 +91,7 @@ "hidden": 0, "is_query_report": 0, "label": "Healthcare Service Unit", + "link_count": 0, "link_to": "Healthcare Service Unit", "link_type": "DocType", "onboard": 0, @@ -91,6 +102,7 @@ "hidden": 0, "is_query_report": 0, "label": "Medical Code Standard", + "link_count": 0, "link_to": "Medical Code Standard", "link_type": "DocType", "onboard": 0, @@ -101,6 +113,7 @@ "hidden": 0, "is_query_report": 0, "label": "Medical Code", + "link_count": 0, "link_to": "Medical Code", "link_type": "DocType", "onboard": 0, @@ -110,6 +123,7 @@ "hidden": 0, "is_query_report": 0, "label": "Consultation Setup", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -118,6 +132,7 @@ "hidden": 0, "is_query_report": 0, "label": "Appointment Type", + "link_count": 0, "link_to": "Appointment Type", "link_type": "DocType", "onboard": 0, @@ -128,6 +143,7 @@ "hidden": 0, "is_query_report": 0, "label": "Clinical Procedure Template", + "link_count": 0, "link_to": "Clinical Procedure Template", "link_type": "DocType", "onboard": 0, @@ -138,6 +154,7 @@ "hidden": 0, "is_query_report": 0, "label": "Prescription Dosage", + "link_count": 0, "link_to": "Prescription Dosage", "link_type": "DocType", "onboard": 0, @@ -148,6 +165,7 @@ "hidden": 0, "is_query_report": 0, "label": "Prescription Duration", + "link_count": 0, "link_to": "Prescription Duration", "link_type": "DocType", "onboard": 0, @@ -158,6 +176,7 @@ "hidden": 0, "is_query_report": 0, "label": "Antibiotic", + "link_count": 0, "link_to": "Antibiotic", "link_type": "DocType", "onboard": 0, @@ -167,6 +186,7 @@ "hidden": 0, "is_query_report": 0, "label": "Consultation", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -175,6 +195,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient Appointment", + "link_count": 0, "link_to": "Patient Appointment", "link_type": "DocType", "onboard": 0, @@ -185,6 +206,7 @@ "hidden": 0, "is_query_report": 0, "label": "Clinical Procedure", + "link_count": 0, "link_to": "Clinical Procedure", "link_type": "DocType", "onboard": 0, @@ -195,6 +217,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient Encounter", + "link_count": 0, "link_to": "Patient Encounter", "link_type": "DocType", "onboard": 0, @@ -205,6 +228,7 @@ "hidden": 0, "is_query_report": 0, "label": "Vital Signs", + "link_count": 0, "link_to": "Vital Signs", "link_type": "DocType", "onboard": 0, @@ -215,6 +239,7 @@ "hidden": 0, "is_query_report": 0, "label": "Complaint", + "link_count": 0, "link_to": "Complaint", "link_type": "DocType", "onboard": 0, @@ -225,6 +250,7 @@ "hidden": 0, "is_query_report": 0, "label": "Diagnosis", + "link_count": 0, "link_to": "Diagnosis", "link_type": "DocType", "onboard": 0, @@ -235,6 +261,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fee Validity", + "link_count": 0, "link_to": "Fee Validity", "link_type": "DocType", "onboard": 0, @@ -244,6 +271,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -252,6 +280,7 @@ "hidden": 0, "is_query_report": 0, "label": "Healthcare Settings", + "link_count": 0, "link_to": "Healthcare Settings", "link_type": "DocType", "onboard": 1, @@ -261,6 +290,7 @@ "hidden": 0, "is_query_report": 0, "label": "Laboratory Setup", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -269,6 +299,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lab Test Template", + "link_count": 0, "link_to": "Lab Test Template", "link_type": "DocType", "onboard": 0, @@ -279,6 +310,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lab Test Sample", + "link_count": 0, "link_to": "Lab Test Sample", "link_type": "DocType", "onboard": 0, @@ -289,6 +321,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lab Test UOM", + "link_count": 0, "link_to": "Lab Test UOM", "link_type": "DocType", "onboard": 0, @@ -299,6 +332,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sensitivity", + "link_count": 0, "link_to": "Sensitivity", "link_type": "DocType", "onboard": 0, @@ -308,6 +342,7 @@ "hidden": 0, "is_query_report": 0, "label": "Laboratory", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -316,6 +351,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lab Test", + "link_count": 0, "link_to": "Lab Test", "link_type": "DocType", "onboard": 0, @@ -326,6 +362,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sample Collection", + "link_count": 0, "link_to": "Sample Collection", "link_type": "DocType", "onboard": 0, @@ -336,6 +373,7 @@ "hidden": 0, "is_query_report": 0, "label": "Dosage Form", + "link_count": 0, "link_to": "Dosage Form", "link_type": "DocType", "onboard": 0, @@ -345,6 +383,7 @@ "hidden": 0, "is_query_report": 0, "label": "Rehabilitation and Physiotherapy", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -353,6 +392,7 @@ "hidden": 0, "is_query_report": 0, "label": "Exercise Type", + "link_count": 0, "link_to": "Exercise Type", "link_type": "DocType", "onboard": 1, @@ -363,6 +403,7 @@ "hidden": 0, "is_query_report": 0, "label": "Therapy Type", + "link_count": 0, "link_to": "Therapy Type", "link_type": "DocType", "onboard": 1, @@ -373,6 +414,7 @@ "hidden": 0, "is_query_report": 0, "label": "Therapy Plan", + "link_count": 0, "link_to": "Therapy Plan", "link_type": "DocType", "onboard": 0, @@ -383,6 +425,7 @@ "hidden": 0, "is_query_report": 0, "label": "Therapy Session", + "link_count": 0, "link_to": "Therapy Session", "link_type": "DocType", "onboard": 0, @@ -393,6 +436,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient Assessment Template", + "link_count": 0, "link_to": "Patient Assessment Template", "link_type": "DocType", "onboard": 0, @@ -403,6 +447,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient Assessment", + "link_count": 0, "link_to": "Patient Assessment", "link_type": "DocType", "onboard": 0, @@ -412,6 +457,7 @@ "hidden": 0, "is_query_report": 0, "label": "Records and History", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -420,6 +466,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient History", + "link_count": 0, "link_to": "patient_history", "link_type": "Page", "onboard": 0, @@ -430,6 +477,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient Progress", + "link_count": 0, "link_to": "patient-progress", "link_type": "Page", "onboard": 0, @@ -440,6 +488,7 @@ "hidden": 0, "is_query_report": 0, "label": "Patient Medical Record", + "link_count": 0, "link_to": "Patient Medical Record", "link_type": "DocType", "onboard": 0, @@ -450,6 +499,7 @@ "hidden": 0, "is_query_report": 0, "label": "Inpatient Record", + "link_count": 0, "link_to": "Inpatient Record", "link_type": "DocType", "onboard": 0, @@ -459,6 +509,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -467,6 +518,7 @@ "hidden": 0, "is_query_report": 1, "label": "Patient Appointment Analytics", + "link_count": 0, "link_to": "Patient Appointment Analytics", "link_type": "Report", "onboard": 0, @@ -477,21 +529,26 @@ "hidden": 0, "is_query_report": 1, "label": "Lab Test Report", + "link_count": 0, "link_to": "Lab Test Report", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:34.841396", + "modified": "2021-08-05 12:15:59.434612", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", "onboarding": "Healthcare", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, "restrict_to_domain": "Healthcare", + "roles": [], + "sequence_id": 13, "shortcuts": [ { "color": "Orange", @@ -532,5 +589,6 @@ "link_to": "Healthcare", "type": "Dashboard" } - ] + ], + "title": "Healthcare" } \ No newline at end of file diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index 4500ba4560..575fa7be6f 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -1,28 +1,32 @@ { - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Outgoing Salary", "label": "Outgoing Salary" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Human Resource\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Employee\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Leave Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Job Applicant\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee Lifecycle\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Shift Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Leaves\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Expense Claims\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fleet Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Recruitment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loans\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Training\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Performance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:48:58.322521", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "hr", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "HR", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Employee", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -31,6 +35,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee", + "link_count": 0, "link_to": "Employee", "link_type": "DocType", "onboard": 1, @@ -41,6 +46,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employment Type", + "link_count": 0, "link_to": "Employment Type", "link_type": "DocType", "onboard": 0, @@ -51,6 +57,7 @@ "hidden": 0, "is_query_report": 0, "label": "Branch", + "link_count": 0, "link_to": "Branch", "link_type": "DocType", "onboard": 0, @@ -61,6 +68,7 @@ "hidden": 0, "is_query_report": 0, "label": "Department", + "link_count": 0, "link_to": "Department", "link_type": "DocType", "onboard": 0, @@ -71,6 +79,7 @@ "hidden": 0, "is_query_report": 0, "label": "Designation", + "link_count": 0, "link_to": "Designation", "link_type": "DocType", "onboard": 0, @@ -81,6 +90,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Grade", + "link_count": 0, "link_to": "Employee Grade", "link_type": "DocType", "onboard": 0, @@ -91,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Group", + "link_count": 0, "link_to": "Employee Group", "link_type": "DocType", "onboard": 0, @@ -101,6 +112,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Health Insurance", + "link_count": 0, "link_to": "Employee Health Insurance", "link_type": "DocType", "onboard": 0, @@ -110,6 +122,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Lifecycle", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -118,6 +131,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Onboarding", + "link_count": 0, "link_to": "Employee Onboarding", "link_type": "DocType", "onboard": 0, @@ -128,6 +142,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Skill Map", + "link_count": 0, "link_to": "Employee Skill Map", "link_type": "DocType", "onboard": 0, @@ -138,6 +153,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Promotion", + "link_count": 0, "link_to": "Employee Promotion", "link_type": "DocType", "onboard": 0, @@ -148,6 +164,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Transfer", + "link_count": 0, "link_to": "Employee Transfer", "link_type": "DocType", "onboard": 0, @@ -157,6 +174,7 @@ "hidden": 0, "is_query_report": 0, "label": "Grievance Type", + "link_count": 0, "link_to": "Grievance Type", "link_type": "DocType", "onboard": 0, @@ -166,6 +184,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Grievance", + "link_count": 0, "link_to": "Employee Grievance", "link_type": "DocType", "onboard": 0, @@ -176,6 +195,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Separation", + "link_count": 0, "link_to": "Employee Separation", "link_type": "DocType", "onboard": 0, @@ -186,6 +206,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Onboarding Template", + "link_count": 0, "link_to": "Employee Onboarding Template", "link_type": "DocType", "onboard": 0, @@ -196,6 +217,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Separation Template", + "link_count": 0, "link_to": "Employee Separation Template", "link_type": "DocType", "onboard": 0, @@ -205,6 +227,7 @@ "hidden": 0, "is_query_report": 0, "label": "Shift Management", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -213,6 +236,7 @@ "hidden": 0, "is_query_report": 0, "label": "Shift Type", + "link_count": 0, "link_to": "Shift Type", "link_type": "DocType", "onboard": 0, @@ -223,6 +247,7 @@ "hidden": 0, "is_query_report": 0, "label": "Shift Request", + "link_count": 0, "link_to": "Shift Request", "link_type": "DocType", "onboard": 0, @@ -233,6 +258,7 @@ "hidden": 0, "is_query_report": 0, "label": "Shift Assignment", + "link_count": 0, "link_to": "Shift Assignment", "link_type": "DocType", "onboard": 0, @@ -242,6 +268,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leaves", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -250,6 +277,7 @@ "hidden": 0, "is_query_report": 0, "label": "Holiday List", + "link_count": 0, "link_to": "Holiday List", "link_type": "DocType", "onboard": 0, @@ -260,6 +288,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Type", + "link_count": 0, "link_to": "Leave Type", "link_type": "DocType", "onboard": 0, @@ -270,6 +299,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Period", + "link_count": 0, "link_to": "Leave Period", "link_type": "DocType", "onboard": 0, @@ -280,6 +310,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Policy", + "link_count": 0, "link_to": "Leave Policy", "link_type": "DocType", "onboard": 0, @@ -290,6 +321,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Policy Assignment", + "link_count": 0, "link_to": "Leave Policy Assignment", "link_type": "DocType", "onboard": 0, @@ -300,6 +332,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Application", + "link_count": 0, "link_to": "Leave Application", "link_type": "DocType", "onboard": 0, @@ -310,6 +343,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Allocation", + "link_count": 0, "link_to": "Leave Allocation", "link_type": "DocType", "onboard": 0, @@ -320,6 +354,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Encashment", + "link_count": 0, "link_to": "Leave Encashment", "link_type": "DocType", "onboard": 0, @@ -330,6 +365,7 @@ "hidden": 0, "is_query_report": 0, "label": "Leave Block List", + "link_count": 0, "link_to": "Leave Block List", "link_type": "DocType", "onboard": 0, @@ -340,6 +376,7 @@ "hidden": 0, "is_query_report": 0, "label": "Compensatory Leave Request", + "link_count": 0, "link_to": "Compensatory Leave Request", "link_type": "DocType", "onboard": 0, @@ -349,6 +386,7 @@ "hidden": 0, "is_query_report": 0, "label": "Attendance", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -357,6 +395,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Attendance Tool", + "link_count": 0, "link_to": "Employee Attendance Tool", "link_type": "DocType", "onboard": 1, @@ -367,6 +406,7 @@ "hidden": 0, "is_query_report": 0, "label": "Attendance", + "link_count": 0, "link_to": "Attendance", "link_type": "DocType", "onboard": 1, @@ -377,6 +417,7 @@ "hidden": 0, "is_query_report": 0, "label": "Attendance Request", + "link_count": 0, "link_to": "Attendance Request", "link_type": "DocType", "onboard": 0, @@ -387,6 +428,7 @@ "hidden": 0, "is_query_report": 0, "label": "Upload Attendance", + "link_count": 0, "link_to": "Upload Attendance", "link_type": "DocType", "onboard": 0, @@ -397,6 +439,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Checkin", + "link_count": 0, "link_to": "Employee Checkin", "link_type": "DocType", "onboard": 0, @@ -406,6 +449,7 @@ "hidden": 0, "is_query_report": 0, "label": "Expense Claims", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -414,6 +458,7 @@ "hidden": 0, "is_query_report": 0, "label": "Expense Claim", + "link_count": 0, "link_to": "Expense Claim", "link_type": "DocType", "onboard": 0, @@ -424,6 +469,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Advance", + "link_count": 0, "link_to": "Employee Advance", "link_type": "DocType", "onboard": 0, @@ -433,6 +479,7 @@ "hidden": 0, "is_query_report": 0, "label": "Travel Request", + "link_count": 0, "link_to": "Travel Request", "link_type": "DocType", "onboard": 0, @@ -442,6 +489,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -450,6 +498,7 @@ "hidden": 0, "is_query_report": 0, "label": "HR Settings", + "link_count": 0, "link_to": "HR Settings", "link_type": "DocType", "onboard": 0, @@ -460,6 +509,7 @@ "hidden": 0, "is_query_report": 0, "label": "Daily Work Summary Group", + "link_count": 0, "link_to": "Daily Work Summary Group", "link_type": "DocType", "onboard": 0, @@ -470,6 +520,7 @@ "hidden": 0, "is_query_report": 0, "label": "Team Updates", + "link_count": 0, "link_to": "team-updates", "link_type": "Page", "onboard": 0, @@ -479,6 +530,7 @@ "hidden": 0, "is_query_report": 0, "label": "Fleet Management", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -486,6 +538,7 @@ "hidden": 0, "is_query_report": 0, "label": "Driver", + "link_count": 0, "link_to": "Driver", "link_type": "DocType", "onboard": 0, @@ -496,6 +549,7 @@ "hidden": 0, "is_query_report": 0, "label": "Vehicle", + "link_count": 0, "link_to": "Vehicle", "link_type": "DocType", "onboard": 0, @@ -506,6 +560,7 @@ "hidden": 0, "is_query_report": 0, "label": "Vehicle Log", + "link_count": 0, "link_to": "Vehicle Log", "link_type": "DocType", "onboard": 0, @@ -516,6 +571,7 @@ "hidden": 0, "is_query_report": 1, "label": "Vehicle Expenses", + "link_count": 0, "link_to": "Vehicle Expenses", "link_type": "Report", "onboard": 0, @@ -525,6 +581,7 @@ "hidden": 0, "is_query_report": 0, "label": "Recruitment", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -533,6 +590,7 @@ "hidden": 0, "is_query_report": 0, "label": "Job Opening", + "link_count": 0, "link_to": "Job Opening", "link_type": "DocType", "onboard": 1, @@ -542,6 +600,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Referral", + "link_count": 0, "link_to": "Employee Referral", "link_type": "DocType", "onboard": 0, @@ -552,6 +611,7 @@ "hidden": 0, "is_query_report": 0, "label": "Job Applicant", + "link_count": 0, "link_to": "Job Applicant", "link_type": "DocType", "onboard": 1, @@ -562,6 +622,7 @@ "hidden": 0, "is_query_report": 0, "label": "Job Offer", + "link_count": 0, "link_to": "Job Offer", "link_type": "DocType", "onboard": 1, @@ -572,6 +633,7 @@ "hidden": 0, "is_query_report": 0, "label": "Staffing Plan", + "link_count": 0, "link_to": "Staffing Plan", "link_type": "DocType", "onboard": 0, @@ -581,6 +643,7 @@ "hidden": 0, "is_query_report": 0, "label": "Appointment Letter", + "link_count": 0, "link_to": "Appointment Letter", "link_type": "DocType", "onboard": 0, @@ -590,6 +653,7 @@ "hidden": 0, "is_query_report": 0, "label": "Appointment Letter Template", + "link_count": 0, "link_to": "Appointment Letter Template", "link_type": "DocType", "onboard": 0, @@ -599,6 +663,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loans", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -607,6 +672,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Application", + "link_count": 0, "link_to": "Loan Application", "link_type": "DocType", "onboard": 0, @@ -617,6 +683,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan", + "link_count": 0, "link_to": "Loan", "link_type": "DocType", "onboard": 0, @@ -627,6 +694,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Type", + "link_count": 0, "link_to": "Loan Type", "link_type": "DocType", "onboard": 0, @@ -636,6 +704,7 @@ "hidden": 0, "is_query_report": 0, "label": "Training", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -644,6 +713,7 @@ "hidden": 0, "is_query_report": 0, "label": "Training Program", + "link_count": 0, "link_to": "Training Program", "link_type": "DocType", "onboard": 0, @@ -654,6 +724,7 @@ "hidden": 0, "is_query_report": 0, "label": "Training Event", + "link_count": 0, "link_to": "Training Event", "link_type": "DocType", "onboard": 0, @@ -664,6 +735,7 @@ "hidden": 0, "is_query_report": 0, "label": "Training Result", + "link_count": 0, "link_to": "Training Result", "link_type": "DocType", "onboard": 0, @@ -674,6 +746,7 @@ "hidden": 0, "is_query_report": 0, "label": "Training Feedback", + "link_count": 0, "link_to": "Training Feedback", "link_type": "DocType", "onboard": 0, @@ -683,6 +756,7 @@ "hidden": 0, "is_query_report": 0, "label": "Performance", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -691,6 +765,7 @@ "hidden": 0, "is_query_report": 0, "label": "Appraisal", + "link_count": 0, "link_to": "Appraisal", "link_type": "DocType", "onboard": 0, @@ -701,6 +776,7 @@ "hidden": 0, "is_query_report": 0, "label": "Appraisal Template", + "link_count": 0, "link_to": "Appraisal Template", "link_type": "DocType", "onboard": 0, @@ -711,6 +787,7 @@ "hidden": 0, "is_query_report": 0, "label": "Energy Point Rule", + "link_count": 0, "link_to": "Energy Point Rule", "link_type": "DocType", "onboard": 0, @@ -721,6 +798,7 @@ "hidden": 0, "is_query_report": 0, "label": "Energy Point Log", + "link_count": 0, "link_to": "Energy Point Log", "link_type": "DocType", "onboard": 0, @@ -730,6 +808,7 @@ "hidden": 0, "is_query_report": 0, "label": "Key Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -738,6 +817,7 @@ "hidden": 0, "is_query_report": 1, "label": "Monthly Attendance Sheet", + "link_count": 0, "link_to": "Monthly Attendance Sheet", "link_type": "Report", "onboard": 0, @@ -748,6 +828,7 @@ "hidden": 0, "is_query_report": 1, "label": "Recruitment Analytics", + "link_count": 0, "link_to": "Recruitment Analytics", "link_type": "Report", "onboard": 0, @@ -758,6 +839,7 @@ "hidden": 0, "is_query_report": 1, "label": "Employee Analytics", + "link_count": 0, "link_to": "Employee Analytics", "link_type": "Report", "onboard": 0, @@ -768,6 +850,7 @@ "hidden": 0, "is_query_report": 1, "label": "Employee Leave Balance", + "link_count": 0, "link_to": "Employee Leave Balance", "link_type": "Report", "onboard": 0, @@ -778,6 +861,7 @@ "hidden": 0, "is_query_report": 1, "label": "Employee Leave Balance Summary", + "link_count": 0, "link_to": "Employee Leave Balance Summary", "link_type": "Report", "onboard": 0, @@ -788,6 +872,7 @@ "hidden": 0, "is_query_report": 1, "label": "Employee Advance Summary", + "link_count": 0, "link_to": "Employee Advance Summary", "link_type": "Report", "onboard": 0, @@ -797,6 +882,7 @@ "hidden": 0, "is_query_report": 0, "label": "Other Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -805,6 +891,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Information", + "link_count": 0, "link_to": "Employee Information", "link_type": "Report", "onboard": 0, @@ -815,6 +902,7 @@ "hidden": 0, "is_query_report": 1, "label": "Employee Birthday", + "link_count": 0, "link_to": "Employee Birthday", "link_type": "Report", "onboard": 0, @@ -825,6 +913,7 @@ "hidden": 0, "is_query_report": 1, "label": "Employees Working on a Holiday", + "link_count": 0, "link_to": "Employees working on a holiday", "link_type": "Report", "onboard": 0, @@ -835,20 +924,26 @@ "hidden": 0, "is_query_report": 1, "label": "Daily Work Summary Replies", + "link_count": 0, "link_to": "Daily Work Summary Replies", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2021-05-13 17:19:40.524444", + "modified": "2021-08-05 12:15:59.842918", "modified_by": "Administrator", "module": "HR", "name": "HR", "onboarding": "Human Resource", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 14, "shortcuts": [ { "color": "Green", @@ -889,5 +984,6 @@ "stats_filter": "{\n \"status\": \"Open\"\n}", "type": "Dashboard" } - ] + ], + "title": "HR" } \ No newline at end of file diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json index d0b67f7c64..ca528ec6bd 100644 --- a/erpnext/loan_management/workspace/loan_management/loan_management.json +++ b/erpnext/loan_management/workspace/loan_management/loan_management.json @@ -1,23 +1,27 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Processes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Disbursement and Repayment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Security\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-12 16:35:55.299820", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "loan", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Loans", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Loan", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -26,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Type", + "link_count": 0, "link_to": "Loan Type", "link_type": "DocType", "onboard": 0, @@ -36,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Application", + "link_count": 0, "link_to": "Loan Application", "link_type": "DocType", "onboard": 0, @@ -46,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan", + "link_count": 0, "link_to": "Loan", "link_type": "DocType", "onboard": 0, @@ -55,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Processes", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -63,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Process Loan Security Shortfall", + "link_count": 0, "link_to": "Process Loan Security Shortfall", "link_type": "DocType", "onboard": 0, @@ -73,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Process Loan Interest Accrual", + "link_count": 0, "link_to": "Process Loan Interest Accrual", "link_type": "DocType", "onboard": 0, @@ -82,6 +92,7 @@ "hidden": 0, "is_query_report": 0, "label": "Disbursement and Repayment", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -90,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Disbursement", + "link_count": 0, "link_to": "Loan Disbursement", "link_type": "DocType", "onboard": 0, @@ -100,6 +112,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Repayment", + "link_count": 0, "link_to": "Loan Repayment", "link_type": "DocType", "onboard": 0, @@ -110,6 +123,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Write Off", + "link_count": 0, "link_to": "Loan Write Off", "link_type": "DocType", "onboard": 0, @@ -120,6 +134,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Interest Accrual", + "link_count": 0, "link_to": "Loan Interest Accrual", "link_type": "DocType", "onboard": 0, @@ -129,6 +144,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Security", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -137,6 +153,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Security Type", + "link_count": 0, "link_to": "Loan Security Type", "link_type": "DocType", "onboard": 0, @@ -147,6 +164,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Security Price", + "link_count": 0, "link_to": "Loan Security Price", "link_type": "DocType", "onboard": 0, @@ -157,6 +175,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Security", + "link_count": 0, "link_to": "Loan Security", "link_type": "DocType", "onboard": 0, @@ -167,6 +186,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Security Pledge", + "link_count": 0, "link_to": "Loan Security Pledge", "link_type": "DocType", "onboard": 0, @@ -177,6 +197,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Security Unpledge", + "link_count": 0, "link_to": "Loan Security Unpledge", "link_type": "DocType", "onboard": 0, @@ -187,6 +208,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Security Shortfall", + "link_count": 0, "link_to": "Loan Security Shortfall", "link_type": "DocType", "onboard": 0, @@ -196,6 +218,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -204,6 +227,7 @@ "hidden": 0, "is_query_report": 1, "label": "Loan Repayment and Closure", + "link_count": 0, "link_to": "Loan Repayment and Closure", "link_type": "Report", "onboard": 0, @@ -214,19 +238,26 @@ "hidden": 0, "is_query_report": 1, "label": "Loan Security Status", + "link_count": 0, "link_to": "Loan Security Status", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2021-05-25 17:31:53.586508", + "modified": "2021-08-05 12:18:13.350904", "modified_by": "Administrator", "module": "Loan Management", "name": "Loans", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 16, "shortcuts": [ { "color": "Green", @@ -247,5 +278,6 @@ "link_to": "Loan Dashboard", "type": "Dashboard" } - ] + ], + "title": "Loans" } \ No newline at end of file diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index a355203e4d..84eabcd2bd 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -1,26 +1,31 @@ { - "category": "Domains", + "category": "", "charts": [ { "chart_name": "Produced Quantity" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Manufacturing\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Plan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Forecasting\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM Stock Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Planning Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Production\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bill of Materials\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", "creation": "2020-03-02 17:11:37.032604", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "organization", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Manufacturing", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Production", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -29,6 +34,7 @@ "hidden": 0, "is_query_report": 0, "label": "Work Order", + "link_count": 0, "link_to": "Work Order", "link_type": "DocType", "onboard": 1, @@ -39,6 +45,7 @@ "hidden": 0, "is_query_report": 0, "label": "Production Plan", + "link_count": 0, "link_to": "Production Plan", "link_type": "DocType", "onboard": 1, @@ -49,6 +56,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Entry", + "link_count": 0, "link_to": "Stock Entry", "link_type": "DocType", "onboard": 1, @@ -59,6 +67,7 @@ "hidden": 0, "is_query_report": 0, "label": "Job Card", + "link_count": 0, "link_to": "Job Card", "link_type": "DocType", "onboard": 0, @@ -69,6 +78,7 @@ "hidden": 0, "is_query_report": 0, "label": "Downtime Entry", + "link_count": 0, "link_to": "Downtime Entry", "link_type": "DocType", "onboard": 0, @@ -78,6 +88,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bill of Materials", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -86,6 +97,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item", + "link_count": 0, "link_to": "Item", "link_type": "DocType", "onboard": 1, @@ -96,6 +108,7 @@ "hidden": 0, "is_query_report": 0, "label": "Bill of Materials", + "link_count": 0, "link_to": "BOM", "link_type": "DocType", "onboard": 1, @@ -106,6 +119,7 @@ "hidden": 0, "is_query_report": 0, "label": "Workstation", + "link_count": 0, "link_to": "Workstation", "link_type": "DocType", "onboard": 0, @@ -116,6 +130,7 @@ "hidden": 0, "is_query_report": 0, "label": "Operation", + "link_count": 0, "link_to": "Operation", "link_type": "DocType", "onboard": 0, @@ -126,6 +141,7 @@ "hidden": 0, "is_query_report": 0, "label": "Routing", + "link_count": 0, "link_to": "Routing", "link_type": "DocType", "onboard": 0, @@ -135,6 +151,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -143,6 +160,7 @@ "hidden": 0, "is_query_report": 1, "label": "Production Planning Report", + "link_count": 0, "link_to": "Production Planning Report", "link_type": "Report", "onboard": 0, @@ -153,6 +171,7 @@ "hidden": 0, "is_query_report": 1, "label": "Work Order Summary", + "link_count": 0, "link_to": "Work Order Summary", "link_type": "Report", "onboard": 0, @@ -163,6 +182,7 @@ "hidden": 0, "is_query_report": 1, "label": "Quality Inspection Summary", + "link_count": 0, "link_to": "Quality Inspection Summary", "link_type": "Report", "onboard": 0, @@ -173,6 +193,7 @@ "hidden": 0, "is_query_report": 1, "label": "Downtime Analysis", + "link_count": 0, "link_to": "Downtime Analysis", "link_type": "Report", "onboard": 0, @@ -183,6 +204,7 @@ "hidden": 0, "is_query_report": 1, "label": "Job Card Summary", + "link_count": 0, "link_to": "Job Card Summary", "link_type": "Report", "onboard": 0, @@ -193,6 +215,7 @@ "hidden": 0, "is_query_report": 1, "label": "BOM Search", + "link_count": 0, "link_to": "BOM Search", "link_type": "Report", "onboard": 0, @@ -203,6 +226,7 @@ "hidden": 0, "is_query_report": 1, "label": "BOM Stock Report", + "link_count": 0, "link_to": "BOM Stock Report", "link_type": "Report", "onboard": 0, @@ -213,6 +237,7 @@ "hidden": 0, "is_query_report": 1, "label": "Production Analytics", + "link_count": 0, "link_to": "Production Analytics", "link_type": "Report", "onboard": 0, @@ -223,6 +248,7 @@ "hidden": 0, "is_query_report": 1, "label": "BOM Operations Time", + "link_count": 0, "link_to": "BOM Operations Time", "link_type": "Report", "onboard": 0, @@ -232,6 +258,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tools", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -240,6 +267,7 @@ "hidden": 0, "is_query_report": 0, "label": "BOM Update Tool", + "link_count": 0, "link_to": "BOM Update Tool", "link_type": "DocType", "onboard": 0, @@ -250,6 +278,7 @@ "hidden": 0, "is_query_report": 0, "label": "BOM Comparison Tool", + "link_count": 0, "link_to": "bom-comparison-tool", "link_type": "Page", "onboard": 0, @@ -259,6 +288,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -267,21 +297,26 @@ "hidden": 0, "is_query_report": 0, "label": "Manufacturing Settings", + "link_count": 0, "link_to": "Manufacturing Settings", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:39.365928", + "modified": "2021-08-05 12:16:00.825741", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", "onboarding": "Manufacturing", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, "restrict_to_domain": "Manufacturing", + "roles": [], + "sequence_id": 17, "shortcuts": [ { "color": "Green", @@ -346,5 +381,6 @@ "restrict_to_domain": "Manufacturing", "type": "Dashboard" } - ] + ], + "title": "Manufacturing" } \ No newline at end of file diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json index 2557d77d88..e6d4445945 100644 --- a/erpnext/non_profit/workspace/non_profit/non_profit.json +++ b/erpnext/non_profit/workspace/non_profit/non_profit.json @@ -1,23 +1,27 @@ { - "category": "Domains", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Member\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Profit Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Membership\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter Member\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Grant Application\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Membership\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Volunteer\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Donation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tax Exemption Certification (India)\", \"col\": 4}}]", "creation": "2020-03-02 17:23:47.811421", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "non-profit", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Non Profit", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Loan Management", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -26,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Type", + "link_count": 0, "link_to": "Loan Type", "link_type": "DocType", "onboard": 0, @@ -36,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan Application", + "link_count": 0, "link_to": "Loan Application", "link_type": "DocType", "onboard": 0, @@ -46,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loan", + "link_count": 0, "link_to": "Loan", "link_type": "DocType", "onboard": 0, @@ -55,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Grant Application", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -63,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Grant Application", + "link_count": 0, "link_to": "Grant Application", "link_type": "DocType", "onboard": 0, @@ -72,6 +81,7 @@ "hidden": 0, "is_query_report": 0, "label": "Membership", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -80,6 +90,7 @@ "hidden": 0, "is_query_report": 0, "label": "Member", + "link_count": 0, "link_to": "Member", "link_type": "DocType", "onboard": 1, @@ -90,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Membership", + "link_count": 0, "link_to": "Membership", "link_type": "DocType", "onboard": 1, @@ -100,6 +112,7 @@ "hidden": 0, "is_query_report": 0, "label": "Membership Type", + "link_count": 0, "link_to": "Membership Type", "link_type": "DocType", "onboard": 0, @@ -110,6 +123,7 @@ "hidden": 0, "is_query_report": 0, "label": "Membership Settings", + "link_count": 0, "link_to": "Non Profit Settings", "link_type": "DocType", "onboard": 0, @@ -119,6 +133,7 @@ "hidden": 0, "is_query_report": 0, "label": "Volunteer", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -127,6 +142,7 @@ "hidden": 0, "is_query_report": 0, "label": "Volunteer", + "link_count": 0, "link_to": "Volunteer", "link_type": "DocType", "onboard": 1, @@ -137,6 +153,7 @@ "hidden": 0, "is_query_report": 0, "label": "Volunteer Type", + "link_count": 0, "link_to": "Volunteer Type", "link_type": "DocType", "onboard": 0, @@ -146,6 +163,7 @@ "hidden": 0, "is_query_report": 0, "label": "Chapter", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -154,6 +172,7 @@ "hidden": 0, "is_query_report": 0, "label": "Chapter", + "link_count": 0, "link_to": "Chapter", "link_type": "DocType", "onboard": 1, @@ -163,6 +182,7 @@ "hidden": 0, "is_query_report": 0, "label": "Donation", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -171,6 +191,7 @@ "hidden": 0, "is_query_report": 0, "label": "Donor", + "link_count": 0, "link_to": "Donor", "link_type": "DocType", "onboard": 0, @@ -181,6 +202,7 @@ "hidden": 0, "is_query_report": 0, "label": "Donor Type", + "link_count": 0, "link_to": "Donor Type", "link_type": "DocType", "onboard": 0, @@ -190,6 +212,7 @@ "hidden": 0, "is_query_report": 0, "label": "Donation", + "link_count": 0, "link_to": "Donation", "link_type": "DocType", "onboard": 0, @@ -199,6 +222,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tax Exemption Certification (India)", + "link_count": 0, "link_type": "DocType", "onboard": 0, "type": "Card Break" @@ -207,20 +231,26 @@ "hidden": 0, "is_query_report": 0, "label": "Tax Exemption 80G Certificate", + "link_count": 0, "link_to": "Tax Exemption 80G Certificate", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2021-03-11 11:38:09.140655", + "modified": "2021-08-05 12:16:01.146206", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, "restrict_to_domain": "Non Profit", + "roles": [], + "sequence_id": 18, "shortcuts": [ { "label": "Member", @@ -247,5 +277,6 @@ "link_to": "Chapter Member", "type": "DocType" } - ] + ], + "title": "Non Profit" } \ No newline at end of file diff --git a/erpnext/payroll/workspace/payroll/payroll.json b/erpnext/payroll/workspace/payroll/payroll.json index 814973063d..b55bdc7711 100644 --- a/erpnext/payroll/workspace/payroll/payroll.json +++ b/erpnext/payroll/workspace/payroll/payroll.json @@ -1,27 +1,32 @@ { - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Outgoing Salary", "label": "Outgoing Salary" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Payroll\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Structure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payroll Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Slip\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Income Tax Slab\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payroll\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Compensations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-05-27 19:54:23.405607", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "money-coins-1", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Payroll", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Payroll", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -30,6 +35,7 @@ "hidden": 0, "is_query_report": 0, "label": "Salary Component", + "link_count": 0, "link_to": "Salary Component", "link_type": "DocType", "onboard": 1, @@ -40,6 +46,7 @@ "hidden": 0, "is_query_report": 0, "label": "Salary Structure", + "link_count": 0, "link_to": "Salary Structure", "link_type": "DocType", "onboard": 1, @@ -50,6 +57,7 @@ "hidden": 0, "is_query_report": 0, "label": "Salary Structure Assignment", + "link_count": 0, "link_to": "Salary Structure Assignment", "link_type": "DocType", "onboard": 1, @@ -60,6 +68,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payroll Entry", + "link_count": 0, "link_to": "Payroll Entry", "link_type": "DocType", "onboard": 1, @@ -70,6 +79,7 @@ "hidden": 0, "is_query_report": 0, "label": "Salary Slip", + "link_count": 0, "link_to": "Salary Slip", "link_type": "DocType", "onboard": 1, @@ -79,6 +89,7 @@ "hidden": 0, "is_query_report": 0, "label": "Taxation", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -87,6 +98,7 @@ "hidden": 0, "is_query_report": 0, "label": "Payroll Period", + "link_count": 0, "link_to": "Payroll Period", "link_type": "DocType", "onboard": 1, @@ -97,6 +109,7 @@ "hidden": 0, "is_query_report": 0, "label": "Income Tax Slab", + "link_count": 0, "link_to": "Income Tax Slab", "link_type": "DocType", "onboard": 1, @@ -107,6 +120,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Other Income", + "link_count": 0, "link_to": "Employee Other Income", "link_type": "DocType", "onboard": 1, @@ -117,6 +131,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Tax Exemption Declaration", + "link_count": 0, "link_to": "Employee Tax Exemption Declaration", "link_type": "DocType", "onboard": 1, @@ -127,6 +142,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Tax Exemption Proof Submission", + "link_count": 0, "link_to": "Employee Tax Exemption Proof Submission", "link_type": "DocType", "onboard": 1, @@ -137,6 +153,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Tax Exemption Category", + "link_count": 0, "link_to": "Employee Tax Exemption Category", "link_type": "DocType", "onboard": 0, @@ -147,6 +164,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Tax Exemption Sub Category", + "link_count": 0, "link_to": "Employee Tax Exemption Sub Category", "link_type": "DocType", "onboard": 0, @@ -156,6 +174,7 @@ "hidden": 0, "is_query_report": 0, "label": "Compensations", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -164,6 +183,7 @@ "hidden": 0, "is_query_report": 0, "label": "Additional Salary", + "link_count": 0, "link_to": "Additional Salary", "link_type": "DocType", "onboard": 1, @@ -174,6 +194,7 @@ "hidden": 0, "is_query_report": 0, "label": "Retention Bonus", + "link_count": 0, "link_to": "Retention Bonus", "link_type": "DocType", "onboard": 1, @@ -184,6 +205,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Incentive", + "link_count": 0, "link_to": "Employee Incentive", "link_type": "DocType", "onboard": 1, @@ -194,6 +216,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Benefit Application", + "link_count": 0, "link_to": "Employee Benefit Application", "link_type": "DocType", "onboard": 0, @@ -204,6 +227,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Benefit Claim", + "link_count": 0, "link_to": "Employee Benefit Claim", "link_type": "DocType", "onboard": 0, @@ -213,6 +237,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -221,6 +246,7 @@ "hidden": 0, "is_query_report": 1, "label": "Salary Register", + "link_count": 0, "link_to": "Salary Register", "link_type": "Report", "onboard": 0, @@ -231,6 +257,7 @@ "hidden": 0, "is_query_report": 1, "label": "Salary Payments Based On Payment Mode", + "link_count": 0, "link_to": "Salary Payments Based On Payment Mode", "link_type": "Report", "onboard": 0, @@ -241,6 +268,7 @@ "hidden": 0, "is_query_report": 1, "label": "Salary Payments via ECS", + "link_count": 0, "link_to": "Salary Payments via ECS", "link_type": "Report", "onboard": 0, @@ -251,6 +279,7 @@ "hidden": 0, "is_query_report": 1, "label": "Income Tax Deductions", + "link_count": 0, "link_to": "Income Tax Deductions", "link_type": "Report", "onboard": 0, @@ -261,6 +290,7 @@ "hidden": 0, "is_query_report": 1, "label": "Professional Tax Deductions", + "link_count": 0, "link_to": "Professional Tax Deductions", "link_type": "Report", "onboard": 0, @@ -271,6 +301,7 @@ "hidden": 0, "is_query_report": 1, "label": "Provident Fund Deductions", + "link_count": 0, "link_to": "Provident Fund Deductions", "link_type": "Report", "onboard": 0, @@ -281,20 +312,26 @@ "hidden": 0, "is_query_report": 1, "label": "Bank Remittance", + "link_count": 0, "link_to": "Bank Remittance", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:37.205628", + "modified": "2021-08-05 12:16:01.335324", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll", "onboarding": "Payroll", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 19, "shortcuts": [ { "label": "Salary Structure", @@ -329,5 +366,6 @@ "link_to": "Payroll", "type": "Dashboard" } - ] + ], + "title": "Payroll" } \ No newline at end of file diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index c023a73ff4..065f1eda1f 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -1,28 +1,32 @@ { - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Project Summary", "label": "Open Projects" } ], + "content": "[{\"type\": \"chart\", \"data\": {\"chart_name\": \"Open Projects\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Task\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Timesheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project Billing Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Projects\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Time Tracking\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:46:04.874669", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "project", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Projects", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Projects", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -31,6 +35,7 @@ "hidden": 0, "is_query_report": 0, "label": "Project", + "link_count": 0, "link_to": "Project", "link_type": "DocType", "onboard": 1, @@ -41,6 +46,7 @@ "hidden": 0, "is_query_report": 0, "label": "Task", + "link_count": 0, "link_to": "Task", "link_type": "DocType", "onboard": 1, @@ -51,6 +57,7 @@ "hidden": 0, "is_query_report": 0, "label": "Project Template", + "link_count": 0, "link_to": "Project Template", "link_type": "DocType", "onboard": 0, @@ -61,6 +68,7 @@ "hidden": 0, "is_query_report": 0, "label": "Project Type", + "link_count": 0, "link_to": "Project Type", "link_type": "DocType", "onboard": 0, @@ -71,6 +79,7 @@ "hidden": 0, "is_query_report": 0, "label": "Project Update", + "link_count": 0, "link_to": "Project Update", "link_type": "DocType", "onboard": 0, @@ -80,6 +89,7 @@ "hidden": 0, "is_query_report": 0, "label": "Time Tracking", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -88,6 +98,7 @@ "hidden": 0, "is_query_report": 0, "label": "Timesheet", + "link_count": 0, "link_to": "Timesheet", "link_type": "DocType", "onboard": 1, @@ -98,6 +109,7 @@ "hidden": 0, "is_query_report": 0, "label": "Activity Type", + "link_count": 0, "link_to": "Activity Type", "link_type": "DocType", "onboard": 1, @@ -108,6 +120,7 @@ "hidden": 0, "is_query_report": 0, "label": "Activity Cost", + "link_count": 0, "link_to": "Activity Cost", "link_type": "DocType", "onboard": 0, @@ -117,6 +130,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -125,6 +139,7 @@ "hidden": 0, "is_query_report": 1, "label": "Daily Timesheet Summary", + "link_count": 0, "link_to": "Daily Timesheet Summary", "link_type": "Report", "onboard": 1, @@ -135,6 +150,7 @@ "hidden": 0, "is_query_report": 1, "label": "Employee Hours Utilization", + "link_count": 0, "link_to": "Employee Hours Utilization Based On Timesheet", "link_type": "Report", "onboard": 0, @@ -145,6 +161,7 @@ "hidden": 0, "is_query_report": 1, "label": "Project Profitability", + "link_count": 0, "link_to": "Project Profitability", "link_type": "Report", "onboard": 0, @@ -155,6 +172,7 @@ "hidden": 0, "is_query_report": 1, "label": "Project wise Stock Tracking", + "link_count": 0, "link_to": "Project wise Stock Tracking", "link_type": "Report", "onboard": 0, @@ -165,6 +183,7 @@ "hidden": 0, "is_query_report": 1, "label": "Project Billing Summary", + "link_count": 0, "link_to": "Project Billing Summary", "link_type": "Report", "onboard": 0, @@ -175,19 +194,26 @@ "hidden": 0, "is_query_report": 1, "label": "Delayed Tasks Summary", + "link_count": 0, "link_to": "Delayed Tasks Summary", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2021-04-25 16:27:16.548780", + "modified": "2021-08-05 12:16:01.540145", "modified_by": "Administrator", "module": "Projects", "name": "Projects", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 20, "shortcuts": [ { "color": "Blue", @@ -220,5 +246,6 @@ "link_to": "Project", "type": "Dashboard" } - ] + ], + "title": "Projects" } \ No newline at end of file diff --git a/erpnext/quality_management/workspace/quality/quality.json b/erpnext/quality_management/workspace/quality/quality.json index e5fef43550..4dc8129d89 100644 --- a/erpnext/quality_management/workspace/quality/quality.json +++ b/erpnext/quality_management/workspace/quality/quality.json @@ -1,22 +1,27 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Goal\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Procedure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Inspection\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Review\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Action\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Conformance\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goal and Procedure\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Feedback\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Meeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Review and Action\", \"col\": 4}}]", "creation": "2020-03-02 15:49:28.632014", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "quality", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Quality", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Goal and Procedure", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Goal", + "link_count": 0, "link_to": "Quality Goal", "link_type": "DocType", "onboard": 1, @@ -35,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Procedure", + "link_count": 0, "link_to": "Quality Procedure", "link_type": "DocType", "onboard": 1, @@ -45,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tree of Procedures", + "link_count": 0, "link_to": "Quality Procedure", "link_type": "DocType", "onboard": 0, @@ -54,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Feedback", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -62,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Feedback", + "link_count": 0, "link_to": "Quality Feedback", "link_type": "DocType", "onboard": 1, @@ -72,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Feedback Template", + "link_count": 0, "link_to": "Quality Feedback Template", "link_type": "DocType", "onboard": 0, @@ -81,6 +92,7 @@ "hidden": 0, "is_query_report": 0, "label": "Meeting", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -89,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Meeting", + "link_count": 0, "link_to": "Quality Meeting", "link_type": "DocType", "onboard": 0, @@ -98,6 +111,7 @@ "hidden": 0, "is_query_report": 0, "label": "Review and Action", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -106,6 +120,7 @@ "hidden": 0, "is_query_report": 0, "label": "Non Conformance", + "link_count": 0, "link_to": "Non Conformance", "link_type": "DocType", "onboard": 0, @@ -116,6 +131,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Review", + "link_count": 0, "link_to": "Quality Review", "link_type": "DocType", "onboard": 0, @@ -126,19 +142,26 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Action", + "link_count": 0, "link_to": "Quality Action", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:35.120213", + "modified": "2021-08-05 12:16:01.699912", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 21, "shortcuts": [ { "color": "Grey", @@ -186,5 +209,6 @@ "stats_filter": "{\"status\": \"Open\"}", "type": "DocType" } - ] + ], + "title": "Quality" } \ No newline at end of file diff --git a/erpnext/selling/workspace/retail/retail.json b/erpnext/selling/workspace/retail/retail.json index e20f8347c2..9d2e6cabbc 100644 --- a/erpnext/selling/workspace/retail/retail.json +++ b/erpnext/selling/workspace/retail/retail.json @@ -1,22 +1,27 @@ { - "category": "Domains", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Point Of Sale\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings & Configurations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loyalty Program\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening & Closing\", \"col\": 4}}]", "creation": "2020-03-02 17:18:32.505616", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "retail", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Retail", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Settings & Configurations", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Point-of-Sale Profile", + "link_count": 0, "link_to": "POS Profile", "link_type": "DocType", "onboard": 1, @@ -35,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "POS Settings", + "link_count": 0, "link_to": "POS Settings", "link_type": "DocType", "onboard": 0, @@ -44,6 +51,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loyalty Program", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -52,6 +60,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loyalty Program", + "link_count": 0, "link_to": "Loyalty Program", "link_type": "DocType", "onboard": 0, @@ -62,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Loyalty Point Entry", + "link_count": 0, "link_to": "Loyalty Point Entry", "link_type": "DocType", "onboard": 0, @@ -71,6 +81,7 @@ "hidden": 0, "is_query_report": 0, "label": "Opening & Closing", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -79,6 +90,7 @@ "hidden": 0, "is_query_report": 0, "label": "POS Opening Entry", + "link_count": 0, "link_to": "POS Opening Entry", "link_type": "DocType", "onboard": 0, @@ -89,20 +101,26 @@ "hidden": 0, "is_query_report": 0, "label": "POS Closing Entry", + "link_count": 0, "link_to": "POS Closing Entry", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:36.758038", + "modified": "2021-08-05 12:16:01.840988", "modified_by": "Administrator", "module": "Selling", "name": "Retail", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, "restrict_to_domain": "Retail", + "roles": [], + "sequence_id": 22, "shortcuts": [ { "doc_view": "", @@ -110,5 +128,6 @@ "link_to": "point-of-sale", "type": "Page" } - ] + ], + "title": "Retail" } \ No newline at end of file diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index 879034a0df..345187f93c 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -1,5 +1,5 @@ { - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Sales Order Trends", @@ -7,22 +7,27 @@ } ], "charts_label": "Selling ", + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Selling\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Sales Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Selling\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-01-28 11:49:12.092882", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, - "hide_custom": 1, + "for_user": "", + "hide_custom": 0, "icon": "sell", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Selling", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Selling", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -31,6 +36,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customer", + "link_count": 0, "link_to": "Customer", "link_type": "DocType", "onboard": 1, @@ -41,6 +47,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quotation", + "link_count": 0, "link_to": "Quotation", "link_type": "DocType", "onboard": 1, @@ -51,6 +58,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Order", + "link_count": 0, "link_to": "Sales Order", "link_type": "DocType", "onboard": 1, @@ -61,6 +69,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Invoice", + "link_count": 0, "link_to": "Sales Invoice", "link_type": "DocType", "onboard": 1, @@ -71,6 +80,7 @@ "hidden": 0, "is_query_report": 0, "label": "Blanket Order", + "link_count": 0, "link_to": "Blanket Order", "link_type": "DocType", "onboard": 1, @@ -81,6 +91,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Partner", + "link_count": 0, "link_to": "Sales Partner", "link_type": "DocType", "onboard": 0, @@ -91,6 +102,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Person", + "link_count": 0, "link_to": "Sales Person", "link_type": "DocType", "onboard": 0, @@ -100,6 +112,7 @@ "hidden": 0, "is_query_report": 0, "label": "Items and Pricing", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -108,6 +121,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item", + "link_count": 0, "link_to": "Item", "link_type": "DocType", "onboard": 1, @@ -118,6 +132,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Price", + "link_count": 0, "link_to": "Item Price", "link_type": "DocType", "onboard": 1, @@ -128,6 +143,7 @@ "hidden": 0, "is_query_report": 0, "label": "Price List", + "link_count": 0, "link_to": "Price List", "link_type": "DocType", "onboard": 1, @@ -138,6 +154,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Group", + "link_count": 0, "link_to": "Item Group", "link_type": "DocType", "onboard": 1, @@ -148,6 +165,7 @@ "hidden": 0, "is_query_report": 0, "label": "Product Bundle", + "link_count": 0, "link_to": "Product Bundle", "link_type": "DocType", "onboard": 0, @@ -158,6 +176,7 @@ "hidden": 0, "is_query_report": 0, "label": "Promotional Scheme", + "link_count": 0, "link_to": "Promotional Scheme", "link_type": "DocType", "onboard": 0, @@ -168,6 +187,7 @@ "hidden": 0, "is_query_report": 0, "label": "Pricing Rule", + "link_count": 0, "link_to": "Pricing Rule", "link_type": "DocType", "onboard": 0, @@ -178,6 +198,7 @@ "hidden": 0, "is_query_report": 0, "label": "Shipping Rule", + "link_count": 0, "link_to": "Shipping Rule", "link_type": "DocType", "onboard": 0, @@ -188,6 +209,7 @@ "hidden": 0, "is_query_report": 0, "label": "Coupon Code", + "link_count": 0, "link_to": "Coupon Code", "link_type": "DocType", "onboard": 0, @@ -197,6 +219,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -205,6 +228,7 @@ "hidden": 0, "is_query_report": 0, "label": "Selling Settings", + "link_count": 0, "link_to": "Selling Settings", "link_type": "DocType", "onboard": 0, @@ -215,6 +239,7 @@ "hidden": 0, "is_query_report": 0, "label": "Terms and Conditions Template", + "link_count": 0, "link_to": "Terms and Conditions", "link_type": "DocType", "onboard": 1, @@ -225,6 +250,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Taxes and Charges Template", + "link_count": 0, "link_to": "Sales Taxes and Charges Template", "link_type": "DocType", "onboard": 1, @@ -235,6 +261,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lead Source", + "link_count": 0, "link_to": "Lead Source", "link_type": "DocType", "onboard": 0, @@ -245,6 +272,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customer Group", + "link_count": 0, "link_to": "Customer Group", "link_type": "DocType", "onboard": 0, @@ -255,6 +283,7 @@ "hidden": 0, "is_query_report": 0, "label": "Contact", + "link_count": 0, "link_to": "Contact", "link_type": "DocType", "onboard": 0, @@ -265,6 +294,7 @@ "hidden": 0, "is_query_report": 0, "label": "Address", + "link_count": 0, "link_to": "Address", "link_type": "DocType", "onboard": 0, @@ -275,6 +305,7 @@ "hidden": 0, "is_query_report": 0, "label": "Territory", + "link_count": 0, "link_to": "Territory", "link_type": "DocType", "onboard": 0, @@ -285,6 +316,7 @@ "hidden": 0, "is_query_report": 0, "label": "Campaign", + "link_count": 0, "link_to": "Campaign", "link_type": "DocType", "onboard": 0, @@ -294,6 +326,7 @@ "hidden": 0, "is_query_report": 0, "label": "Key Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -302,6 +335,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Analytics", + "link_count": 0, "link_to": "Sales Analytics", "link_type": "Report", "onboard": 1, @@ -312,6 +346,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Order Analysis", + "link_count": 0, "link_to": "Sales Order Analysis", "link_type": "Report", "onboard": 1, @@ -322,6 +357,7 @@ "hidden": 0, "is_query_report": 0, "label": "Sales Funnel", + "link_count": 0, "link_to": "sales-funnel", "link_type": "Page", "onboard": 1, @@ -332,6 +368,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Order Trends", + "link_count": 0, "link_to": "Sales Order Trends", "link_type": "Report", "onboard": 0, @@ -342,6 +379,7 @@ "hidden": 0, "is_query_report": 1, "label": "Quotation Trends", + "link_count": 0, "link_to": "Quotation Trends", "link_type": "Report", "onboard": 0, @@ -352,6 +390,7 @@ "hidden": 0, "is_query_report": 1, "label": "Customer Acquisition and Loyalty", + "link_count": 0, "link_to": "Customer Acquisition and Loyalty", "link_type": "Report", "onboard": 0, @@ -362,6 +401,7 @@ "hidden": 0, "is_query_report": 1, "label": "Inactive Customers", + "link_count": 0, "link_to": "Inactive Customers", "link_type": "Report", "onboard": 0, @@ -372,6 +412,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Person-wise Transaction Summary", + "link_count": 0, "link_to": "Sales Person-wise Transaction Summary", "link_type": "Report", "onboard": 0, @@ -382,6 +423,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item-wise Sales History", + "link_count": 0, "link_to": "Item-wise Sales History", "link_type": "Report", "onboard": 0, @@ -391,6 +433,7 @@ "hidden": 0, "is_query_report": 0, "label": "Other Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -399,6 +442,7 @@ "hidden": 0, "is_query_report": 1, "label": "Lead Details", + "link_count": 0, "link_to": "Lead Details", "link_type": "Report", "onboard": 0, @@ -409,6 +453,7 @@ "hidden": 0, "is_query_report": 1, "label": "Customer Addresses And Contacts", + "link_count": 0, "link_to": "Address And Contacts", "link_type": "Report", "onboard": 0, @@ -419,6 +464,7 @@ "hidden": 0, "is_query_report": 1, "label": "Available Stock for Packing Items", + "link_count": 0, "link_to": "Available Stock for Packing Items", "link_type": "Report", "onboard": 0, @@ -429,6 +475,7 @@ "hidden": 0, "is_query_report": 1, "label": "Pending SO Items For Purchase Request", + "link_count": 0, "link_to": "Pending SO Items For Purchase Request", "link_type": "Report", "onboard": 0, @@ -439,6 +486,7 @@ "hidden": 0, "is_query_report": 1, "label": "Delivery Note Trends", + "link_count": 0, "link_to": "Delivery Note Trends", "link_type": "Report", "onboard": 0, @@ -449,6 +497,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Invoice Trends", + "link_count": 0, "link_to": "Sales Invoice Trends", "link_type": "Report", "onboard": 0, @@ -459,6 +508,7 @@ "hidden": 0, "is_query_report": 1, "label": "Customer Credit Balance", + "link_count": 0, "link_to": "Customer Credit Balance", "link_type": "Report", "onboard": 0, @@ -469,6 +519,7 @@ "hidden": 0, "is_query_report": 1, "label": "Customers Without Any Sales Transactions", + "link_count": 0, "link_to": "Customers Without Any Sales Transactions", "link_type": "Report", "onboard": 0, @@ -479,6 +530,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Partners Commission", + "link_count": 0, "link_to": "Sales Partners Commission", "link_type": "Report", "onboard": 0, @@ -489,6 +541,7 @@ "hidden": 0, "is_query_report": 1, "label": "Territory Target Variance Based On Item Group", + "link_count": 0, "link_to": "Territory Target Variance Based On Item Group", "link_type": "Report", "onboard": 0, @@ -499,6 +552,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Person Target Variance Based On Item Group", + "link_count": 0, "link_to": "Sales Person Target Variance Based On Item Group", "link_type": "Report", "onboard": 0, @@ -509,20 +563,26 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Partner Target Variance Based On Item Group", + "link_count": 0, "link_to": "Sales Partner Target Variance based on Item Group", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:35.971277", + "modified": "2021-08-05 12:16:01.990702", "modified_by": "Administrator", "module": "Selling", "name": "Selling", "onboarding": "Selling", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 23, "shortcuts": [ { "color": "Grey", @@ -559,5 +619,6 @@ "type": "Dashboard" } ], - "shortcuts_label": "Quick Access" + "shortcuts_label": "Quick Access", + "title": "Selling" } \ No newline at end of file diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 6ca3d637da..ef4b050ceb 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,27 +1,35 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Projects Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"HR Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Selling Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Buying Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Support Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Shopping Cart Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Portal Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Manufacturing Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Education Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Hotel Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Domain Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Products Settings\", \"col\": 4}}]", "creation": "2020-03-12 14:47:51.166455", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "Settings", - "extends_another_page": 1, + "extends": "", + "extends_another_page": 0, + "for_user": "", "hide_custom": 0, - "icon": "settings", + "icon": "setting", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "ERPNext Settings", "links": [], - "modified": "2021-06-12 01:58:11.399566", + "modified": "2021-08-05 12:15:59.052327", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 12, "shortcuts": [ { "icon": "project", @@ -118,5 +126,6 @@ "link_to": "Products Settings", "type": "DocType" } - ] -} + ], + "title": "ERPNext Settings" +} \ No newline at end of file diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index 1576d5a399..cc9569f642 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -1,23 +1,27 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", "creation": "2020-01-23 13:46:38.833076", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "getting-started", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Home", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Accounting", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -26,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Chart of Accounts", + "link_count": 0, "link_to": "Account", "link_type": "DocType", "onboard": 1, @@ -36,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Company", + "link_count": 0, "link_to": "Company", "link_type": "DocType", "onboard": 1, @@ -46,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customer", + "link_count": 0, "link_to": "Customer", "link_type": "DocType", "onboard": 1, @@ -56,6 +63,7 @@ "hidden": 0, "is_query_report": 0, "label": "Supplier", + "link_count": 0, "link_to": "Supplier", "link_type": "DocType", "onboard": 1, @@ -65,6 +73,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -73,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item", + "link_count": 0, "link_to": "Item", "link_type": "DocType", "onboard": 1, @@ -83,6 +93,7 @@ "hidden": 0, "is_query_report": 0, "label": "Warehouse", + "link_count": 0, "link_to": "Warehouse", "link_type": "DocType", "onboard": 1, @@ -93,6 +104,7 @@ "hidden": 0, "is_query_report": 0, "label": "Brand", + "link_count": 0, "link_to": "Brand", "link_type": "DocType", "onboard": 1, @@ -103,6 +115,7 @@ "hidden": 0, "is_query_report": 0, "label": "Unit of Measure (UOM)", + "link_count": 0, "link_to": "UOM", "link_type": "DocType", "onboard": 1, @@ -113,6 +126,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Reconciliation", + "link_count": 0, "link_to": "Stock Reconciliation", "link_type": "DocType", "onboard": 1, @@ -122,6 +136,7 @@ "hidden": 0, "is_query_report": 0, "label": "Human Resources", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -130,6 +145,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee", + "link_count": 0, "link_to": "Employee", "link_type": "DocType", "onboard": 1, @@ -140,6 +156,7 @@ "hidden": 0, "is_query_report": 0, "label": "Employee Attendance Tool", + "link_count": 0, "link_to": "Employee Attendance Tool", "link_type": "DocType", "onboard": 1, @@ -150,6 +167,7 @@ "hidden": 0, "is_query_report": 0, "label": "Salary Structure", + "link_count": 0, "link_to": "Salary Structure", "link_type": "DocType", "onboard": 1, @@ -159,6 +177,7 @@ "hidden": 0, "is_query_report": 0, "label": "CRM", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -167,6 +186,7 @@ "hidden": 0, "is_query_report": 0, "label": "Lead", + "link_count": 0, "link_to": "Lead", "link_type": "DocType", "onboard": 1, @@ -177,6 +197,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customer Group", + "link_count": 0, "link_to": "Customer Group", "link_type": "DocType", "onboard": 1, @@ -187,6 +208,7 @@ "hidden": 0, "is_query_report": 0, "label": "Territory", + "link_count": 0, "link_to": "Territory", "link_type": "DocType", "onboard": 1, @@ -196,6 +218,7 @@ "hidden": 0, "is_query_report": 0, "label": "Data Import and Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -204,6 +227,7 @@ "hidden": 0, "is_query_report": 0, "label": "Import Data", + "link_count": 0, "link_to": "Data Import", "link_type": "DocType", "onboard": 1, @@ -214,6 +238,7 @@ "hidden": 0, "is_query_report": 0, "label": "Opening Invoice Creation Tool", + "link_count": 0, "link_to": "Opening Invoice Creation Tool", "link_type": "DocType", "onboard": 1, @@ -224,6 +249,7 @@ "hidden": 0, "is_query_report": 0, "label": "Chart of Accounts Importer", + "link_count": 0, "link_to": "Chart of Accounts Importer", "link_type": "DocType", "onboard": 1, @@ -234,6 +260,7 @@ "hidden": 0, "is_query_report": 0, "label": "Letter Head", + "link_count": 0, "link_to": "Letter Head", "link_type": "DocType", "onboard": 1, @@ -244,19 +271,26 @@ "hidden": 0, "is_query_report": 0, "label": "Email Account", + "link_count": 0, "link_to": "Email Account", "link_type": "DocType", "onboard": 1, "type": "Link" } ], - "modified": "2021-04-19 15:48:44.089927", + "modified": "2021-08-10 15:33:20.704740", "modified_by": "Administrator", "module": "Setup", "name": "Home", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, - "pin_to_top": 1, + "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 1, "shortcuts": [ { "label": "Item", @@ -283,5 +317,6 @@ "link_to": "leaderboard", "type": "Page" } - ] + ], + "title": "Home" } \ No newline at end of file diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 529ce8eb61..26d10ce703 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -1,28 +1,32 @@ { "cards_label": "Masters & Reports", - "category": "Modules", + "category": "", "charts": [ { "chart_name": "Warehouse wise Stock Value" } ], + "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Stock\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Receipt\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Delivery Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Masters & Reports\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Transactions\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Serial No and Batch\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Incorrect Data Report\", \"col\": 4}}]", "creation": "2020-03-02 15:43:10.096528", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "stock", "idx": 0, "is_default": 0, - "is_standard": 1, + "is_standard": 0, "label": "Stock", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Items and Pricing", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -31,6 +35,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item", + "link_count": 0, "link_to": "Item", "link_type": "DocType", "onboard": 1, @@ -41,6 +46,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Group", + "link_count": 0, "link_to": "Item Group", "link_type": "DocType", "onboard": 1, @@ -51,6 +57,7 @@ "hidden": 0, "is_query_report": 0, "label": "Product Bundle", + "link_count": 0, "link_to": "Product Bundle", "link_type": "DocType", "onboard": 1, @@ -61,6 +68,7 @@ "hidden": 0, "is_query_report": 0, "label": "Price List", + "link_count": 0, "link_to": "Price List", "link_type": "DocType", "onboard": 0, @@ -71,6 +79,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Price", + "link_count": 0, "link_to": "Item Price", "link_type": "DocType", "onboard": 0, @@ -81,6 +90,7 @@ "hidden": 0, "is_query_report": 0, "label": "Shipping Rule", + "link_count": 0, "link_to": "Shipping Rule", "link_type": "DocType", "onboard": 0, @@ -91,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Pricing Rule", + "link_count": 0, "link_to": "Pricing Rule", "link_type": "DocType", "onboard": 0, @@ -101,6 +112,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Alternative", + "link_count": 0, "link_to": "Item Alternative", "link_type": "DocType", "onboard": 0, @@ -111,6 +123,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Manufacturer", + "link_count": 0, "link_to": "Item Manufacturer", "link_type": "DocType", "onboard": 0, @@ -121,6 +134,7 @@ "hidden": 0, "is_query_report": 0, "label": "Customs Tariff Number", + "link_count": 0, "link_to": "Customs Tariff Number", "link_type": "DocType", "onboard": 0, @@ -130,6 +144,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Transactions", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -138,6 +153,7 @@ "hidden": 0, "is_query_report": 0, "label": "Material Request", + "link_count": 0, "link_to": "Material Request", "link_type": "DocType", "onboard": 1, @@ -148,6 +164,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Entry", + "link_count": 0, "link_to": "Stock Entry", "link_type": "DocType", "onboard": 1, @@ -158,6 +175,7 @@ "hidden": 0, "is_query_report": 0, "label": "Delivery Note", + "link_count": 0, "link_to": "Delivery Note", "link_type": "DocType", "onboard": 1, @@ -168,6 +186,7 @@ "hidden": 0, "is_query_report": 0, "label": "Purchase Receipt", + "link_count": 0, "link_to": "Purchase Receipt", "link_type": "DocType", "onboard": 1, @@ -178,6 +197,7 @@ "hidden": 0, "is_query_report": 0, "label": "Pick List", + "link_count": 0, "link_to": "Pick List", "link_type": "DocType", "onboard": 1, @@ -188,6 +208,7 @@ "hidden": 0, "is_query_report": 0, "label": "Delivery Trip", + "link_count": 0, "link_to": "Delivery Trip", "link_type": "DocType", "onboard": 0, @@ -197,6 +218,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -205,6 +227,7 @@ "hidden": 0, "is_query_report": 1, "label": "Stock Ledger", + "link_count": 0, "link_to": "Stock Ledger", "link_type": "Report", "onboard": 1, @@ -215,6 +238,7 @@ "hidden": 0, "is_query_report": 1, "label": "Stock Balance", + "link_count": 0, "link_to": "Stock Balance", "link_type": "Report", "onboard": 1, @@ -225,6 +249,7 @@ "hidden": 0, "is_query_report": 1, "label": "Stock Projected Qty", + "link_count": 0, "link_to": "Stock Projected Qty", "link_type": "Report", "onboard": 1, @@ -235,6 +260,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Summary", + "link_count": 0, "link_to": "stock-balance", "link_type": "Page", "onboard": 0, @@ -245,6 +271,7 @@ "hidden": 0, "is_query_report": 1, "label": "Stock Ageing", + "link_count": 0, "link_to": "Stock Ageing", "link_type": "Report", "onboard": 0, @@ -255,6 +282,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item Price Stock", + "link_count": 0, "link_to": "Item Price Stock", "link_type": "Report", "onboard": 0, @@ -264,6 +292,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -272,6 +301,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Settings", + "link_count": 0, "link_to": "Stock Settings", "link_type": "DocType", "onboard": 1, @@ -282,6 +312,7 @@ "hidden": 0, "is_query_report": 0, "label": "Warehouse", + "link_count": 0, "link_to": "Warehouse", "link_type": "DocType", "onboard": 1, @@ -292,6 +323,7 @@ "hidden": 0, "is_query_report": 0, "label": "Unit of Measure (UOM)", + "link_count": 0, "link_to": "UOM", "link_type": "DocType", "onboard": 1, @@ -302,6 +334,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Variant Settings", + "link_count": 0, "link_to": "Item Variant Settings", "link_type": "DocType", "onboard": 1, @@ -312,6 +345,7 @@ "hidden": 0, "is_query_report": 0, "label": "Brand", + "link_count": 0, "link_to": "Brand", "link_type": "DocType", "onboard": 1, @@ -322,6 +356,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item Attribute", + "link_count": 0, "link_to": "Item Attribute", "link_type": "DocType", "onboard": 0, @@ -332,6 +367,7 @@ "hidden": 0, "is_query_report": 0, "label": "UOM Conversion Factor", + "link_count": 0, "link_to": "UOM Conversion Factor", "link_type": "DocType", "onboard": 0, @@ -341,6 +377,7 @@ "hidden": 0, "is_query_report": 0, "label": "Serial No and Batch", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -349,6 +386,7 @@ "hidden": 0, "is_query_report": 0, "label": "Serial No", + "link_count": 0, "link_to": "Serial No", "link_type": "DocType", "onboard": 1, @@ -359,6 +397,7 @@ "hidden": 0, "is_query_report": 0, "label": "Batch", + "link_count": 0, "link_to": "Batch", "link_type": "DocType", "onboard": 1, @@ -369,6 +408,7 @@ "hidden": 0, "is_query_report": 0, "label": "Installation Note", + "link_count": 0, "link_to": "Installation Note", "link_type": "DocType", "onboard": 0, @@ -379,6 +419,7 @@ "hidden": 0, "is_query_report": 0, "label": "Serial No Service Contract Expiry", + "link_count": 0, "link_to": "Serial No Service Contract Expiry", "link_type": "Report", "onboard": 0, @@ -389,6 +430,7 @@ "hidden": 0, "is_query_report": 0, "label": "Serial No Status", + "link_count": 0, "link_to": "Serial No Status", "link_type": "Report", "onboard": 0, @@ -399,6 +441,7 @@ "hidden": 0, "is_query_report": 0, "label": "Serial No Warranty Expiry", + "link_count": 0, "link_to": "Serial No Warranty Expiry", "link_type": "Report", "onboard": 0, @@ -408,6 +451,7 @@ "hidden": 0, "is_query_report": 0, "label": "Tools", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -416,6 +460,7 @@ "hidden": 0, "is_query_report": 0, "label": "Stock Reconciliation", + "link_count": 0, "link_to": "Stock Reconciliation", "link_type": "DocType", "onboard": 1, @@ -426,6 +471,7 @@ "hidden": 0, "is_query_report": 0, "label": "Landed Cost Voucher", + "link_count": 0, "link_to": "Landed Cost Voucher", "link_type": "DocType", "onboard": 1, @@ -436,6 +482,7 @@ "hidden": 0, "is_query_report": 0, "label": "Packing Slip", + "link_count": 0, "link_to": "Packing Slip", "link_type": "DocType", "onboard": 1, @@ -446,6 +493,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Inspection", + "link_count": 0, "link_to": "Quality Inspection", "link_type": "DocType", "onboard": 0, @@ -456,6 +504,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quality Inspection Template", + "link_count": 0, "link_to": "Quality Inspection Template", "link_type": "DocType", "onboard": 0, @@ -466,6 +515,7 @@ "hidden": 0, "is_query_report": 0, "label": "Quick Stock Balance", + "link_count": 0, "link_to": "Quick Stock Balance", "link_type": "DocType", "onboard": 0, @@ -475,6 +525,7 @@ "hidden": 0, "is_query_report": 0, "label": "Key Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -483,6 +534,7 @@ "hidden": 0, "is_query_report": 0, "label": "Item-wise Price List Rate", + "link_count": 0, "link_to": "Item-wise Price List Rate", "link_type": "Report", "onboard": 1, @@ -493,6 +545,7 @@ "hidden": 0, "is_query_report": 1, "label": "Stock Analytics", + "link_count": 0, "link_to": "Stock Analytics", "link_type": "Report", "onboard": 1, @@ -503,6 +556,7 @@ "hidden": 0, "is_query_report": 1, "label": "Stock Qty vs Serial No Count", + "link_count": 0, "link_to": "Stock Qty vs Serial No Count", "link_type": "Report", "onboard": 1, @@ -513,6 +567,7 @@ "hidden": 0, "is_query_report": 1, "label": "Delivery Note Trends", + "link_count": 0, "link_to": "Delivery Note Trends", "link_type": "Report", "onboard": 0, @@ -523,6 +578,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Receipt Trends", + "link_count": 0, "link_to": "Purchase Receipt Trends", "link_type": "Report", "onboard": 0, @@ -533,6 +589,7 @@ "hidden": 0, "is_query_report": 1, "label": "Sales Order Analysis", + "link_count": 0, "link_to": "Sales Order Analysis", "link_type": "Report", "onboard": 0, @@ -543,6 +600,7 @@ "hidden": 0, "is_query_report": 1, "label": "Purchase Order Analysis", + "link_count": 0, "link_to": "Purchase Order Analysis", "link_type": "Report", "onboard": 0, @@ -553,6 +611,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item Shortage Report", + "link_count": 0, "link_to": "Item Shortage Report", "link_type": "Report", "onboard": 0, @@ -563,6 +622,7 @@ "hidden": 0, "is_query_report": 1, "label": "Batch-Wise Balance History", + "link_count": 0, "link_to": "Batch-Wise Balance History", "link_type": "Report", "onboard": 0, @@ -572,6 +632,7 @@ "hidden": 0, "is_query_report": 0, "label": "Other Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -580,6 +641,7 @@ "hidden": 0, "is_query_report": 1, "label": "Requested Items To Be Transferred", + "link_count": 0, "link_to": "Requested Items To Be Transferred", "link_type": "Report", "onboard": 0, @@ -590,6 +652,7 @@ "hidden": 0, "is_query_report": 1, "label": "Batch Item Expiry Status", + "link_count": 0, "link_to": "Batch Item Expiry Status", "link_type": "Report", "onboard": 0, @@ -600,6 +663,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item Prices", + "link_count": 0, "link_to": "Item Prices", "link_type": "Report", "onboard": 0, @@ -610,6 +674,7 @@ "hidden": 0, "is_query_report": 1, "label": "Itemwise Recommended Reorder Level", + "link_count": 0, "link_to": "Itemwise Recommended Reorder Level", "link_type": "Report", "onboard": 0, @@ -620,6 +685,7 @@ "hidden": 0, "is_query_report": 1, "label": "Item Variant Details", + "link_count": 0, "link_to": "Item Variant Details", "link_type": "Report", "onboard": 0, @@ -630,6 +696,7 @@ "hidden": 0, "is_query_report": 1, "label": "Subcontracted Raw Materials To Be Transferred", + "link_count": 0, "link_to": "Subcontracted Raw Materials To Be Transferred", "link_type": "Report", "onboard": 0, @@ -640,6 +707,7 @@ "hidden": 0, "is_query_report": 1, "label": "Subcontracted Item To Be Received", + "link_count": 0, "link_to": "Subcontracted Item To Be Received", "link_type": "Report", "onboard": 0, @@ -650,6 +718,7 @@ "hidden": 0, "is_query_report": 1, "label": "Stock and Account Value Comparison", + "link_count": 0, "link_to": "Stock and Account Value Comparison", "link_type": "Report", "onboard": 0, @@ -659,6 +728,7 @@ "hidden": 0, "is_query_report": 0, "label": "Incorrect Data Report", + "link_count": 0, "link_type": "DocType", "onboard": 0, "type": "Card Break" @@ -667,6 +737,7 @@ "hidden": 0, "is_query_report": 0, "label": "Incorrect Serial No Qty and Valuation", + "link_count": 0, "link_to": "Incorrect Serial No Valuation", "link_type": "Report", "onboard": 0, @@ -676,6 +747,7 @@ "hidden": 0, "is_query_report": 0, "label": "Incorrect Balance Qty After Transaction", + "link_count": 0, "link_to": "Incorrect Balance Qty After Transaction", "link_type": "Report", "onboard": 0, @@ -685,20 +757,26 @@ "hidden": 0, "is_query_report": 0, "label": "Stock and Account Value Comparison", + "link_count": 0, "link_to": "Stock and Account Value Comparison", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2021-05-13 13:10:24.914983", + "modified": "2021-08-05 12:16:02.361509", "modified_by": "Administrator", "module": "Stock", "name": "Stock", "onboarding": "Stock", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 24, "shortcuts": [ { "color": "Green", @@ -753,5 +831,6 @@ "type": "Dashboard" } ], - "shortcuts_label": "Quick Access" + "shortcuts_label": "Quick Access", + "title": "Stock" } \ No newline at end of file diff --git a/erpnext/support/workspace/support/support.json b/erpnext/support/workspace/support/support.json index 01a8676f05..4c5829d7a0 100644 --- a/erpnext/support/workspace/support/support.json +++ b/erpnext/support/workspace/support/support.json @@ -1,22 +1,27 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Issue\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Maintenance Visit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Issues\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Warranty\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:48:23.224699", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "icon": "support", "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Support", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Issues", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -25,6 +30,7 @@ "hidden": 0, "is_query_report": 0, "label": "Issue", + "link_count": 0, "link_to": "Issue", "link_type": "DocType", "onboard": 1, @@ -35,6 +41,7 @@ "hidden": 0, "is_query_report": 0, "label": "Issue Type", + "link_count": 0, "link_to": "Issue Type", "link_type": "DocType", "onboard": 0, @@ -45,6 +52,7 @@ "hidden": 0, "is_query_report": 0, "label": "Issue Priority", + "link_count": 0, "link_to": "Issue Priority", "link_type": "DocType", "onboard": 0, @@ -54,6 +62,7 @@ "hidden": 0, "is_query_report": 0, "label": "Maintenance", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -62,6 +71,7 @@ "hidden": 0, "is_query_report": 0, "label": "Maintenance Schedule", + "link_count": 0, "link_to": "Maintenance Schedule", "link_type": "DocType", "onboard": 0, @@ -72,6 +82,7 @@ "hidden": 0, "is_query_report": 0, "label": "Maintenance Visit", + "link_count": 0, "link_to": "Maintenance Visit", "link_type": "DocType", "onboard": 0, @@ -81,6 +92,7 @@ "hidden": 0, "is_query_report": 0, "label": "Service Level Agreement", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -89,6 +101,7 @@ "hidden": 0, "is_query_report": 0, "label": "Service Level Agreement", + "link_count": 0, "link_to": "Service Level Agreement", "link_type": "DocType", "onboard": 0, @@ -98,6 +111,7 @@ "hidden": 0, "is_query_report": 0, "label": "Warranty", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -106,6 +120,7 @@ "hidden": 0, "is_query_report": 0, "label": "Warranty Claim", + "link_count": 0, "link_to": "Warranty Claim", "link_type": "DocType", "onboard": 0, @@ -116,6 +131,7 @@ "hidden": 0, "is_query_report": 0, "label": "Serial No", + "link_count": 0, "link_to": "Serial No", "link_type": "DocType", "onboard": 0, @@ -125,6 +141,7 @@ "hidden": 0, "is_query_report": 0, "label": "Settings", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -133,6 +150,7 @@ "hidden": 0, "is_query_report": 0, "label": "Support Settings", + "link_count": 0, "link_to": "Support Settings", "link_type": "DocType", "onboard": 0, @@ -142,6 +160,7 @@ "hidden": 0, "is_query_report": 0, "label": "Reports", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -150,19 +169,26 @@ "hidden": 0, "is_query_report": 1, "label": "First Response Time for Issues", + "link_count": 0, "link_to": "First Response Time for Issues", "link_type": "Report", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:37.073482", + "modified": "2021-08-05 12:16:02.699923", "modified_by": "Administrator", "module": "Support", "name": "Support", + "onboarding": "", "owner": "Administrator", + "parent_page": "", "pin_to_bottom": 0, "pin_to_top": 0, + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 25, "shortcuts": [ { "color": "Yellow", @@ -182,5 +208,6 @@ "link_to": "Service Level Agreement", "type": "DocType" } - ] + ], + "title": "Support" } \ No newline at end of file diff --git a/erpnext/utilities/workspace/utilities/utilities.json b/erpnext/utilities/workspace/utilities/utilities.json index 2f9250ee45..4ad4afb8f4 100644 --- a/erpnext/utilities/workspace/utilities/utilities.json +++ b/erpnext/utilities/workspace/utilities/utilities.json @@ -1,21 +1,26 @@ { - "category": "Modules", + "category": "", "charts": [], + "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Video\", \"col\": 4}}]", "creation": "2020-09-10 12:21:22.335307", "developer_mode_only": 0, "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", + "extends": "", "extends_another_page": 0, + "for_user": "", "hide_custom": 0, "idx": 0, - "is_standard": 1, + "is_default": 0, + "is_standard": 0, "label": "Utilities", "links": [ { "hidden": 0, "is_query_report": 0, "label": "Video", + "link_count": 0, "onboard": 0, "type": "Card Break" }, @@ -24,6 +29,7 @@ "hidden": 0, "is_query_report": 0, "label": "Video", + "link_count": 0, "link_to": "Video", "link_type": "DocType", "onboard": 0, @@ -34,18 +40,26 @@ "hidden": 0, "is_query_report": 0, "label": "Video Settings", + "link_count": 0, "link_to": "Video Settings", "link_type": "DocType", "onboard": 0, "type": "Link" } ], - "modified": "2020-12-01 13:38:36.711884", + "modified": "2021-08-05 12:16:03.350804", "modified_by": "Administrator", "module": "Utilities", "name": "Utilities", + "onboarding": "", "owner": "user@erpnext.com", - "pin_to_bottom": 1, + "parent_page": "", + "pin_to_bottom": 0, "pin_to_top": 0, - "shortcuts": [] + "public": 1, + "restrict_to_domain": "", + "roles": [], + "sequence_id": 30, + "shortcuts": [], + "title": "Utilities" } \ No newline at end of file From 8a6b82b19629aa0d3d6df767cef8b548623bc520 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 13 Aug 2021 12:59:27 +0530 Subject: [PATCH 253/253] ci: ignore js files in unittests (#26934) * ci: ignore js files in unittests - Avoid running python unittests on PRs that ONLY change JS files. * ci: ignore md files in test workflows --- .github/workflows/patch.yml | 8 +++++++- .github/workflows/server-tests.yml | 6 ++++++ .github/workflows/ui-tests.yml | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index dc72987a41..72d4028ce6 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -1,6 +1,12 @@ name: Patch -on: [pull_request, workflow_dispatch] +on: + pull_request: + paths-ignore: + - '**.js' + - '**.md' + workflow_dispatch: + jobs: test: diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 606002e3cd..3a1ecd399c 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -2,9 +2,15 @@ name: Server on: pull_request: + paths-ignore: + - '**.js' + - '**.md' workflow_dispatch: push: branches: [ develop ] + paths-ignore: + - '**.js' + - '**.md' jobs: test: diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 9e29b6f1d2..3959268c76 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -2,6 +2,8 @@ name: UI on: pull_request: + paths-ignore: + - '**.md' workflow_dispatch: jobs: