From 9ceeefbd2b19eae898db07e7bc1ec90b7b40611b Mon Sep 17 00:00:00 2001 From: Anupam K Date: Mon, 27 Jul 2020 19:56:07 +0530 Subject: [PATCH 01/73] refactor: CRM Reports --- .../crm/report/lead_details/lead_details.js | 52 ++++++ .../crm/report/lead_details/lead_details.json | 4 +- .../crm/report/lead_details/lead_details.py | 164 ++++++++++++++++++ 3 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 erpnext/crm/report/lead_details/lead_details.js create mode 100644 erpnext/crm/report/lead_details/lead_details.py diff --git a/erpnext/crm/report/lead_details/lead_details.js b/erpnext/crm/report/lead_details/lead_details.js new file mode 100644 index 0000000000..f92070daf3 --- /dev/null +++ b/erpnext/crm/report/lead_details/lead_details.js @@ -0,0 +1,52 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Lead Details"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -12), + "reqd": 1 + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1 + }, + { + "fieldname":"status", + "label": __("Status"), + "fieldtype": "Select", + options: [ + { "value": "Lead", "label": __("Lead") }, + { "value": "Open", "label": __("Open") }, + { "value": "Replied", "label": __("Replied") }, + { "value": "Opportunity", "label": __("Opportunity") }, + { "value": "Quotation", "label": __("Quotation") }, + { "value": "Lost Quotation", "label": __("Lost Quotation") }, + { "value": "Interested", "label": __("Interested") }, + { "value": "Converted", "label": __("Converted") }, + { "value": "Do Not Contact", "label": __("Do Not Contact") }, + ], + }, + { + "fieldname":"territory", + "label": __("Territory"), + "fieldtype": "Link", + "options": "Territory", + } + ] +}; \ No newline at end of file diff --git a/erpnext/crm/report/lead_details/lead_details.json b/erpnext/crm/report/lead_details/lead_details.json index cdeb6bbe38..6ba7305dc7 100644 --- a/erpnext/crm/report/lead_details/lead_details.json +++ b/erpnext/crm/report/lead_details/lead_details.json @@ -7,7 +7,7 @@ "doctype": "Report", "idx": 3, "is_standard": "Yes", - "modified": "2020-01-22 16:51:56.591110", + "modified": "2020-07-26 23:59:49.897577", "modified_by": "Administrator", "module": "CRM", "name": "Lead Details", @@ -16,7 +16,7 @@ "query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2)\n\t) as 'Address::180',\n\t`tabAddress`.state as \"State::100\",\n\t`tabAddress`.pincode as \"Pincode::70\",\n\t`tabAddress`.country as \"Country::100\",\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n\t`tabLead`.notes as \"Notes::360\",\n `tabLead`.owner as \"Owner:Link/User:120\"\nFROM\n\t`tabLead`\n\tleft join `tabDynamic Link` on (\n\t\t`tabDynamic Link`.link_name=`tabLead`.name \n\t\tand `tabDynamic Link`.parenttype = 'Address'\n\t)\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.name=`tabDynamic Link`.parent\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc", "ref_doctype": "Lead", "report_name": "Lead Details", - "report_type": "Query Report", + "report_type": "Script Report", "roles": [ { "role": "Sales User" diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py new file mode 100644 index 0000000000..5b1849a256 --- /dev/null +++ b/erpnext/crm/report/lead_details/lead_details.py @@ -0,0 +1,164 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe import _ +import frappe + +def execute(filters=None): + columns, data = get_columns(), get_leads(filters) + return columns, data + +def get_columns(): + columns = [ + { + "label": _("Lead"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Lead", + "width": 150, + }, + { + "label": _("Lead Name"), + "fieldname": "lead_name", + "fieldtype": "Data", + "width": 120 + }, + { + "fieldname":"status", + "label": _("Status"), + "fieldtype": "Data", + "width": 100 + }, + { + "fieldname":"lead_owner", + "label": _("Lead Owner"), + "fieldtype": "Link", + "options": "User", + "width": 100 + }, + { + "label": _("Territory"), + "fieldname": "territory", + "fieldtype": "Link", + "options": "Territory", + "width": 100 + }, + { + "label": _("Source"), + "fieldname": "source", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Email"), + "fieldname": "email_id", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Mobile"), + "fieldname": "mobile_no", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Phone"), + "fieldname": "phone", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Notes"), + "fieldname": "notes", + "fieldtype": "Data", + "width": 120 + }, + { + "label": _("Owner"), + "fieldname": "owner", + "fieldtype": "Link", + "options": "user", + "width": 120 + }, + { + "label": _("Company"), + "fieldname": "company", + "fieldtype": "Link", + "options": "Company", + "width": 120 + }, + { + "fieldname":"address", + "label": _("Address"), + "fieldtype": "Data", + "width": 130 + }, + { + "fieldname":"state", + "label": _("State"), + "fieldtype": "Data", + "width": 100 + }, + { + "fieldname":"pincode", + "label": _("Postal Code"), + "fieldtype": "Data", + "width": 90 + }, + { + "fieldname":"country", + "label": _("Country"), + "fieldtype": "Link", + "options": "Country", + "width": 100 + }, + + ] + return columns + +def get_leads(filters): + return frappe.db.sql(""" + SELECT + `tabLead`.name, + `tabLead`.lead_name, + `tabLead`.status, + `tabLead`.lead_owner, + `tabLead`.territory, + `tabLead`.source, + `tabLead`.email_id, + `tabLead`.mobile_no, + `tabLead`.phone, + `tabLead`.notes, + `tabLead`.owner, + `tabLead`.company, + concat_ws(', ', + trim(',' from `tabAddress`.address_line1), + trim(',' from tabAddress.address_line2) + ) AS address, + `tabAddress`.state, + `tabAddress`.pincode, + `tabAddress`.country + FROM + `tabLead` left join `tabDynamic Link` on ( + `tabLead`.name = `tabDynamic Link`.link_name and + `tabDynamic Link`.parenttype = 'Address') + left join `tabAddress` on ( + `tabAddress`.name=`tabDynamic Link`.parent) + WHERE + company = %(company)s + AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s + {conditions} + ORDER BY + `tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) + +def get_conditions(filters) : + conditions = [] + + if filters.get("territory"): + conditions.append("territory=%(territory)s") + + if filters.get("status"): + conditions.append("status=%(status)s") + + return " and {}".format(" and ".join(conditions)) if conditions else "" From 6bab21483c947b8d53afc1d142a5afc6f81a5cf4 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Thu, 30 Jul 2020 00:41:06 +0530 Subject: [PATCH 02/73] report lost opportunity --- .../crm/report/lead_details/lead_details.py | 4 +- .../lost_opportunity/lost_opportunity.js | 41 +++++++++ .../lost_opportunity/lost_opportunity.json | 5 +- .../lost_opportunity/lost_opportunity.py | 86 +++++++++++++++++++ 4 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 erpnext/crm/report/lost_opportunity/lost_opportunity.js create mode 100644 erpnext/crm/report/lost_opportunity/lost_opportunity.py diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py index 5b1849a256..079f32e040 100644 --- a/erpnext/crm/report/lead_details/lead_details.py +++ b/erpnext/crm/report/lead_details/lead_details.py @@ -6,7 +6,7 @@ from frappe import _ import frappe def execute(filters=None): - columns, data = get_columns(), get_leads(filters) + columns, data = get_columns(), get_data(filters) return columns, data def get_columns(): @@ -117,7 +117,7 @@ def get_columns(): ] return columns -def get_leads(filters): +def get_data(filters): return frappe.db.sql(""" SELECT `tabLead`.name, diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.js b/erpnext/crm/report/lost_opportunity/lost_opportunity.js new file mode 100644 index 0000000000..c6bf888da3 --- /dev/null +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.js @@ -0,0 +1,41 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Lost Opportunity"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"opportunity_from", + "label": __("Opportunity From"), + "fieldtype": "Link", + "options": "DocType", + "get_query": function() { + return { + "filters": { + "name": ["in", ["Customer", "Lead"]], + } + } + } + }, + { + "fieldname":"party_name", + "label": __("Party"), + "fieldtype": "Dynamic Link", + "options": "opportunity_from" + }, + { + "fieldname":"contact_by", + "label": __("Next Contact By"), + "fieldtype": "Link", + "options": "User" + }, + ] +}; \ No newline at end of file diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.json b/erpnext/crm/report/lost_opportunity/lost_opportunity.json index e7c5068b86..e7a8e12ba7 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.json +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.json @@ -1,13 +1,14 @@ { "add_total_row": 0, "creation": "2018-12-31 16:30:57.188837", + "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", "idx": 0, "is_standard": "Yes", "json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"contact_by\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}", - "modified": "2019-06-26 16:33:08.083618", + "modified": "2020-07-29 15:49:02.848845", "modified_by": "Administrator", "module": "CRM", "name": "Lost Opportunity", @@ -15,7 +16,7 @@ "prepared_report": 0, "ref_doctype": "Opportunity", "report_name": "Lost Opportunity", - "report_type": "Report Builder", + "report_type": "Script Report", "roles": [ { "role": "Sales User" diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py new file mode 100644 index 0000000000..75c0b2d981 --- /dev/null +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py @@ -0,0 +1,86 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe import _ +import frappe + +def execute(filters=None): + columns, data = get_columns(), get_data(filters) + return columns, data + +def get_columns(): + columns = [ + { + "label": _("Opportunity"), + "fieldname": "name", + "fieldtype": "Link", + "options": "Opportunity", + "width": 170, + }, + { + "label": _("Opportunity From"), + "fieldname": "opportunity_from", + "fieldtype": "Link", + "options": "DocType", + "width": 130 + }, + { + "label": _("Party"), + "fieldname":"party_name", + "fieldtype": "Dynamic Link", + "options": "opportunity_from", + "width": 160 + }, + { + "label": _("Customer/Lead Name"), + "fieldname":"customer_name", + "fieldtype": "Data", + "width": 150 + }, + { + "label": _("Opportunity Type"), + "fieldname": "opportunity_type", + "fieldtype": "Data", + "width": 130 + }, + { + "label": _("Next Contact By"), + "fieldname": "contact_by", + "fieldtype": "Link", + "options": "User", + "width": 120 + } + ] + return columns + +def get_data(filters): + return frappe.db.sql(""" + SELECT + `tabOpportunity`.name, + `tabOpportunity`.opportunity_from, + `tabOpportunity`.party_name, + `tabOpportunity`.customer_name, + `tabOpportunity`.opportunity_type, + `tabOpportunity`.contact_by + FROM + `tabOpportunity` + WHERE + status = 'Lost' and company = %(company)s + {conditions} + ORDER BY + creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) + +def get_conditions(filters) : + conditions = [] + + if filters.get("opportunity_from"): + conditions.append("opportunity_from=%(opportunity_from)s") + + if filters.get("party_name"): + conditions.append("party_name=%(party_name)s") + + if filters.get("contact_by"): + conditions.append("contact_by=%(contact_by)s") + + return " and {}".format(" and ".join(conditions)) if conditions else "" From a306af8c089a07d3ac8f65439868997a5cd5f37c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 6 Aug 2020 20:52:02 +0530 Subject: [PATCH 03/73] fix: Add help link in navbar settings --- erpnext/patches.txt | 1 + .../v13_0/add_standard_navbar_items.py | 7 +++ erpnext/public/js/conf.js | 26 ----------- erpnext/setup/install.py | 45 ++++++++++++++++++- 4 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 erpnext/patches/v13_0/add_standard_navbar_items.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3bd416952f..3f63bf651b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -718,3 +718,4 @@ erpnext.patches.v13_0.delete_report_requested_items_to_order erpnext.patches.v12_0.update_item_tax_template_company erpnext.patches.v13_0.move_branch_code_to_bank_account erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes +erpnext.patches.v13_0.add_standard_navbar_items #4 diff --git a/erpnext/patches/v13_0/add_standard_navbar_items.py b/erpnext/patches/v13_0/add_standard_navbar_items.py new file mode 100644 index 0000000000..5de99a5abc --- /dev/null +++ b/erpnext/patches/v13_0/add_standard_navbar_items.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe +from erpnext.setup.install import add_standard_navbar_items + +def execute(): + # Add standard navbar items for ERPNext in Navbar Settings + add_standard_navbar_items() \ No newline at end of file diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js index 9870f81910..2af9140f9e 100644 --- a/erpnext/public/js/conf.js +++ b/erpnext/public/js/conf.js @@ -3,32 +3,6 @@ frappe.provide('erpnext'); -// add toolbar icon -$(document).bind('toolbar_setup', function() { - frappe.app.name = "ERPNext"; - - frappe.help_feedback_link = '

Feedback

' - - - $('[data-link="docs"]').attr("href", "https://erpnext.com/docs") - $('[data-link="issues"]').attr("href", "https://github.com/frappe/erpnext/issues") - - - // default documentation goes to erpnext - // $('[data-link-type="documentation"]').attr('data-path', '/erpnext/manual/index'); - - // additional help links for erpnext - var $help_menu = $('.dropdown-help ul .documentation-links'); - $('
  • '+__('Documentation')+'
  • ').insertBefore($help_menu); - $('
  • '+__('User Forum')+'
  • ').insertBefore($help_menu); - $('
  • '+__('Report an Issue')+'
  • ').insertBefore($help_menu); - -}); - // preferred modules for breadcrumbs $.extend(frappe.breadcrumbs.preferred, { "Item Group": "Stock", diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index aa9fbc0a92..b2f0fa2ed6 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -25,12 +25,13 @@ def after_install(): create_default_success_action() create_default_energy_point_rules() add_company_to_session_defaults() + add_standard_navbar_items() frappe.db.commit() def check_setup_wizard_not_completed(): if frappe.db.get_default('desktop:home_page') != 'setup-wizard': - message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed. + message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed. You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall""" frappe.throw(message) @@ -103,3 +104,45 @@ def add_company_to_session_defaults(): "ref_doctype": "Company" }) settings.save() + +def add_standard_navbar_items(): + navbar_settings = frappe.get_single("Navbar Settings") + + erpnext_navbar_items = [ + { + 'item_label': 'Documentation', + 'item_type': 'Route', + 'route': 'https://erpnext.com/docs/user/manual', + 'is_standard': 1 + }, + { + 'item_label': 'User Forum', + 'item_type': 'Route', + 'route': 'https://discuss.erpnext.com', + 'is_standard': 1 + }, + { + 'item_label': 'Report an Issue', + 'item_type': 'Route', + 'route': 'https://github.com/frappe/erpnext/issues', + 'is_standard': 1 + } + ] + + current_nabvar_items = navbar_settings.help_dropdown + navbar_settings.set('help_dropdown', []) + + for item in erpnext_navbar_items: + navbar_settings.append('help_dropdown', item) + + for item in current_nabvar_items: + navbar_settings.append('help_dropdown', { + 'item_label': item.item_label, + 'item_type': item.item_type, + 'route': item.route, + 'action': item.action, + 'is_standard': item.is_standard, + 'hidden': item.hidden + }) + + navbar_settings.save() From d01caac85260f692a647d62fa5d46ff5566d81ca Mon Sep 17 00:00:00 2001 From: Anupam K Date: Mon, 3 Aug 2020 09:41:16 +0530 Subject: [PATCH 04/73] report lead detail --- .../crm/report/lead_details/lead_details.py | 15 ++++----------- .../lost_opportunity/lost_opportunity.py | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py index 079f32e040..9f4ae60679 100644 --- a/erpnext/crm/report/lead_details/lead_details.py +++ b/erpnext/crm/report/lead_details/lead_details.py @@ -68,12 +68,6 @@ def get_columns(): "fieldtype": "Data", "width": 120 }, - { - "label": _("Notes"), - "fieldname": "notes", - "fieldtype": "Data", - "width": 120 - }, { "label": _("Owner"), "fieldname": "owner", @@ -129,7 +123,6 @@ def get_data(filters): `tabLead`.email_id, `tabLead`.mobile_no, `tabLead`.phone, - `tabLead`.notes, `tabLead`.owner, `tabLead`.company, concat_ws(', ', @@ -153,12 +146,12 @@ def get_data(filters): `tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) def get_conditions(filters) : - conditions = [] + conditions = "" if filters.get("territory"): - conditions.append("territory=%(territory)s") + conditions+=" and territory=%(territory)s " if filters.get("status"): - conditions.append("status=%(status)s") + conditions+=" and status=%(status)s " - return " and {}".format(" and ".join(conditions)) if conditions else "" + return conditions diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py index 75c0b2d981..094fa961f7 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py @@ -50,6 +50,12 @@ def get_columns(): "fieldtype": "Link", "options": "User", "width": 120 + }, + { + "label": _("Lost Reasons"), + "fieldname": "lost_reason", + "fieldtype": "Data", + "width": 220 } ] return columns @@ -62,14 +68,20 @@ def get_data(filters): `tabOpportunity`.party_name, `tabOpportunity`.customer_name, `tabOpportunity`.opportunity_type, - `tabOpportunity`.contact_by + `tabOpportunity`.contact_by, + GROUP_CONCAT(`tabLost Reason Detail`.lost_reason separator ', ') lost_reason FROM `tabOpportunity` + LEFT JOIN `tabLost Reason Detail` + ON `tabLost Reason Detail`.parenttype = 'Opportunity' and `tabLost Reason Detail`.parent = `tabOpportunity`.name WHERE - status = 'Lost' and company = %(company)s + `tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s {conditions} + GROUP BY + `tabOpportunity`.name ORDER BY - creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) + `tabOpportunity`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) + def get_conditions(filters) : conditions = [] From 58d05ac0e0a8ef89db41dc8dd5dea7686da8b74e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 10 Aug 2020 19:36:45 +0530 Subject: [PATCH 05/73] fix: check setup complete flag insted setup page --- erpnext/setup/install.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index aa9fbc0a92..50f9d84fce 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -7,6 +7,7 @@ import frappe from erpnext.accounts.doctype.cash_flow_mapper.default_cash_flow_mapper import DEFAULT_MAPPERS from .default_success_action import get_default_success_action from frappe import _ +from frappe.utils import cint from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.custom.doctype.custom_field.custom_field import create_custom_field from erpnext.setup.default_energy_point_rules import get_default_energy_point_rules @@ -29,8 +30,8 @@ def after_install(): def check_setup_wizard_not_completed(): - if frappe.db.get_default('desktop:home_page') != 'setup-wizard': - message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed. + if cint(frappe.db.get_single_value('System Settings', 'setup_complete') or 0): + message = """ERPNext can only be installed on a fresh site where the setup wizard is not completed. You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall""" frappe.throw(message) From 49dd8782fdaed88aa71cdc3ac38fcbc79900e6fc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Aug 2020 14:39:04 +0530 Subject: [PATCH 06/73] fix: margin overflow cutting the focus state --- erpnext/public/less/products.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/less/products.less b/erpnext/public/less/products.less index 79f57b331a..5e744ceac5 100644 --- a/erpnext/public/less/products.less +++ b/erpnext/public/less/products.less @@ -22,6 +22,8 @@ } .filter-options { + margin-left: -5px; + padding-left: 5px; max-height: 300px; overflow: auto; } From afa98bb39b56b5fa9d077f57aa2b1a840d1be36c Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 18 Jun 2020 17:41:53 +0530 Subject: [PATCH 07/73] feat: Assign shift for period or create ongoing assignment --- .../shift_assignment/shift_assignment.json | 37 ++++-- .../shift_assignment/shift_assignment.py | 105 ++++++++++++------ 2 files changed, 100 insertions(+), 42 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.json b/erpnext/hr/doctype/shift_assignment/shift_assignment.json index 72cbba8a0d..ce2a10f229 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.json +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.json @@ -10,9 +10,11 @@ "employee", "employee_name", "shift_type", + "status", "column_break_3", "company", - "date", + "start_date", + "end_date", "shift_request", "department", "amended_from" @@ -59,12 +61,6 @@ "options": "Company", "reqd": 1 }, - { - "fieldname": "date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Date" - }, { "fieldname": "shift_request", "fieldtype": "Link", @@ -80,11 +76,36 @@ "options": "Shift Assignment", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "reqd": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "end_date", + "fieldtype": "Date", + "label": "End Date", + "show_days": 1, + "show_seconds": 1 + }, + { + "allow_on_submit": 1, + "default": "Active", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Active\nInactive", + "show_days": 1, + "show_seconds": 1 } ], "is_submittable": 1, "links": [], - "modified": "2019-12-12 15:49:06.956901", + "modified": "2020-06-15 14:27:54.310773", "modified_by": "Administrator", "module": "HR", "name": "Shift Assignment", diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 40c78cdf07..e2bb98076f 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -11,38 +11,64 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday from datetime import timedelta, datetime -class OverlapError(frappe.ValidationError): pass - class ShiftAssignment(Document): def validate(self): self.validate_overlapping_dates() + if self.end_date and self.end_date <= self.start_date: + frappe.throw(_("End Date should not be less than Start Date")) + def validate_overlapping_dates(self): - if not self.name: - self.name = "New Shift Assignment" + if not self.name: + self.name = "New Shift Assignment" - d = frappe.db.sql(""" - select - name, shift_type, date - from `tabShift Assignment` - where employee = %(employee)s and docstatus < 2 - and date = %(date)s - and name != %(name)s""", { - "employee": self.employee, - "shift_type": self.shift_type, - "date": self.date, - "name": self.name - }, as_dict = 1) + condition = """and ( + end_date is null + or + %(start_date)s between start_date and end_date + """ - for date_overlap in d: - if date_overlap['name']: - self.throw_overlap_error(date_overlap) + if self.end_date: + condition += """ or + %(end_date)s between start_date and end_date + or + start_date between %(start_date)s and %(end_date)s + ) """ + else: + condition += """ ) """ - def throw_overlap_error(self, d): - msg = _("Employee {0} has already applied for {1} on {2} : ").format(self.employee, - d['shift_type'], formatdate(d['date'])) \ - + """ {0}""".format(d["name"]) - frappe.throw(msg, OverlapError) + assigned_shifts = frappe.db.sql(""" + select name, shift_type, start_date ,end_date, docstatus, status + from `tabShift Assignment` + where + employee=%(employee)s and docstatus < 2 + and name != %(name)s + and status = "Active" + {0} + """.format(condition), { + "employee": self.employee, + "shift_type": self.shift_type, + "start_date": self.start_date, + "end_date": self.end_date, + "name": self.name + }, as_dict = 1) + + for shift in assigned_shifts: + if shift.name: + self.throw_overlap_error(shift) + + def throw_overlap_error(self, shift_details): + shift_details = frappe._dict(shift_details) + if shift_details.docstatus == 0: + msg = _("Employee {0} has already applied for {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + if shift_details.docstatus == 1 and shift_details.status == "Active": + msg = _("Employee {0} already have Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + if shift_details.start_date: + msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) + if shift_details.end_date: + msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y")) + if msg: + frappe.throw(msg) @frappe.whitelist() def get_events(start, end, filters=None): @@ -62,19 +88,22 @@ def get_events(start, end, filters=None): return events def add_assignments(events, start, end, conditions=None): - query = """select name, date, employee_name, + query = """select name, start_date, end_date, employee_name, employee, docstatus from `tabShift Assignment` where - date <= %(date)s - and docstatus < 2""" + start_date >= %(start_date)s + and docstatus < 2""".format() if conditions: query += conditions - for d in frappe.db.sql(query, {"date":start, "date":end}, as_dict=True): + for d in frappe.db.sql(query, {"start_date":start}, as_dict=True): + from pprint import pprint + pprint(d) e = { "name": d.name, "doctype": "Shift Assignment", - "date": d.date, + "start_date": d.start_date, + "end_date": d.end_date if d.end_date else nowdate(), "title": cstr(d.employee_name) + \ cstr(d.shift_type), "docstatus": d.docstatus @@ -92,7 +121,14 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals :param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date. """ default_shift = frappe.db.get_value('Employee', employee, 'default_shift') - shift_type_name = frappe.db.get_value('Shift Assignment', {'employee':employee, 'date': for_date, 'docstatus': '1'}, 'shift_type') + shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date']) + + shift_type_name = shift_assignment_details[0] + + # if end_date present means that shift is over after end_date else it is a ongoing shift. + if shift_assignment_details[1] and for_date >= shift_assignment_details[1] : + shift_type_name = None + if not shift_type_name and consider_default_shift: shift_type_name = default_shift if shift_type_name: @@ -117,12 +153,13 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals direction = '<' if next_shift_direction == 'reverse' else '>' sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc' dates = frappe.db.get_all('Shift Assignment', - 'date', - {'employee':employee, 'date':(direction, for_date), 'docstatus': '1'}, + 'start_date', + {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"}, as_list=True, limit=MAX_DAYS, order_by="date "+sort_order) + for date in dates: - shift_details = get_employee_shift(employee, date[0], consider_default_shift, None) + shift_details = get_employee_shift(employee, date.start_date, consider_default_shift, None) if shift_details: shift_type_name = shift_details.shift_type.name for_date = date[0] @@ -134,7 +171,7 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False): """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee """ - # write and verify a test case for midnight shift. + # write and verify a test case for midnight shift. prev_shift = curr_shift = next_shift = None curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward') if curr_shift: From dd46d19765f9b8b4f890863dc44e43e2c1cff377 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 18 Jun 2020 17:44:04 +0530 Subject: [PATCH 08/73] feat: map event calendar to show ongoing shift assignment and fixed period assigment --- .../hr/doctype/shift_assignment/shift_assignment_calendar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js index c2c9bc073a..17a986deb2 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment_calendar.js @@ -3,8 +3,8 @@ frappe.views.calendar["Shift Assignment"] = { field_map: { - "start": "date", - "end": "date", + "start": "start_date", + "end": "end_date", "id": "name", "docstatus": 1 }, From a6ec7e31d24b47dc866a9c361ec9ceddbcbf3079 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 18 Jun 2020 17:44:44 +0530 Subject: [PATCH 09/73] test: shift assignment --- .../shift_assignment/test_shift_assignment.py | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py index 7fe80a236c..4c3c1ed579 100644 --- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate +from frappe.utils import nowdate, add_days test_dependencies = ["Shift Type"] @@ -20,8 +20,61 @@ class TestShiftAssignment(unittest.TestCase): "shift_type": "Day Shift", "company": "_Test Company", "employee": "_T-Employee-00001", - "date": nowdate() + "start_date": nowdate() }).insert() shift_assignment.submit() self.assertEqual(shift_assignment.docstatus, 1) + + def test_overlapping_for_ongoing_shift(self): + # shift should be Ongoing if Only start_date is present and status = Active + + shift_assignment_1 = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date": nowdate(), + "status": 'Active' + }).insert() + shift_assignment_1.submit() + + self.assertEqual(shift_assignment_1.docstatus, 1) + + shift_assignment = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date": add_days(nowdate(), 2) + }) + + self.assertRaises(frappe.ValidationError, shift_assignment.save) + + def test_overlapping_for_fixed_period_shift(self): + # shift should is for Fixed period if Only start_date and end_date both are present and status = Active + + shift_assignment_1 = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date": nowdate(), + "end_date": add_days(nowdate(), 30), + "status": 'Active' + }).insert() + shift_assignment_1.submit() + + + # it should not allowed within period of any shift. + shift_assignment_3 = frappe.get_doc({ + "doctype": "Shift Assignment", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "start_date":add_days(nowdate(), 10), + "end_date": add_days(nowdate(), 35), + "status": 'Active' + }) + + self.assertRaises(frappe.ValidationError, shift_assignment_3.save) \ No newline at end of file From b3e00de0ae0a03f8cac2c166350c922544f78da8 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 17:37:41 +0530 Subject: [PATCH 10/73] refactor: Shift Request --- erpnext/hr/doctype/department/department.json | 94 +++- .../department_approver.py | 7 +- .../hr/doctype/shift_request/shift_request.js | 15 +- .../doctype/shift_request/shift_request.json | 529 +++++------------- .../hr/doctype/shift_request/shift_request.py | 44 +- .../shift_request/test_shift_request.py | 20 +- 6 files changed, 267 insertions(+), 442 deletions(-) diff --git a/erpnext/hr/doctype/department/department.json b/erpnext/hr/doctype/department/department.json index a54c1d18e7..dcb6a742b7 100644 --- a/erpnext/hr/doctype/department/department.json +++ b/erpnext/hr/doctype/department/department.json @@ -17,10 +17,10 @@ "payroll_cost_center", "column_break_9", "leave_block_list", - "leave_section", + "approvers", "leave_approvers", - "expense_section", "expense_approvers", + "shift_request_approver", "lft", "rgt", "old_parent" @@ -33,14 +33,18 @@ "label": "Department", "oldfieldname": "department_name", "oldfieldtype": "Data", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "parent_department", "fieldtype": "Link", "in_list_view": 1, "label": "Parent Department", - "options": "Department" + "options": "Department", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", @@ -48,7 +52,9 @@ "in_standard_filter": 1, "label": "Company", "options": "Company", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "bold": 1, @@ -56,17 +62,23 @@ "fieldname": "is_group", "fieldtype": "Check", "in_list_view": 1, - "label": "Is Group" + "label": "Is Group", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disabled" + "label": "Disabled", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_4", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "description": "Days for which Holidays are blocked for this department.", @@ -74,31 +86,25 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Leave Block List", - "options": "Leave Block List" + "options": "Leave Block List", + "show_days": 1, + "show_seconds": 1 }, { - "fieldname": "leave_section", - "fieldtype": "Section Break", - "label": "Leave Approvers" - }, - { - "description": "The first Leave Approver in the list will be set as the default Leave Approver.", "fieldname": "leave_approvers", "fieldtype": "Table", "label": "Leave Approver", - "options": "Department Approver" + "options": "Department Approver", + "show_days": 1, + "show_seconds": 1 }, { - "fieldname": "expense_section", - "fieldtype": "Section Break", - "label": "Expense Approvers" - }, - { - "description": "The first Expense Approver in the list will be set as the default Expense Approver.", "fieldname": "expense_approvers", "fieldtype": "Table", "label": "Expense Approver", - "options": "Department Approver" + "options": "Department Approver", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "lft", @@ -106,7 +112,9 @@ "hidden": 1, "label": "lft", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "rgt", @@ -114,7 +122,9 @@ "hidden": 1, "label": "rgt", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "old_parent", @@ -122,28 +132,52 @@ "hidden": 1, "ignore_user_permissions": 1, "label": "Old Parent", - "print_hide": 1 + "print_hide": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_3", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "payroll_cost_center", "fieldtype": "Link", "label": "Payroll Cost Center", - "options": "Cost Center" + "options": "Cost Center", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_9", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "description": "The first Approver in the list will be set as the default Approver.", + "fieldname": "approvers", + "fieldtype": "Section Break", + "label": "Approvers", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "shift_request_approver", + "fieldtype": "Table", + "label": "Shift Request Approver", + "options": "Department Approver", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-sitemap", "idx": 1, "is_tree": 1, "links": [], - "modified": "2020-05-05 18:49:28.503931", + "modified": "2020-06-23 15:42:00.563272", "modified_by": "Administrator", "module": "HR", "name": "Department", diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index afd54b8346..7bd8fd4aba 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -15,7 +15,7 @@ class DepartmentApprover(Document): def get_approvers(doctype, txt, searchfield, start, page_len, filters): if not filters.get("employee"): - frappe.throw(_("Please select Employee Record first.")) + frappe.throw(_("Please select Employee first.")) approvers = [] department_details = {} @@ -41,9 +41,12 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" field_name = "Leave Approver" - else: + elif filters.get("doctype") == "Leave Application": parentfield = "expense_approvers" field_name = "Expense Approver" + else: + parentfield = "shift_request_approver" + field_name = "Approver" if department_list: for d in department_list: approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from diff --git a/erpnext/hr/doctype/shift_request/shift_request.js b/erpnext/hr/doctype/shift_request/shift_request.js index 1db7c7d10e..b17a6f3845 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.js +++ b/erpnext/hr/doctype/shift_request/shift_request.js @@ -2,7 +2,16 @@ // For license information, please see license.txt frappe.ui.form.on('Shift Request', { - refresh: function(frm) { - - } + setup: function(frm) { + frm.set_query("approver", function() { + return { + query: "erpnext.hr.doctype.department_approver.department_approver.get_approvers", + filters: { + employee: frm.doc.employee, + doctype: frm.doc.doctype + } + }; + }); + frm.set_query("employee", erpnext.queries.employee); + }, }); diff --git a/erpnext/hr/doctype/shift_request/shift_request.json b/erpnext/hr/doctype/shift_request/shift_request.json index dd056470cd..fee55dd14b 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.json +++ b/erpnext/hr/doctype/shift_request/shift_request.json @@ -1,396 +1,175 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "HR-SHR-.YY.-.MM.-.#####", - "beta": 0, - "creation": "2018-04-13 16:32:27.974273", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "autoname": "HR-SHR-.YY.-.MM.-.#####", + "creation": "2018-04-13 16:32:27.974273", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "shift_type", + "employee", + "employee_name", + "department", + "status", + "column_break_4", + "company", + "approver", + "from_date", + "to_date", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shift_type", - "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": "Shift Type", - "length": 0, - "no_copy": 0, - "options": "Shift Type", - "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": "shift_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Shift Type", + "options": "Shift Type", + "reqd": 1, + "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": "employee", - "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": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1, + "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, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "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": "Employee Name", - "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 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1, + "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, - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "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 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1, + "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": "column_break_4", - "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 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break", + "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": "company", - "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": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "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": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "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": "from_date", - "fieldtype": "Date", - "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": "From Date", - "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 - }, + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date", + "reqd": 1, + "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": "to_date", - "fieldtype": "Date", - "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": "To Date", - "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 - }, + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "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": "amended_from", - "fieldtype": "Link", - "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": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Shift Request", - "permlevel": 0, - "print_hide": 1, - "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 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Shift Request", + "print_hide": 1, + "read_only": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Draft\nApproved\nRejected", + "reqd": 1, + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "approver", + "fieldtype": "Link", + "label": "Approver", + "options": "User", + "reqd": 1, + "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": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:36.577448", - "modified_by": "Administrator", - "module": "HR", - "name": "Shift Request", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2020-06-23 15:56:44.536207", + "modified_by": "Administrator", + "module": "HR", + "name": "Shift Request", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 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", - "title_field": "employee_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index ff5de08ee5..f39bdb8105 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate +from erpnext.hr.doctype.department_approver.department_approver import get_approvers class OverlapError(frappe.ValidationError): pass @@ -14,15 +15,19 @@ class ShiftRequest(Document): def validate(self): self.validate_dates() self.validate_shift_request_overlap_dates() + self.validate_approver() def on_submit(self): - date_list = self.get_working_days(self.from_date, self.to_date) - for date in date_list: + if self.status not in ["Approved", "Rejected"]: + frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted")) + if self.status == "Approved": assignment_doc = frappe.new_doc("Shift Assignment") assignment_doc.company = self.company assignment_doc.shift_type = self.shift_type assignment_doc.employee = self.employee - assignment_doc.date = date + assignment_doc.start_date = self.from_date + if self.to_date: + assignment_doc.end_date = self.to_date assignment_doc.shift_request = self.name assignment_doc.insert() assignment_doc.submit() @@ -34,6 +39,13 @@ class ShiftRequest(Document): shift_assignment_doc = frappe.get_doc("Shift Assignment", shift['name']) shift_assignment_doc.cancel() + def validate_approver(self): + department = frappe.get_value("Employee", self.employee, "department") + # shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver") + approvers = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department)) + approvers = [approver[0] for approver in approvers] + if self.approver not in approvers: + frappe.throw(__("Only Approvers can Approve this Request.")) def validate_dates(self): if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): @@ -68,28 +80,4 @@ class ShiftRequest(Document): msg = _("Employee {0} has already applied for {1} between {2} and {3} : ").format(self.employee, d['shift_type'], formatdate(d['from_date']), formatdate(d['to_date'])) \ + """ {0}""".format(d["name"]) - frappe.throw(msg, OverlapError) - - def get_working_days(self, start_date, end_date): - start_date, end_date = getdate(start_date), getdate(end_date) - - from datetime import timedelta - - date_list = [] - employee_holiday_list = [] - - employee_holidays = frappe.db.sql("""select holiday_date from `tabHoliday` - where parent in (select holiday_list from `tabEmployee` - where name = %s)""",self.employee,as_dict=1) - - for d in employee_holidays: - employee_holiday_list.append(d.holiday_date) - - reference_date = start_date - - while reference_date <= end_date: - if reference_date not in employee_holiday_list: - date_list.append(reference_date) - reference_date += timedelta(days=1) - - return date_list \ No newline at end of file + frappe.throw(msg, OverlapError) \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 1d0cf719c2..3dcfcbf4a5 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate +from frappe.utils import nowdate, add_days class TestShiftRequest(unittest.TestCase): def setUp(self): @@ -13,14 +13,20 @@ class TestShiftRequest(unittest.TestCase): frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) def test_make_shift_request(self): + 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 = frappe.get_doc({ "doctype": "Shift Request", "shift_type": "Day Shift", "company": "_Test Company", "employee": "_T-Employee-00001", "employee_name": "_Test Employee", - "start_date": nowdate(), - "end_date": nowdate() + "from_date": nowdate(), + "to_date": add_days(nowdate(), 10), + "approver": approver, + "status": "Approved" }) shift_request.insert() shift_request.submit() @@ -34,4 +40,10 @@ class TestShiftRequest(unittest.TestCase): 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) \ No newline at end of file + self.assertEqual(shift_assignment_doc.docstatus, 2) + +def set_shift_approver(department): + department_doc = frappe.get_doc("Department", department) + department_doc.append('shift_request_approver',{'approver': "test1@example.com"}) + department_doc.save() + department_doc.reload() \ No newline at end of file From feef4a6c1e486b4d6b6a1176b4a748229c359f08 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 18:18:02 +0530 Subject: [PATCH 11/73] test: employee checkin --- .../hr/doctype/shift_assignment/shift_assignment.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index e2bb98076f..296a86a2f9 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -121,13 +121,15 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals :param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date. """ default_shift = frappe.db.get_value('Employee', employee, 'default_shift') + shift_type_name = None shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date']) - shift_type_name = shift_assignment_details[0] + if shift_assignment_details: + shift_type_name = shift_assignment_details[0] - # if end_date present means that shift is over after end_date else it is a ongoing shift. - if shift_assignment_details[1] and for_date >= shift_assignment_details[1] : - shift_type_name = None + # if end_date present means that shift is over after end_date else it is a ongoing shift. + if shift_assignment_details[1] and for_date >= shift_assignment_details[1] : + shift_type_name = None if not shift_type_name and consider_default_shift: shift_type_name = default_shift From 43ba7dd4c8a5e2d09b2c38de800e59e20ad4d579 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 18:18:56 +0530 Subject: [PATCH 12/73] feat: don't allow to request for default shift --- erpnext/hr/doctype/shift_request/shift_request.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index f39bdb8105..9738c6c97b 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -16,6 +16,7 @@ class ShiftRequest(Document): self.validate_dates() self.validate_shift_request_overlap_dates() self.validate_approver() + self.validate_default_shift() def on_submit(self): if self.status not in ["Approved", "Rejected"]: @@ -39,11 +40,17 @@ class ShiftRequest(Document): shift_assignment_doc = frappe.get_doc("Shift Assignment", shift['name']) shift_assignment_doc.cancel() + def validate_default_shift(self): + default_shift = frappe.get_value("Employee", self.employee, "default_shift") + if self.shift_type == default_shift: + frappe.throw(_("You can not request for your Default Shift: {0}").format(frappe.bold(self.shift_type))) + def validate_approver(self): department = frappe.get_value("Employee", self.employee, "department") - # shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver") + shift_approver = frappe.get_value("Employee", self.employee, "shift_request_approver") approvers = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department)) approvers = [approver[0] for approver in approvers] + approvers.append(shift_approver) if self.approver not in approvers: frappe.throw(__("Only Approvers can Approve this Request.")) From 17e6d0918b4213f29f2500cd16a7f9232728e9ce Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 23 Jun 2020 18:19:53 +0530 Subject: [PATCH 13/73] feat: Employee level Shift Request Approver --- .../department_approver.py | 4 ++- erpnext/hr/doctype/employee/employee.json | 34 ++++++++++++++++--- .../shift_assignment/shift_assignment.py | 21 ++++++------ .../hr/doctype/shift_request/shift_request.py | 3 +- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 7bd8fd4aba..b5aa4ac6d4 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -20,7 +20,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): approvers = [] department_details = {} department_list = [] - employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver"], as_dict=True) + employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver", "expense_approver", "shift_request_approver"], as_dict=True) employee_department = filters.get("department") or employee.department if employee_department: @@ -37,6 +37,8 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): if filters.get("doctype") == "Expense Claim" and employee.expense_approver: approvers.append(frappe.db.get_value("User", employee.expense_approver, ['name', 'first_name', 'last_name'])) + if filters.get("doctype") == "Shift Request" and employee.shift_request_approver: + approvers.append(frappe.db.get_value("User", employee.shift_request_approver, ['name', 'first_name', 'last_name'])) if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index f2afe065d1..8c02e4f1d6 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -51,10 +51,14 @@ "column_break_31", "grade", "branch", + "approvers_section", + "expense_approver", + "leave_approver", + "column_break_45", + "shift_request_approver", "attendance_and_leave_details", "leave_policy", "attendance_device_id", - "leave_approver", "column_break_44", "holiday_list", "default_shift", @@ -62,7 +66,6 @@ "salary_mode", "payroll_cost_center", "column_break_52", - "expense_approver", "bank_name", "bank_ac_no", "health_insurance_section", @@ -806,14 +809,37 @@ "fieldname": "expense_approver", "fieldtype": "Link", "label": "Expense Approver", - "options": "User" + "options": "User", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "approvers_section", + "fieldtype": "Section Break", + "label": "Approvers", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "column_break_45", + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 + }, + { + "fieldname": "shift_request_approver", + "fieldtype": "Link", + "label": "Shift Request Approver", + "options": "User", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-user", "idx": 24, "image_field": "image", "links": [], - "modified": "2020-07-03 21:28:04.109189", + "modified": "2020-07-28 01:36:04.109189", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 296a86a2f9..c81345da78 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -97,8 +97,6 @@ def add_assignments(events, start, end, conditions=None): query += conditions for d in frappe.db.sql(query, {"start_date":start}, as_dict=True): - from pprint import pprint - pprint(d) e = { "name": d.name, "doctype": "Shift Assignment", @@ -155,17 +153,20 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals direction = '<' if next_shift_direction == 'reverse' else '>' sort_order = 'desc' if next_shift_direction == 'reverse' else 'asc' dates = frappe.db.get_all('Shift Assignment', - 'start_date', + ['start_date', 'end_date'], {'employee':employee, 'start_date':(direction, for_date), 'docstatus': '1', "status": "Active"}, as_list=True, - limit=MAX_DAYS, order_by="date "+sort_order) + limit=MAX_DAYS, order_by="start_date "+sort_order) - for date in dates: - shift_details = get_employee_shift(employee, date.start_date, consider_default_shift, None) - if shift_details: - shift_type_name = shift_details.shift_type.name - for_date = date[0] - break + if dates: + for date in dates: + if date[1] and date[1] < for_date: + continue + shift_details = get_employee_shift(employee, date[0], consider_default_shift, None) + if shift_details: + shift_type_name = shift_details.shift_type.name + for_date = date[0] + break return get_shift_details(shift_type_name, for_date) diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 9738c6c97b..8a2e7eda6d 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -7,7 +7,6 @@ import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import formatdate, getdate -from erpnext.hr.doctype.department_approver.department_approver import get_approvers class OverlapError(frappe.ValidationError): pass @@ -52,7 +51,7 @@ class ShiftRequest(Document): approvers = [approver[0] for approver in approvers] approvers.append(shift_approver) if self.approver not in approvers: - frappe.throw(__("Only Approvers can Approve this Request.")) + frappe.throw(_("Only Approvers can Approve this Request.")) def validate_dates(self): if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): From 3aa1101d5ef86c1a0070af3d7cf8a465ff6b005a Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 28 Jul 2020 13:32:13 +0530 Subject: [PATCH 14/73] fix: requested changes --- erpnext/hr/doctype/department_approver/department_approver.py | 2 +- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 4 ++-- erpnext/hr/doctype/shift_type/shift_type.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index b5aa4ac6d4..e65c9f8eba 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -46,7 +46,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): elif filters.get("doctype") == "Leave Application": parentfield = "expense_approvers" field_name = "Expense Approver" - else: + elif filters.get("doctype") == "Shift Request": parentfield = "shift_request_approver" field_name = "Approver" if department_list: diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index c81345da78..f25d39b78f 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -16,7 +16,7 @@ class ShiftAssignment(Document): self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: - frappe.throw(_("End Date should not be less than Start Date")) + frappe.throw(_("End Date must not be greater than Start Date")) def validate_overlapping_dates(self): if not self.name: @@ -62,7 +62,7 @@ class ShiftAssignment(Document): if shift_details.docstatus == 0: msg = _("Employee {0} has already applied for {1}: {2}").format(self.employee, self.shift_type, shift_details.name) if shift_details.docstatus == 1 and shift_details.status == "Active": - msg = _("Employee {0} already have Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + msg = _("Employee {0} already has Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) if shift_details.start_date: msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) if shift_details.end_date: diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index 19735648aa..dfe094e6b7 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -79,7 +79,7 @@ class ShiftType(Document): mark_attendance(employee, date, 'Absent', self.name) def get_assigned_employee(self, from_date=None, consider_default_shift=False): - filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} + filters = {'start_date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} if not from_date: del filters['date'] assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True) From b48110caf13c7c1c9165c7e51490f2baf6dec7d7 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 10 Aug 2020 14:17:31 +0530 Subject: [PATCH 15/73] Update erpnext/hr/doctype/shift_assignment/shift_assignment.py Co-authored-by: Marica --- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index f25d39b78f..f5bd0d068e 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -16,7 +16,7 @@ class ShiftAssignment(Document): self.validate_overlapping_dates() if self.end_date and self.end_date <= self.start_date: - frappe.throw(_("End Date must not be greater than Start Date")) + frappe.throw(_("End Date must not be lesser than Start Date")) def validate_overlapping_dates(self): if not self.name: From d5c921e358a3c9a0c11d9b4dc5513b35b68c00da Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 10 Aug 2020 14:28:55 +0530 Subject: [PATCH 16/73] Fix: Requested Changes --- erpnext/hr/doctype/department_approver/department_approver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index e65c9f8eba..6626ece963 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -43,7 +43,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" field_name = "Leave Approver" - elif filters.get("doctype") == "Leave Application": + elif filters.get("doctype") == "Expense Claim": parentfield = "expense_approvers" field_name = "Expense Approver" elif filters.get("doctype") == "Shift Request": From dcf598dc2d30adc45edf9750080a1e7acb873cbd Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 10 Aug 2020 18:03:12 +0530 Subject: [PATCH 17/73] Fix: requested Changes --- .../shift_assignment/shift_assignment.py | 8 ++-- .../doctype/shift_request/shift_request.json | 48 ++++++------------- .../hr/doctype/shift_request/shift_request.py | 2 + erpnext/hr/doctype/shift_type/shift_type.py | 7 ++- 4 files changed, 24 insertions(+), 41 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index f5bd0d068e..20553b9a20 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -60,15 +60,17 @@ class ShiftAssignment(Document): def throw_overlap_error(self, shift_details): shift_details = frappe._dict(shift_details) if shift_details.docstatus == 0: - msg = _("Employee {0} has already applied for {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + msg = _("Employee {0} has already applied for {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.docstatus == 1 and shift_details.status == "Active": - msg = _("Employee {0} already has Active Shift {1}: {2}").format(self.employee, self.shift_type, shift_details.name) + msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.start_date: msg += _(" from {0}").format(getdate(self.start_date).strftime("%d-%m-%Y")) + title = "Ongoing Shift" if shift_details.end_date: msg += _(" to {0}").format(getdate(self.end_date).strftime("%d-%m-%Y")) + title = "Active Shift" if msg: - frappe.throw(msg) + frappe.throw(msg, title=title) @frappe.whitelist() def get_events(start, end, filters=None): diff --git a/erpnext/hr/doctype/shift_request/shift_request.json b/erpnext/hr/doctype/shift_request/shift_request.json index fee55dd14b..64cbdfff7d 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.json +++ b/erpnext/hr/doctype/shift_request/shift_request.json @@ -26,9 +26,7 @@ "in_list_view": 1, "label": "Shift Type", "options": "Shift Type", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "employee", @@ -36,18 +34,14 @@ "in_list_view": 1, "label": "Employee", "options": "Employee", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fetch_from": "employee.employee_name", "fieldname": "employee_name", "fieldtype": "Data", "label": "Employee Name", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fetch_from": "employee.department", @@ -55,15 +49,11 @@ "fieldtype": "Link", "label": "Department", "options": "Department", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "column_break_4", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "company", @@ -71,24 +61,18 @@ "in_list_view": 1, "label": "Company", "options": "Company", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "from_date", "fieldtype": "Date", "label": "From Date", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "fieldname": "to_date", "fieldtype": "Date", - "label": "To Date", - "show_days": 1, - "show_seconds": 1 + "label": "To Date" }, { "fieldname": "amended_from", @@ -97,9 +81,7 @@ "no_copy": 1, "options": "Shift Request", "print_hide": 1, - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "default": "Draft", @@ -107,23 +89,21 @@ "fieldtype": "Select", "label": "Status", "options": "Draft\nApproved\nRejected", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { + "fetch_from": "employee.shift_request_approver", + "fetch_if_empty": 1, "fieldname": "approver", "fieldtype": "Link", "label": "Approver", "options": "User", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-06-23 15:56:44.536207", + "modified": "2020-08-10 17:59:31.550558", "modified_by": "Administrator", "module": "HR", "name": "Shift Request", diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py index 8a2e7eda6d..1c2801bf08 100644 --- a/erpnext/hr/doctype/shift_request/shift_request.py +++ b/erpnext/hr/doctype/shift_request/shift_request.py @@ -32,6 +32,8 @@ class ShiftRequest(Document): assignment_doc.insert() assignment_doc.submit() + frappe.msgprint(_("Shift Assignment: {0} created for Employee: {1}").format(frappe.bold(assignment_doc.name), frappe.bold(self.employee))) + def on_cancel(self): shift_assignment_list = frappe.get_list("Shift Assignment", {'employee': self.employee, 'shift_request': self.name}) if shift_assignment_list: diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index dfe094e6b7..dd08d31b01 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -78,10 +78,9 @@ class ShiftType(Document): if shift_details and shift_details.shift_type.name == self.name: mark_attendance(employee, date, 'Absent', self.name) - def get_assigned_employee(self, from_date=None, consider_default_shift=False): - filters = {'start_date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} - if not from_date: - del filters['date'] + def get_assigned_employee(self, consider_default_shift=False): + filters = {'shift_type': self.name, 'docstatus': '1'} + assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True) assigned_employees = [x[0] for x in assigned_employees] From 00a8081aba33896f4c4cbc4bffa7af40ac94e761 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 11 Aug 2020 13:39:27 +0530 Subject: [PATCH 18/73] Fix: button label --- erpnext/hr/doctype/shift_type/shift_type.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_type/shift_type.js b/erpnext/hr/doctype/shift_type/shift_type.js index e633545630..ba53312bce 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.js +++ b/erpnext/hr/doctype/shift_type/shift_type.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Shift Type', { refresh: function(frm) { frm.add_custom_button( - 'Mark Auto Attendance', + 'Mark Attendance', () => frm.call({ doc: frm.doc, method: 'process_auto_attendance', From 74f0a1ab7cfb62aa9531dd642dae786bb2f8e005 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 11 Aug 2020 19:00:43 +0530 Subject: [PATCH 19/73] fix: changes Requested --- .../doctype/department_approver/department_approver.py | 2 +- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 9 +++------ erpnext/hr/doctype/shift_type/shift_type.py | 6 ++++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 6626ece963..9b2de0e1cb 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -48,7 +48,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): field_name = "Expense Approver" elif filters.get("doctype") == "Shift Request": parentfield = "shift_request_approver" - field_name = "Approver" + field_name = "Shift Request Approver" if department_list: for d in department_list: approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 20553b9a20..4f5b59b7e2 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -41,7 +41,7 @@ class ShiftAssignment(Document): select name, shift_type, start_date ,end_date, docstatus, status from `tabShift Assignment` where - employee=%(employee)s and docstatus < 2 + employee=%(employee)s and docstatus = 1 and name != %(name)s and status = "Active" {0} @@ -53,14 +53,11 @@ class ShiftAssignment(Document): "name": self.name }, as_dict = 1) - for shift in assigned_shifts: - if shift.name: - self.throw_overlap_error(shift) + if len(assigned_shifts): + self.throw_overlap_error(assigned_shifts[0]) def throw_overlap_error(self, shift_details): shift_details = frappe._dict(shift_details) - if shift_details.docstatus == 0: - msg = _("Employee {0} has already applied for {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.docstatus == 1 and shift_details.status == "Active": msg = _("Employee {0} already has Active Shift {1}: {2}").format(frappe.bold(self.employee), frappe.bold(self.shift_type), frappe.bold(shift_details.name)) if shift_details.start_date: diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index dd08d31b01..054e7e3688 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -78,8 +78,10 @@ class ShiftType(Document): if shift_details and shift_details.shift_type.name == self.name: mark_attendance(employee, date, 'Absent', self.name) - def get_assigned_employee(self, consider_default_shift=False): - filters = {'shift_type': self.name, 'docstatus': '1'} + def get_assigned_employee(self, from_date=None, consider_default_shift=False): + filters = {'start_date':('>', from_date), 'shift_type': self.name, 'docstatus': '1'} + if not from_date: + del filters["start_date"] assigned_employees = frappe.get_all('Shift Assignment', 'employee', filters, as_list=True) assigned_employees = [x[0] for x in assigned_employees] From c2710e05ce97f65b602ab2757293b2d8ebb4c611 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Wed, 12 Aug 2020 13:35:25 +0530 Subject: [PATCH 20/73] fix: showing on_going shift on calendar --- erpnext/hr/doctype/shift_assignment/shift_assignment.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 4f5b59b7e2..f8b73349c1 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -91,11 +91,13 @@ def add_assignments(events, start, end, conditions=None): employee, docstatus from `tabShift Assignment` where start_date >= %(start_date)s - and docstatus < 2""".format() + or end_date <= %(end_date)s + or (%(start_date)s between start_date and end_date and %(end_date)s between start_date and end_date) + and docstatus = 1""" if conditions: query += conditions - for d in frappe.db.sql(query, {"start_date":start}, as_dict=True): + for d in frappe.db.sql(query, {"start_date":start, "end_date":end}, as_dict=True): e = { "name": d.name, "doctype": "Shift Assignment", From f096ba4998b056fe89d72250a748785bdf48a78c Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Thu, 13 Aug 2020 14:06:20 +0530 Subject: [PATCH 21/73] patch: Old Shift Assignment --- erpnext/patches.txt | 1 + .../update_start_end_date_for_old_shift_assignment.py | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 361fe8352a..e17e949b3b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -721,3 +721,4 @@ erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes erpnext.patches.v13_0.stock_entry_enhancements erpnext.patches.v12_0.update_state_code_for_daman_and_diu erpnext.patches.v12_0.rename_lost_reason_detail +erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment diff --git a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py new file mode 100644 index 0000000000..e9dafd4162 --- /dev/null +++ b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py @@ -0,0 +1,10 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe + +def execute(): + frappe.reload_doc('hr', 'doctype', 'shift_assignment') + frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date, status='Inactive' where date IS NOT NULL and start_date IS NULL and end_date IS NULL;") \ No newline at end of file From 13be09e883baf8d5021b28aec0f2129116073ef9 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 17 Aug 2020 13:43:33 +0530 Subject: [PATCH 22/73] Update update_start_end_date_for_old_shift_assignment.py --- .../v13_0/update_start_end_date_for_old_shift_assignment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py index e9dafd4162..7c07b987f3 100644 --- a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py +++ b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py @@ -7,4 +7,4 @@ import frappe def execute(): frappe.reload_doc('hr', 'doctype', 'shift_assignment') - frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date, status='Inactive' where date IS NOT NULL and start_date IS NULL and end_date IS NULL;") \ No newline at end of file + frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date where date IS NOT NULL and start_date IS NULL and end_date IS NULL;") From b2604d1f77ed221a07f1f4c50183692af8f23fb7 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 14 Aug 2020 06:42:54 +0000 Subject: [PATCH 23/73] fix(General Ledger): include Accounting Dimension columns in report Prior to this commit, custom Accounting Dimensions are not (by default) shown or even considered in generating a General Ledger report. Moreover, as they are not considered, they are not used to distinguish GL Entry lines in the same voucher with the same Account when the "Group By" value is "Group by Voucher (Consolidated)". This situation leads to lines with different values for the accounting dimension to be ganged together in the General Ledger view of a Journal entry, making the entry difficult for an accountant to read. This commit alleviates the problem by adding a checkbox to control whether Accounting Dimension columns are considered in the General Ledger report. When they are considered, they are displayed and also included in the key that distinguishes lines in the "Group by Voucher (Consolidated)" mode. Resolves #23033. --- .../report/general_ledger/general_ledger.js | 6 +++ .../report/general_ledger/general_ledger.py | 51 ++++++++++++++----- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 1fc0f79478..fb0d359926 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -146,6 +146,12 @@ frappe.query_reports["General Ledger"] = { return frappe.db.get_link_options('Project', txt); } }, + { + "fieldname": "include_dimensions", + "label": __("Consider Accounting Dimensions"), + "fieldtype": "Check", + "default": 0 + }, { "fieldname": "show_opening_entries", "label": __("Show Opening Entries"), diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index fcd36e4e6e..ba0159e1ed 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -106,15 +106,20 @@ def set_account_currency(filters): return filters def get_result(filters, account_details): - gl_entries = get_gl_entries(filters) + accounting_dimensions = [] + if filters.get("include_dimensions"): + accounting_dimensions = get_accounting_dimensions() - data = get_data_with_opening_closing(filters, account_details, gl_entries) + gl_entries = get_gl_entries(filters, accounting_dimensions) + + data = get_data_with_opening_closing(filters, account_details, + accounting_dimensions, gl_entries) result = get_result_as_list(data, filters) return result -def get_gl_entries(filters): +def get_gl_entries(filters, accounting_dimensions): currency_map = get_currency(filters) select_fields = """, debit, credit, debit_in_account_currency, credit_in_account_currency """ @@ -128,6 +133,10 @@ def get_gl_entries(filters): filters['company_fb'] = frappe.db.get_value("Company", filters.get("company"), 'default_finance_book') + dimension_fields = "" + if accounting_dimensions: + dimension_fields = ', '.join(accounting_dimensions) + ',' + distributed_cost_center_query = "" if filters and filters.get('cost_center'): select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency, @@ -141,7 +150,7 @@ def get_gl_entries(filters): party_type, party, voucher_type, - voucher_no, + voucher_no, {dimension_fields} cost_center, project, against_voucher_type, against_voucher, @@ -160,13 +169,14 @@ def get_gl_entries(filters): {conditions} AND posting_date <= %(to_date)s AND cost_center = DCC_allocation.parent - """.format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", '')) + """.format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", '')) gl_entries = frappe.db.sql( """ select name as gl_entry, posting_date, account, party_type, party, - voucher_type, voucher_no, cost_center, project, + voucher_type, voucher_no, {dimension_fields} + cost_center, project, against_voucher_type, against_voucher, account_currency, remarks, against, is_opening, creation {select_fields} from `tabGL Entry` @@ -174,7 +184,7 @@ def get_gl_entries(filters): {distributed_cost_center_query} {order_by_statement} """.format( - select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, + dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query, order_by_statement=order_by_statement ), filters, as_dict=1) @@ -247,12 +257,12 @@ def get_conditions(filters): return "and {}".format(" and ".join(conditions)) if conditions else "" -def get_data_with_opening_closing(filters, account_details, gl_entries): +def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries): data = [] gle_map = initialize_gle_map(gl_entries, filters) - totals, entries = get_accountwise_gle(filters, gl_entries, gle_map) + totals, entries = get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map) # Opening for filtered account data.append(totals.opening) @@ -318,7 +328,7 @@ def initialize_gle_map(gl_entries, filters): return gle_map -def get_accountwise_gle(filters, gl_entries, gle_map): +def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): totals = get_totals_dict() entries = [] consolidated_gle = OrderedDict() @@ -350,8 +360,11 @@ def get_accountwise_gle(filters, gl_entries, gle_map): if filters.get("group_by") != _('Group by Voucher (Consolidated)'): gle_map[gle.get(group_by)].entries.append(gle) elif filters.get("group_by") == _('Group by Voucher (Consolidated)'): - key = (gle.get("voucher_type"), gle.get("voucher_no"), - gle.get("account"), gle.get("cost_center")) + keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")] + for dim in accounting_dimensions: + keylist.append(gle.get(dim)) + keylist.append(gle.get("cost_center")) + key = tuple(keylist) if key not in consolidated_gle: consolidated_gle.setdefault(key, gle) else: @@ -478,7 +491,19 @@ def get_columns(filters): "options": "Project", "fieldname": "project", "width": 100 - }, + } + ]) + + if filters.get("include_dimensions"): + for dim in get_accounting_dimensions(as_list = False): + columns.append({ + "label": _(dim.label), + "options": dim.label, + "fieldname": dim.fieldname, + "width": 100 + }) + + columns.extend([ { "label": _("Cost Center"), "options": "Cost Center", From 310ade7282de87c3c5e4c06bf0b21edabad84b09 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 18 Aug 2020 16:07:24 +0530 Subject: [PATCH 24/73] profit and loss report not working --- erpnext/accounts/report/financial_statements.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 3785ebf215..d5b8cdb1d4 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -14,7 +14,7 @@ import frappe, erpnext from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency from erpnext.accounts.utils import get_fiscal_year from frappe import _ -from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr) +from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint) from six import itervalues from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children @@ -46,7 +46,7 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_ start_date = year_start_date months = get_months(year_start_date, year_end_date) - for i in range(math.ceil(months / months_to_add)): + for i in range(cint(math.ceil(months / months_to_add))): period = frappe._dict({ "from_date": start_date }) From 1c14606c06536187c331ad4dc799abf471a937b7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 18 Aug 2020 19:32:52 +0530 Subject: [PATCH 25/73] fix: Total calculations for multicurrency RCM invoices (#23072) --- erpnext/regional/india/utils.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index fe7e0c807c..2c81748c86 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import frappe, re, json from frappe import _ +import erpnext from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words from erpnext.regional.india import states, state_numbers from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount @@ -678,20 +679,26 @@ def update_grand_total_for_rcm(doc, method): gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ + gst_accounts.get('igst_account') + base_gst_tax = 0 gst_tax = 0 + for tax in doc.get('taxes'): if tax.category not in ("Total", "Valuation and Total"): continue if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: - gst_tax += tax.base_tax_amount_after_discount_amount + base_gst_tax += tax.base_tax_amount_after_discount_amount + gst_tax += tax.tax_amount_after_discount_amount doc.taxes_and_charges_added -= gst_tax doc.total_taxes_and_charges -= gst_tax + doc.base_taxes_and_charges_added -= base_gst_tax + doc.base_total_taxes_and_charges -= base_gst_tax - update_totals(gst_tax, doc) + update_totals(gst_tax, base_gst_tax, doc) -def update_totals(gst_tax, doc): +def update_totals(gst_tax, base_gst_tax, doc): + doc.base_grand_total -= base_gst_tax doc.grand_total -= gst_tax if doc.meta.get_field("rounded_total"): @@ -707,6 +714,7 @@ def update_totals(gst_tax, doc): doc.outstanding_amount = doc.rounded_total or doc.grand_total doc.in_words = money_in_words(doc.grand_total, doc.currency) + doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company)) doc.set_payment_schedule() def make_regional_gl_entries(gl_entries, doc): From 434bab800396294f4aa951d2985918fdcc3749bb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 19 Aug 2020 01:00:59 +0530 Subject: [PATCH 26/73] fix: Print Language for Customer not set for Print Receipt in POS --- .../doctype/pos_invoice/pos_invoice.py | 44 +++++++++---------- .../point_of_sale/pos_past_order_summary.js | 20 +++++---- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 8680b710ac..ba68df7673 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -21,7 +21,7 @@ from six import iteritems class POSInvoice(SalesInvoice): def __init__(self, *args, **kwargs): super(POSInvoice, self).__init__(*args, **kwargs) - + def validate(self): if not cint(self.is_pos): frappe.throw(_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment"))) @@ -58,7 +58,7 @@ class POSInvoice(SalesInvoice): if self.redeem_loyalty_points and self.loyalty_points: self.apply_loyalty_points() self.set_status(update=True) - + def on_cancel(self): # run on cancel method of selling controller super(SalesInvoice, self).on_cancel() @@ -68,10 +68,10 @@ class POSInvoice(SalesInvoice): against_psi_doc = frappe.get_doc("POS Invoice", self.return_against) against_psi_doc.delete_loyalty_point_entry() against_psi_doc.make_loyalty_point_entry() - + def validate_stock_availablility(self): allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') - + for d in self.get('items'): if d.serial_no: filters = { @@ -89,11 +89,11 @@ class POSInvoice(SalesInvoice): for s in serial_nos: if s in reserved_serial_nos: invalid_serial_nos.append(s) - + if len(invalid_serial_nos): multiple_nos = 's' if len(invalid_serial_nos) > 1 else '' frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \ - Please select valid serial no.".format(d.idx, multiple_nos, + Please select valid serial no.".format(d.idx, multiple_nos, frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available")) else: if allow_negative_stock: @@ -105,9 +105,9 @@ class POSInvoice(SalesInvoice): .format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available")) elif flt(available_stock) < flt(d.qty): frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \ - Available quantity {}.'.format(d.idx, frappe.bold(d.item_code), + Available quantity {}.'.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available")) - + def validate_serialised_or_batched_item(self): for d in self.get("items"): serialized = d.get("has_serial_no") @@ -125,7 +125,7 @@ class POSInvoice(SalesInvoice): if batched and no_batch_selected: frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.' .format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item")) - + def validate_return_items(self): if not self.get("is_return"): return @@ -158,7 +158,7 @@ class POSInvoice(SalesInvoice): frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx)) if self.is_return and entry.amount > 0: frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx)) - + def validate_pos_return(self): if self.is_pos and self.is_return: total_amount_in_payments = 0 @@ -167,12 +167,12 @@ class POSInvoice(SalesInvoice): invoice_total = self.rounded_total or self.grand_total if total_amount_in_payments < invoice_total: frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total))) - + def validate_loyalty_transaction(self): if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center): expense_account, cost_center = frappe.db.get_value('Loyalty Program', self.loyalty_program, ["expense_account", "cost_center"]) if not self.loyalty_redemption_account: - self.loyalty_redemption_account = expense_account + self.loyalty_redemption_account = expense_account if not self.loyalty_redemption_cost_center: self.loyalty_redemption_cost_center = cost_center @@ -212,7 +212,7 @@ class POSInvoice(SalesInvoice): if update: self.db_set('status', self.status, update_modified = update_modified) - + def set_pos_fields(self, for_validate=False): """Set retail related fields from POS Profiles""" from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile @@ -315,25 +315,25 @@ class POSInvoice(SalesInvoice): @frappe.whitelist() def get_stock_availability(item_code, warehouse): - latest_sle = frappe.db.sql("""select qty_after_transaction - from `tabStock Ledger Entry` + latest_sle = frappe.db.sql("""select qty_after_transaction + from `tabStock Ledger Entry` where item_code = %s and warehouse = %s order by posting_date desc, posting_time desc limit 1""", (item_code, warehouse), as_dict=1) - + pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item - where p.name = p_item.parent - and p.consolidated_invoice is NULL + where p.name = p_item.parent + and p.consolidated_invoice is NULL and p.docstatus = 1 and p_item.docstatus = 1 and p_item.item_code = %s and p_item.warehouse = %s """, (item_code, warehouse), as_dict=1) - + sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0 pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0 - + if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty: return sle_qty - pos_sales_qty else: @@ -360,14 +360,14 @@ def make_merge_log(invoices): merge_log = frappe.new_doc("POS Invoice Merge Log") merge_log.posting_date = getdate(nowdate()) for inv in invoices: - inv_data = frappe.db.get_values("POS Invoice", inv.get('name'), + inv_data = frappe.db.get_values("POS Invoice", inv.get('name'), ["customer", "posting_date", "grand_total"], as_dict=1)[0] merge_log.customer = inv_data.customer merge_log.append("pos_invoices", { 'pos_invoice': inv.get('name'), 'customer': inv_data.customer, 'posting_date': inv_data.posting_date, - 'grand_total': inv_data.grand_total + 'grand_total': inv_data.grand_total }) if merge_log.get('pos_invoices'): diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js index 24326b2256..30e0918ba6 100644 --- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js +++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js @@ -86,7 +86,7 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_container.append( `
    ` ) - + this.$summary_btns = this.$summary_container.find('.summary-btns'); } @@ -110,7 +110,10 @@ erpnext.PointOfSale.PastOrderSummary = class { {fieldname:'print', fieldtype:'Data', label:'Print Preview'} ], primary_action: () => { - this.events.get_frm().print_preview.printit(true); + const frm = this.events.get_frm(); + frm.doc = this.doc; + frm.print_preview.lang_code = frm.doc.language; + frm.print_preview.printit(true); }, primary_action_label: __('Print'), }); @@ -174,7 +177,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
    Tax Charges
    - ${ + ${ doc.taxes.map((t, i) => { let margin_left = ''; if (i !== 0) margin_left = 'ml-2'; @@ -271,6 +274,7 @@ erpnext.PointOfSale.PastOrderSummary = class { // this.print_dialog.show(); const frm = this.events.get_frm(); frm.doc = this.doc; + frm.print_preview.lang_code = frm.doc.language; frm.print_preview.printit(true); }); } @@ -284,9 +288,9 @@ erpnext.PointOfSale.PastOrderSummary = class { this.$summary_container.find('.print-btn').click(); }); } - + toggle_component(show) { - show ? + show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); } @@ -372,9 +376,9 @@ erpnext.PointOfSale.PastOrderSummary = class { } get_condition_btn_map(after_submission) { - if (after_submission) + if (after_submission) return [{ condition: true, visible_btns: ['Print Receipt', 'Email Receipt', 'New Order'] }]; - + return [ { condition: this.doc.docstatus === 0, visible_btns: ['Edit Order'] }, { condition: !this.doc.is_return && this.doc.docstatus === 1, visible_btns: ['Print Receipt', 'Email Receipt', 'Return']}, @@ -384,7 +388,7 @@ erpnext.PointOfSale.PastOrderSummary = class { load_summary_of(doc, after_submission=false) { this.$summary_wrapper.removeClass("d-none"); - + after_submission ? this.switch_to_post_submit_summary() : this.switch_to_recent_invoice_summary(); From 64ebbf0907986b19601ad9d65fd52b8db875ebf8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 21 Jul 2020 14:25:51 +0530 Subject: [PATCH 27/73] fix: Tax amounts in HSN Wise Outward summary --- .../hsn_wise_summary_of_outward_supplies.py | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 222dfa1eb7..25d18119af 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -7,6 +7,8 @@ from frappe import _ from frappe.utils import flt from frappe.model.meta import get_field_precision from frappe.utils.xlsxutils import handle_html +from six import iteritems +import json def execute(filters=None): return _execute(filters) @@ -21,21 +23,24 @@ def _execute(filters=None): itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) data = [] + added_item = [] for d in item_list: - row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] - total_tax = 0 - for tax in tax_columns: - item_tax = itemised_tax.get(d.name, {}).get(tax, {}) - total_tax += flt(item_tax.get("tax_amount")) + if (d.parent, d.item_code) not in added_item: + row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] + total_tax = 0 + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + total_tax += flt(item_tax.get("tax_amount", 0)) - row += [d.base_net_amount + total_tax] - row += [d.base_net_amount] + row += [d.base_net_amount + total_tax] + row += [d.base_net_amount] - for tax in tax_columns: - item_tax = itemised_tax.get(d.name, {}).get(tax, {}) - row += [item_tax.get("tax_amount", 0)] + for tax in tax_columns: + item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {}) + row += [item_tax.get("tax_amount", 0)] - data.append(row) + data.append(row) + added_item.append((d.parent, d.item_code)) if data: data = get_merged_data(columns, data) # merge same hsn code data return columns, data @@ -90,7 +95,7 @@ def get_conditions(filters): ("gst_hsn_code", " and gst_hsn_code=%(gst_hsn_code)s"), ("company_gstin", " and company_gstin=%(company_gstin)s"), ("from_date", " and posting_date >= %(from_date)s"), - ("to_date", "and posting_date <= %(to_date)s")): + ("to_date", " and posting_date <= %(to_date)s")): if filters.get(opts[0]): conditions += opts[1] @@ -103,7 +108,7 @@ def get_items(filters): match_conditions = " and {0} ".format(match_conditions) - return frappe.db.sql(""" + items = frappe.db.sql(""" select `tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate, `tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty, @@ -118,10 +123,10 @@ def get_items(filters): """ % (conditions, match_conditions), filters, as_dict=1) + return items + +def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): -def get_tax_accounts(item_list, columns, company_currency, - doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): - import json item_row_map = {} tax_columns = [] invoice_item_row = {} @@ -151,6 +156,7 @@ def get_tax_accounts(item_list, columns, company_currency, for parent, description, item_wise_tax_detail, tax_amount in tax_details: description = handle_html(description) + print(parent, description, item_wise_tax_detail, tax_amount) if description not in tax_columns and tax_amount: # as description is text editor earlier and markup can break the column convention in reports tax_columns.append(description) @@ -171,7 +177,7 @@ def get_tax_accounts(item_list, columns, company_currency, for d in item_row_map.get(parent, {}).get(item_code, []): item_tax_amount = tax_amount if item_tax_amount: - itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ + itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({ "tax_amount": flt(item_tax_amount, tax_amount_precision) }) except ValueError: @@ -179,42 +185,32 @@ def get_tax_accounts(item_list, columns, company_currency, tax_columns.sort() for desc in tax_columns: - columns.append(desc + " Amount:Currency/currency:160") + columns.append({ + "label": desc, + "fieldname": frappe.scrub(desc), + "fieldtype": "Float", + "width": 110 + }) - # columns += ["Total Amount:Currency/currency:110"] return itemised_tax, tax_columns def get_merged_data(columns, data): merged_hsn_dict = {} # to group same hsn under one key and perform row addition - add_column_index = [] # store index of columns that needs to be added - tax_col = len(get_columns()) - fields_to_merge = ["stock_qty", "total_amount", "taxable_amount"] # columns for which index needs to be found - - for i,d in enumerate(columns): - # check if fieldname in to_merge list and ignore tax-columns - if i < tax_col and d["fieldname"] in fields_to_merge: - add_column_index.append(i) + result = [] for row in data: - if row[0] in merged_hsn_dict: - to_add_row = merged_hsn_dict.get(row[0]) + merged_hsn_dict.setdefault(row[0], {}) + for i, d in enumerate(columns): + if d['fieldtype'] not in ('Int', 'Float', 'Currency'): + merged_hsn_dict[row[0]][d['fieldname']] = row[i] + else: + if merged_hsn_dict.get(row[0], {}).get(d['fieldname'], ''): + merged_hsn_dict[row[0]][d['fieldname']] += row[i] + else: + merged_hsn_dict[row[0]][d['fieldname']] = row[i] - # add columns from the add_column_index table - for k in add_column_index: - to_add_row[k] += row[k] + for key, value in iteritems(merged_hsn_dict): + result.append(value) - # add tax columns - for k in range(len(columns)): - if tax_col <= k < len(columns): - to_add_row[k] += row[k] - - # update hsn dict with the newly added data - merged_hsn_dict[row[0]] = to_add_row - else: - merged_hsn_dict[row[0]] = row - - # extract data rows to be displayed in report - data = [merged_hsn_dict[d] for d in merged_hsn_dict] - - return data + return result From b240cfe40118c7dfcbea834e29ef1f7d40d01d44 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 21 Jul 2020 14:54:21 +0530 Subject: [PATCH 28/73] fix: Remove print statements --- .../hsn_wise_summary_of_outward_supplies.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 25d18119af..a3ed4cebb1 100644 --- a/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/erpnext/regional/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -95,7 +95,7 @@ def get_conditions(filters): ("gst_hsn_code", " and gst_hsn_code=%(gst_hsn_code)s"), ("company_gstin", " and company_gstin=%(company_gstin)s"), ("from_date", " and posting_date >= %(from_date)s"), - ("to_date", " and posting_date <= %(to_date)s")): + ("to_date", "and posting_date <= %(to_date)s")): if filters.get(opts[0]): conditions += opts[1] @@ -126,7 +126,6 @@ def get_items(filters): return items def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): - item_row_map = {} tax_columns = [] invoice_item_row = {} @@ -156,7 +155,6 @@ def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoic for parent, description, item_wise_tax_detail, tax_amount in tax_details: description = handle_html(description) - print(parent, description, item_wise_tax_detail, tax_amount) if description not in tax_columns and tax_amount: # as description is text editor earlier and markup can break the column convention in reports tax_columns.append(description) From 182ee5e7c116fe70b6d4c9e52045b2c9a13729ac Mon Sep 17 00:00:00 2001 From: Diksha Jadhav Date: Tue, 18 Aug 2020 00:35:04 +0530 Subject: [PATCH 29/73] feat(queries): sort warehouses based on item quantity --- erpnext/controllers/queries.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 31e34987be..a14f4124e5 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -468,24 +468,18 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters): conditions, bin_conditions = [], [] filter_dict = get_doctype_wise_filters(filters) - sub_query = """ select round(`tabBin`.actual_qty, 2) from `tabBin` - where `tabBin`.warehouse = `tabWarehouse`.name - {bin_conditions} """.format( - bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"), - bin_conditions, ignore_permissions=True)) - query = """select `tabWarehouse`.name, - CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty - from `tabWarehouse` + CONCAT_WS(" : ", "Actual Qty", ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty + from `tabWarehouse` left join `tabBin` + on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions} where - `tabWarehouse`.`{key}` like {txt} + `tabWarehouse`.`{key}` like {txt} {fcond} {mcond} - order by - `tabWarehouse`.name desc + order by ifnull(`tabBin`.actual_qty, 0) desc limit {start}, {page_len} """.format( - sub_query=sub_query, + bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True), key=searchfield, fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions), mcond=get_match_cond(doctype), From 8aed48fe31b0de795b6d33fb2bf54f22f6e1c7ef Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 19 Aug 2020 18:30:18 +0530 Subject: [PATCH 30/73] fix: Do not update total for RCM invvoices if net taxes are zero --- erpnext/regional/india/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 2c81748c86..844e34b9ca 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -674,6 +674,9 @@ def update_grand_total_for_rcm(doc, method): if country != 'India': return + if not doc.total_taxes_and_charges: + return + if doc.reverse_charge == 'Y': gst_accounts = get_gst_accounts(doc.company) gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ @@ -721,7 +724,10 @@ def make_regional_gl_entries(gl_entries, doc): country = frappe.get_cached_value('Company', doc.company, 'country') if country != 'India': - return + return gl_entries + + if not doc.total_taxes_and_charges: + return gl_entries if doc.reverse_charge == 'Y': gst_accounts = get_gst_accounts(doc.company) From 2c26144a6b5f22e8c2f2fd9a2e8930e440ae76a1 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Wed, 19 Aug 2020 18:55:06 +0200 Subject: [PATCH 31/73] Update erpnext/patches/v13_0/add_standard_navbar_items.py --- erpnext/patches/v13_0/add_standard_navbar_items.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/add_standard_navbar_items.py b/erpnext/patches/v13_0/add_standard_navbar_items.py index 5de99a5abc..d05b258db0 100644 --- a/erpnext/patches/v13_0/add_standard_navbar_items.py +++ b/erpnext/patches/v13_0/add_standard_navbar_items.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals -import frappe +# import frappe from erpnext.setup.install import add_standard_navbar_items def execute(): # Add standard navbar items for ERPNext in Navbar Settings - add_standard_navbar_items() \ No newline at end of file + add_standard_navbar_items() From 019d4debb27e4ec3b46dbc251d480d7f9307b009 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 20 Aug 2020 15:53:58 +0530 Subject: [PATCH 32/73] fix: incorrect stock value in return case (#23102) --- .../purchase_invoice_item.json | 15 ++++++++++++--- .../sales_invoice_item/sales_invoice_item.json | 16 +++++++++++++--- erpnext/controllers/buying_controller.py | 16 +++++++++++++--- erpnext/controllers/sales_and_purchase_return.py | 3 +++ erpnext/controllers/selling_controller.py | 14 +++++++++++--- erpnext/controllers/stock_controller.py | 9 +++++++-- 6 files changed, 59 insertions(+), 14 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 52a5be0984..f6d76e5050 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-05-22 12:43:10", "doctype": "DocType", @@ -82,6 +81,7 @@ "item_tax_rate", "bom", "include_exploded_items", + "purchase_invoice_item", "col_break6", "purchase_order", "po_detail", @@ -769,12 +769,21 @@ "collapsible": 1, "fieldname": "col_break7", "fieldtype": "Column Break" + }, + { + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "purchase_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Purchase Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, - "links": [], - "modified": "2020-04-22 10:37:35.103176", + "modified": "2020-08-20 11:48:01.398356", "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 004d358ef9..fb3dd6a92a 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-06-04 11:02:19", "doctype": "DocType", @@ -87,6 +86,7 @@ "edit_references", "sales_order", "so_detail", + "sales_invoice_item", "column_break_74", "delivery_note", "dn_detail", @@ -790,12 +790,22 @@ "fieldtype": "Link", "label": "Project", "options": "Project" - } + }, + { + "depends_on": "eval:parent.update_stock == 1", + "fieldname": "sales_invoice_item", + "fieldtype": "Data", + "ignore_user_permissions": 1, + "label": "Sales Invoice Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-07-18 12:24:41.749986", + "modified": "2020-08-20 11:24:41.749986", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 89b48f07ee..f982700c01 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -559,9 +559,19 @@ class BuyingController(StockController): "serial_no": cstr(d.serial_no).strip() }) if self.is_return: - original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", - {"voucher_type": "Purchase Receipt", "voucher_no": self.return_against, - "item_code": d.item_code}, "incoming_rate") + filters = { + "voucher_type": self.doctype, + "voucher_no": self.return_against, + "item_code": d.item_code + } + + if (self.doctype == "Purchase Invoice" and self.update_stock + and d.get("purchase_invoice_item")): + filters["voucher_detail_no"] = d.purchase_invoice_item + elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"): + filters["voucher_detail_no"] = d.purchase_receipt_item + + original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate") sle.update({ "outgoing_rate": original_incoming_rate diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 3f127a201e..a03dee1174 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -281,6 +281,8 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail + target_doc.purchase_invoice_item = source_doc.name + elif doctype == "Delivery Note": target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_invoice = source_doc.against_sales_invoice @@ -296,6 +298,7 @@ def make_return_doc(doctype, source_name, target_doc=None): target_doc.so_detail = source_doc.so_detail target_doc.dn_detail = source_doc.dn_detail target_doc.expense_account = source_doc.expense_account + target_doc.sales_invoice_item = source_doc.name if default_warehouse_for_sales_return: target_doc.warehouse = default_warehouse_for_sales_return diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index b696ac39f6..17f3ae53e7 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -217,7 +217,9 @@ class SellingController(StockController): 'target_warehouse': p.target_warehouse, 'company': self.company, 'voucher_type': self.doctype, - 'allow_zero_valuation': d.allow_zero_valuation_rate + 'allow_zero_valuation': d.allow_zero_valuation_rate, + 'sales_invoice_item': d.get("sales_invoice_item"), + 'delivery_note_item': d.get("dn_detail") })) else: il.append(frappe._dict({ @@ -233,7 +235,9 @@ class SellingController(StockController): 'target_warehouse': d.target_warehouse, 'company': self.company, 'voucher_type': self.doctype, - 'allow_zero_valuation': d.allow_zero_valuation_rate + 'allow_zero_valuation': d.allow_zero_valuation_rate, + 'sales_invoice_item': d.get("sales_invoice_item"), + 'delivery_note_item': d.get("dn_detail") })) return il @@ -302,7 +306,11 @@ class SellingController(StockController): d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 return_rate = 0 if cint(self.is_return) and self.return_against and self.docstatus==1: - return_rate = self.get_incoming_rate_for_return(d.item_code, self.return_against) + against_document_no = (d.get("sales_invoice_item") + if self.doctype == "Sales Invoice" else d.get("delivery_note_item")) + + return_rate = self.get_incoming_rate_for_return(d.item_code, + self.return_against, against_document_no) # On cancellation or if return entry submission, make stock ledger entry for # target warehouse first, to update serial no values properly diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index e8483da544..394883d239 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -301,14 +301,19 @@ class StockController(AccountsController): return serialized_items - def get_incoming_rate_for_return(self, item_code, against_document): + def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None): incoming_rate = 0.0 + cond = '' if against_document and item_code: + if against_document_no: + cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no)) + incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty) from `tabStock Ledger Entry` where voucher_type = %s and voucher_no = %s - and item_code = %s limit 1""", + and item_code = %s {0} limit 1""".format(cond), (self.doctype, against_document, item_code)) + incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 return incoming_rate From afd2dd3570ef8db60a2d0bde01549cb356a326db Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 20 Aug 2020 16:31:38 +0530 Subject: [PATCH 33/73] fix: Unable to submit reverse charge invoice --- erpnext/regional/india/utils.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 844e34b9ca..69e47a43c4 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -726,9 +726,6 @@ def make_regional_gl_entries(gl_entries, doc): if country != 'India': return gl_entries - if not doc.total_taxes_and_charges: - return gl_entries - if doc.reverse_charge == 'Y': gst_accounts = get_gst_accounts(doc.company) gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ @@ -738,6 +735,7 @@ def make_regional_gl_entries(gl_entries, doc): if tax.category not in ("Total", "Valuation and Total"): continue + dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit" if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: account_currency = get_account_currency(tax.account_head) @@ -747,8 +745,8 @@ def make_regional_gl_entries(gl_entries, doc): "cost_center": tax.cost_center, "posting_date": doc.posting_date, "against": doc.supplier, - "credit": tax.base_tax_amount_after_discount_amount, - "credits_in_account_currency": tax.base_tax_amount_after_discount_amount \ + dr_or_cr: tax.base_tax_amount_after_discount_amount, + dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ if account_currency==doc.company_currency \ else tax.tax_amount_after_discount_amount }, account_currency, item=tax) From 4061186429b8bb1d88c8cc8211b0f5aeaa3e413e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 20 Aug 2020 18:01:20 +0530 Subject: [PATCH 34/73] fix: POS page link not visible on Desk --- erpnext/selling/desk_page/retail/retail.json | 29 ++++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/desk_page/retail/retail.json b/erpnext/selling/desk_page/retail/retail.json index 7b30af20cc..581e14cf81 100644 --- a/erpnext/selling/desk_page/retail/retail.json +++ b/erpnext/selling/desk_page/retail/retail.json @@ -3,7 +3,7 @@ { "hidden": 0, "label": "Retail Operations", - "links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point-of-Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"POS Profile\"\n ],\n \"description\": \"Point of Sale\",\n \"label\": \"POS\",\n \"name\": \"pos\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"description\": \"Cashier Closing\",\n \"label\": \"Cashier Closing\",\n \"name\": \"Cashier Closing\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup mode of POS (Online / Offline)\",\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point of Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"POS Profile\"\n ],\n \"description\": \"Point of Sale\",\n \"label\": \"Point of Sale\",\n \"name\": \"point-of-sale\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"description\": \"Setup mode of POS (Online / Offline)\",\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Cashier Closing\",\n \"label\": \"Cashier Closing\",\n \"name\": \"Cashier Closing\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]" } ], "category": "Domains", @@ -14,10 +14,11 @@ "docstatus": 0, "doctype": "Desk Page", "extends_another_page": 0, + "hide_custom": 0, "idx": 0, "is_standard": 1, "label": "Retail", - "modified": "2020-04-26 22:42:39.346750", + "modified": "2020-08-20 18:00:07.515691", "modified_by": "Administrator", "module": "Selling", "name": "Retail", @@ -25,5 +26,27 @@ "pin_to_bottom": 0, "pin_to_top": 0, "restrict_to_domain": "Retail", - "shortcuts": [] + "shortcuts": [ + { + "color": "#9deca2", + "doc_view": "", + "format": "{} Active", + "label": "Point of Sale Profile", + "link_to": "POS Profile", + "stats_filter": "{\n \"disabled\": 0\n}", + "type": "DocType" + }, + { + "doc_view": "", + "label": "Point of Sale", + "link_to": "point-of-sale", + "type": "Page" + }, + { + "doc_view": "", + "label": "POS Settings", + "link_to": "POS Settings", + "type": "DocType" + } + ] } \ No newline at end of file From c23797e350a2210fc61f1821445365be31ffaa37 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 20 Aug 2020 19:12:12 +0530 Subject: [PATCH 35/73] fix: Create Opportunity without Default Company from Email (#23097) * fix: Create Opoortunity without Default Company from Email * fix: Add Prompt to Select Company * Update communication.js Co-authored-by: Nabin Hait --- .../crm/doctype/opportunity/opportunity.py | 3 +- erpnext/public/js/communication.js | 45 +++++++++++++------ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index e152850f17..6096053136 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -325,7 +325,7 @@ def auto_close_opportunity(): doc.save() @frappe.whitelist() -def make_opportunity_from_communication(communication, ignore_communication_links=False): +def make_opportunity_from_communication(communication, company, ignore_communication_links=False): from erpnext.crm.doctype.lead.lead import make_lead_from_communication doc = frappe.get_doc("Communication", communication) @@ -337,6 +337,7 @@ def make_opportunity_from_communication(communication, ignore_communication_link opportunity = frappe.get_doc({ "doctype": "Opportunity", + "company": company, "opportunity_from": opportunity_from, "party_name": lead }).insert(ignore_permissions=True) diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js index 9432d42175..26e5ab8b32 100644 --- a/erpnext/public/js/communication.js +++ b/erpnext/public/js/communication.js @@ -7,7 +7,7 @@ frappe.ui.form.on("Communication", { }, setup_custom_buttons: (frm) => { - let confirm_msg = "Are you sure you want to create {0} from this email"; + let confirm_msg = "Are you sure you want to create {0} from this email?"; if(frm.doc.reference_doctype !== "Issue") { frm.add_custom_button(__("Issue"), () => { frappe.confirm(__(confirm_msg, [__("Issue")]), () => { @@ -62,17 +62,36 @@ frappe.ui.form.on("Communication", { }, make_opportunity_from_communication: (frm) => { - return frappe.call({ - method: "erpnext.crm.doctype.opportunity.opportunity.make_opportunity_from_communication", - args: { - communication: frm.doc.name - }, - freeze: true, - callback: (r) => { - if(r.message) { - frm.reload_doc() + const fields = [{ + fieldtype: 'Link', + label: __('Select a Company'), + fieldname: 'company', + options: 'Company', + reqd: 1, + default: frappe.defaults.get_user_default("Company") + }]; + + frappe.prompt(fields, data => { + frappe.call({ + method: "erpnext.crm.doctype.opportunity.opportunity.make_opportunity_from_communication", + args: { + communication: frm.doc.name, + company: data.company + }, + freeze: true, + callback: (r) => { + if(r.message) { + frm.reload_doc(); + frappe.show_alert({ + message: __("Opportunity {0} created", + ['' + r.message + '']), + indicator: 'green' + }); + } } - } - }) + }); + }, + 'Create an Opportunity', + 'Create'); } -}); \ No newline at end of file +}); From 1ba62c9732cd4d606b5295a2cbf094e65e7b5e1f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 20 Aug 2020 19:39:10 +0530 Subject: [PATCH 36/73] fix:Validate Job offer against vacancies (#23109) --- erpnext/hr/doctype/job_offer/job_offer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py index 3d68bc8d8e..c397a3f5ca 100644 --- a/erpnext/hr/doctype/job_offer/job_offer.py +++ b/erpnext/hr/doctype/job_offer/job_offer.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils import cint from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc from frappe import _ @@ -24,8 +25,7 @@ class JobOffer(Document): check_vacancies = frappe.get_single("HR Settings").check_vacancies if staffing_plan and check_vacancies: job_offers = self.get_job_offer(staffing_plan.from_date, staffing_plan.to_date) - - if not staffing_plan.get("vacancies") or staffing_plan.vacancies - len(job_offers) <= 0: + if not staffing_plan.get("vacancies") or cint(staffing_plan.vacancies) - len(job_offers) <= 0: error_variable = 'for ' + frappe.bold(self.designation) if staffing_plan.get("parent"): error_variable = frappe.bold(get_link_to_form("Staffing Plan", staffing_plan.parent)) @@ -65,7 +65,7 @@ def get_staffing_plan_detail(designation, company, offer_date): AND %s between sp.from_date and sp.to_date """, (designation, company, offer_date), as_dict=1) - return frappe._dict(detail[0]) if detail else None + return frappe._dict(detail[0]) if (detail and detail[0].parent) else None @frappe.whitelist() def make_employee(source_name, target_doc=None): From 111183d0804c6740089547d29eea4a870ffbdd42 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 22 Aug 2020 12:31:06 +0530 Subject: [PATCH 37/73] fix: Installation failed due to global variable (#23114) --- .../consolidated_financial_statement.py | 2 +- .../accounts/report/financial_statements.py | 2 +- .../report/general_ledger/general_ledger.py | 2 +- erpnext/accounts/report/utils.py | 22 +++++-------------- 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index c2c7207e37..219871b1d6 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -378,7 +378,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g if filters and filters.get('presentation_currency') != d.default_currency: currency_info['company'] = d.name currency_info['company_currency'] = d.default_currency - convert_to_presentation_currency(gl_entries, currency_info) + convert_to_presentation_currency(gl_entries, currency_info, filters.get('company')) for entry in gl_entries: key = entry.account_number or entry.account_name diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index d5b8cdb1d4..1b65a318b6 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -423,7 +423,7 @@ def set_gl_entries_by_account( distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec if filters and filters.get('presentation_currency'): - convert_to_presentation_currency(gl_entries, get_currency(filters)) + convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company')) for entry in gl_entries: gl_entries_by_account.setdefault(entry.account, []).append(entry) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index fcd36e4e6e..0a72f6a450 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -180,7 +180,7 @@ def get_gl_entries(filters): filters, as_dict=1) if filters.get('presentation_currency'): - return convert_to_presentation_currency(gl_entries, currency_map) + return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company')) else: return gl_entries diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py index 4a9af490cf..9de8d19f2a 100644 --- a/erpnext/accounts/report/utils.py +++ b/erpnext/accounts/report/utils.py @@ -6,10 +6,6 @@ from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_dat from frappe.utils import cint, get_datetime_str, formatdate, flt __exchange_rates = {} -P_OR_L_ACCOUNTS = list( - sum(frappe.get_list('Account', fields=['name'], or_filters=[{'root_type': 'Income'}, {'root_type': 'Expense'}], as_list=True), ()) -) - def get_currency(filters): """ @@ -73,18 +69,7 @@ def get_rate_as_at(date, from_currency, to_currency): return rate - -def is_p_or_l_account(account_name): - """ - Check if the given `account name` is an `Account` with `root_type` of either 'Income' - or 'Expense'. - :param account_name: - :return: Boolean - """ - return account_name in P_OR_L_ACCOUNTS - - -def convert_to_presentation_currency(gl_entries, currency_info): +def convert_to_presentation_currency(gl_entries, currency_info, company): """ Take a list of GL Entries and change the 'debit' and 'credit' values to currencies in `currency_info`. @@ -96,6 +81,9 @@ def convert_to_presentation_currency(gl_entries, currency_info): presentation_currency = currency_info['presentation_currency'] company_currency = currency_info['company_currency'] + pl_accounts = [d.name for d in frappe.get_list('Account', + filters={'report_type': 'Profit and Loss', 'company': company})] + for entry in gl_entries: account = entry['account'] debit = flt(entry['debit']) @@ -107,7 +95,7 @@ def convert_to_presentation_currency(gl_entries, currency_info): if account_currency != presentation_currency: value = debit or credit - date = currency_info['report_date'] if not is_p_or_l_account(account) else entry['posting_date'] + date = entry['posting_date'] if account in pl_accounts else currency_info['report_date'] converted_value = convert(value, presentation_currency, company_currency, date) if entry.get('debit'): From df175cc8fc481aa73f4d01ed471d5f77f2f8718e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 22 Aug 2020 19:17:18 +0530 Subject: [PATCH 38/73] fix: GLE for subcontracted PR is fg item rate is zero --- .../purchase_receipt/purchase_receipt.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d0ba001d7e..4e173fff4d 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -227,6 +227,14 @@ class PurchaseReceipt(BuyingController): if not stock_value_diff: continue + # If PR is sub-contracted and fg item rate is zero + # in that case if account for shource and target warehouse are same, + # then GL entries should not be posted + if flt(stock_value_diff) == flt(d.rm_supp_cost) \ + and warehouse_account.get(self.supplier_warehouse) \ + and warehouse_account[d.warehouse]["account"] == warehouse_account[self.supplier_warehouse]["account"]: + continue + gl_entries.append(self.get_gl_dict({ "account": warehouse_account[d.warehouse]["account"], "against": stock_rbnb, @@ -242,16 +250,16 @@ class PurchaseReceipt(BuyingController): credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \ if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount")) - - gl_entries.append(self.get_gl_dict({ - "account": warehouse_account[d.from_warehouse]['account'] \ - if d.from_warehouse else stock_rbnb, - "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, - "remarks": self.get("remarks") or _("Accounting Entry for Stock"), - "debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")), - "debit_in_account_currency": -1 * credit_amount - }, credit_currency, item=d)) + if credit_amount: + gl_entries.append(self.get_gl_dict({ + "account": warehouse_account[d.from_warehouse]['account'] \ + if d.from_warehouse else stock_rbnb, + "against": warehouse_account[d.warehouse]["account"], + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Stock"), + "debit": -1 * flt(d.base_net_amount, d.precision("base_net_amount")), + "debit_in_account_currency": -1 * credit_amount + }, credit_currency, item=d)) negative_expense_to_be_booked += flt(d.item_tax_amount) From 337ee0a4bf5419f43700468ac39f8966fc512f77 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Sat, 22 Aug 2020 20:02:27 +0530 Subject: [PATCH 39/73] test: Test case for GLE in subcontracted PR if FG item rate is zero --- .../purchase_receipt/test_purchase_receipt.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d97b9e82c3..2a094a01de 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -121,6 +121,22 @@ class TestPurchaseReceipt(unittest.TestCase): rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")]) self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2)) + def test_subcontracting_gle_fg_item_rate_zero(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + set_perpetual_inventory() + frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM") + make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1", qty=100, basic_rate=100, company="_Test Company with perpetual inventory") + make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1", + qty=100, basic_rate=100, company="_Test Company with perpetual inventory") + pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes", + company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', supplier_warehouse='Work In Progress - TCP1') + + gl_entries = get_gl_entries("Purchase Receipt", pr.name) + + self.assertFalse(gl_entries) + + set_perpetual_inventory(0) + def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"), @@ -688,7 +704,7 @@ def make_purchase_receipt(**args): "received_qty": received_qty, "rejected_qty": rejected_qty, "rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "", - "rate": args.rate or 50, + "rate": args.rate if args.rate != None else 50, "conversion_factor": args.conversion_factor or 1.0, "serial_no": args.serial_no, "stock_uom": args.stock_uom or "_Test UOM", From cb0bc2dcafe2dde05ff765a302c68fa761670dc5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Aug 2020 20:43:50 +0530 Subject: [PATCH 40/73] fix: Negative SLE not created for fraction qty or qty less than 1 --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index e1b3730f2f..f4490f1b01 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -31,7 +31,7 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc sle['posting_time'] = now_datetime().strftime('%H:%M:%S.%f') if cancel: - sle['actual_qty'] = -flt(sle.get('actual_qty'), 0) + sle['actual_qty'] = -flt(sle.get('actual_qty')) if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'): sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code, From 8331a38acee2e1c7101120f6e31df0d21e8aacde Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 23 Aug 2020 21:23:26 +0530 Subject: [PATCH 41/73] fix: Test Case for qty less than 1 --- .../purchase_receipt/test_purchase_receipt.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d97b9e82c3..52e5e5568e 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -18,6 +18,28 @@ class TestPurchaseReceipt(unittest.TestCase): set_perpetual_inventory(0) frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) + 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) + + sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", + "voucher_no": pr.name}, ['actual_qty']) + + self.assertEqual(len(sl_entry), 1) + self.assertEqual(sl_entry[0].actual_qty, 0.5) + + pr.cancel() + + sl_entry_cancelled = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt", + "voucher_no": pr.name}, ['actual_qty'], order_by='creation') + + 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): pr = make_purchase_receipt(do_not_save=True) self.assertRaises(frappe.ValidationError, make_purchase_invoice, pr.name) From 64e84643fb82fb2e4509e661bade43ab929fd80f Mon Sep 17 00:00:00 2001 From: Anupam K Date: Sun, 23 Aug 2020 17:15:40 +0530 Subject: [PATCH 42/73] lost opportunity --- .../crm/report/lead_details/lead_details.py | 11 +-- .../lost_opportunity/lost_opportunity.js | 26 +++++++ .../lost_opportunity/lost_opportunity.py | 71 ++++++++++++++----- 3 files changed, 84 insertions(+), 24 deletions(-) diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py index 9f4ae60679..eeaaec2bce 100644 --- a/erpnext/crm/report/lead_details/lead_details.py +++ b/erpnext/crm/report/lead_details/lead_details.py @@ -140,18 +140,19 @@ def get_data(filters): `tabAddress`.name=`tabDynamic Link`.parent) WHERE company = %(company)s - AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s + AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s {conditions} ORDER BY `tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) def get_conditions(filters) : - conditions = "" + conditions = [] if filters.get("territory"): - conditions+=" and territory=%(territory)s " + conditions.append(" and `tabLead`.territory=%(territory)s") if filters.get("status"): - conditions+=" and status=%(status)s " + conditions.append(" and `tabLead`.status=%(status)s") + + return " ".join(conditions) if conditions else "" - return conditions diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.js b/erpnext/crm/report/lost_opportunity/lost_opportunity.js index c6bf888da3..d79f8c8480 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.js +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.js @@ -12,6 +12,32 @@ frappe.query_reports["Lost Opportunity"] = { "default": frappe.defaults.get_user_default("Company"), "reqd": 1 }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -12), + "reqd": 1 + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1 + }, + { + "fieldname":"lost_reason", + "label": __("Lost Reason"), + "fieldtype": "Link", + "options": "Opportunity Lost Reason" + }, + { + "fieldname":"territory", + "label": __("Territory"), + "fieldtype": "Link", + "options": "Territory" + }, { "fieldname":"opportunity_from", "label": __("Opportunity From"), diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py index 094fa961f7..1aa4afe186 100644 --- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py +++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py @@ -44,18 +44,32 @@ def get_columns(): "fieldtype": "Data", "width": 130 }, - { - "label": _("Next Contact By"), - "fieldname": "contact_by", - "fieldtype": "Link", - "options": "User", - "width": 120 - }, { "label": _("Lost Reasons"), "fieldname": "lost_reason", "fieldtype": "Data", "width": 220 + }, + { + "label": _("Sales Stage"), + "fieldname": "sales_stage", + "fieldtype": "Link", + "options": "Sales Stage", + "width": 150 + }, + { + "label": _("Territory"), + "fieldname": "territory", + "fieldtype": "Link", + "options": "Territory", + "width": 150 + }, + { + "label": _("Next Contact By"), + "fieldname": "contact_by", + "fieldtype": "Link", + "options": "User", + "width": 150 } ] return columns @@ -69,30 +83,49 @@ def get_data(filters): `tabOpportunity`.customer_name, `tabOpportunity`.opportunity_type, `tabOpportunity`.contact_by, - GROUP_CONCAT(`tabLost Reason Detail`.lost_reason separator ', ') lost_reason + GROUP_CONCAT(`tabOpportunity Lost Reason Detail`.lost_reason separator ', ') lost_reason, + `tabOpportunity`.sales_stage, + `tabOpportunity`.territory FROM - `tabOpportunity` - LEFT JOIN `tabLost Reason Detail` - ON `tabLost Reason Detail`.parenttype = 'Opportunity' and `tabLost Reason Detail`.parent = `tabOpportunity`.name + `tabOpportunity` + {join} WHERE `tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s - {conditions} + AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s + {conditions} GROUP BY - `tabOpportunity`.name + `tabOpportunity`.name ORDER BY - `tabOpportunity`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1) + `tabOpportunity`.creation asc """.format(conditions=get_conditions(filters), join=get_join(filters)), filters, as_dict=1) -def get_conditions(filters) : +def get_conditions(filters): conditions = [] + if filters.get("territory"): + conditions.append(" and `tabOpportunity`.territory=%(territory)s") + if filters.get("opportunity_from"): - conditions.append("opportunity_from=%(opportunity_from)s") + conditions.append(" and `tabOpportunity`.opportunity_from=%(opportunity_from)s") if filters.get("party_name"): - conditions.append("party_name=%(party_name)s") + conditions.append(" and `tabOpportunity`.party_name=%(party_name)s") if filters.get("contact_by"): - conditions.append("contact_by=%(contact_by)s") + conditions.append(" and `tabOpportunity`.contact_by=%(contact_by)s") - return " and {}".format(" and ".join(conditions)) if conditions else "" + return " ".join(conditions) if conditions else "" + +def get_join(filters): + join = """LEFT JOIN `tabOpportunity Lost Reason Detail` + ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and + `tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name""" + + if filters.get("lost_reason"): + join = """JOIN `tabOpportunity Lost Reason Detail` + ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and + `tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and + `tabOpportunity Lost Reason Detail`.lost_reason = '{0}' + """.format(filters.get("lost_reason")) + + return join \ No newline at end of file From 7556517a4fde103476cc39e0d32289595e56271e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 24 Aug 2020 14:07:09 +0000 Subject: [PATCH 43/73] feat: validate if removed item attributes exist in variants (#22911) * feat: validate if removed item attributes exist in variants * Update erpnext/stock/doctype/item/item.py Co-authored-by: Marica * Update erpnext/stock/doctype/item/item.py Co-authored-by: Marica * refactor: smaller loop Co-authored-by: marination * feat: don't run validation for new entries Co-authored-by: Marica * fix: use tuple as is Co-authored-by: Marica * refactor: error description Co-authored-by: Marica * refactor: remove unused variable Co-authored-by: Marica --- erpnext/stock/doctype/item/item.py | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index d209f48353..d22fda85f4 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -111,6 +111,7 @@ class Item(WebsiteGenerator): self.synced_with_hub = 0 self.validate_has_variants() + self.validate_attributes_in_variants() self.validate_stock_exists_for_template_item() self.validate_attributes() self.validate_variant_attributes() @@ -806,6 +807,77 @@ class Item(WebsiteGenerator): if frappe.db.exists("Item", {"variant_of": self.name}): frappe.throw(_("Item has variants.")) + def validate_attributes_in_variants(self): + if not self.has_variants or self.get("__islocal"): + return + + old_doc = self.get_doc_before_save() + old_doc_attributes = set([attr.attribute for attr in old_doc.attributes]) + own_attributes = [attr.attribute for attr in self.attributes] + + # Check if old attributes were removed from the list + # Is old_attrs is a subset of new ones + # that means we need not check any changes + if old_doc_attributes.issubset(set(own_attributes)): + return + + from collections import defaultdict + + # get all item variants + items = [item["name"] for item in frappe.get_all("Item", {"variant_of": self.name})] + + # get all deleted attributes + deleted_attribute = list(old_doc_attributes.difference(set(own_attributes))) + + # fetch all attributes of these items + item_attributes = frappe.get_all( + "Item Variant Attribute", + filters={ + "parent": ["in", items], + "attribute": ["in", deleted_attribute] + }, + fields=["attribute", "parent"] + ) + not_included = defaultdict(list) + + for attr in item_attributes: + if attr["attribute"] not in own_attributes: + not_included[attr["parent"]].append(attr["attribute"]) + + if not len(not_included): + return + + def body(docnames): + docnames.sort() + return "
    ".join(docnames) + + def table_row(title, body): + return """ + {0} + {1} + """.format(title, body) + + rows = '' + for docname, attr_list in not_included.items(): + link = "{0}".format(frappe.bold(_(docname))) + rows += table_row(link, body(attr_list)) + + error_description = _('The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template.') + + message = """ +
    {0}

    + + + + + + {3} +
    {1}{2}
    + """.format(error_description, _('Variant Items'), _('Attributes'), rows) + + frappe.throw(message, title=_("Variant Attribute Error"), is_minimizable=True, wide=True) + + def validate_stock_exists_for_template_item(self): if self.stock_ledger_created() and self._doc_before_save: if (cint(self._doc_before_save.has_variants) != cint(self.has_variants) From ff5d19609fc89a56ea70bb219dea74b3ec5729a9 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 11:59:57 +0530 Subject: [PATCH 44/73] fix(hot): Pricing Rule encoding fixed --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 11 ++++------- erpnext/accounts/doctype/pricing_rule/utils.py | 11 ++++++++--- erpnext/controllers/accounts_controller.py | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index cff7d5ba22..a2e35bc7c4 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -1,5 +1,4 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt # For license information, please see license.txt @@ -9,6 +8,8 @@ import json import copy from frappe import throw, _ from frappe.utils import flt, cint, getdate +from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, + get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) from frappe.model.document import Document @@ -207,9 +208,6 @@ def get_serial_no_for_item(args): return item_details def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): - from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, - get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) - if isinstance(doc, string_types): doc = json.loads(doc) @@ -237,7 +235,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa update_args_for_pricing_rule(args) - pricing_rules = (get_applied_pricing_rules(args) + pricing_rules = (get_applied_pricing_rules(args.get('pricing_rules')) if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc)) if pricing_rules: @@ -365,8 +363,7 @@ def set_discount_amount(rate, item_details): item_details.rate = rate def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): - from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items - for d in json.loads(pricing_rules): + for d in get_applied_pricing_rules(pricing_rules): if not d or not frappe.db.exists("Pricing Rule", d): continue pricing_rule = frappe.get_cached_doc('Pricing Rule', d) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 3fd316f75e..53b0cf7bba 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -447,9 +447,14 @@ def apply_pricing_rule_on_transaction(doc): apply_pricing_rule_for_free_items(doc, item_details.free_item_data) doc.set_missing_values() -def get_applied_pricing_rules(item_row): - return (json.loads(item_row.get("pricing_rules")) - if item_row.get("pricing_rules") else []) +def get_applied_pricing_rules(pricing_rules): + if pricing_rules: + if pricing_rules.startswith('['): + return json.loads(pricing_rules) + else: + return pricing_rules.split(',') + + return [] def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): free_item = pricing_rule.free_item diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 3091193b8d..d61e44b53d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -325,7 +325,7 @@ class AccountsController(TransactionBase): apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data')) elif pricing_rule_args.get("validate_applied_rule"): - for pricing_rule in get_applied_pricing_rules(item): + for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')): pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule) for field in ['discount_percentage', 'discount_amount', 'rate']: if item.get(field) < pricing_rule_doc.get(field): From 8ad0fa48bf068302e4582c018e82dc752e13a565 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 12:24:31 +0530 Subject: [PATCH 45/73] fix(minor): circular imports? --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index a2e35bc7c4..f4e5ec69dc 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -8,8 +8,6 @@ import json import copy from frappe import throw, _ from frappe.utils import flt, cint, getdate -from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, - get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) from frappe.model.document import Document @@ -208,6 +206,9 @@ def get_serial_no_for_item(args): return item_details def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): + from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, + get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) + if isinstance(doc, string_types): doc = json.loads(doc) @@ -363,6 +364,7 @@ def set_discount_amount(rate, item_details): item_details.rate = rate def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): + from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules for d in get_applied_pricing_rules(pricing_rules): if not d or not frappe.db.exists("Pricing Rule", d): continue pricing_rule = frappe.get_cached_doc('Pricing Rule', d) From b5b8f9e72d1b9bb0375c293bf71d9add533a7a22 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 14:14:23 +0530 Subject: [PATCH 46/73] fix(minor): import --- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index f4e5ec69dc..aa6194cbc3 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -364,7 +364,8 @@ def set_discount_amount(rate, item_details): item_details.rate = rate def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): - from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules + from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules, + get_pricing_rule_items) for d in get_applied_pricing_rules(pricing_rules): if not d or not frappe.db.exists("Pricing Rule", d): continue pricing_rule = frappe.get_cached_doc('Pricing Rule', d) From ee921052c0025a88a6d7f2e4030bb5c4e874d606 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 14:35:49 +0530 Subject: [PATCH 47/73] fix(minor): test dependencies --- erpnext/accounts/doctype/coupon_code/test_coupon_code.py | 3 ++- erpnext/accounts/doctype/pos_profile/test_pos_profile.py | 4 +++- .../doctype/clinical_procedure/test_clinical_procedure.py | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py index 3a0d4162ae..340b9dd58a 100644 --- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py +++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py @@ -9,6 +9,8 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde from erpnext.stock.get_item_details import get_item_details from frappe.test_runner import make_test_objects +test_dependencies = ['Item'] + def test_create_test_data(): frappe.set_user("Administrator") # create test item @@ -95,7 +97,6 @@ def test_create_test_data(): }) coupon_code.insert() - class TestCouponCode(unittest.TestCase): def setUp(self): test_create_test_data() diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 8a4050cf9e..edf86590c8 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -8,6 +8,8 @@ import unittest from erpnext.stock.get_item_details import get_pos_profile from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes +test_dependencies = ['Item'] + class TestPOSProfile(unittest.TestCase): def test_pos_profile(self): make_pos_profile() @@ -88,7 +90,7 @@ def make_pos_profile(**args): "write_off_account": args.write_off_account or "_Test Write Off - _TC", "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC" }) - + payments = [{ 'mode_of_payment': 'Cash', 'default': 1 diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py index 207351ff20..4ee5f6bad3 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py @@ -7,6 +7,8 @@ import unittest import frappe from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_clinical_procedure_template +test_dependencies = ['Item'] + class TestClinicalProcedure(unittest.TestCase): def test_procedure_template_item(self): patient, medical_department, practitioner = create_healthcare_docs() From 42092cc3776ff8d36ce39607555bb8b2bac5911d Mon Sep 17 00:00:00 2001 From: Anupam K Date: Tue, 25 Aug 2020 15:29:18 +0530 Subject: [PATCH 48/73] fix: Homepage Featured Product image fetch issue --- erpnext/portal/doctype/homepage/homepage.js | 28 -- .../homepage_featured_product.json | 383 +++++------------- 2 files changed, 99 insertions(+), 312 deletions(-) diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js index ca34d69576..c7c66e0055 100644 --- a/erpnext/portal/doctype/homepage/homepage.js +++ b/erpnext/portal/doctype/homepage/homepage.js @@ -21,34 +21,6 @@ frappe.ui.form.on('Homepage', { }); frappe.ui.form.on('Homepage Featured Product', { - item_code: function(frm, cdt, cdn) { - var featured_product = frappe.model.get_doc(cdt, cdn); - if (featured_product.item_code) { - frappe.call({ - method: 'frappe.client.get_value', - args: { - 'doctype': 'Item', - 'filters': {'name': featured_product.item_code}, - 'fieldname': [ - 'item_name', - 'web_long_description', - 'description', - 'image', - 'thumbnail' - ] - }, - callback: function(r) { - if (!r.exc) { - $.extend(featured_product, r.message); - if (r.message.web_long_description) { - featured_product.description = r.message.web_long_description; - } - frm.refresh_field('products'); - } - } - }); - } - }, view: function(frm, cdt, cdn){ var child= locals[cdt][cdn] diff --git a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json index c8b4ae9b74..01c32efec9 100644 --- a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json +++ b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json @@ -1,301 +1,116 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "hash", - "beta": 0, - "creation": "2016-04-22 05:57:06.261401", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, + "actions": [], + "autoname": "hash", + "creation": "2016-04-22 05:57:06.261401", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "col_break1", + "item_name", + "view", + "section_break_5", + "description", + "column_break_7", + "image", + "thumbnail", + "route" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "unique": 0, + "bold": 1, + "fieldname": "item_code", + "fieldtype": "Link", + "in_filter": 1, + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "print_width": "150px", + "reqd": 1, + "search_index": 1, "width": "150px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 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, - "precision": "", - "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, - "fieldname": "item_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Item Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150", - "read_only": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "150", + "read_only": 1, + "reqd": 1, "width": "150" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "view", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "View", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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": "view", + "fieldtype": "Button", + "in_list_view": 1, + "label": "View" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "collapsible": 1, + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Description" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Small Text", - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fetch_from": "item_code.web_long_description", + "fieldname": "description", + "fieldtype": "Text Editor", + "in_filter": 1, + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Small Text", + "print_width": "300px", "width": "300px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "column_break_7", - "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, - "precision": "", - "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": "column_break_7", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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 - }, + "fetch_from": "item_code.website_image", + "fetch_if_empty": 1, + "fieldname": "image", + "fieldtype": "Attach Image", + "label": "Image" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "thumbnail", - "fieldtype": "Attach Image", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Thumbnail", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "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": "thumbnail", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Thumbnail" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "route", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "route", - "length": 0, - "no_copy": 0, - "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 + "fieldname": "route", + "fieldtype": "Small Text", + "label": "route", + "read_only": 1 } - ], - "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": "2016-08-09 06:09:34.731971", - "modified_by": "Administrator", - "module": "Portal", - "name": "Homepage Featured Product", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-08-25 15:27:49.573537", + "modified_by": "Administrator", + "module": "Portal", + "name": "Homepage Featured Product", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From d43d1408cfcd674107613d1f1f1310cd6b1f7a57 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 25 Aug 2020 17:13:05 +0530 Subject: [PATCH 49/73] fix: conversion factor for BOM exploded item rate --- erpnext/manufacturing/doctype/bom/bom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c51f655a66..3189433837 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -494,7 +494,7 @@ class BOM(WebsiteGenerator): 'image' : d.image, 'stock_uom' : d.stock_uom, 'stock_qty' : flt(d.stock_qty), - 'rate' : flt(d.base_rate) / flt(d.conversion_factor), + 'rate' : flt(d.base_rate) / (flt(d.conversion_factor) or 1.0), 'include_item_in_manufacturing': d.include_item_in_manufacturing })) From 5bf0a51ed4c26ebbd7221c4d33a1eba6204f3fc7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 25 Aug 2020 18:22:06 +0530 Subject: [PATCH 50/73] feat: add Maintenance Visit link in Customer dashboard --- erpnext/selling/doctype/customer/customer_dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index 09e474dc2e..cf234650c8 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -33,7 +33,7 @@ def get_data(): }, { 'label': _('Support'), - 'items': ['Issue'] + 'items': ['Issue', 'Maintenance Visit'] }, { 'label': _('Projects'), From 12f7aeb14746413f721580a7e5737f0e1977d535 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 25 Aug 2020 19:03:35 +0530 Subject: [PATCH 51/73] feat: add Maintenance Visit link in Sales Order dashboard --- erpnext/selling/doctype/sales_order/sales_order_dashboard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py index 4126bc6a70..05a760de27 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py +++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py @@ -10,6 +10,7 @@ def get_data(): 'Payment Entry': 'reference_name', 'Payment Request': 'reference_name', 'Auto Repeat': 'reference_document', + 'Maintenance Visit': 'prevdoc_docname' }, 'internal_links': { 'Quotation': ['items', 'prevdoc_docname'] @@ -17,7 +18,7 @@ def get_data(): 'transactions': [ { 'label': _('Fulfillment'), - 'items': ['Sales Invoice', 'Pick List', 'Delivery Note'] + 'items': ['Sales Invoice', 'Pick List', 'Delivery Note', 'Maintenance Visit'] }, { 'label': _('Purchasing'), From 258b256dc646aee1757bf0c34b934b688d8272ca Mon Sep 17 00:00:00 2001 From: Michelle Alva <50285544+michellealva@users.noreply.github.com> Date: Tue, 25 Aug 2020 20:37:39 +0530 Subject: [PATCH 52/73] fix: Add Delivery Note link in Sales Invoice Dashboard (#23161) --- .../accounts/doctype/sales_invoice/sales_invoice_dashboard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index f1069282ed..2980213f3b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -13,7 +13,8 @@ def get_data(): 'Auto Repeat': 'reference_document', }, 'internal_links': { - 'Sales Order': ['items', 'sales_order'] + 'Sales Order': ['items', 'sales_order'], + 'Delivery Note': ['items', 'delivery_note'] }, 'transactions': [ { From 91461a0016a4e70b8472494e8aa1a51e6ef3b19f Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 25 Aug 2020 21:01:17 +0530 Subject: [PATCH 53/73] fix: heading is hidden in the mobile view (#23145) --- erpnext/support/doctype/issue/issue.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 9e15757ce0..858564a527 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -209,11 +209,11 @@ function set_time_to_resolve_and_response(frm) { frm.dashboard.set_headline_alert( '
    ' + - '
    ' + - ' ' + + '
    ' + + 'Time to Respond: '+ time_to_respond.diff_display +' ' + '
    ' + - '
    ' + - ' ' + + '
    ' + + 'Time to Resolve: '+ time_to_resolve.diff_display +' ' + '
    ' + '
    ' ); From 7a927cefbd050990d8b72f93be0bf0a138e3ec78 Mon Sep 17 00:00:00 2001 From: Michelle Alva <50285544+michellealva@users.noreply.github.com> Date: Wed, 26 Aug 2020 10:32:26 +0530 Subject: [PATCH 54/73] fix: Social Media Status hidden in Mobie View (#23158) --- erpnext/crm/doctype/social_media_post/social_media_post.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js index 3a14f2d2e9..0ce8b44e19 100644 --- a/erpnext/crm/doctype/social_media_post/social_media_post.js +++ b/erpnext/crm/doctype/social_media_post/social_media_post.js @@ -30,14 +30,14 @@ frappe.ui.form.on('Social Media Post', { let color = frm.doc.twitter_post_id ? "green" : "red"; let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted"; html += `
    - + Twitter : ${status}
    ` ; } if (frm.doc.linkedin){ let color = frm.doc.linkedin_post_id ? "green" : "red"; let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted"; html += `
    - + LinkedIn : ${status}
    ` ; } html = `
    ${html}
    `; From d888a59bd1684890b4db52e9c470cc93f7da78a7 Mon Sep 17 00:00:00 2001 From: bhavesh95863 <34086262+bhavesh95863@users.noreply.github.com> Date: Wed, 26 Aug 2020 12:44:20 +0530 Subject: [PATCH 55/73] fix: set company filter in condition --- .../hr/report/employee_leave_balance/employee_leave_balance.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index db1d191758..1b92358184 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -132,6 +132,9 @@ def get_conditions(filters): if filters.get('employee'): conditions['name'] = filters.get('employee') + if filters.get('company'): + conditions['company'] = filters.get('company') + return conditions def get_department_leave_approver_map(department=None): From 38fad588340e30a11137a123330cb03f64fad887 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 16:30:09 +0530 Subject: [PATCH 56/73] fix(patch) --- erpnext/patches/v12_0/create_irs_1099_field_united_states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py index 43bd0ccdd7..bce4ab10b3 100644 --- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py +++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py @@ -7,6 +7,7 @@ def execute(): frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True) frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True) + frappe.reload_doc('crm', 'doctype', 'quotation_lost_reason_detail', force=True) company = frappe.get_all('Company', filters = {'country': 'United States'}) if not company: From 544d18327c67c95bceb9d8b9b11b4958da3ff27c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 25 Aug 2020 20:01:31 +0530 Subject: [PATCH 57/73] fix(patch): quotation_lost_reason_detail --- erpnext/patches/v12_0/create_irs_1099_field_united_states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py index bce4ab10b3..7feaffdf40 100644 --- a/erpnext/patches/v12_0/create_irs_1099_field_united_states.py +++ b/erpnext/patches/v12_0/create_irs_1099_field_united_states.py @@ -7,7 +7,7 @@ def execute(): frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True) frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True) - frappe.reload_doc('crm', 'doctype', 'quotation_lost_reason_detail', force=True) + frappe.reload_doc('setup', 'doctype', 'quotation_lost_reason_detail', force=True) company = frappe.get_all('Company', filters = {'country': 'United States'}) if not company: From a163d8bc843296bed1095f55c19fd0e3a210e1ae Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 26 Aug 2020 12:00:45 +0530 Subject: [PATCH 58/73] fix(minor): rename_lost_reason_detail --- erpnext/patches/v12_0/rename_lost_reason_detail.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v12_0/rename_lost_reason_detail.py b/erpnext/patches/v12_0/rename_lost_reason_detail.py index 044d0232e0..d0dc356bd0 100644 --- a/erpnext/patches/v12_0/rename_lost_reason_detail.py +++ b/erpnext/patches/v12_0/rename_lost_reason_detail.py @@ -3,6 +3,7 @@ import frappe def execute(): if frappe.db.exists("DocType", "Lost Reason Detail"): + frappe.reload_doc("crm", "doctype", "opportunity_lost_reason") frappe.reload_doc("crm", "doctype", "opportunity_lost_reason_detail") frappe.reload_doc("setup", "doctype", "quotation_lost_reason_detail") @@ -10,8 +11,8 @@ def execute(): frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason Detail` SELECT * FROM `tabLost Reason Detail` WHERE `parenttype` = 'Quotation'""") - frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`) - SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason` + frappe.db.sql("""INSERT INTO `tabQuotation Lost Reason` (`name`, `creation`, `modified`, `modified_by`, `owner`, `docstatus`, `parent`, `parentfield`, `parenttype`, `idx`, `_comments`, `_assign`, `_user_tags`, `_liked_by`, `order_lost_reason`) + SELECT o.`name`, o.`creation`, o.`modified`, o.`modified_by`, o.`owner`, o.`docstatus`, o.`parent`, o.`parentfield`, o.`parenttype`, o.`idx`, o.`_comments`, o.`_assign`, o.`_user_tags`, o.`_liked_by`, o.`lost_reason` FROM `tabOpportunity Lost Reason` o LEFT JOIN `tabQuotation Lost Reason` q ON q.name = o.name WHERE q.name IS NULL""") - + frappe.delete_doc("DocType", "Lost Reason Detail") \ No newline at end of file From 73f648473f9ac70afd469d845b3103cd3f9e9829 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 26 Aug 2020 13:09:58 +0530 Subject: [PATCH 59/73] fix(minor): update_start_end_date_for_old_shift_assignment.py --- .../v13_0/update_start_end_date_for_old_shift_assignment.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py index 7c07b987f3..0f521cb57a 100644 --- a/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py +++ b/erpnext/patches/v13_0/update_start_end_date_for_old_shift_assignment.py @@ -7,4 +7,7 @@ import frappe def execute(): frappe.reload_doc('hr', 'doctype', 'shift_assignment') - frappe.db.sql("update `tabShift Assignment` set end_date=date, start_date=date where date IS NOT NULL and start_date IS NULL and end_date IS NULL;") + if frappe.db.has_column('Shift Assignment', 'date'): + frappe.db.sql("""update `tabShift Assignment` + set end_date=date, start_date=date + where date IS NOT NULL and start_date IS NULL and end_date IS NULL;""") From 9766adbf4eaf6abf990746bbbabfabf928dd4819 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 26 Aug 2020 14:53:23 +0530 Subject: [PATCH 60/73] fix: don't overwrite appointment duration if already specified --- .../doctype/patient_appointment/patient_appointment.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index f7ed31bfea..2d6b64532b 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -226,7 +226,9 @@ let check_and_set_availability = function(frm) { primary_action_label: __('Book'), primary_action: function() { frm.set_value('appointment_time', selected_slot); - frm.set_value('duration', duration); + if (!frm.doc.duration) { + frm.set_value('duration', duration); + } frm.set_value('practitioner', d.get_value('practitioner')); frm.set_value('department', d.get_value('department')); frm.set_value('appointment_date', d.get_value('appointment_date')); From c7830f71072509cb7fb751b9836bd8f4d6a9e65d Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 26 Aug 2020 15:28:22 +0530 Subject: [PATCH 61/73] feat: Added phone field in product Inquiry --- erpnext/templates/generators/item/item_inquiry.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js index 52ddae2624..e7db3a368d 100644 --- a/erpnext/templates/generators/item/item_inquiry.js +++ b/erpnext/templates/generators/item/item_inquiry.js @@ -20,6 +20,13 @@ frappe.ready(() => { options: 'Email', reqd: 1 }, + { + fieldtype: 'Data', + label: __('Phone Number'), + fieldname: 'phone', + options: 'Phone', + reqd: 1 + }, { fieldtype: 'Data', label: __('Subject'), From 733fd5f03c7c855457c27600285fd2d0ace4bad6 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 26 Aug 2020 18:23:12 +0530 Subject: [PATCH 62/73] fix: get_applied_pricing_rule in taxes_and_totals --- erpnext/controllers/taxes_and_totals.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 2a14be8532..92cfdb7f1a 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -9,6 +9,7 @@ from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction from erpnext.controllers.accounts_controller import validate_conversion_rate, \ validate_taxes_and_charges, validate_inclusive_tax from erpnext.stock.get_item_details import _get_item_tax_template +from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules class calculate_taxes_and_totals(object): def __init__(self, doc): @@ -209,7 +210,7 @@ class calculate_taxes_and_totals(object): elif tax.charge_type == "On Previous Row Total": current_tax_fraction = (tax_rate / 100.0) * \ self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item - + elif tax.charge_type == "On Item Quantity": inclusive_tax_amount_per_qty = flt(tax_rate) @@ -607,7 +608,7 @@ class calculate_taxes_and_totals(object): base_rate_with_margin = 0.0 if item.price_list_rate: if item.pricing_rules and not self.doc.ignore_pricing_rule: - for d in json.loads(item.pricing_rules): + for d in get_applied_pricing_rules(item.pricing_rules): pricing_rule = frappe.get_cached_doc('Pricing Rule', d) if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ From bb5d886930da4caf37ba0ea5de38ede957155752 Mon Sep 17 00:00:00 2001 From: bhavesh95863 <34086262+bhavesh95863@users.noreply.github.com> Date: Wed, 26 Aug 2020 18:23:21 +0530 Subject: [PATCH 63/73] fix: account & cost center filter by company --- .../doctype/shipping_rule/shipping_rule.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.js b/erpnext/accounts/doctype/shipping_rule/shipping_rule.js index 53ee08a773..d0904eec3e 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.js +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.js @@ -3,6 +3,22 @@ frappe.ui.form.on('Shipping Rule', { refresh: function(frm) { + frm.set_query("cost_center", function() { + return { + filters: { + company: frm.doc.company + } + } + }) + + frm.set_query("account", function() { + return { + filters: { + company: frm.doc.company + } + } + }) + frm.trigger('toggle_reqd'); }, calculate_based_on: function(frm) { @@ -12,4 +28,4 @@ frappe.ui.form.on('Shipping Rule', { frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === 'Fixed'); frm.toggle_reqd("conditions", frm.doc.calculate_based_on !== 'Fixed'); } -}); \ No newline at end of file +}); From 1bd83e69eeb3cb62afa2b6bbd80f5af80b6658c8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 27 Aug 2020 13:06:52 +0530 Subject: [PATCH 64/73] feat: added Installation Note and Warranty Claim links to Customer dashboard (#23183) --- erpnext/selling/doctype/customer/customer_dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index cf234650c8..532c11b86e 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -33,7 +33,7 @@ def get_data(): }, { 'label': _('Support'), - 'items': ['Issue', 'Maintenance Visit'] + 'items': ['Issue', 'Maintenance Visit', 'Installation Note', 'Warranty Claim'] }, { 'label': _('Projects'), From ead2f6abf0a8d11700ad16f022abdd99273666c1 Mon Sep 17 00:00:00 2001 From: michellealva Date: Thu, 27 Aug 2020 13:57:15 +0530 Subject: [PATCH 65/73] fix: Chage fiedtype of Customer's PO in Sales Invoice --- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 31613e50b0..2397b7d0cb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -447,7 +447,7 @@ { "allow_on_submit": 1, "fieldname": "po_no", - "fieldtype": "Small Text", + "fieldtype": "Data", "hide_days": 1, "hide_seconds": 1, "label": "Customer's Purchase Order", @@ -1946,7 +1946,7 @@ "idx": 181, "is_submittable": 1, "links": [], - "modified": "2020-08-03 23:31:12.675040", + "modified": "2020-08-27 01:56:28.532140", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From c8eea556d178607f2b4f09f1b683c64eed7f2bfc Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 27 Aug 2020 18:54:25 +0530 Subject: [PATCH 66/73] fix: BOM Update Tool failing due to Too Many Writes error --- .../manufacturing/doctype/bom_update_tool/bom_update_tool.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index e6c10ad12b..742d18c4cd 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -90,6 +90,7 @@ def update_latest_price_in_all_boms(): update_cost() def replace_bom(args): + frappe.db.auto_commit_on_many_writes = 1 args = frappe._dict(args) doc = frappe.get_doc("BOM Update Tool") @@ -97,6 +98,8 @@ def replace_bom(args): doc.new_bom = args.new_bom doc.replace_bom() + frappe.db.auto_commit_on_many_writes = 0 + def update_cost(): frappe.db.auto_commit_on_many_writes = 1 bom_list = get_boms_in_bottom_up_order() From 33f984c7af1f6b140b8306f0c8d5ee690bdf4805 Mon Sep 17 00:00:00 2001 From: bhavesh95863 <34086262+bhavesh95863@users.noreply.github.com> Date: Thu, 27 Aug 2020 21:49:21 +0530 Subject: [PATCH 67/73] fix: can't multiply sequence by non-int of type 'float --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 4 ++-- 1 file changed, 2 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 c23a6ad58f..62fc4163e1 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -356,7 +356,7 @@ erpnext.PointOfSale.ItemCart = class { onchange: function() { if (this.value || this.value == 0) { const frm = me.events.get_frm(); - frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', this.value); + frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', parseFloat(this.value)); me.hide_discount_control(this.value); } }, @@ -948,4 +948,4 @@ erpnext.PointOfSale.ItemCart = class { show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none'); } -} \ No newline at end of file +} From 3e9f493f15b05c364a6a2df11b11113899d0006c Mon Sep 17 00:00:00 2001 From: bhavesh95863 <34086262+bhavesh95863@users.noreply.github.com> Date: Fri, 28 Aug 2020 11:15:02 +0530 Subject: [PATCH 68/73] fix: change parseFloat to flt --- erpnext/selling/page/point_of_sale/pos_item_cart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 62fc4163e1..eadeb8fde8 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -356,7 +356,7 @@ erpnext.PointOfSale.ItemCart = class { onchange: function() { if (this.value || this.value == 0) { const frm = me.events.get_frm(); - frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', parseFloat(this.value)); + frappe.model.set_value(frm.doc.doctype, frm.doc.name, 'additional_discount_percentage', flt(this.value)); me.hide_discount_control(this.value); } }, From 6887382937874ee962336c43674acf715852cce8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 28 Aug 2020 11:52:00 +0530 Subject: [PATCH 69/73] fix: Ignore cpmpany and bank account doctype while deleting company transactions (#22953) --- erpnext/setup/doctype/company/delete_company_transactions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 8ecc13b2fb..c94831ef93 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -26,7 +26,8 @@ def delete_company_transactions(company_name): tabDocField where fieldtype='Link' and options='Company'"""): if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", "Party Account", "Employee", "Sales Taxes and Charges Template", - "Purchase Taxes and Charges Template", "POS Profile", 'BOM'): + "Purchase Taxes and Charges Template", "POS Profile", "BOM", + "Company", "Bank Account"): delete_for_doctype(doctype, company_name) # reset company values From e9274283bd27415bfb410d5b1991730ea147272d Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 28 Aug 2020 12:21:10 +0530 Subject: [PATCH 70/73] fix: Raise Error on over receipt/consumption for sub-contrcated PR --- erpnext/controllers/buying_controller.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index f982700c01..ac567b7dea 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -276,6 +276,9 @@ class BuyingController(StockController): qty_to_be_received_map = get_qty_to_be_received(purchase_orders) for item in self.get('items'): + if not item.purchase_order: + continue + # reset raw_material cost item.rm_supp_cost = 0 @@ -288,6 +291,12 @@ class BuyingController(StockController): fg_yet_to_be_received = qty_to_be_received_map.get(item_key) + if not fg_yet_to_be_received: + frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}") + .format(item.idx, frappe.bold(item.item_code), + frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)), + title=_("Limit Crossed")) + transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) From 731bae170e58da232972f621d9436b397c909411 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 28 Aug 2020 14:09:02 +0530 Subject: [PATCH 71/73] fix: Test for Over Receipt via PRs on a PO --- .../purchase_receipt/test_purchase_receipt.py | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 4a8236dd11..67161aa6dd 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import unittest +import json import frappe, erpnext import frappe.defaults from frappe.utils import cint, flt, cstr, today, random_string @@ -152,13 +153,78 @@ class TestPurchaseReceipt(unittest.TestCase): qty=100, basic_rate=100, company="_Test Company with perpetual inventory") pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes", company="_Test Company with perpetual inventory", warehouse='Stores - TCP1', supplier_warehouse='Work In Progress - TCP1') - + gl_entries = get_gl_entries("Purchase Receipt", pr.name) self.assertFalse(gl_entries) set_perpetual_inventory(0) + def test_subcontracting_over_receipt(self): + """ + Behaviour: Raise multiple PRs against one PO that in total + receive more than the required qty in the PO. + Expected Result: Error Raised for Over Receipt against PO. + """ + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.buying.doctype.purchase_order.test_purchase_order import (update_backflush_based_on, + make_subcontracted_item, create_purchase_order) + from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, + make_rm_stock_entry as make_subcontract_transfer_entry) + + update_backflush_based_on("Material Transferred for Subcontract") + item_code = "_Test Subcontracted FG Item 1" + make_subcontracted_item(item_code) + + po = create_purchase_order(item_code=item_code, qty=1, + is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") + + #stock raw materials in a warehouse before transfer + make_stock_entry(target="_Test Warehouse - _TC", + item_code="_Test Item Home Desktop 100", qty=1, basic_rate=100) + make_stock_entry(target="_Test Warehouse - _TC", + item_code = "Test Extra Item 1", qty=1, basic_rate=100) + make_stock_entry(target="_Test Warehouse - _TC", + item_code = "_Test Item", qty=1, basic_rate=100) + + rm_items = [ + { + "item_code": item_code, + "rm_item_code": po.supplied_items[0].rm_item_code, + "item_name": "_Test Item", + "qty": po.supplied_items[0].required_qty, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos" + }, + { + "item_code": item_code, + "rm_item_code": po.supplied_items[1].rm_item_code, + "item_name": "Test Extra Item 1", + "qty": po.supplied_items[1].required_qty, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos" + }, + { + "item_code": item_code, + "rm_item_code": po.supplied_items[2].rm_item_code, + "item_name": "_Test Item Home Desktop 100", + "qty": po.supplied_items[2].required_qty, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos" + } + ] + rm_item_string = json.dumps(rm_items) + se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) + se.to_warehouse = "_Test Warehouse 1 - _TC" + se.save() + se.submit() + + pr1 = make_purchase_receipt(po.name) + pr2 = make_purchase_receipt(po.name) + + pr1.submit() + self.assertRaises(frappe.ValidationError, pr2.submit) + def test_serial_no_supplier(self): pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1) self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"), From b23840bf7b668971364ac05cb2425d396b617dd5 Mon Sep 17 00:00:00 2001 From: Syed Mujeer Hashmi Date: Sat, 29 Aug 2020 12:48:48 +0530 Subject: [PATCH 72/73] fix: Filter out cancelled entries in customer ledger summary Signed-off-by: Syed Mujeer Hashmi --- .../report/customer_ledger_summary/customer_ledger_summary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index 2cb10b11e1..10b32fea56 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -173,7 +173,7 @@ class PartyLedgerSummaryReport(object): from `tabGL Entry` gle {join} where - gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != '' + gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != '' and gle.posting_date <= %(to_date)s {conditions} order by gle.posting_date """.format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True) @@ -248,7 +248,7 @@ class PartyLedgerSummaryReport(object): from `tabGL Entry` where - docstatus < 2 + docstatus < 2 and is_cancelled = 0 and (voucher_type, voucher_no) in ( select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc where acc.name = gle.account and acc.account_type = '{income_or_expense}' From e268d294b3390daad2a6031db22bdc264a3fda53 Mon Sep 17 00:00:00 2001 From: marination Date: Sun, 30 Aug 2020 21:01:34 +0530 Subject: [PATCH 73/73] fix: Better error feedback on creating SO from Quotation --- erpnext/selling/doctype/quotation/quotation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index ab095ebfe0..20ae19f5db 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -285,9 +285,17 @@ def _make_customer(source_name, ignore_permissions=False): return customer else: raise - except frappe.MandatoryError: + except frappe.MandatoryError as e: + mandatory_fields = e.args[0].split(':')[1].split(',') + mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields] + frappe.local.message_log = [] - frappe.throw(_("Please create Customer from Lead {0}").format(lead_name)) + lead_link = frappe.utils.get_link_to_form("Lead", lead_name) + message = _("Could not auto create Customer due to the following missing mandatory field(s):") + "
    " + message += "
    • " + "
    • ".join(mandatory_fields) + "
    " + message += _("Please create Customer from Lead {0}.").format(lead_link) + + frappe.throw(message, title=_("Mandatory Missing")) else: return customer_name else: