From 97c1bcf14f3bc2495c5fa94227a0be053f25e9e3 Mon Sep 17 00:00:00 2001 From: Joel Mesmer Date: Thu, 17 Aug 2017 22:20:18 +0200 Subject: [PATCH 01/39] improvement for self-postbacks --- erpnext/templates/includes/product_page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js index 3905959928..658fa53588 100644 --- a/erpnext/templates/includes/product_page.js +++ b/erpnext/templates/includes/product_page.js @@ -73,7 +73,7 @@ frappe.ready(function() { } } - if (window.location.search.indexOf(item_code)!==-1) { + if (window.location.search == ("?variant=" + item_code)) { return; } From adbf8adfb9b7c627f747e93100cb0691428c7e30 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 31 Jul 2017 20:45:36 +0530 Subject: [PATCH 02/39] Update BOM cost in all BOMs based on latest rm rate --- erpnext/config/manufacturing.py | 4 +- .../img/manufacturing/bom-replace-tool.png | Bin 47759 -> 0 bytes .../img/manufacturing/bom-update-tool.png | Bin 0 -> 94015 bytes .../manufacturing/tools/bom-replace-tool.md | 44 ---- .../en/manufacturing/tools/bom-update-tool.md | 54 ++++ .../manual/en/manufacturing/tools/index.txt | 2 +- erpnext/hooks.py | 3 +- erpnext/manufacturing/doctype/bom/bom.js | 14 +- erpnext/manufacturing/doctype/bom/bom.json | 35 ++- erpnext/manufacturing/doctype/bom/bom.py | 46 +++- erpnext/manufacturing/doctype/bom/test_bom.py | 28 ++ .../doctype/bom_replace_tool/README.md | 1 - .../bom_replace_tool/bom_replace_tool.js | 22 -- .../bom_replace_tool/bom_replace_tool.json | 119 --------- .../__init__.py | 0 .../bom_update_tool/bom_update_tool.js | 34 +++ .../bom_update_tool/bom_update_tool.json | 244 ++++++++++++++++++ .../bom_update_tool.py} | 23 +- .../bom_update_tool/test_bom_update_tool.js | 23 ++ .../manufacturing_settings.json | 130 +++++++++- .../test_manufacturing_settings.js | 23 ++ erpnext/patches.txt | 3 +- erpnext/patches/v5_0/reset_values_in_tools.py | 4 +- .../patches/v8_6/rename_bom_update_tool.py | 7 + erpnext/public/js/help_links.js | 4 +- 25 files changed, 649 insertions(+), 218 deletions(-) delete mode 100644 erpnext/docs/assets/img/manufacturing/bom-replace-tool.png create mode 100644 erpnext/docs/assets/img/manufacturing/bom-update-tool.png delete mode 100644 erpnext/docs/user/manual/en/manufacturing/tools/bom-replace-tool.md create mode 100644 erpnext/docs/user/manual/en/manufacturing/tools/bom-update-tool.md delete mode 100644 erpnext/manufacturing/doctype/bom_replace_tool/README.md delete mode 100644 erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.js delete mode 100644 erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.json rename erpnext/manufacturing/doctype/{bom_replace_tool => bom_update_tool}/__init__.py (100%) create mode 100644 erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js create mode 100644 erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json rename erpnext/manufacturing/doctype/{bom_replace_tool/bom_replace_tool.py => bom_update_tool/bom_update_tool.py} (63%) create mode 100644 erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js create mode 100644 erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js create mode 100644 erpnext/patches/v8_6/rename_bom_update_tool.py diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py index a930ddc6ef..11711ad004 100644 --- a/erpnext/config/manufacturing.py +++ b/erpnext/config/manufacturing.py @@ -70,8 +70,8 @@ def get_data(): "items": [ { "type": "doctype", - "name": "BOM Replace Tool", - "description": _("Replace Item / BOM in all BOMs"), + "name": "BOM Update Tool", + "description": _("Replace BOM and update latest price in all BOMs"), }, ] }, diff --git a/erpnext/docs/assets/img/manufacturing/bom-replace-tool.png b/erpnext/docs/assets/img/manufacturing/bom-replace-tool.png deleted file mode 100644 index 51ac99324b563dee3b0f3af97fd130b3abe3fe6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47759 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 ZdicW*A2~+PMk?U%;yGQdytCJX{|~ntK-d5P diff --git a/erpnext/docs/assets/img/manufacturing/bom-update-tool.png b/erpnext/docs/assets/img/manufacturing/bom-update-tool.png new file mode 100644 index 0000000000000000000000000000000000000000..7dbd47fe0e1c7a9ac33f39ef6c6c233533f20204 GIT binary patch literal 94015 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

  • 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 Yuqp;lnU{G2AmC%5XZmZgj@=*s28pet_y7O^ literal 0 HcmV?d00001 diff --git a/erpnext/docs/user/manual/en/manufacturing/tools/bom-replace-tool.md b/erpnext/docs/user/manual/en/manufacturing/tools/bom-replace-tool.md deleted file mode 100644 index 98d1f3b985..0000000000 --- a/erpnext/docs/user/manual/en/manufacturing/tools/bom-replace-tool.md +++ /dev/null @@ -1,44 +0,0 @@ -# BOM Replace Tool - -Replace BOM is the utility to replace BOM of sub-assembly item, which is already updated in the BOM of Finished Good item. - -To use the Production Planning Tool, go to: - -> Manufacturing > Tools > BOM Replace Tool - -Let's consider a scenario to understand this better. - -If company manufactures computers, Bill of Material of its finished item will constitute of: - -1. Monitor -1. Key Board -1. Mouse -1. CPU - -Out of all the items above, CPU is asembled separately. Hence separate BOM will be created for the CPU. Following are the items from the BOM of CPU. - -1. 250 GB Hard Disk -1. Mother Board -1. Processor -1. SMTP -1. DVD player - -If we have more items to be added , or existing items to be edited in the BOM of CPU, then we should create new BOM for it. - -1. _350 GB Hard Disk_ -1. Mother Board -1. Processor -1. SMTP -1. DVD player - -To update new BOM updated in the BOM of finished item, where CPU is selected as raw-material, you can use BOM Replace tool. - -BOM replace Tool - -In this tool, you should select Current BOM, and New BOM. On clicking Replace button, current BOM of CPU will be replaced with New BOM in the BOM of finished Item (Computer). - -**Will BOM Replace Tool work for replacing finsihed item in BOM?** - -No. You should Cancel and Amend current BOM, or create a new BOM for finished item. - -{next} diff --git a/erpnext/docs/user/manual/en/manufacturing/tools/bom-update-tool.md b/erpnext/docs/user/manual/en/manufacturing/tools/bom-update-tool.md new file mode 100644 index 0000000000..41503531b6 --- /dev/null +++ b/erpnext/docs/user/manual/en/manufacturing/tools/bom-update-tool.md @@ -0,0 +1,54 @@ +# BOM Update Tool + +From BOM Update Tool, you can replace a sub-assembly BOM and update costs of all BOMs. + +### Replace BOM +Using this utility, you can replace an existing BOM of sub-assembly item, with a new one. The system will update the new BOM in all the parent BOMs where it was used. + +To use the BOM Update Tool, go to: + +> Manufacturing > Tools > BOM Update Tool + +Let's consider a scenario to understand this better. + +Suppose a company manufactures computers, Bill of Material of of the computer will look like this: + +1. Monitor +1. Key Board +1. Mouse +1. CPU + +Out of all the items above, CPU is asembled separately. Hence separate BOM will be created for the CPU. Following are the items from the BOM of CPU. + +1. 250 GB Hard Disk +1. Mother Board +1. Processor +1. SMTP +1. DVD player + +If we have more items to be added , or existing items to be edited in the BOM of CPU, then we should create new BOM for it. + +1. _350 GB Hard Disk_ +1. Mother Board +1. Processor +1. SMTP +1. DVD player + +To update new BOM in all the parent BOMs, where CPU is selected as raw-material, you can use Replace utility. + +BOM Update Tool + +In this tool, you should select Current BOM, and New BOM. On clicking Replace button, current BOM of CPU will be replaced with New BOM in the BOM of finished Item (Computer). + +**Will BOM Replace Tool work for replacing finsihed item in BOM?** + +No. You should Cancel and Amend current BOM, or create a new BOM for finished item. + +### Update BOM Cost +Using the button **Update latest price in all BOMs**, you can update cost of all Bill of Materials, based on latest purchase price / price list rate / valuation rate of raw materials. + +On clicking of this buttom, system will create a background process to update all the BOM's cost. It is processed via background jobs because this process can take a few minutes (depending on the number of BOMs) to update all the BOMs. + +This functionality can also be executed automatically on daily basis. For that, you need to enable "Update BOM Cost Automatically" from Manufacturing Settings. + +{next} diff --git a/erpnext/docs/user/manual/en/manufacturing/tools/index.txt b/erpnext/docs/user/manual/en/manufacturing/tools/index.txt index aaf7cecc70..a385154589 100644 --- a/erpnext/docs/user/manual/en/manufacturing/tools/index.txt +++ b/erpnext/docs/user/manual/en/manufacturing/tools/index.txt @@ -1,2 +1,2 @@ production-planning-tool -bom-replace-tool \ No newline at end of file +bom-update-tool \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4df83afa10..7e65fc9262 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -196,7 +196,8 @@ scheduler_events = { "erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary", "erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status", "erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards", - "erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history" + "erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history", + "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", ] } diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 347314b2c1..c58c89cd5d 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -5,7 +5,7 @@ frappe.provide("erpnext.bom"); frappe.ui.form.on("BOM", { setup: function(frm) { - frm.add_fetch('buying_price_list', 'currency', 'currency') + frm.add_fetch('buying_price_list', 'currency', 'currency'); frm.set_query("bom_no", "items", function() { return { @@ -13,15 +13,15 @@ frappe.ui.form.on("BOM", { 'currency': frm.doc.currency, 'company': frm.doc.company } - } + }; }); - + frm.set_query("source_warehouse", "items", function() { return { filters: { 'company': frm.doc.company, } - } + }; }); }, @@ -57,10 +57,14 @@ frappe.ui.form.on("BOM", { doc: frm.doc, method: "update_cost", freeze: true, + args: { + update_parent: true, + from_child_bom:false + }, callback: function(r) { if(!r.exc) frm.refresh_fields(); } - }) + }); } }); diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index f41087b686..886c9428e9 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -232,7 +232,7 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, @@ -262,7 +262,7 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, @@ -291,6 +291,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "set_rate_of_sub_assembly_item_based_on_bom", + "fieldtype": "Check", + "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": "Set rate of sub-assembly item based on BOM", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 7130a3e94d..71ff43f48a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -92,7 +92,7 @@ class BOM(WebsiteGenerator): def validate_rm_item(self, item): if (item[0]['name'] in [it.item_code for it in self.items]) and item[0]['name'] == self.item: - frappe.throw(_("Raw material cannot be same as main Item")) + frappe.throw(_("BOM #{0}: Raw material cannot be same as main Item").format(self.name)) def set_bom_material_details(self): for item in self.get("items"): @@ -155,15 +155,17 @@ class BOM(WebsiteGenerator): rate = frappe.db.get_value("Item Price", {"price_list": self.buying_price_list, "item_code": arg["item_code"]}, "price_list_rate") or 0 - if not rate and arg['bom_no']: - rate = self.get_bom_unitcost(arg['bom_no']) + if arg['bom_no'] and (not rate or self.set_rate_of_sub_assembly_item_based_on_bom): + rate = self.get_bom_unitcost(arg['bom_no']) return rate - def update_cost(self): + def update_cost(self, update_parent=True, from_child_bom=False): if self.docstatus == 2: return + existing_bom_cost = self.total_cost + for d in self.get("items"): rate = self.get_bom_material_detail({'item_code': d.item_code, 'bom_no': d.bom_no, 'stock_qty': d.stock_qty})["rate"] @@ -176,7 +178,16 @@ class BOM(WebsiteGenerator): self.save() self.update_exploded_items() - frappe.msgprint(_("Cost Updated")) + # update parent BOMs + if self.total_cost != existing_bom_cost and update_parent: + parent_boms = frappe.db.sql_list("""select distinct parent from `tabBOM Item` + where bom_no = %s and docstatus=1""", self.name) + + for bom in parent_boms: + frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) + + if not from_child_bom: + frappe.msgprint(_("Cost Updated")) def get_bom_unitcost(self, bom_no): bom = frappe.db.sql("""select name, total_cost/quantity as unit_cost from `tabBOM` @@ -566,4 +577,27 @@ def get_children(): where bom_item.parent=%s and bom_item.item_code = item.name order by bom_item.idx - """, frappe.form_dict.parent, as_dict=True) \ No newline at end of file + """, frappe.form_dict.parent, as_dict=True) + +def get_boms_in_bottom_up_order(bom_no=None): + def _get_parent(bom_no): + return frappe.db.sql_list("""select distinct parent from `tabBOM Item` + where bom_no = %s and docstatus=1""", bom_no) + + count = 0 + bom_list = [] + if bom_no: + bom_list.append(bom_no) + else: + # get all leaf BOMs + bom_list = frappe.db.sql_list("""select name from `tabBOM` bom where docstatus=1 + and not exists(select bom_no from `tabBOM Item` + where parent=bom.name and ifnull(bom_no, '')!='')""") + + while(count < len(bom_list)): + for child_bom in _get_parent(bom_list[count]): + if child_bom not in bom_list: + bom_list.append(child_bom) + count += 1 + + return bom_list \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 4e520ef233..d678444e4d 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals import unittest import frappe from frappe.utils import cstr +from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation +from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost test_records = frappe.get_test_records('BOM') @@ -46,6 +48,32 @@ class TestBOM(unittest.TestCase): bom.save() self.assertTrue(_get_default_bom_in_item(), bom.name) + + def test_update_bom_cost_in_all_boms(self): + # get current rate for '_Test Item 2' + rm_rate = frappe.db.sql("""select rate from `tabBOM Item` + where parent='BOM-_Test Item Home Desktop Manufactured-001' + and item_code='_Test Item 2' and docstatus=1""") + rm_rate = rm_rate[0][0] if rm_rate else 0 + + # update valuation rate of item '_Test Item 2' + warehouse_list = frappe.db.sql_list("""select warehouse from `tabBin` + where item_code='_Test Item 2' and actual_qty > 0""") + + if not warehouse_list: + warehouse_list.append("_Test Warehouse - _TC") + + for warehouse in warehouse_list: + create_stock_reconciliation(item_code="_Test Item 2", warehouse=warehouse, + qty=200, rate=rm_rate + 10) + + # update cost of all BOMs based on latest valuation rate + update_cost() + # check if new valuation rate updated in all BOMs + for d in frappe.db.sql("""select rate from `tabBOM Item` + where item_code='_Test Item 2' and docstatus=1""", as_dict=1): + self.assertEqual(d.rate, rm_rate + 10) + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/README.md b/erpnext/manufacturing/doctype/bom_replace_tool/README.md deleted file mode 100644 index 4abce74e85..0000000000 --- a/erpnext/manufacturing/doctype/bom_replace_tool/README.md +++ /dev/null @@ -1 +0,0 @@ -Tool to replace one Item with another in all Bill of Material (BOM) trees. \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.js b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.js deleted file mode 100644 index 8a3f70ce1d..0000000000 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.js +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - - -cur_frm.cscript.refresh = function(doc) { - cur_frm.disable_save(); -} - -cur_frm.set_query("current_bom", function(doc) { - return{ - query: "erpnext.controllers.queries.bom", - filters: {name: "!" + doc.new_bom} - } -}); - - -cur_frm.set_query("new_bom", function(doc) { - return{ - query: "erpnext.controllers.queries.bom", - filters: {name: "!" + doc.current_bom} - } -}); \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.json b/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.json deleted file mode 100644 index bf7e6fc38d..0000000000 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "allow_copy": 1, - "allow_import": 0, - "allow_rename": 0, - "creation": "2012-12-06 12:10:10", - "custom": 0, - "description": "Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "The BOM which will be replaced", - "fieldname": "current_bom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Current BOM", - "no_copy": 0, - "options": "BOM", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "description": "The new BOM after replacement", - "fieldname": "new_bom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "New BOM", - "no_copy": 0, - "options": "BOM", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "replace", - "fieldtype": "Button", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Replace", - "no_copy": 0, - "options": "replace_bom", - "permlevel": 0, - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "hide_heading": 1, - "hide_toolbar": 1, - "icon": "fa fa-magic", - "idx": 1, - "in_create": 1, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "modified": "2015-08-12 08:52:46.035343", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM Replace Tool", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "read_only": 1, - "read_only_onload": 0 -} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/__init__.py b/erpnext/manufacturing/doctype/bom_update_tool/__init__.py similarity index 100% rename from erpnext/manufacturing/doctype/bom_replace_tool/__init__.py rename to erpnext/manufacturing/doctype/bom_update_tool/__init__.py diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js new file mode 100644 index 0000000000..a4b48af5a5 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.js @@ -0,0 +1,34 @@ +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('BOM Update Tool', { + setup: function(frm) { + frm.set_query("current_bom", function() { + return { + query: "erpnext.controllers.queries.bom", + filters: {name: "!" + frm.doc.new_bom} + }; + }); + + frm.set_query("new_bom", function() { + return { + query: "erpnext.controllers.queries.bom", + filters: {name: "!" + frm.doc.current_bom} + }; + }); + }, + + refresh: function(frm) { + frm.disable_save(); + }, + + update_latest_price_in_all_boms: function() { + frappe.call({ + method: "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.enqueue_update_cost", + freeze: true, + callback: function() { + frappe.msgprint(__("Latest price updated in all BOMs")); + } + }); + } +}); \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json new file mode 100644 index 0000000000..ab63c0b540 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json @@ -0,0 +1,244 @@ +{ + "allow_copy": 1, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2012-12-06 12:10:10", + "custom": 0, + "description": "Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM.\nIt also updates latest price in all the BOMs.", + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "replace_bom_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Replace BOM", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "The BOM which will be replaced", + "fieldname": "current_bom", + "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": "Current BOM", + "length": 0, + "no_copy": 0, + "options": "BOM", + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "The new BOM after replacement", + "fieldname": "new_bom", + "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": "New BOM", + "length": 0, + "no_copy": 0, + "options": "BOM", + "permlevel": 0, + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "replace", + "fieldtype": "Button", + "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": "Replace", + "length": 0, + "no_copy": 0, + "options": "replace_bom", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "update_cost_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Update Cost", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "update_latest_price_in_all_boms", + "fieldtype": "Button", + "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": "Update latest price in all BOMs", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 1, + "hide_toolbar": 1, + "icon": "icon-magic", + "idx": 1, + "image_view": 0, + "in_create": 1, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-31 18:08:05.919276", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM Update Tool", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 0, + "email": 0, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 0, + "role": "Manufacturing Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 1, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "track_changes": 0, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py similarity index 63% rename from erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py rename to erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index f0a834c37b..91b5070dbd 100644 --- a/erpnext/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -1,14 +1,15 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt from __future__ import unicode_literals import frappe from frappe.utils import cstr, flt from frappe import _ - +from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order from frappe.model.document import Document -class BOMReplaceTool(Document): +class BOMUpdateTool(Document): def replace_bom(self): self.validate_bom() self.update_new_bom() @@ -40,3 +41,17 @@ class BOMReplaceTool(Document): return [d[0] for d in frappe.db.sql("""select distinct parent from `tabBOM Item` where ifnull(bom_no, '') = %s and docstatus < 2""", self.new_bom)] + +@frappe.whitelist() +def enqueue_update_cost(): + frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost") + frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes.")) + +def update_latest_price_in_all_boms(): + if frappe.db.get_single_value("Manufacturing Settings", "update_bom_costs_automatically"): + update_cost() + +def update_cost(): + bom_list = get_boms_in_bottom_up_order() + for bom in bom_list: + frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js new file mode 100644 index 0000000000..d220df2824 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: BOM Update Tool", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially('BOM Update Tool', [ + // insert a new BOM Update Tool + () => frappe.tests.make([ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 9f12e4554a..455a983e76 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -1,23 +1,32 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2014-11-27 14:12:07.542534", "custom": 0, "docstatus": 0, "doctype": "DocType", "document_type": "Document", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "capacity_planning", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Capacity Planning", "length": 0, "no_copy": 0, @@ -26,6 +35,7 @@ "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, @@ -33,16 +43,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Disables creation of time logs against Production Orders. Operations shall not be tracked against Production Order", "fieldname": "disable_capacity_planning", "fieldtype": "Check", "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": "Disable Capacity Planning and Time Tracking", "length": 0, "no_copy": 0, @@ -51,6 +66,7 @@ "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, @@ -58,16 +74,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Plan time logs outside Workstation Working Hours.", "fieldname": "allow_overtime", "fieldtype": "Check", "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": "Allow Overtime", "length": 0, "no_copy": 0, @@ -76,6 +97,7 @@ "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, @@ -83,16 +105,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "", "fieldname": "allow_production_on_holidays", "fieldtype": "Check", "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": "Allow Production on Holidays", "length": 0, "no_copy": 0, @@ -102,6 +129,7 @@ "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, @@ -109,15 +137,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "column_break_3", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -125,6 +158,7 @@ "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, @@ -132,17 +166,22 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "30", "description": "Try planning operations for X days in advance.", "fieldname": "capacity_planning_for_days", "fieldtype": "Int", "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": "Capacity Planning For (Days)", "length": 0, "no_copy": 0, @@ -151,6 +190,7 @@ "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, @@ -158,16 +198,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Default 10 mins", "fieldname": "mins_between_operations", "fieldtype": "Int", "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": "Time Between Operations (in mins)", "length": 0, "no_copy": 0, @@ -176,6 +221,7 @@ "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, @@ -183,15 +229,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -199,6 +250,7 @@ "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, @@ -206,15 +258,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "over_production_allowance_percentage", "fieldtype": "Percent", "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": "Over Production Allowance Percentage", "length": 0, "no_copy": 0, @@ -223,6 +280,7 @@ "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, @@ -230,16 +288,21 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "BOM", "fieldname": "backflush_raw_materials_based_on", "fieldtype": "Select", "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": "Backflush Raw Materials Based On", "length": 0, "no_copy": 0, @@ -249,6 +312,7 @@ "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, @@ -256,15 +320,22 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", + "columns": 0, + "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.", + "fieldname": "update_bom_costs_automatically", + "fieldtype": "Check", "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": "Update BOM Cost Automatically", "length": 0, "no_copy": 0, "permlevel": 0, @@ -272,6 +343,7 @@ "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, @@ -279,15 +351,49 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "default_wip_warehouse", "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": "Default Work In Progress Warehouse", "length": 0, "no_copy": 0, @@ -297,6 +403,7 @@ "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, @@ -304,15 +411,20 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "default_fg_warehouse", "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": "Default Finished Goods Warehouse", "length": 0, "no_copy": 0, @@ -322,6 +434,7 @@ "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, @@ -329,18 +442,19 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, - "icon": "fa fa-wrench", + "icon": "icon-wrench", "idx": 0, + "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2015-12-10 00:03:20.895790", + "modified": "2017-07-31 19:25:04.242693", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", @@ -368,8 +482,12 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js new file mode 100644 index 0000000000..2b2589eddd --- /dev/null +++ b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Manufacturing Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially('Manufacturing Settings', [ + // insert a new Manufacturing Settings + () => frappe.tests.make([ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d781ec788f..8080bcdec1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -432,4 +432,5 @@ erpnext.patches.v8_5.update_customer_group_in_POS_profile erpnext.patches.v8_6.update_timesheet_company_from_PO erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager erpnext.patches.v8_5.remove_project_type_property_setter -erpnext.patches.v8_7.add_more_gst_fields \ No newline at end of file +erpnext.patches.v8_7.add_more_gst_fields +erpnext.patches.v8_6.rename_bom_update_tool \ No newline at end of file diff --git a/erpnext/patches/v5_0/reset_values_in_tools.py b/erpnext/patches/v5_0/reset_values_in_tools.py index 5aac83eef2..fd970ba1b0 100644 --- a/erpnext/patches/v5_0/reset_values_in_tools.py +++ b/erpnext/patches/v5_0/reset_values_in_tools.py @@ -6,7 +6,7 @@ import frappe def execute(): for dt in ["Payment Tool", "Bank Reconciliation", "Payment Reconciliation", "Leave Control Panel", - "Salary Manager", "Upload Attenadance", "Production Planning Tool", "BOM Replace Tool", "Customize Form", - "Employee Attendance Tool", "Rename Tool", "BOM Replace Tool", "Process Payroll", "Naming Series"]: + "Salary Manager", "Upload Attenadance", "Production Planning Tool", "BOM Update Tool", "Customize Form", + "Employee Attendance Tool", "Rename Tool", "BOM Update Tool", "Process Payroll", "Naming Series"]: frappe.db.sql("delete from `tabSingles` where doctype=%s", dt) \ No newline at end of file diff --git a/erpnext/patches/v8_6/rename_bom_update_tool.py b/erpnext/patches/v8_6/rename_bom_update_tool.py new file mode 100644 index 0000000000..45a4ddc788 --- /dev/null +++ b/erpnext/patches/v8_6/rename_bom_update_tool.py @@ -0,0 +1,7 @@ +import frappe +def execute(): + frappe.delete_doc_if_exists("DocType", "BOM Replace Tool") + + frappe.reload_doctype("BOM") + frappe.db.sql("update tabBOM set conversion_rate=1 where conversion_rate is null or conversion_rate=0") + frappe.db.sql("update tabBOM set set_rate_of_sub_assembly_item_based_on_bom=1") \ No newline at end of file diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 7de87b7964..d8af0e516d 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -508,8 +508,8 @@ frappe.help.help_links['Form/Production Planning Tool'] = [ { label: 'Production Planning Tool', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/tools/production-planning-tool' }, ] -frappe.help.help_links['Form/BOM Replace Tool'] = [ - { label: 'BOM Replace Tool', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/tools/bom-replace-tool' }, +frappe.help.help_links['Form/BOM Update Tool'] = [ + { label: 'BOM Update Tool', url: 'https://frappe.github.io/erpnext/user/manual/en/manufacturing/tools/bom-update-tool' }, ] //Customize From 52cc08dd2df39d8b64ac1a95b6861985ca7ac487 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Aug 2017 14:37:33 +0530 Subject: [PATCH 03/39] Test case added for replacing BOM --- .../bom_update_tool/test_bom_update_tool.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py new file mode 100644 index 0000000000..b0851a6780 --- /dev/null +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -0,0 +1,25 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + + +from __future__ import unicode_literals +import unittest +import frappe + +test_records = frappe.get_test_records('BOM') + +class TestBOMUpdateTool(unittest.TestCase): + def test_replace_bom(self): + current_bom = "BOM-_Test Item Home Desktop Manufactured-001" + + bom_doc = frappe.copy_doc(test_records[0]) + bom_doc.items[1].item_code = "_Test Item" + bom_doc.insert() + + update_tool = frappe.get_doc("BOM Update Tool") + update_tool.current_bom = current_bom + update_tool.new_bom = bom_doc.name + update_tool.replace_bom() + + self.assertFalse(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", current_bom)) + self.assertTrue(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", bom_doc.name)) \ No newline at end of file From 6c6e45157bf223b59d62115ee172f26adb23c61f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Aug 2017 18:26:52 +0530 Subject: [PATCH 04/39] Fixed test case for replace bom --- .../doctype/bom_update_tool/test_bom_update_tool.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index b0851a6780..154addf14e 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -22,4 +22,9 @@ class TestBOMUpdateTool(unittest.TestCase): update_tool.replace_bom() self.assertFalse(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", current_bom)) - self.assertTrue(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", bom_doc.name)) \ No newline at end of file + self.assertTrue(frappe.db.sql("select name from `tabBOM Item` where bom_no=%s", bom_doc.name)) + + # reverse, as it affects other testcases + update_tool.current_bom = bom_doc.name + update_tool.new_bom = current_bom + update_tool.replace_bom() \ No newline at end of file From 382f5eb1b1d8c7987443d92194c86b61e5db2159 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 22 Aug 2017 19:06:21 +0530 Subject: [PATCH 05/39] Run sales/purchase register for one month by default (#10493) --- .../item_wise_purchase_register/item_wise_purchase_register.js | 2 +- .../report/item_wise_sales_register/item_wise_sales_register.js | 2 +- erpnext/accounts/report/purchase_register/purchase_register.js | 2 +- erpnext/accounts/report/sales_register/sales_register.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js index 8d33524abb..46ed9dab39 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Item-wise Purchase Register"] = { "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_user_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js index 65cec518fe..b57a7fce9d 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Item-wise Sales Register"] = frappe.query_reports["Sales R "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { diff --git a/erpnext/accounts/report/purchase_register/purchase_register.js b/erpnext/accounts/report/purchase_register/purchase_register.js index cd795310da..42b35c2a99 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.js +++ b/erpnext/accounts/report/purchase_register/purchase_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Purchase Register"] = { "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_user_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { diff --git a/erpnext/accounts/report/sales_register/sales_register.js b/erpnext/accounts/report/sales_register/sales_register.js index 2ac4ae8b2a..0495976cf1 100644 --- a/erpnext/accounts/report/sales_register/sales_register.js +++ b/erpnext/accounts/report/sales_register/sales_register.js @@ -7,7 +7,7 @@ frappe.query_reports["Sales Register"] = { "fieldname":"from_date", "label": __("From Date"), "fieldtype": "Date", - "default": frappe.defaults.get_default("year_start_date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), "width": "80" }, { From 34c218b2de908fe05b4b0f785f34a7d875b921f7 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 22 Aug 2017 19:07:16 +0530 Subject: [PATCH 06/39] Minor improvements based on skip_transfer (#10457) --- .../manufacturing/doctype/production_order/production_order.js | 2 +- .../manufacturing/doctype/production_order/production_order.py | 3 ++- .../doctype/production_order_item/production_order_item.json | 3 ++- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_order/production_order.js b/erpnext/manufacturing/doctype/production_order/production_order.js index c61eec620a..33acb41d4b 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.js +++ b/erpnext/manufacturing/doctype/production_order/production_order.js @@ -91,7 +91,7 @@ frappe.ui.form.on("Production Order", { frm.set_indicator_formatter('operation', function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" }); }, - + refresh: function(frm) { erpnext.toggle_naming_series(); erpnext.production_order.set_custom_buttons(frm); diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py index 022e9f3d18..5fedc72889 100644 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ b/erpnext/manufacturing/doctype/production_order/production_order.py @@ -523,7 +523,8 @@ def set_production_order_ops(name): @frappe.whitelist() def make_stock_entry(production_order_id, purpose, qty=None): production_order = frappe.get_doc("Production Order", production_order_id) - if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group"): + if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group") \ + and not production_order.skip_transfer: wip_warehouse = production_order.wip_warehouse else: wip_warehouse = None diff --git a/erpnext/manufacturing/doctype/production_order_item/production_order_item.json b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json index 00e3adf13b..c0b1d009ba 100644 --- a/erpnext/manufacturing/doctype/production_order_item/production_order_item.json +++ b/erpnext/manufacturing/doctype/production_order_item/production_order_item.json @@ -229,6 +229,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:!parent.skip_transfer", "fieldname": "transferred_qty", "fieldtype": "Float", "hidden": 0, @@ -353,7 +354,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-07-10 17:37:20.212361", + "modified": "2017-08-18 18:11:10.311736", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Order Item", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7c7e630f01..fbadbc54de 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -556,7 +556,7 @@ class StockEntry(StockController): item_dict = self.get_bom_raw_materials(self.fg_completed_qty) for item in item_dict.values(): - if self.pro_doc: + if self.pro_doc and not self.pro_doc.skip_transfer: item["from_warehouse"] = self.pro_doc.wip_warehouse item["to_warehouse"] = self.to_warehouse if self.purpose=="Subcontract" else "" From bb326f2bbd33f15ccbe74f89d7558595f2530502 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 22 Aug 2017 19:07:42 +0530 Subject: [PATCH 07/39] Chart of accounts for Taiwan (#10435) --- .../verified/tw_chart_of_accounts.json | 722 ++++++++++++++++++ erpnext/setup/doctype/company/test_company.py | 2 +- 2 files changed, 723 insertions(+), 1 deletion(-) create mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/verified/tw_chart_of_accounts.json diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/tw_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tw_chart_of_accounts.json new file mode 100644 index 0000000000..a79283a40a --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tw_chart_of_accounts.json @@ -0,0 +1,722 @@ +{ + "country_code": "tw", + "name": "Taiwan - Chart of Accounts", + "tree": { + "1-\u8cc7\u7522": { + "11~12-\u6d41\u52d5\u8cc7\u7522": { + "111-\u73fe\u91d1\u53ca\u7d04\u7576\u73fe\u91d1": { + "1111-\u5eab\u5b58\u73fe\u91d1": { + "account_type": "Cash" + }, + "1112-\u96f6\u7528\u91d1/\u9031\u8f49\u91d1": { + "account_type": "Cash" + }, + "1113-\u9280\u884c\u5b58\u6b3e": { + "account_type": "Bank", + "\u4e2d\u570b\u4fe1\u8a17": { + "account_type": "Bank" + }, + "\u53f0\u5317\u5bcc\u90a6": { + "account_type": "Bank" + } + }, + "1116-\u5728\u9014\u73fe\u91d1": { + "account_type": "Cash" + }, + "1117-\u7d04\u7576\u73fe\u91d1": { + "account_type": "Cash" + }, + "1118-\u5176\u4ed6\u73fe\u91d1\u53ca\u7d04\u7576\u73fe\u91d1": { + "account_type": "Cash" + }, + "account_type": "Cash" + }, + "112-\u77ed\u671f\u6295\u8cc7": { + "1121-\u77ed\u671f\u6295\u8cc7 \u2014\u80a1\u7968": {} + }, + "113-\u61c9\u6536\u7968\u64da": { + "1131-\u61c9\u6536\u7968\u64da": { + "account_type": "Receivable" + }, + "1132-\u61c9\u6536\u7968\u64da\u8cbc\u73fe ": { + "account_type": "Receivable" + }, + "1138-\u5176\u4ed6\u61c9\u6536\u7968\u64da ": { + "account_type": "Receivable" + }, + "1139-\u5099\u62b5\u5446\u5e33 \uff0d\u61c9\u6536\u7968\u64da ": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "114-\u61c9\u6536\u5e33\u6b3e": { + "1141-\u61c9\u6536\u5e33\u6b3e ": { + "account_type": "Receivable" + }, + "1142-\u61c9\u6536\u5206\u671f\u5e33\u6b3e ": { + "account_type": "Receivable" + }, + "1149-\u5099\u62b5\u5446\u5e33 \uff0d\u61c9\u6536\u5e33\u6b3e ": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "118-\u5176\u4ed6\u61c9\u6536\u6b3e": { + "1184-\u61c9\u6536\u6536\u76ca": { + "account_type": "Receivable" + }, + "1185-\u61c9\u6536\u9000\u7a05\u6b3e": { + "account_type": "Receivable" + }, + "1189-\u5099\u62b5\u5446\u5e33 \u2014 \u5176\u4ed6\u61c9\u6536\u6b3e ": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "121~122-\u5b58\u8ca8": { + "1219-\u5099\u62b5\u5b58\u8ca8\u8dcc\u50f9\u640d\u5931": {}, + "1229-\u5099\u62b5\u5b58\u8ca8\u8dcc\u50f9\u640d\u5931": {}, + "account_type": "Stock", + "is_group": 1 + }, + "125-\u9810\u4ed8\u8cbb\u7528": { + "1251-\u9810\u4ed8\u85aa\u8cc7": {}, + "1252-\u9810\u4ed8\u79df\u91d1": {}, + "1253-\u9810\u4ed8\u4fdd\u96aa\u8cbb": {}, + "1254-\u7528\u54c1\u76e4\u5b58": {}, + "1255-\u9810\u4ed8\u6240\u5f97\u7a05": {}, + "1258-\u5176\u4ed6\u9810\u4ed8\u8cbb\u7528": {} + }, + "126-\u9810\u4ed8\u6b3e\u9805": { + "1261-\u9810\u4ed8\u8ca8\u6b3e": {}, + "1268-\u5176\u4ed6\u9810\u4ed8\u6b3e\u9805": {} + }, + "128~129-\u5176\u4ed6\u6d41\u52d5\u8cc7\u7522": { + "1281-\u9032\u9805\u7a05\u984d": {}, + "1282-\u7559\u62b5\u7a05\u984d": {}, + "1283-\u66ab\u4ed8\u6b3e": {}, + "1284-\u4ee3\u4ed8\u6b3e": {}, + "1285-\u54e1\u5de5\u501f\u652f": {} + } + }, + "13-\u57fa\u91d1\u53ca\u9577\u671f\u6295\u8cc7": { + "131-\u57fa\u91d1": { + "1311-\u511f\u50b5\u57fa\u91d1": {}, + "1313-\u610f\u5916\u640d\u5931\u6e96\u5099\u57fa\u91d1": {}, + "1314-\u9000\u4f11\u57fa\u91d1": {}, + "1318-\u5176\u4ed6\u57fa\u91d1": {} + }, + "132-\u9577\u671f\u6295\u8cc7": { + "1321-\u9577\u671f\u80a1\u6b0a\u6295\u8cc7": {}, + "1322-\u9577\u671f\u50b5\u5238\u6295\u8cc7": {}, + "1323-\u9577\u671f\u4e0d\u52d5\u7522\u6295\u8cc7": {}, + "1328-\u5176\u4ed6\u9577\u671f\u6295\u8cc7": {} + } + }, + "14~15-\u56fa\u5b9a\u8cc7\u7522": { + "141-\u571f\u5730": { + "1411-\u571f\u5730": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "142-\u571f\u5730\u6539\u826f\u7269": { + "1421-\u571f\u5730\u6539\u826f\u7269": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "143-\u623f\u5c4b\u53ca\u5efa\u7269": { + "1431-\u623f\u5c4b\u53ca\u5efa\u7269": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "144~146-\u6a5f(\u5668)\u5177\u53ca\u8a2d\u5099": { + "1441-\u6a5f(\u5668)\u5177": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "151-\u79df\u8cc3\u8cc7\u7522": { + "1511-\u79df\u8cc3\u8cc7\u7522": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "152-\u79df\u8cc3\u6b0a\u76ca\u6539\u826f": { + "1521-\u79df\u8cc3\u6b0a\u76ca\u6539\u826f": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "156-\u672a\u5b8c\u5de5\u7a0b\u53ca\u9810\u4ed8\u8cfc\u7f6e\u8a2d\u5099\u6b3e": { + "1561-\u672a\u5b8c\u5de5\u7a0b": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "158-\u96dc\u9805\u56fa\u5b9a\u8cc7\u7522": { + "1581-\u96dc\u9805\u56fa\u5b9a\u8cc7\u7522": { + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "account_type": "Fixed Asset" + }, + "16-\u905e\u8017\u8cc7\u7522": { + "161-\u905e\u8017\u8cc7\u7522": { + "is_group": 1 + } + }, + "17-\u7121\u5f62\u8cc7\u7522": { + "171-\u5546\u6a19\u6b0a": { + "1711-\u5546\u6a19\u6b0a": {} + }, + "172-\u5c08\u5229\u6b0a": { + "1721-\u5c08\u5229\u6b0a": {} + }, + "176-\u5546\u8b7d": { + "1761-\u5546\u8b7d": {} + }, + "177-\u958b\u8fa6\u8cbb": { + "1771-\u958b\u8fa6\u8cbb": {} + }, + "178-\u5176\u4ed6\u7121\u5f62\u8cc7\u7522": { + "1781-\u905e\u5ef6\u9000\u4f11\u91d1\u6210\u672c": {} + } + }, + "18-\u5176\u4ed6\u8cc7\u7522": { + "181-\u905e\u5ef6\u8cc7\u7522": { + "1811-\u50b5\u5238\u767c\u884c\u6210\u672c": {}, + "1812-\u9577\u671f\u9810\u4ed8\u79df\u91d1": {}, + "1813-\u9577\u671f\u9810\u4ed8\u4fdd\u96aa\u8cbb": {}, + "1814-\u905e\u5ef6\u6240\u5f97\u7a05\u8cc7\u7522": {}, + "1815-\u9810\u4ed8\u9000\u4f11\u91d1": {}, + "1818-\u5176\u4ed6\u905e\u5ef6\u8cc7\u7522": {} + }, + "182-\u9592\u7f6e\u8cc7\u7522": { + "1821-\u9592\u7f6e\u8cc7\u7522": {} + }, + "184-\u9577\u671f\u61c9\u6536\u7968\u64da\u53ca\u6b3e\u9805\u8207\u50ac\u6536\u5e33\u6b3e": { + "1841-\u9577\u671f\u61c9\u6536\u7968\u64da": { + "account_type": "Receivable" + }, + "1842-\u9577\u671f\u61c9\u6536\u5e33\u6b3e": { + "account_type": "Receivable" + }, + "1843-\u50ac\u6536\u5e33\u6b3e": { + "account_type": "Receivable" + }, + "1848-\u5176\u4ed6\u9577\u671f\u61c9\u6536\u6b3e\u9805": { + "account_type": "Receivable" + }, + "1849-\u5099\u62b5\u5446\u5e33\u2014\u9577\u671f\u61c9\u6536\u7968\u64da\u53ca\u6b3e\u9805\u8207\u50ac\u6536\u5e33\u6b3e": { + "account_type": "Receivable" + }, + "account_type": "Receivable" + }, + "185-\u51fa\u79df\u8cc7\u7522": { + "1851-\u51fa\u79df\u8cc7\u7522": {}, + "1858-\u51fa\u79df\u8cc7\u7522 \u2014\u91cd\u4f30\u589e\u503c": {}, + "1859-\u7d2f\u7a4d\u6298\u820a \u2014\u51fa\u79df\u8cc7\u7522": { + "account_type": "Accumulated Depreciation" + } + }, + "186-\u5b58\u51fa\u4fdd\u8b49\u91d1": { + "1861-\u5b58\u51fa\u4fdd\u8b49\u91d1": {} + }, + "188-\u96dc\u9805\u8cc7\u7522": { + "1881-\u53d7\u9650\u5236\u5b58\u6b3e": {}, + "1888-\u96dc\u9805\u8cc7\u7522 \u2014\u5176\u4ed6": {} + } + }, + "Temporary Accounts": { + "Temporary Opening": { + "account_type": "Temporary" + }, + "account_type": "Temporary" + }, + "root_type": "Asset" + }, + "2-\u8ca0\u50b5": { + "21~22-\u6d41\u52d5\u8ca0\u50b5": { + "211-\u77ed\u671f\u501f\u6b3e": { + "2111-\u9280\u884c\u900f\u652f": {}, + "2112-\u9280\u884c\u501f\u6b3e": {} + }, + "212-\u61c9\u4ed8\u77ed\u671f\u7968\u5238": { + "2121-\u61c9\u4ed8\u5546\u696d\u672c\u7968": { + "account_type": "Payable" + }, + "2122-\u9280\u884c\u627f\u514c\u532f\u7968": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "213-\u61c9\u4ed8\u7968\u64da": { + "2131-\u61c9\u4ed8\u7968\u64da": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "214-\u61c9\u4ed8\u5e33\u6b3e": { + "2141-\u61c9\u4ed8\u5e33\u6b3e": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "216-\u61c9\u4ed8\u6240\u5f97\u7a05": { + "2161-\u61c9\u4ed8\u6240\u5f97\u7a05": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "account_type": "Tax", + "tax_rate": 5.0 + }, + "217-\u61c9\u4ed8\u8cbb\u7528": { + "2171-\u61c9\u4ed8\u85aa\u5de5": {}, + "2172-\u61c9\u4ed8\u79df\u91d1": {}, + "2173-\u61c9\u4ed8\u5229\u606f": {}, + "2174-\u61c9\u4ed8\u71df\u696d\u7a05": {}, + "2175-\u61c9\u4ed8\u7a05\u6350 \u2014\u5176\u4ed6": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "2178-\u5176\u4ed6\u61c9\u4ed8\u8cbb\u7528": {} + }, + "218~219-\u5176\u4ed6\u61c9\u4ed8\u6b3e": { + "2184-\u61c9\u4ed8\u571f\u5730\u623f\u5c4b\u6b3e": {}, + "2185-\u61c9\u4ed8\u8a2d\u5099\u6b3e": {}, + "2192-\u61c9\u4ed8\u80a1\u5229": {} + }, + "226-\u9810\u6536\u6b3e\u9805": { + "2261-\u9810\u6536\u8ca8\u6b3e": {}, + "2262-\u9810\u6536\u6536\u5165": {}, + "2268-\u5176\u4ed6\u9810\u6536\u6b3e": {} + }, + "227-\u4e00\u5e74\u6216\u4e00\u71df\u696d\u9031\u671f\u5167\u5230\u671f\u9577\u671f\u8ca0\u50b5": { + "is_group": 1 + }, + "228~229-\u5176\u4ed6\u6d41\u52d5\u8ca0\u50b5": { + "2281-\u92b7\u9805\u7a05\u984d": {}, + "2283-\u66ab\u6536\u6b3e ": {}, + "2284-\u4ee3\u6536\u6b3e": {}, + "2285-\u4f30\u8a08\u552e\u5f8c\u670d\u52d9/\u4fdd\u56fa\u8ca0\u50b5": {}, + "2291-\u905e\u5ef6\u6240\u5f97\u7a05\u8ca0\u50b5": {}, + "2292-\u905e\u5ef6\u514c\u63db\u5229\u76ca": {} + } + }, + "23-\u9577\u671f\u8ca0\u50b5": { + "231-\u61c9\u4ed8\u516c\u53f8\u50b5": { + "2311-\u61c9\u4ed8\u516c\u53f8\u50b5": {}, + "2319-\u61c9\u4ed8\u516c\u53f8\u50b5\u6ea2(\u6298)\u50f9": {} + }, + "232-\u9577\u671f\u501f\u6b3e": { + "2321-\u9577\u671f\u9280\u884c\u501f\u6b3e": {}, + "2324-\u9577\u671f\u501f\u6b3e \u2014\u696d\u4e3b": {}, + "2325-\u9577\u671f\u501f\u6b3e \u2014\u54e1\u5de5": {}, + "2327-\u9577\u671f\u501f\u6b3e \u2014\u95dc\u4fc2\u4eba": {}, + "2328-\u9577\u671f\u501f\u6b3e \u2014\u5176\u4ed6": {} + }, + "233-\u9577\u671f\u61c9\u4ed8\u7968\u64da\u53ca\u6b3e\u9805": { + "2331-\u9577\u671f\u61c9\u4ed8\u7968\u64da": { + "account_type": "Payable" + }, + "2332-\u9577\u671f\u61c9\u4ed8\u5e33\u6b3e": { + "account_type": "Payable" + }, + "2333-\u9577\u671f\u61c9\u4ed8\u79df\u8cc3\u8ca0\u50b5": { + "account_type": "Payable" + }, + "account_type": "Payable" + }, + "234-\u4f30\u8a08\u61c9\u4ed8\u571f\u5730\u589e\u503c\u7a05": { + "2341-\u4f30\u8a08\u61c9\u4ed8\u571f\u5730\u589e\u503c\u7a05": {} + }, + "235-\u61c9\u8a08\u9000\u4f11\u91d1\u8ca0\u50b5": { + "2351-\u61c9\u8a08\u9000\u4f11\u91d1\u8ca0\u50b5": {} + }, + "238-\u5176\u4ed6\u9577\u671f\u8ca0\u50b5": { + "2388-\u5176\u4ed6\u9577\u671f\u8ca0\u50b5\u2014\u5176\u4ed6": {} + } + }, + "28-\u5176\u4ed6\u8ca0\u50b5": { + "281-\u905e\u5ef6\u8ca0\u50b5": { + "2811-\u905e\u5ef6\u6536\u5165": {}, + "2814-\u905e\u5ef6\u6240\u5f97\u7a05\u8ca0\u50b5": {}, + "2818-\u5176\u4ed6\u905e\u5ef6\u8ca0\u50b5": {} + }, + "286-\u5b58\u5165\u4fdd\u8b49\u91d1": { + "2861-\u5b58\u5165\u4fdd\u8b49\u91d1": {} + }, + "288-\u96dc\u9805\u8ca0\u50b5": { + "2888-\u96dc\u9805\u8ca0\u50b5 \u2014\u5176\u4ed6": {} + } + }, + "Stock Received But Not Billed": { + "account_type": "Stock Received But Not Billed" + }, + "root_type": "Liability" + }, + "3-\u696d\u4e3b\u6b0a\u76ca": { + "31-\u8cc7\u672c": { + "311-\u8cc7\u672c\uff08\u80a1\u672c\uff09 ": { + "3111-\u666e\u901a\u80a1\u80a1\u672c": {}, + "3112-\u7279\u5225\u80a1\u80a1\u672c": {}, + "3113-\u9810\u6536\u80a1\u672c": {}, + "3114-\u5f85\u5206\u914d\u80a1\u7968\u80a1\u5229": {}, + "3115-\u8cc7\u672c": {} + } + }, + "32-\u8cc7\u672c\u516c\u7a4d": { + "321-\u80a1\u7968\u6ea2\u50f9": { + "3211-\u666e\u901a\u80a1\u80a1\u7968\u6ea2\u50f9": {}, + "3212-\u7279\u5225\u80a1\u80a1\u7968\u6ea2\u50f9": {} + }, + "323-\u8cc7\u7522\u91cd\u4f30\u589e\u503c\u6e96\u5099": { + "3231-\u8cc7\u7522\u91cd\u4f30\u589e\u503c\u6e96\u5099": {} + }, + "324-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u516c\u7a4d": { + "3241-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u516c\u7a4d": {} + }, + "325-\u5408\u4f75\u516c\u7a4d": { + "3251-\u5408\u4f75\u516c\u7a4d": {} + }, + "326-\u53d7\u8d08\u516c\u7a4d": { + "3261-\u53d7\u8d08\u516c\u7a4d": {} + }, + "328-\u5176\u4ed6\u8cc7\u672c\u516c\u7a4d": { + "3281-\u6b0a\u76ca\u6cd5\u9577\u671f\u80a1\u6b0a\u6295\u8cc7\u8cc7\u672c\u516c\u7a4d": {}, + "3282-\u8cc7\u672c\u516c\u7a4d\u2014 \u5eab\u85cf\u80a1\u7968\u4ea4\u6613": {} + } + }, + "33-\u4fdd\u7559\u76c8\u9918(\u7d2f\u7a4d\u8667\u640d)": { + "331-\u6cd5\u5b9a\u76c8\u9918\u516c\u7a4d": { + "3311-\u6cd5\u5b9a\u76c8\u9918\u516c\u7a4d": {} + }, + "332-\u7279\u5225\u76c8\u9918\u516c\u7a4d": { + "3321-\u610f\u5916\u640d\u5931\u6e96\u5099": {}, + "3322-\u6539\u826f\u64f4\u5145\u6e96\u5099": {}, + "3323-\u511f\u50b5\u6e96\u5099": {}, + "3328-\u5176\u4ed6\u7279\u5225\u76c8\u9918\u516c\u7a4d": {} + }, + "335-\u672a\u5206\u914d\u76c8\u9918(\u7d2f\u7a4d\u8667\u640d) ": { + "is_group": 1 + } + }, + "34-\u6b0a\u76ca\u8abf\u6574": { + "341-\u9577\u671f\u80a1\u6b0a\u6295\u8cc7\u672a\u5be6\u73fe\u8dcc\u50f9\u640d\u5931": { + "3411-\u9577\u671f\u80a1\u6b0a\u6295\u8cc7\u672a\u5be6\u73fe\u8dcc\u50f9\u640d\u5931": {} + }, + "342-\u7d2f\u7a4d\u63db\u7b97\u8abf\u6574\u6578": { + "3421-\u7d2f\u7a4d\u63db\u7b97\u8abf\u6574\u6578": {} + }, + "343-\u672a\u8a8d\u5217\u70ba\u9000\u4f11\u91d1\u6210\u672c\u4e4b\u6de8\u640d\u5931": { + "3431-\u672a\u8a8d\u5217\u70ba\u9000\u4f11\u91d1\u6210\u672c\u4e4b\u6de8\u640d\u5931": {} + } + }, + "35-\u5eab\u85cf\u80a1": { + "351-\u5eab\u85cf\u80a1": { + "3511-\u5eab\u85cf\u80a1": {} + } + }, + "36-\u5c11\u6578\u80a1\u6b0a": { + "361-\u5c11\u6578\u80a1\u6b0a": { + "3611-\u5c11\u6578\u80a1\u6b0a": {} + } + }, + "root_type": "Equity" + }, + "4-\u71df\u696d\u6536\u5165": { + "41-\u92b7\u8ca8\u6536\u5165": { + "411-\u92b7\u8ca8\u6536\u5165": { + "4111-\u92b7\u8ca8\u6536\u5165": {}, + "4112-\u5206\u671f\u4ed8\u6b3e\u92b7\u8ca8\u6536\u5165": {} + }, + "417-\u92b7\u8ca8\u9000\u56de": { + "4171-\u92b7\u8ca8\u9000\u56de": {} + }, + "419-\u92b7\u8ca8\u6298\u8b93": { + "4191-\u92b7\u8ca8\u6298\u8b93": {} + } + }, + "46-\u52de\u52d9\u6536\u5165": { + "461-\u52de\u52d9\u6536\u5165": { + "4611-\u52de\u52d9\u6536\u5165": {} + } + }, + "47-\u696d\u52d9\u6536\u5165": { + "471-\u696d\u52d9\u6536\u5165": { + "4711-\u696d\u52d9\u6536\u5165": {} + } + }, + "48-\u5176\u4ed6\u71df\u696d\u6536\u5165": { + "488-\u5176\u4ed6\u71df\u696d\u6536\u5165\u2014\u5176\u4ed6": { + "4888-\u5176\u4ed6\u71df\u696d\u6536\u5165\u2014\u5176\u4ed6": {} + } + }, + "root_type": "Income" + }, + "5-\u71df\u696d\u6210\u672c": { + "51-\u92b7\u8ca8\u6210\u672c": { + "511-\u92b7\u8ca8\u6210\u672c": { + "5111-\u92b7\u8ca8\u6210\u672c": { + "account_type": "Cost of Goods Sold" + }, + "5112-\u5206\u671f\u4ed8\u6b3e\u92b7\u8ca8\u6210\u672c": { + "account_type": "Cost of Goods Sold" + }, + "account_type": "Cost of Goods Sold" + }, + "512-\u9032\u8ca8": { + "5121-\u9032\u8ca8": {}, + "5122-\u9032\u8ca8\u8cbb\u7528": {}, + "5123-\u9032\u8ca8\u9000\u51fa": {}, + "5124-\u9032\u8ca8\u6298\u8b93": {} + }, + "513-\u9032\u6599": { + "5131-\u9032\u6599": {}, + "5132-\u9032\u6599\u8cbb\u7528": {}, + "5133-\u9032\u6599\u9000\u51fa": {}, + "5134-\u9032\u6599\u6298\u8b93": {} + }, + "514-\u76f4\u63a5\u4eba\u5de5": { + "5141-\u76f4\u63a5\u4eba\u5de5": {} + }, + "515~518-\u88fd\u9020\u8cbb\u7528": { + "5151-\u9593\u63a5\u4eba\u5de5": {}, + "5152-\u79df\u91d1\u652f\u51fa": {}, + "5153-\u6587\u5177\u7528\u54c1": {}, + "5154-\u65c5\u8cbb": {}, + "5155-\u904b\u8cbb": {}, + "5156-\u90f5\u96fb\u8cbb": {}, + "5157-\u4fee\u7e55\u8cbb": {}, + "5158-\u5305\u88dd\u8cbb": {}, + "5161-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "5162-\u4fdd\u96aa\u8cbb": {}, + "5163-\u52a0\u5de5\u8cbb": {}, + "5166-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "5168-\u6298\u820a ": { + "account_type": "Depreciation" + }, + "5169-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "5172-\u4f19\u98df\u8cbb": {}, + "5173-\u8077\u5de5\u798f\u5229": {}, + "5176-\u8a13\u7df4\u8cbb": {}, + "5177-\u9593\u63a5\u6750\u6599": {}, + "5188-\u5176\u4ed6\u88fd\u9020\u8cbb\u7528": {} + }, + "Expenses Included In Valuation": { + "account_type": "Expenses Included In Valuation" + }, + "account_type": "Cost of Goods Sold" + }, + "56-\u52de\u52d9\u6210\u672c\u88fd": { + "561-\u52de\u52d9\u6210\u672c": { + "5611-\u52de\u52d9\u6210\u672c": {} + } + }, + "57-\u696d\u52d9\u6210\u672c": { + "571-\u696d\u52d9\u6210\u672c": { + "5711-\u696d\u52d9\u6210\u672c": {} + } + }, + "58-\u5176\u4ed6\u71df\u696d\u6210\u672c": { + "588-\u5176\u4ed6\u71df\u696d\u6210\u672c\u2014\u5176\u4ed6 ": { + "5888-\u5176\u4ed6\u71df\u696d\u6210\u672c\u2014\u5176\u4ed6": {} + } + }, + "Stock Adjustment": { + "account_type": "Stock Adjustment" + }, + "root_type": "Expense" + }, + "6-\u71df\u696d\u8cbb\u7528": { + "61-\u63a8\u92b7\u8cbb\u7528": { + "615~618-\u63a8\u92b7\u8cbb\u7528": { + "6151-\u85aa\u8cc7\u652f\u51fa": {}, + "6152-\u79df\u91d1\u652f\u51fa": {}, + "6153-\u6587\u5177\u7528\u54c1": {}, + "6154-\u65c5\u8cbb": {}, + "6155-\u904b\u8cbb": {}, + "6156-\u90f5\u96fb\u8cbb": {}, + "6157-\u4fee\u7e55\u8cbb": {}, + "6159-\u5ee3\u544a\u8cbb": {}, + "6161-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "6162-\u4fdd\u96aa\u8cbb": {}, + "6164-\u4ea4\u969b\u8cbb": {}, + "6165-\u6350\u8d08": {}, + "6166-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "6167-\u5446\u5e33\u640d\u5931": {}, + "6168-\u6298\u820a ": { + "account_type": "Depreciation" + }, + "6169-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "6172-\u4f19\u98df\u8cbb": {}, + "6173-\u8077\u5de5\u798f\u5229": {}, + "6175-\u4f63\u91d1\u652f\u51fa": {}, + "6176-\u8a13\u7df4\u8cbb": {}, + "6188-\u5176\u4ed6\u63a8\u92b7\u8cbb\u7528": {} + } + }, + "62-\u7ba1\u7406\u53ca\u7e3d\u52d9\u8cbb\u7528": { + "625~628-\u7ba1\u7406\u53ca\u7e3d\u52d9\u8cbb\u7528": { + "6251-\u85aa\u8cc7\u652f\u51fa": {}, + "6252-\u79df\u91d1\u652f\u51fa": {}, + "6253-\u6587\u5177\u7528\u54c1": {}, + "6254-\u65c5\u8cbb": {}, + "6255-\u904b\u8cbb": {}, + "6256-\u90f5\u96fb\u8cbb": {}, + "6257-\u4fee\u7e55\u8cbb": {}, + "6259-\u5ee3\u544a\u8cbb": {}, + "6261-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "6262-\u4fdd\u96aa\u8cbb": {}, + "6264-\u4ea4\u969b\u8cbb": {}, + "6265-\u6350\u8d08": {}, + "6266-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "6267-\u5446\u5e33\u640d\u5931": {}, + "6268-\u6298\u820a": { + "account_type": "Depreciation" + }, + "6269-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "6271-\u5916\u92b7\u640d\u5931": {}, + "6272-\u4f19\u98df\u8cbb": {}, + "6273-\u8077\u5de5\u798f\u5229": {}, + "6274-\u7814\u7a76\u767c\u5c55\u8cbb\u7528": {}, + "6275-\u4f63\u91d1\u652f\u51fa": {}, + "6276-\u8a13\u7df4\u8cbb": {}, + "6278-\u52de\u52d9\u8cbb": {}, + "6288-\u5176\u4ed6\u7ba1\u7406\u53ca\u7e3d\u52d9\u8cbb\u7528": {} + } + }, + "63-\u7814\u7a76\u767c\u5c55\u8cbb\u7528": { + "635~638-\u7814\u7a76\u767c\u5c55\u8cbb\u7528": { + "6351-\u85aa\u8cc7\u652f\u51fa": {}, + "6352-\u79df\u91d1\u652f\u51fa": {}, + "6353-\u6587\u5177\u7528\u54c1": {}, + "6354-\u65c5\u8cbb": {}, + "6355-\u904b\u8cbb": {}, + "6356-\u90f5\u96fb\u8cbb": {}, + "6357-\u4fee\u7e55\u8cbb": {}, + "6361-\u6c34\u96fb\u74e6\u65af\u8cbb": {}, + "6362-\u4fdd\u96aa\u8cbb": {}, + "6364-\u4ea4\u969b\u8cbb": {}, + "6366-\u7a05\u6350": { + "account_type": "Tax", + "tax_rate": 5.0 + }, + "6368-\u6298\u820a": { + "account_type": "Depreciation" + }, + "6369-\u5404\u9805\u8017\u7aed\u53ca\u6524\u63d0": {}, + "6372-\u4f19\u98df\u8cbb": {}, + "6373-\u8077\u5de5\u798f\u5229": {}, + "6376-\u8a13\u7df4\u8cbb": {}, + "6378-\u5176\u4ed6\u7814\u7a76\u767c\u5c55\u8cbb\u7528": {} + } + }, + "root_type": "Expense" + }, + "7-\u71df\u696d\u5916\u6536\u5165\u53ca\u8cbb\u7528": { + "71~74-\u71df\u696d\u5916\u6536\u5165": { + "711-\u5229\u606f\u6536\u5165": { + "7111-\u5229\u606f\u6536\u5165": {} + }, + "712-\u6295\u8cc7\u6536\u76ca": { + "7121-\u6b0a\u76ca\u6cd5\u8a8d\u5217\u4e4b\u6295\u8cc7\u6536\u76ca": {}, + "7122-\u80a1\u5229\u6536\u5165": {}, + "7123-\u77ed\u671f\u6295\u8cc7\u5e02\u50f9\u56de\u5347\u5229\u76ca": {} + }, + "713-\u514c\u63db\u5229\u76ca": { + "7131-\u514c\u63db\u5229\u76ca": {} + }, + "714-\u8655\u5206\u6295\u8cc7\u6536\u76ca": { + "7141-\u8655\u5206\u6295\u8cc7\u6536\u76ca": {} + }, + "715-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u6536\u5165": { + "7151-\u8655\u5206\u8cc7\u7522\u6ea2\u50f9\u6536\u5165": {} + }, + "748-\u5176\u4ed6\u71df\u696d\u5916\u6536\u5165": { + "7481-\u6350\u8d08\u6536\u5165": {}, + "7482-\u79df\u91d1\u6536\u5165": {}, + "7483-\u4f63\u91d1\u6536\u5165": {}, + "7484-\u51fa\u552e\u4e0b\u8173\u53ca\u5ee2\u6599\u6536\u5165": {}, + "7485-\u5b58\u8ca8\u76e4\u76c8": {}, + "7486-\u5b58\u8ca8\u8dcc\u50f9\u56de\u5347\u5229\u76ca": {}, + "7487-\u58de\u5e33\u8f49\u56de\u5229\u76ca": {}, + "7488-\u5176\u4ed6\u71df\u696d\u5916\u6536\u5165\u2014\u5176\u4ed6": {} + } + }, + "75~78-\u71df\u696d\u5916\u8cbb\u7528": { + "751-\u5229\u606f\u8cbb\u7528": { + "7511-\u5229\u606f\u8cbb\u7528": {} + }, + "752-\u6295\u8cc7\u640d\u5931": { + "7521-\u6b0a\u76ca\u6cd5\u8a8d\u5217\u4e4b\u6295\u8cc7\u640d\u5931": {}, + "7523-\u77ed\u671f\u6295\u8cc7\u672a\u5be6\u73fe\u8dcc\u50f9\u640d\u5931": {} + }, + "753-\u514c\u63db\u640d\u5931": { + "7531-\u514c\u63db\u640d\u5931": {} + }, + "754-\u8655\u5206\u6295\u8cc7\u640d\u5931": { + "7541-\u8655\u5206\u6295\u8cc7\u640d\u5931": {} + }, + "755-\u8655\u5206\u8cc7\u7522\u640d\u5931": { + "7551-\u8655\u5206\u8cc7\u7522\u640d\u5931 ": {} + }, + "788-\u5176\u4ed6\u71df\u696d\u5916\u8cbb\u7528": { + "7881-\u505c\u5de5\u640d\u5931": {}, + "7882-\u707d\u5bb3\u640d\u5931": {}, + "7885-\u5b58\u8ca8\u76e4\u640d": {}, + "7886-\u5b58\u8ca8\u8dcc\u50f9\u53ca\u5446\u6eef\u640d\u5931": {}, + "7888-\u5176\u4ed6\u71df\u696d\u5916\u8cbb\u7528\u2014\u5176\u4ed6": {} + } + }, + "root_type": "Income" + }, + "8-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca)": { + "81-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca)": { + "811-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca) ": { + "8111-\u6240\u5f97\u7a05\u8cbb\u7528(\u5229\u76ca) ": {} + } + }, + "root_type": "Expense" + }, + "9-\u975e\u7d93\u5e38\u71df\u696d\u640d\u76ca": { + "91-\u505c\u696d\u90e8\u9580\u640d\u76ca": { + "911-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u505c\u696d\u524d\u71df\u696d\u640d\u76ca": { + "9111-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u505c\u696d\u524d\u71df\u696d\u640d\u76ca": {} + }, + "912-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u8655\u5206\u640d\u76ca": { + "9121-\u505c\u696d\u90e8\u9580\u640d\u76ca\u2014\u8655\u5206\u640d\u76ca": {} + } + }, + "92-\u975e\u5e38\u640d\u76ca": { + "921-\u975e\u5e38\u640d\u76ca": { + "9211-\u975e\u5e38\u640d\u76ca": {} + } + }, + "93-\u6703\u8a08\u539f\u5247\u8b8a\u52d5\u7d2f\u7a4d\u5f71\u97ff\u6578": { + "931-\u6703\u8a08\u539f\u5247\u8b8a\u52d5\u7d2f\u7a4d\u5f71\u97ff\u6578": { + "9311-\u6703\u8a08\u539f\u5247\u8b8a\u52d5\u7d2f\u7a4d\u5f71\u97ff\u6578": {} + } + }, + "94-\u5c11\u6578\u80a1\u6b0a\u6de8\u5229": { + "941-\u5c11\u6578\u80a1\u6b0a\u6de8\u5229": { + "9411-\u5c11\u6578\u80a1\u6b0a\u6de8\u5229": {} + } + }, + "root_type": "Expense" + } + } +} \ No newline at end of file diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index adbc5985f6..a5afbdb450 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -47,7 +47,7 @@ class TestCompany(unittest.TestCase): def test_coa_based_on_country_template(self): countries = ["India", "Brazil", "United Arab Emirates", "Canada", "Germany", "France", "Guatemala", "Indonesia", "Mexico", "Nicaragua", "Netherlands", "Singapore", - "Brazil", "Argentina", "Hungary"] + "Brazil", "Argentina", "Hungary", "Taiwan"] for country in countries: templates = get_charts_for_country(country) From d46a9572af977d4aefd39690039c533d47647102 Mon Sep 17 00:00:00 2001 From: mbauskar Date: Tue, 22 Aug 2017 20:52:24 +0600 Subject: [PATCH 08/39] bumped to version 8.9.0 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ecf0d35c6b..0f310836e3 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from erpnext.hooks import regional_overrides -__version__ = '8.8.6' +__version__ = '8.9.0' def get_default_company(user=None): '''Get default company for user''' From fcb246290241fdfb3e5218c785aa24caa9c726c0 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 23 Aug 2017 09:44:33 +0530 Subject: [PATCH 09/39] Minor fix in itemised sales register --- .../item_wise_purchase_register.py | 17 +++++++++-------- .../item_wise_sales_register.py | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 8f9948e41c..ef7b4d5161 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -125,14 +125,15 @@ def get_purchase_receipts_against_purchase_order(item_list): po_pr_map = frappe._dict() po_item_rows = list(set([d.po_detail for d in item_list])) - purchase_receipts = frappe.db.sql(""" - select parent, purchase_order_item - from `tabPurchase Receipt Item` - where docstatus=1 and purchase_order_item in (%s) - group by purchase_order_item, parent - """ % (', '.join(['%s']*len(po_item_rows))), tuple(po_item_rows), as_dict=1) + if po_item_rows: + purchase_receipts = frappe.db.sql(""" + select parent, purchase_order_item + from `tabPurchase Receipt Item` + where docstatus=1 and purchase_order_item in (%s) + group by purchase_order_item, parent + """ % (', '.join(['%s']*len(po_item_rows))), tuple(po_item_rows), as_dict=1) - for pr in purchase_receipts: - po_pr_map.setdefault(pr.po_detail, []).append(pr.parent) + for pr in purchase_receipts: + po_pr_map.setdefault(pr.po_detail, []).append(pr.parent) return po_pr_map \ No newline at end of file diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 30c545f58c..48ea313dbe 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -127,15 +127,16 @@ def get_delivery_notes_against_sales_order(item_list): so_dn_map = frappe._dict() so_item_rows = list(set([d.so_detail for d in item_list])) - delivery_notes = frappe.db.sql(""" - select parent, so_detail - from `tabDelivery Note Item` - where docstatus=1 and so_detail in (%s) - group by so_detail, parent - """ % (', '.join(['%s']*len(so_item_rows))), tuple(so_item_rows), as_dict=1) + if so_item_rows: + delivery_notes = frappe.db.sql(""" + select parent, so_detail + from `tabDelivery Note Item` + where docstatus=1 and so_detail in (%s) + group by so_detail, parent + """ % (', '.join(['%s']*len(so_item_rows))), tuple(so_item_rows), as_dict=1) - for dn in delivery_notes: - so_dn_map.setdefault(dn.so_detail, []).append(dn.parent) + for dn in delivery_notes: + so_dn_map.setdefault(dn.so_detail, []).append(dn.parent) return so_dn_map From cb40bd86f842f364aa2dc5ae80399972b3c6f787 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 23 Aug 2017 13:04:50 +0530 Subject: [PATCH 10/39] Supplier bill no in GST itemised purchase register (#10513) --- .../gst_itemised_purchase_register.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py index 1d94c97ed2..1a54cc3ff2 100644 --- a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py +++ b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py @@ -13,7 +13,8 @@ def execute(filters=None): dict(fieldtype='Data', label='Invoice Type', width=120), dict(fieldtype='Data', label='Export Type', width=120), dict(fieldtype='Data', label='E-Commerce GSTIN', width=130), - dict(fieldtype='Data', label='HSN Code', width=120) + dict(fieldtype='Data', label='HSN Code', width=120), + dict(fieldtype='Data', label='Supplier Invoice No', width=120) ], additional_query_columns=[ 'supplier_gstin', 'company_gstin', @@ -21,5 +22,6 @@ def execute(filters=None): 'invoice_type', 'export_type', 'ecommerce_gstin', - 'gst_hsn_code' + 'gst_hsn_code', + 'bill_no' ]) From 918f029d98855144721b2151cbe12e866036cf83 Mon Sep 17 00:00:00 2001 From: mbauskar Date: Wed, 23 Aug 2017 13:36:43 +0600 Subject: [PATCH 11/39] bumped to version 8.9.1 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 0f310836e3..16c346baa8 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from erpnext.hooks import regional_overrides -__version__ = '8.9.0' +__version__ = '8.9.1' def get_default_company(user=None): '''Get default company for user''' From dd8c0febd5c85c689805f9c0abd7f70ed5eee76e Mon Sep 17 00:00:00 2001 From: Makarand Bauskar Date: Wed, 23 Aug 2017 13:17:37 +0530 Subject: [PATCH 12/39] [minor] changed the modified date for bom for rename_bom_update_tool patch (#10509) --- erpnext/manufacturing/doctype/bom/bom.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 886c9428e9..46a1ffd101 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -1671,7 +1671,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-11 14:09:30.492628", + "modified": "2017-08-23 14:09:30.492628", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", From 4a546e6c1850d1d80575dd30db367f14093cf9c5 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 23 Aug 2017 16:16:28 +0530 Subject: [PATCH 13/39] [minor] delivery note standard filter --- erpnext/stock/doctype/delivery_note/delivery_note.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 4477c1d0ec..41f8b8493e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -3113,7 +3113,7 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, - "in_standard_filter": 1, + "in_standard_filter": 0, "label": "Installation Status", "length": 0, "no_copy": 0, @@ -3487,7 +3487,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-08-09 15:44:14.253457", + "modified": "2017-08-23 13:25:34.182268", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 641d88fd6b16ba5ba0b78ece1bcc287981a47a5f Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Wed, 23 Aug 2017 18:32:12 +0530 Subject: [PATCH 14/39] [UI Test] UI Test added for Journal Entry (#10512) * [UI Test] UI Test added for Journal Entry * [mod] Edited date in journal Entry * [fix] Codacy fixed --- .../journal_entry/test_journal_entry.js | 39 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 1 + 2 files changed, 40 insertions(+) create mode 100644 erpnext/accounts/doctype/journal_entry/test_journal_entry.js diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.js b/erpnext/accounts/doctype/journal_entry/test_journal_entry.js new file mode 100644 index 0000000000..28ccd95592 --- /dev/null +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.js @@ -0,0 +1,39 @@ +QUnit.module('Journal Entry'); + +QUnit.test("test journal entry", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Journal Entry', [ + {posting_date:frappe.datetime.add_days(frappe.datetime.nowdate(), 0)}, + {accounts: [ + [ + {'account':'Debtors - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {'party_type':'Customer'}, + {'party':'Test Customer 1'}, + {'credit_in_account_currency':1000}, + {'is_advance':'Yes'}, + ], + [ + {'account':'HDFC - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {'debit_in_account_currency':1000}, + ] + ]}, + {cheque_no:1234}, + {cheque_date: frappe.datetime.add_days(frappe.datetime.nowdate(), -1)}, + {user_remark: 'Test'}, + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.total_debit==1000, "total debit correct"); + assert.ok(cur_frm.doc.total_credit==1000, "total credit correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index cb33c90c1f..f76b1c0c1d 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -103,3 +103,4 @@ erpnext/schools/doctype/assessment_plan/test_assessment_plan.js erpnext/schools/doctype/assessment_result/test_assessment_result.js erpnext/schools/doctype/assessment_result_tool/test_assessment_result_tool.js erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js +erpnext/accounts/doctype/journal_entry/test_journal_entry.js From 72fa958f2c8e87ba11aa1d3a0613fb447939aebb Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 23 Aug 2017 18:47:57 +0530 Subject: [PATCH 15/39] [layout] lead and opportunity (#10487) --- erpnext/crm/doctype/lead/lead.json | 167 ++++----- .../crm/doctype/opportunity/opportunity.json | 320 +++++++++--------- 2 files changed, 245 insertions(+), 242 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index e7c2d33f3c..c9d04ac1fc 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -76,36 +76,6 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "salutation", - "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": "Salutation", - "length": 0, - "no_copy": 0, - "options": "Salutation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -141,21 +111,21 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "gender", - "fieldtype": "Link", + "fieldname": "company_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_list_view": 1, "in_standard_filter": 0, - "label": "Gender", + "label": "Organization Name", "length": 0, "no_copy": 0, - "options": "Gender", + "oldfieldname": "company_name", + "oldfieldtype": "Data", "permlevel": 0, - "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -260,6 +230,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "gender", + "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": "Gender", + "length": 0, + "no_copy": 0, + "options": "Gender", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -295,36 +296,6 @@ }, { "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Organization Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "company_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -435,6 +406,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Follow Up", "length": 0, "no_copy": 0, "permlevel": 0, @@ -514,11 +486,12 @@ { "allow_bulk_edit": 0, "allow_on_submit": 0, - "bold": 0, + "bold": 1, "collapsible": 0, "columns": 0, - "fieldname": "contact_by", - "fieldtype": "Link", + "description": "", + "fieldname": "contact_date", + "fieldtype": "Datetime", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -526,12 +499,11 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Next Contact By", + "label": "Next Contact Date", "length": 0, - "no_copy": 0, - "oldfieldname": "contact_by", - "oldfieldtype": "Link", - "options": "User", + "no_copy": 1, + "oldfieldname": "contact_date", + "oldfieldtype": "Date", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -550,9 +522,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "Add to calendar on this date", - "fieldname": "contact_date", - "fieldtype": "Datetime", + "fieldname": "contact_by", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -560,11 +531,12 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Next Contact Date", + "label": "Next Contact By", "length": 0, - "no_copy": 1, - "oldfieldname": "contact_date", - "oldfieldtype": "Date", + "no_copy": 0, + "oldfieldname": "contact_by", + "oldfieldtype": "Link", + "options": "User", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -726,6 +698,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "salutation", + "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": "Salutation", + "length": 0, + "no_copy": 0, + "options": "Salutation", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1144,7 +1147,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-22 14:29:12.700000", + "modified": "2017-08-21 02:28:21.581948", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index dc8b3e72a0..89eb1919d0 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -419,6 +419,164 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "collapsible_depends_on": "contact_by", + "columns": 0, + "fieldname": "next_contact", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Follow Up", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "contact_by", + "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": 1, + "label": "Next Contact By", + "length": 0, + "no_copy": 0, + "oldfieldname": "contact_by", + "oldfieldtype": "Link", + "options": "User", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "75px" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "contact_date", + "fieldtype": "Datetime", + "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": "Next Contact Date", + "length": 0, + "no_copy": 0, + "oldfieldname": "contact_date", + "oldfieldtype": "Date", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break2", + "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, + "oldfieldtype": "Column Break", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0, + "width": "50%" + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_discuss", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To Discuss", + "length": 0, + "no_copy": 1, + "oldfieldname": "to_discuss", + "oldfieldtype": "Small Text", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -435,7 +593,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "", + "label": "Items", "length": 0, "no_copy": 0, "oldfieldtype": "Section Break", @@ -986,164 +1144,6 @@ "unique": 0, "width": "50px" }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "contact_by", - "columns": 0, - "fieldname": "next_contact", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Next Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Your sales person who will contact the customer in future", - "fieldname": "contact_by", - "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": 1, - "label": "Next Contact By", - "length": 0, - "no_copy": 0, - "oldfieldname": "contact_by", - "oldfieldtype": "Link", - "options": "User", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "75px" - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Your sales person will get a reminder on this date to contact the customer", - "fieldname": "contact_date", - "fieldtype": "Datetime", - "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": "Next Contact Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "contact_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break2", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_discuss", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To Discuss", - "length": 0, - "no_copy": 1, - "oldfieldname": "to_discuss", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1189,7 +1189,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-07 21:25:10.836517", + "modified": "2017-08-21 02:07:46.486433", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", From 7e506af0b91a97c4543dfd7e52afc9ff3cb509d7 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 24 Aug 2017 15:23:33 +0530 Subject: [PATCH 16/39] [fix] warehouse query fixes https://github.com/frappe/erpnext/issues/10537 (#10538) --- erpnext/controllers/queries.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 6d69a48ab8..11c0790976 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -232,7 +232,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date from `tabDelivery Note` where `tabDelivery Note`.`%(key)s` like %(txt)s and - `tabDelivery Note`.docstatus = 1 and `tabDelivery Note`.is_return = 0 + `tabDelivery Note`.docstatus = 1 and `tabDelivery Note`.is_return = 0 and status not in ("Stopped", "Closed") %(fcond)s and (`tabDelivery Note`.per_billed < 100 or `tabDelivery Note`.grand_total = 0) %(mcond)s order by `tabDelivery Note`.`%(key)s` asc @@ -367,31 +367,30 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, 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=get_filters_cond(doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True)) - response = frappe.db.sql("""select `tabWarehouse`.name, + query = """select `tabWarehouse`.name, CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty from `tabWarehouse` where - `tabWarehouse`.`{key}` like %(txt)s + `tabWarehouse`.`{key}` like '{txt}' {fcond} {mcond} order by `tabWarehouse`.name desc limit - %(start)s, %(page_len)s + {start}, {page_len} """.format( sub_query=sub_query, key=frappe.db.escape(searchfield), fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions), - mcond=get_match_cond(doctype) - ), - { - "txt": "%%%s%%" % frappe.db.escape(txt), - "start": start, - "page_len": page_len - }) - return response + mcond=get_match_cond(doctype), + start=start, + page_len=page_len, + txt=frappe.db.escape('%{0}%'.format(txt)) + ) + + return frappe.db.sql(query) def get_doctype_wise_filters(filters): From 6b651d734e714f2c4b7f48dfda1d5dade1c89004 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 24 Aug 2017 15:47:49 +0530 Subject: [PATCH 17/39] [fix] status in purchase_receipt.py (#10534) --- erpnext/patches.txt | 3 ++- erpnext/patches/v8_7/fix_purchase_receipt_status.py | 12 ++++++++++++ .../doctype/purchase_receipt/purchase_receipt.py | 5 ----- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 erpnext/patches/v8_7/fix_purchase_receipt_status.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d781ec788f..35530fd9cf 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -432,4 +432,5 @@ erpnext.patches.v8_5.update_customer_group_in_POS_profile erpnext.patches.v8_6.update_timesheet_company_from_PO erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager erpnext.patches.v8_5.remove_project_type_property_setter -erpnext.patches.v8_7.add_more_gst_fields \ No newline at end of file +erpnext.patches.v8_7.add_more_gst_fields +erpnext.patches.v8_7.fix_purchase_receipt_status \ No newline at end of file diff --git a/erpnext/patches/v8_7/fix_purchase_receipt_status.py b/erpnext/patches/v8_7/fix_purchase_receipt_status.py new file mode 100644 index 0000000000..f7037dd7df --- /dev/null +++ b/erpnext/patches/v8_7/fix_purchase_receipt_status.py @@ -0,0 +1,12 @@ +import frappe + +def execute(): + # there is no more status called "Submitted", there was an old issue that used + # to set it as Submitted, fixed in this commit + frappe.db.sql(""" + update + `tabPurchase Receipt` + set + status = 'To Bill' + where + status = 'Submitted'""") \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 0444369ff3..d12c288a28 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -115,9 +115,6 @@ class PurchaseReceipt(BuyingController): frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) - # Set status as Submitted - frappe.db.set(self, 'status', 'Submitted') - self.update_prevdoc_status() if self.per_billed < 100: self.update_billing_status() @@ -152,8 +149,6 @@ class PurchaseReceipt(BuyingController): if submitted: frappe.throw(_("Purchase Invoice {0} is already submitted").format(submitted[0][0])) - frappe.db.set(self,'status','Cancelled') - self.update_prevdoc_status() self.update_billing_status() From 3da5574d7918dcc943614c91b5bae2af19667e55 Mon Sep 17 00:00:00 2001 From: Utkarsh Goswami Date: Thu, 24 Aug 2017 15:48:57 +0530 Subject: [PATCH 18/39] Test for appraisal (#10525) --- .../hr/doctype/appraisal/test_appraisal.js | 58 +++++++++++++++++++ .../test_appraisal_template.js | 30 ++++++++++ erpnext/tests/ui/tests.txt | 4 +- 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 erpnext/hr/doctype/appraisal/test_appraisal.js create mode 100644 erpnext/hr/doctype/appraisal_template/test_appraisal_template.js diff --git a/erpnext/hr/doctype/appraisal/test_appraisal.js b/erpnext/hr/doctype/appraisal/test_appraisal.js new file mode 100644 index 0000000000..91da7d3624 --- /dev/null +++ b/erpnext/hr/doctype/appraisal/test_appraisal.js @@ -0,0 +1,58 @@ +QUnit.module('hr'); + +QUnit.test("Test: Expense Claim [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creating Appraisal + () => frappe.set_route('List','Appraisal','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Appraisal'), + () => { + cur_frm.set_value('kra_template','Test Appraisal 1'), + cur_frm.set_value('start_date','2017-08-21'), + cur_frm.set_value('end_date','2017-09-21'); + }, + () => frappe.timeout(1), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 1','score',4), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 1','score_earned',2), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 2','score',4), + () => frappe.model.set_value('Appraisal Goal','New Appraisal Goal 2','score_earned',2), + () => frappe.timeout(1), + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + + () => frappe.timeout(1), + () => cur_frm.set_value('employee',employee_name), + () => cur_frm.set_value('employee_name','Test Employee 1'), + () => cur_frm.set_value('company','Test Company'), + () => frappe.click_button('Calculate Total Score'), + () => frappe.timeout(1), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.save(), + + // Submitting the Appraisal + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if the appraisal is correctly set for the employee + () => { + assert.equal('Submitted',cur_frm.get_field('status').value, + 'Appraisal is submitted'); + + assert.equal('Test Employee 1',cur_frm.get_field('employee_name').value, + 'Appraisal is created for correct employee'); + + assert.equal(4,cur_frm.get_field('total_score').value, + 'Total score is correctly calculated'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js new file mode 100644 index 0000000000..4e245c7117 --- /dev/null +++ b/erpnext/hr/doctype/appraisal_template/test_appraisal_template.js @@ -0,0 +1,30 @@ +QUnit.module('hr'); +QUnit.test("Test: Appraisal Template [HR]", function (assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + // Job Opening creation + () => { + frappe.tests.make('Appraisal Template', [ + { kra_title: 'Test Appraisal 1'}, + { description: 'This is just a test'}, + { goals: [ + [ + { kra: 'Design'}, + { per_weightage: 50} + ], + [ + { kra: 'Code creation'}, + { per_weightage: 50} + ] + ]}, + ]); + }, + () => frappe.timeout(5), + () => { + assert.equal('Test Appraisal 1',cur_frm.doc.kra_title, 'Appraisal name correctly set'); + }, + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index f76b1c0c1d..970b00d8c7 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -66,6 +66,8 @@ erpnext/hr/doctype/process_payroll/test_process_payroll.js erpnext/hr/doctype/job_opening/test_job_opening.js erpnext/hr/doctype/job_applicant/test_job_applicant.js erpnext/hr/doctype/offer_letter/test_offer_letter.js +erpnext/hr/doctype/appraisal_template/test_appraisal_template.js +erpnext/hr/doctype/appraisal/test_appraisal.js erpnext/buying/doctype/supplier/test_supplier.js erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js @@ -102,5 +104,5 @@ erpnext/schools/doctype/assessment_group/test_assessment_group.js erpnext/schools/doctype/assessment_plan/test_assessment_plan.js erpnext/schools/doctype/assessment_result/test_assessment_result.js erpnext/schools/doctype/assessment_result_tool/test_assessment_result_tool.js -erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js erpnext/accounts/doctype/journal_entry/test_journal_entry.js +erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js \ No newline at end of file From 1010a2a2a48ac8a6ba1239fe544de205008e1faa Mon Sep 17 00:00:00 2001 From: Zarrar Date: Thu, 24 Aug 2017 16:22:06 +0530 Subject: [PATCH 19/39] Resolving some issues in school [ issue no:- #10464 ] (#10529) * guardian_name issue fix * guardian table issue resolved * resolved fetching issue + unnecessary code removed --- erpnext/schools/doctype/student/student.js | 21 ++++++------------- .../student_applicant/student_applicant.js | 11 ++++------ .../student_applicant/student_applicant.json | 4 ++-- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/erpnext/schools/doctype/student/student.js b/erpnext/schools/doctype/student/student.js index d3d248b2cb..cadf272a1f 100644 --- a/erpnext/schools/doctype/student/student.js +++ b/erpnext/schools/doctype/student/student.js @@ -3,6 +3,11 @@ frappe.ui.form.on('Student', { setup: function(frm) { + frm.add_fetch("guardian", "guardian_name", "guardian_name"); + frm.add_fetch("student", "title", "full_name"); + frm.add_fetch("student", "gender", "gender"); + frm.add_fetch("student", "date_of_birth", "date_of_birth"); + frm.set_query("student", "siblings", function(doc, cdt, cdn) { return { "filters": { @@ -11,18 +16,4 @@ frappe.ui.form.on('Student', { }; }) } -}); - -frappe.ui.form.on("Student Guardian", { - guardian: function(frm) { - frm.add_fetch("guardian", "guardian_name", "guardian_name"); - } -}); - -frappe.ui.form.on('Student Sibling', { - student: function(frm) { - frm.add_fetch("student", "title", "full_name"); - frm.add_fetch("student", "gender", "gender"); - frm.add_fetch("student", "date_of_birth", "date_of_birth"); - } -}); +}); \ No newline at end of file diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.js b/erpnext/schools/doctype/student_applicant/student_applicant.js index 9b08ee5f6a..40a6ac3a3d 100644 --- a/erpnext/schools/doctype/student_applicant/student_applicant.js +++ b/erpnext/schools/doctype/student_applicant/student_applicant.js @@ -39,15 +39,12 @@ frappe.ui.form.on("Student Applicant", { method: "erpnext.schools.api.enroll_student", frm: frm }) - } -}); + }, - -frappe.ui.form.on('Student Sibling', { - student: function(frm) { + setup: function(frm) { + frm.add_fetch("guardian", "guardian_name", "guardian_name"); frm.add_fetch("student", "title", "full_name"); frm.add_fetch("student", "gender", "gender"); frm.add_fetch("student", "date_of_birth", "date_of_birth"); } -}); - +}); \ No newline at end of file diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.json b/erpnext/schools/doctype/student_applicant/student_applicant.json index 271f873195..578f84ceff 100644 --- a/erpnext/schools/doctype/student_applicant/student_applicant.json +++ b/erpnext/schools/doctype/student_applicant/student_applicant.json @@ -881,7 +881,7 @@ "in_standard_filter": 0, "label": "Guardians", "length": 0, - "no_copy": 1, + "no_copy": 0, "options": "Student Guardian", "permlevel": 0, "precision": "", @@ -1058,7 +1058,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-06-30 08:21:50.917086", + "modified": "2017-08-23 06:12:36.996978", "modified_by": "Administrator", "module": "Schools", "name": "Student Applicant", From caab5829430fb7ddcc152a0d93e1254989b209f8 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 24 Aug 2017 16:22:28 +0530 Subject: [PATCH 20/39] Tax breakup for actual amount and fixes in itemised purchase register (#10515) * Tax breakup for actual amount and fixes in itemised pur register * Rounding of itemised tax breakup amount --- .../item_wise_purchase_register.py | 8 +++-- .../item_wise_sales_register.py | 27 +++++++++++---- erpnext/controllers/taxes_and_totals.py | 33 ++++++++++++++----- .../includes/itemised_tax_breakup.html | 4 ++- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 3171ff01bf..fa458df472 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import get_tax_accounts @@ -14,10 +14,12 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if not filters: filters = {} columns = get_columns(additional_table_columns) + company_currency = erpnext.get_company_currency(filters.company) + item_list = get_items(filters, additional_query_columns) aii_account_map = get_aii_accounts() if item_list: - itemised_tax, tax_columns = get_tax_accounts(item_list, columns, + itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") columns.append({ @@ -26,7 +28,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum "fieldtype": "Data", "width": 80 }) - company_currency = frappe.db.get_value("Company", filters.company, "default_currency") + po_pr_map = get_purchase_receipts_against_purchase_order(item_list) data = [] diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 7009d5dc79..0fc58316ef 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -2,9 +2,10 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt +from frappe.model.meta import get_field_precision from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments def execute(filters=None): @@ -14,16 +15,17 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum if not filters: filters = {} columns = get_columns(additional_table_columns) + company_currency = erpnext.get_company_currency(filters.company) + item_list = get_items(filters, additional_query_columns) if item_list: - itemised_tax, tax_columns = get_tax_accounts(item_list, columns) + itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) columns.append({ "fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80 }) - company_currency = frappe.db.get_value("Company", filters.get("company"), "default_currency") mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list])) so_dn_map = get_delivery_notes_against_sales_order(item_list) @@ -140,16 +142,25 @@ def get_delivery_notes_against_sales_order(item_list): return so_dn_map -def get_tax_accounts(item_list, columns, 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 = {} itemised_tax = {} + + tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"), + currency=company_currency) or 2 + for d in item_list: invoice_item_row.setdefault(d.parent, []).append(d) item_row_map.setdefault(d.parent, {}).setdefault(d.item_code, []).append(d) + conditions = "" + if doctype == "Purchase Invoice": + conditions = " and category in ('Total', 'Valuation and Total')" + tax_details = frappe.db.sql(""" select parent, description, item_wise_tax_detail, @@ -159,8 +170,9 @@ def get_tax_accounts(item_list, columns, doctype="Sales Invoice", tax_doctype="S parenttype = %s and docstatus = 1 and (description is not null and description != '') and parent in (%s) + %s order by description - """ % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row))), + """ % (tax_doctype, '%s', ', '.join(['%s']*len(invoice_item_row)), conditions), tuple([doctype] + invoice_item_row.keys())) for parent, description, item_wise_tax_detail, charge_type, tax_amount in tax_details: @@ -192,7 +204,7 @@ def get_tax_accounts(item_list, columns, doctype="Sales Invoice", tax_doctype="S if item_tax_amount: itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ "tax_rate": tax_rate, - "tax_amount": item_tax_amount + "tax_amount": flt(item_tax_amount, tax_amount_precision) }) except ValueError: @@ -201,7 +213,8 @@ def get_tax_accounts(item_list, columns, doctype="Sales Invoice", tax_doctype="S for d in invoice_item_row.get(parent, []): itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ "tax_rate": "NA", - "tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total) + "tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total, + tax_amount_precision) }) tax_columns.sort() diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 97bd771fa2..c627664ea5 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -270,7 +270,7 @@ class calculate_taxes_and_totals(object): if tax.item_wise_tax_detail.get(key): item_wise_tax_amount += tax.item_wise_tax_detail[key][1] - tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount, tax.precision("base_tax_amount"))] + tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)] def round_off_totals(self, tax): tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount")) @@ -521,12 +521,20 @@ def get_itemised_tax_breakup_html(doc): frappe.flags.company = doc.company # get headers - tax_accounts = list(set([d.description for d in doc.taxes])) + tax_accounts = [] + for tax in doc.taxes: + if getattr(tax, "category", None) and tax.category=="Valuation": + continue + if tax.description not in tax_accounts: + tax_accounts.append(tax.description) + headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts) # get tax breakup data itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc) - + + get_rounded_tax_amount(itemised_tax, doc.precision("tax_amount", "taxes")) + frappe.flags.company = None return frappe.render_template( @@ -554,6 +562,9 @@ def get_itemised_tax_breakup_data(doc): def get_itemised_tax(taxes): itemised_tax = {} for tax in taxes: + if getattr(tax, "category", None) and tax.category=="Valuation": + continue + tax_amount_precision = tax.precision("tax_amount") tax_rate_precision = tax.precision("rate") @@ -562,16 +573,16 @@ def get_itemised_tax(taxes): for item_code, tax_data in item_tax_map.items(): itemised_tax.setdefault(item_code, frappe._dict()) - if isinstance(tax_data, list) and tax_data[0]: + if isinstance(tax_data, list): precision = tax_amount_precision if tax.charge_type == "Actual" else tax_rate_precision itemised_tax[item_code][tax.description] = frappe._dict(dict( - tax_rate=flt(tax_data[0], precision), - tax_amount=flt(tax_data[1], tax_amount_precision) + tax_rate=flt(tax_data[0]), + tax_amount=flt(tax_data[1]) )) else: itemised_tax[item_code][tax.description] = frappe._dict(dict( - tax_rate=flt(tax_data, tax_rate_precision), + tax_rate=flt(tax_data), tax_amount=0.0 )) @@ -584,4 +595,10 @@ def get_itemised_taxable_amount(items): itemised_taxable_amount.setdefault(item_code, 0) itemised_taxable_amount[item_code] += item.net_amount - return itemised_taxable_amount \ No newline at end of file + return itemised_taxable_amount + +def get_rounded_tax_amount(itemised_tax, precision): + # Rounding based on tax_amount precision + for taxes in itemised_tax.values(): + for tax_account in taxes: + taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision) \ No newline at end of file diff --git a/erpnext/templates/includes/itemised_tax_breakup.html b/erpnext/templates/includes/itemised_tax_breakup.html index 2ffc8b4b83..75212d5a1a 100644 --- a/erpnext/templates/includes/itemised_tax_breakup.html +++ b/erpnext/templates/includes/itemised_tax_breakup.html @@ -22,7 +22,9 @@ {% set tax_details = taxes.get(tax_account) %} {% if tax_details %} - ({{ tax_details.tax_rate }}) + {% if tax_details.tax_rate or not tax_details.tax_amount %} + ({{ tax_details.tax_rate }}) + {% endif %} {{ frappe.utils.fmt_money(tax_details.tax_amount, None, company_currency) }} {% else %} From 17b2720f5b4105c52398689d096322a826f604a7 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Thu, 24 Aug 2017 17:50:28 +0530 Subject: [PATCH 21/39] [UI Test] UI Test for Payment Entry (#10521) --- .../payment_entry/tests/test_payment_entry.js | 29 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js diff --git a/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js new file mode 100644 index 0000000000..a4ef0ca4eb --- /dev/null +++ b/erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js @@ -0,0 +1,29 @@ +QUnit.module('Accounts'); + +QUnit.test("test payment entry", function(assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Payment Entry', [ + {payment_type:'Receive'}, + {mode_of_payment:'Cash'}, + {party_type:'Customer'}, + {party:'Test Customer 3'}, + {paid_amount:675}, + {reference_no:123}, + {reference_date: frappe.datetime.add_days(frappe.datetime.nowdate(), 0)}, + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.total_allocated_amount==675, "Allocated AmountCorrect"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 970b00d8c7..9d949efbab 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -105,4 +105,5 @@ erpnext/schools/doctype/assessment_plan/test_assessment_plan.js erpnext/schools/doctype/assessment_result/test_assessment_result.js erpnext/schools/doctype/assessment_result_tool/test_assessment_result_tool.js erpnext/accounts/doctype/journal_entry/test_journal_entry.js -erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js \ No newline at end of file +erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js +erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js \ No newline at end of file From 8de7c0245ca1d5b0fd367e878cccbc2d574d442f Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Thu, 24 Aug 2017 17:51:23 +0530 Subject: [PATCH 22/39] Fixes in the naming series (#10480) * rewrite the js * fix the space issue in the naming series --- .../doctype/naming_series/naming_series.js | 90 +++++++++------- .../doctype/naming_series/naming_series.json | 100 +++++++++++++++++- .../doctype/naming_series/naming_series.py | 8 +- .../naming_series/test_naming_series.js | 23 ++++ 4 files changed, 171 insertions(+), 50 deletions(-) create mode 100644 erpnext/setup/doctype/naming_series/test_naming_series.js diff --git a/erpnext/setup/doctype/naming_series/naming_series.js b/erpnext/setup/doctype/naming_series/naming_series.js index e2584bf3aa..7c76d9ca4b 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.js +++ b/erpnext/setup/doctype/naming_series/naming_series.js @@ -1,47 +1,55 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt +// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt -cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) { - cur_frm.disable_save(); - cur_frm.toolbar.print_icon.addClass("hide"); - return cur_frm.call({ - doc: cur_frm.doc, - method: 'get_transactions', - callback: function(r) { - cur_frm.cscript.update_selects(r); - cur_frm.cscript.select_doc_for_series(doc, cdt, cdn); - } - }); -} +frappe.ui.form.on("Naming Series", { + onload: function(frm) { + frm.disable_save(); + frm.events.get_doc_and_prefix(frm); + }, -cur_frm.cscript.update_selects = function(r) { - set_field_options('select_doc_for_series', r.message.transactions); - set_field_options('prefix', r.message.prefixes); -} + get_doc_and_prefix: function(frm) { + frappe.call({ + method: "get_transactions", + doc: frm.doc, + callback: function(r) { + frm.set_df_property("select_doc_for_series", "options", r.message.transactions); + frm.set_df_property("prefix", "options", r.message.prefixes); + } + }); + }, -cur_frm.cscript.select_doc_for_series = function(doc, cdt, cdn) { - cur_frm.set_value('user_must_always_select', 0); - cur_frm.toggle_display(['help_html','set_options', 'user_must_always_select', 'update'], - doc.select_doc_for_series); + select_doc_for_series: function(frm) { + frm.set_value("user_must_always_select", 0); + frappe.call({ + method: "get_options", + doc: frm.doc, + callback: function(r) { + frm.set_value("set_options", r.message); + if(r.message && r.message.split('\n')[0]=='') + frm.set_value('user_must_always_select', 1); + frm.refresh(); + } + }); + }, - var callback = function(r, rt){ - locals[cdt][cdn].set_options = r.message; - refresh_field('set_options'); - if(r.message && r.message.split('\n')[0]=='') - cur_frm.set_value('user_must_always_select', 1); + prefix: function(frm) { + frappe.call({ + method: "get_current", + doc: frm.doc, + callback: function(r) { + frm.refresh_field("current_value"); + } + }); + }, + + update: function(frm) { + frappe.call({ + method: "update_series", + doc: frm.doc, + callback: function(r) { + frm.events.get_doc_and_prefix(frm); + } + }); } - - if(doc.select_doc_for_series) - return $c_obj(doc,'get_options','',callback); -} - -cur_frm.cscript.update = function() { - return cur_frm.call_server('update_series', '', cur_frm.cscript.update_selects); -} - -cur_frm.cscript.prefix = function(doc, dt, dn) { - return cur_frm.call_server('get_current', '', function(r) { - refresh_field('current_value'); - }); -} +}); diff --git a/erpnext/setup/doctype/naming_series/naming_series.json b/erpnext/setup/doctype/naming_series/naming_series.json index 0daf1a146c..f936dcf3c9 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.json +++ b/erpnext/setup/doctype/naming_series/naming_series.json @@ -1,29 +1,40 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2013-01-25 11:35:08", "custom": 0, "description": "Set prefix for numbering series on your transactions", "docstatus": 0, "doctype": "DocType", + "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Set prefix for numbering series on your transactions", "fieldname": "setup_series", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Setup Series", + "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -31,20 +42,28 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "select_doc_for_series", "fieldtype": "Select", "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": "Select Transaction", + "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -52,21 +71,30 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "fieldname": "help_html", "fieldtype": "HTML", "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": "Help HTML", + "length": 0, "no_copy": 0, "options": "
    \nEdit list of Series in the box below. Rules:\n
      \n
    • Each Series Prefix on a new line.
    • \n
    • Allowed special characters are \"/\" and \"-\"
    • \n
    • Optionally, set the number of digits in the series using dot (.) followed by hashes (#). For example, \".####\" means that the series will have four digits. Default is five digits.
    • \n
    \nExamples:
    \nINV-
    \nINV-10-
    \nINVK-
    \nINV-.####
    \n
    ", "permlevel": 0, "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, @@ -74,20 +102,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "fieldname": "set_options", "fieldtype": "Text", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Series List for this Transaction", + "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -95,21 +132,30 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "description": "Check this if you want to force the user to select a series before saving. There will be no default if you check this.", "fieldname": "user_must_always_select", "fieldtype": "Check", "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": "User must always select", + "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -117,20 +163,30 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "select_doc_for_series", "fieldname": "update", "fieldtype": "Button", "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": "Update", + "length": 0, "no_copy": 0, + "options": "", "permlevel": 0, "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, @@ -138,21 +194,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Change the starting / current sequence number of an existing series.", "fieldname": "update_series", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Update Series", + "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -160,20 +224,28 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "prefix", "fieldtype": "Select", "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": "Prefix", + "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -181,21 +253,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "This is the number of the last created transaction with this prefix", "fieldname": "current_value", "fieldtype": "Int", "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": "Current Value", + "length": 0, "no_copy": 0, "permlevel": 0, "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, @@ -203,21 +283,29 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "update_series_start", "fieldtype": "Button", "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": "Update Series Number", + "length": 0, "no_copy": 0, "options": "update_series_start", "permlevel": 0, "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, @@ -225,16 +313,18 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 1, "icon": "fa fa-sort-by-order", "idx": 1, + "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-02-05 05:11:41.473793", + "max_attachments": 0, + "modified": "2017-08-17 03:41:37.685910", "modified_by": "Administrator", "module": "Setup", "name": "Naming Series", @@ -261,6 +351,10 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 1, - "read_only_onload": 0 + "read_only_onload": 0, + "show_name_in_global_search": 0, + "track_changes": 0, + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index 536b72f6a9..45345846bc 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -21,7 +21,6 @@ class NamingSeries(Document): where fieldname='naming_series'"""))) doctypes = list(set(get_doctypes_with_read()) | set(doctypes)) - prefixes = "" for d in doctypes: options = "" @@ -34,9 +33,8 @@ class NamingSeries(Document): if options: prefixes = prefixes + "\n" + options - prefixes.replace("\n\n", "\n") - prefixes = "\n".join(sorted(prefixes.split())) + prefixes = "\n".join(sorted(prefixes.split("\n"))) return { "transactions": "\n".join([''] + sorted(doctypes)), @@ -112,9 +110,7 @@ class NamingSeries(Document): where dt.name = df.dt and df.fieldname='naming_series' and dt.name != %s""", self.select_doc_for_series) )) - sr = [[frappe.get_meta(p).get_field("naming_series").options, p] - for p in parent] - + sr = [[frappe.get_meta(p).get_field("naming_series").options, p] for p in parent] dt = frappe.get_doc("DocType", self.select_doc_for_series) options = self.scrub_options_list(self.set_options.split("\n")) for series in options: diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.js b/erpnext/setup/doctype/naming_series/test_naming_series.js new file mode 100644 index 0000000000..22b664b2e6 --- /dev/null +++ b/erpnext/setup/doctype/naming_series/test_naming_series.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Naming Series", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Naming Series + () => frappe.tests.make('Naming Series', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); From 9a04da27b0decc314bf0433b3a423207ad3ae2f1 Mon Sep 17 00:00:00 2001 From: Utkarsh Goswami Date: Fri, 25 Aug 2017 12:01:04 +0530 Subject: [PATCH 23/39] Test for training (#10545) --- .../training_event/test_training_event.js | 55 +++++++++++++++++++ .../test_training_feedback.js | 52 ++++++++++++++++++ .../test_training_result.js | 53 ++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 + 4 files changed, 163 insertions(+) create mode 100644 erpnext/hr/doctype/training_event/test_training_event.js create mode 100644 erpnext/hr/doctype/training_feedback/test_training_feedback.js create mode 100644 erpnext/hr/doctype/training_result_employee/test_training_result.js diff --git a/erpnext/hr/doctype/training_event/test_training_event.js b/erpnext/hr/doctype/training_event/test_training_event.js new file mode 100644 index 0000000000..a359af3329 --- /dev/null +++ b/erpnext/hr/doctype/training_event/test_training_event.js @@ -0,0 +1,55 @@ +QUnit.module('hr'); + +QUnit.test("Test: Training Event [HR]", function (assert) { + assert.expect(4); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creation of Training Event + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => { + frappe.tests.make('Training Event', [ + { event_name: 'Test Training Event 1'}, + { location: 'Mumbai'}, + { start_time: '2017-09-01 11:00:0'}, + { end_time: '2017-09-01 17:00:0'}, + { introduction: 'This is just a test'}, + { employees: [ + [ + {employee: employee_name}, + {employee_name: 'Test Employee 1'} + ] + ]}, + ]); + }, + () => frappe.timeout(7), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(8), + () => { + // To check if the fields are correctly set + assert.ok(cur_frm.get_field('event_name').value == 'Test Training Event 1', + 'Event created successfully'); + + assert.ok(cur_frm.get_field('event_status').value=='Scheduled', + 'Status of event is correctly set'); + + assert.ok(cur_frm.doc.employees[0].employee_name=='Test Employee 1', + 'Attendee Employee is correctly set'); + }, + + () => frappe.set_route('List','Training Event','List'), + () => frappe.timeout(2), + // Checking the submission of Training Event + () => { + assert.ok(cur_list.data[0].docstatus==1,'Training Event Submitted successfully'); + }, + () => frappe.timeout(2), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/training_feedback/test_training_feedback.js b/erpnext/hr/doctype/training_feedback/test_training_feedback.js new file mode 100644 index 0000000000..9daa51f927 --- /dev/null +++ b/erpnext/hr/doctype/training_feedback/test_training_feedback.js @@ -0,0 +1,52 @@ +QUnit.module('hr'); + +QUnit.test("Test: Training Feedback [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creating Training Feedback + () => frappe.set_route('List','Training Feedback','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Training Feedback'), + () => frappe.timeout(1), + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => cur_frm.set_value('employee',employee_name), + () => cur_frm.set_value('employee_name','Test Employee 1'), + () => cur_frm.set_value('training_event','Test Training Event 1'), + () => cur_frm.set_value('event_name','Test Training Event 1'), + () => cur_frm.set_value('feedback','Great Experience. This is just a test.'), + () => frappe.timeout(1), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.save(), + + // Submitting the feedback + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if the feedback is given by correct employee + () => { + assert.equal('Test Employee 1',cur_frm.get_field('employee_name').value, + 'Feedback is given by correct employee'); + + assert.equal('Test Training Event 1',cur_frm.get_field('training_event').value, + 'Feedback is given for correct event'); + }, + + () => frappe.set_route('List','Training Feedback','List'), + () => frappe.timeout(2), + + // Checking the submission of Training Result + () => { + assert.ok(cur_list.data[0].docstatus==1,'Training Feedback Submitted successfully'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/training_result_employee/test_training_result.js b/erpnext/hr/doctype/training_result_employee/test_training_result.js new file mode 100644 index 0000000000..2ebf8962ee --- /dev/null +++ b/erpnext/hr/doctype/training_result_employee/test_training_result.js @@ -0,0 +1,53 @@ +QUnit.module('hr'); + +QUnit.test("Test: Training Result [HR]", function (assert) { + assert.expect(5); + let done = assert.async(); + frappe.run_serially([ + // Creating Training Result + () => frappe.set_route('List','Training Result','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Training Result'), + () => { + cur_frm.set_value('training_event','Test Training Event 1'); + }, + () => frappe.timeout(1), + () => frappe.model.set_value('Training Result Employee','New Training Result Employee 1','hours',4), + () => frappe.model.set_value('Training Result Employee','New Training Result Employee 1','grade','A'), + () => frappe.model.set_value('Training Result Employee','New Training Result Employee 1','comments','Nice Seminar'), + () => frappe.timeout(1), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.save(), + + // Submitting the Training Result + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(4), + + // Checking if the fields are correctly set + () => { + assert.equal('Test Training Event 1',cur_frm.get_field('training_event').value, + 'Training Result is created'); + + assert.equal('Test Employee 1',cur_frm.doc.employees[0].employee_name, + 'Training Result is created for correct employee'); + + assert.equal(4,cur_frm.doc.employees[0].hours, + 'Hours field is correctly calculated'); + + assert.equal('A',cur_frm.doc.employees[0].grade, + 'Grade field is correctly set'); + }, + + () => frappe.set_route('List','Training Result','List'), + () => frappe.timeout(2), + + // Checking the submission of Training Result + () => { + assert.ok(cur_list.data[0].docstatus==1,'Training Result Submitted successfully'); + }, + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 9d949efbab..61ab529f11 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -68,6 +68,9 @@ erpnext/hr/doctype/job_applicant/test_job_applicant.js erpnext/hr/doctype/offer_letter/test_offer_letter.js erpnext/hr/doctype/appraisal_template/test_appraisal_template.js erpnext/hr/doctype/appraisal/test_appraisal.js +erpnext/hr/doctype/training_event/test_training_event.js +erpnext/hr/doctype/training_result_employee/test_training_result.js +erpnext/hr/doctype/training_feedback/test_training_feedback.js erpnext/buying/doctype/supplier/test_supplier.js erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js From bf4a97412497297931c4a4223d0acba7003c8455 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 25 Aug 2017 12:10:49 +0530 Subject: [PATCH 24/39] IMproved print format for AR/AP report (#10528) --- .../accounts_receivable.html | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 853b805135..8fafce6781 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -10,15 +10,15 @@ {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} - {%= __("Date") %} - {%= __("Ref") %} - {%= __("Party") %} - {%= __("Invoiced Amount") %} - {%= __("Paid Amount") %} - {%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %} - {%= __("Outstanding Amount") %} + {%= __("Date") %} + {%= __("Ref") %} + {%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %} + {%= __("Invoiced Amount") %} + {%= __("Paid Amount") %} + {%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %} + {%= __("Outstanding Amount") %} {% } else { %} - {%= __("Party") %} + {%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %} {%= __("Total Invoiced Amount") %} {%= __("Total Paid Amount") %} {%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %} @@ -34,8 +34,12 @@ {%= dateutil.str_to_user(data[i][__("Posting Date")]) %} {%= data[i][__("Voucher Type")] %}
    {%= data[i][__("Voucher No")] %} - {%= data[i][__("Customer Name")] || data[i][__("Customer")] || data[i][__("Supplier Name")] || data[i][__("Supplier")] %} -
    {%= __("Remarks") %}: {%= data[i][__("Remarks")] %} + + {% if(!(filters.customer || filters.supplier)) { %} + {%= data[i][__("Customer Name")] || data[i][__("Customer")] || data[i][__("Supplier Name")] || data[i][__("Supplier")] %}
    {%= __("Remarks") %}: + {% } %} + {%= data[i][__("Remarks")] %} + {%= format_currency(data[i]["Invoiced Amount"], data[i]["currency"]) %} @@ -59,8 +63,13 @@ {% } else { %} {% if(data[i][__("Customer")] || data[i][__("Supplier")]|| " ") { %} {% if((data[i][__("Customer")] || data[i][__("Supplier")]) != __("'Total'")) { %} - {%= data[i][__("Customer")] || data[i][__("Supplier")] %} -
    {%= __("Remarks") %}: {%= data[i][__("Remarks")] %} + + {% if(!(filters.customer || filters.supplier)) { %} + {%= data[i][__("Customer")] || data[i][__("Supplier")] %} +
    {%= __("Remarks") %}: + {% } %} + {%= data[i][__("Remarks")] %} + {% } else { %} {%= __("Total") %} {% } %} From e33a1e0515dff83df012a158c0a5866d71ff1f77 Mon Sep 17 00:00:00 2001 From: Ashwini Save Date: Fri, 25 Aug 2017 12:44:33 +0530 Subject: [PATCH 25/39] Hide "Help Article" button for timeline Communication type "Comment". (#10541) --- erpnext/support/doctype/issue/issue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index c1b76e5374..306736f3dc 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -24,7 +24,7 @@ frappe.ui.form.on("Issue", { frm.timeline.wrapper.find('.comment-header .asset-details .btn-add-to-kb').remove(); $('') - .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details')) + .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) .on('click', function() { var content = $(this).parents('.timeline-item:first').find('.timeline-item-content').html(); var doc = frappe.model.get_new_doc('Help Article'); From 579c8e68f9487ec6526ed0e848d427133ade2706 Mon Sep 17 00:00:00 2001 From: Utkarsh Goswami Date: Fri, 25 Aug 2017 16:12:49 +0530 Subject: [PATCH 26/39] Tests for Expense Claims [HR] (#10514) --- .../expense_claim/test_expense_claim.js | 59 +++++++++++++++++++ .../test_expense_claim_type.js | 30 ++++++++++ erpnext/tests/ui/tests.txt | 2 + 3 files changed, 91 insertions(+) create mode 100644 erpnext/hr/doctype/expense_claim/test_expense_claim.js create mode 100644 erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.js b/erpnext/hr/doctype/expense_claim/test_expense_claim.js new file mode 100644 index 0000000000..c7c764cab5 --- /dev/null +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.js @@ -0,0 +1,59 @@ +QUnit.module('hr'); + +QUnit.test("Test: Expense Claim [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + let employee_name; + let d; + frappe.run_serially([ + // Creating Expense Claim + () => frappe.set_route('List','Expense Claim','List'), + () => frappe.timeout(0.3), + () => frappe.click_button('Make a new Expense Claim'), + () => { + cur_frm.set_value('exp_approver','Administrator'), + cur_frm.set_value('is_paid',1), + cur_frm.set_value('expenses',[]), + d = frappe.model.add_child(cur_frm.doc,'Expense Claim Detail','expenses'), + d.expense_date = '2017-08-01', + d.expense_type = 'Test Expense Type 1', + d.description = 'This is just to test Expense Claim', + d.claim_amount = 2000, + d.sanctioned_amount=2000, + refresh_field('expenses'); + }, + () => frappe.timeout(2), + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => frappe.timeout(1), + () => cur_frm.set_value('employee',employee_name), + () => cur_frm.set_value('employee_name','Test Employee 1'), + () => cur_frm.set_value('company','Test Company'), + () => cur_frm.set_value('payable_account','Creditors - TC'), + () => cur_frm.set_value('cost_center','Main - TC'), + () => cur_frm.set_value('mode_of_payment','Cash'), + () => cur_frm.save(), + () => frappe.timeout(1), + () => cur_frm.set_value('approval_status','Approved'), + () => frappe.timeout(1), + () => cur_frm.save(), + // Submitting the Expense Claim + () => frappe.click_button('Submit'), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if the amount is correctly reimbursed for the employee + () => { + assert.equal(employee_name,cur_frm.get_field('employee').value, + 'Expense Claim is created for correct employee'); + assert.equal(1,cur_frm.get_field('is_paid').value, + 'Expense is paid as required'); + assert.equal(2000,cur_frm.get_field('total_amount_reimbursed').value, + 'Amount is reimbursed correctly'); + }, + () => done() + ]); +}); + diff --git a/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js new file mode 100644 index 0000000000..595454fca0 --- /dev/null +++ b/erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js @@ -0,0 +1,30 @@ +QUnit.module('hr'); + +QUnit.test("Test: Expense Claim Type [HR]", function (assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + // Creating a Expense Claim Type + () => { + frappe.tests.make('Expense Claim Type', [ + { expense_type: 'Test Expense Type 1'}, + { description:'This is just a test'}, + { accounts: [ + [ + { company: 'Test Company'}, + { default_account: 'Round Off - TC'} + ] + ]}, + ]); + }, + () => frappe.timeout(5), + + // Checking if the created type is present in the list + () => { + assert.equal('Test Expense Type 1', cur_frm.doc.expense_type, + 'Expense Claim Type created successfully'); + }, + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 61ab529f11..12e2da8783 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -68,6 +68,8 @@ erpnext/hr/doctype/job_applicant/test_job_applicant.js erpnext/hr/doctype/offer_letter/test_offer_letter.js erpnext/hr/doctype/appraisal_template/test_appraisal_template.js erpnext/hr/doctype/appraisal/test_appraisal.js +erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js +erpnext/hr/doctype/expense_claim/test_expense_claim.js erpnext/hr/doctype/training_event/test_training_event.js erpnext/hr/doctype/training_result_employee/test_training_result.js erpnext/hr/doctype/training_feedback/test_training_feedback.js From 7dc113e977449df06ef2e2e81ff5891de9423860 Mon Sep 17 00:00:00 2001 From: Zarrar Date: Fri, 25 Aug 2017 17:48:36 +0530 Subject: [PATCH 27/39] instructor-naming (#10550) --- .../course_schedule/test_course_schedule.py | 8 +-- .../doctype/instructor/instructor.json | 2 +- .../schools/doctype/instructor/instructor.py | 16 ++++- .../school_settings/school_settings.json | 63 ++++++++++++++++++- .../school_settings/school_settings.py | 7 +++ 5 files changed, 89 insertions(+), 7 deletions(-) diff --git a/erpnext/schools/doctype/course_schedule/test_course_schedule.py b/erpnext/schools/doctype/course_schedule/test_course_schedule.py index 6a3456fefc..f1313820a5 100644 --- a/erpnext/schools/doctype/course_schedule/test_course_schedule.py +++ b/erpnext/schools/doctype/course_schedule/test_course_schedule.py @@ -17,7 +17,7 @@ class TestCourseSchedule(unittest.TestCase): cs1 = make_course_schedule_test_record(simulate= True) cs2 = make_course_schedule_test_record(schedule_date=cs1.schedule_date, from_time= cs1.from_time, - to_time= cs1.to_time, instructor="_T-Instructor-00002", room="RM0002", do_not_save= 1) + to_time= cs1.to_time, instructor="_Test Instructor 2", room="RM0002", do_not_save= 1) self.assertRaises(OverlapError, cs2.save) def test_instructor_conflict(self): @@ -31,14 +31,14 @@ class TestCourseSchedule(unittest.TestCase): cs1 = make_course_schedule_test_record(simulate= True) cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, - student_group="Course-TC101-2014-2015 (_Test Academic Term)", instructor="_T-Instructor-00002", do_not_save= 1) + student_group="Course-TC101-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", do_not_save= 1) self.assertRaises(OverlapError, cs2.save) def test_no_conflict(self): cs1 = make_course_schedule_test_record(simulate= True) make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time, - student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_T-Instructor-00002", room="RM0002") + student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room="RM0002") def make_course_schedule_test_record(**args): args = frappe._dict(args) @@ -46,7 +46,7 @@ def make_course_schedule_test_record(**args): course_schedule = frappe.new_doc("Course Schedule") course_schedule.student_group = args.student_group or "Course-TC101-2014-2015 (_Test Academic Term)" course_schedule.course = args.course or "TC101" - course_schedule.instructor = args.instructor or "_T-Instructor-00001" + course_schedule.instructor = args.instructor or "_Test Instructor" course_schedule.room = args.room or "RM0001" course_schedule.schedule_date = args.schedule_date or today() diff --git a/erpnext/schools/doctype/instructor/instructor.json b/erpnext/schools/doctype/instructor/instructor.json index a98fe693e7..cd0b4f10f8 100644 --- a/erpnext/schools/doctype/instructor/instructor.json +++ b/erpnext/schools/doctype/instructor/instructor.json @@ -208,7 +208,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-06-30 08:21:49.055531", + "modified": "2017-08-25 01:03:14.602994", "modified_by": "Administrator", "module": "Schools", "name": "Instructor", diff --git a/erpnext/schools/doctype/instructor/instructor.py b/erpnext/schools/doctype/instructor/instructor.py index 4331b91610..ba179f76aa 100644 --- a/erpnext/schools/doctype/instructor/instructor.py +++ b/erpnext/schools/doctype/instructor/instructor.py @@ -4,7 +4,21 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document +from frappe.model.naming import make_autoname class Instructor(Document): - pass + def autoname(self): + naming_method = frappe.db.get_value("School Settings", None, "instructor_created_by") + if not naming_method: + frappe.throw(_("Please setup Instructor Naming System in School > School Settings")) + else: + if naming_method == 'Naming Series': + self.name = make_autoname(self.naming_series + '.####') + elif naming_method == 'Employee Number': + if not self.employee: + frappe.throw("Please select Employee") + self.name = self.employee + elif naming_method == 'Full Name': + self.name = self.instructor_name diff --git a/erpnext/schools/doctype/school_settings/school_settings.json b/erpnext/schools/doctype/school_settings/school_settings.json index 3b5aae69e2..b6d9890ebd 100644 --- a/erpnext/schools/doctype/school_settings/school_settings.json +++ b/erpnext/schools/doctype/school_settings/school_settings.json @@ -194,6 +194,67 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Full Name", + "fieldname": "instructor_created_by", + "fieldtype": "Select", + "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": "Instructor Records to be created by", + "length": 0, + "no_copy": 0, + "options": "Full Name\nNaming Series\nEmployee Number", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "has_web_view": 0, @@ -206,7 +267,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-06-30 08:21:50.339169", + "modified": "2017-08-25 02:36:48.744456", "modified_by": "Administrator", "module": "Schools", "name": "School Settings", diff --git a/erpnext/schools/doctype/school_settings/school_settings.py b/erpnext/schools/doctype/school_settings/school_settings.py index 999014ad80..88235cfc34 100644 --- a/erpnext/schools/doctype/school_settings/school_settings.py +++ b/erpnext/schools/doctype/school_settings/school_settings.py @@ -26,3 +26,10 @@ class SchoolSettings(Document): def get_defaults(self): return frappe.defaults.get_defaults() + + def validate(self): + from frappe.custom.doctype.property_setter.property_setter import make_property_setter + if self.get('instructor_created_by')=='Naming Series': + make_property_setter('Instructor', "naming_series", "hidden", 0, "Check") + else: + make_property_setter('Instructor', "naming_series", "hidden", 1, "Check") From c3a2204653e1ca679ac1ab5cdec25c1f9556ca3c Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 28 Aug 2017 11:09:35 +0530 Subject: [PATCH 28/39] [UI Test] UI Test for Sales Order Cancel, amend and save (#10570) --- .../test_quotation_submit_cancel_amend.js | 41 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js b/erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js new file mode 100644 index 0000000000..26a099e4d6 --- /dev/null +++ b/erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js @@ -0,0 +1,41 @@ +QUnit.module('Quotation'); + +QUnit.test("test quotation submit cancel amend", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Quotation', [ + {customer: 'Test Customer 1'}, + {items: [ + [ + {'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)}, + {'qty': 5}, + {'item_code': 'Test Product 1'} + ] + ]}, + {customer_address: 'Test1-Billing'}, + {shipping_address_name: 'Test1-Shipping'}, + {contact_person: 'Contact 1-Test Customer 1'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get uom details + assert.ok(cur_frm.doc.grand_total== 500, "Grand total correct "); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(1), + () => frappe.tests.click_button('Close'), + () => frappe.tests.click_button('Cancel'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.5), + () => frappe.tests.click_button('Amend'), + () => cur_frm.save(), + () => done() + ]); +}); diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 12e2da8783..a8b8d7e41a 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -111,4 +111,5 @@ erpnext/schools/doctype/assessment_result/test_assessment_result.js erpnext/schools/doctype/assessment_result_tool/test_assessment_result_tool.js erpnext/accounts/doctype/journal_entry/test_journal_entry.js erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js -erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js \ No newline at end of file +erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js +erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js \ No newline at end of file From da2164373cb04011db9caab993e5662b6919f2c9 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 28 Aug 2017 11:10:26 +0530 Subject: [PATCH 29/39] [UI Test] UI Test added for Batch (#10564) --- erpnext/stock/doctype/batch/test_batch.js | 23 +++++++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 erpnext/stock/doctype/batch/test_batch.js diff --git a/erpnext/stock/doctype/batch/test_batch.js b/erpnext/stock/doctype/batch/test_batch.js new file mode 100644 index 0000000000..af7f50ff91 --- /dev/null +++ b/erpnext/stock/doctype/batch/test_batch.js @@ -0,0 +1,23 @@ +QUnit.module('Stock'); + +QUnit.test("test Batch", function(assert) { + assert.expect(1); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Batch', [ + {batch_id:'TEST-BATCH-001'}, + {item:'Test Product 4'}, + {expiry_date:frappe.datetime.add_days(frappe.datetime.now_date(), 2)}, + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.batch_id=='TEST-BATCH-001', "Batch Id correct"); + }, + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index a8b8d7e41a..d1dc96cbe1 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -112,4 +112,5 @@ erpnext/schools/doctype/assessment_result_tool/test_assessment_result_tool.js erpnext/accounts/doctype/journal_entry/test_journal_entry.js erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js -erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js \ No newline at end of file +erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js +erpnext/stock/doctype/batch/test_batch.js \ No newline at end of file From 554cf9be44d156e674ebb66208f917bbb1e32a61 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 28 Aug 2017 11:11:36 +0530 Subject: [PATCH 30/39] [UI Test] UI Test Added for Bank Reconciliation (#10563) --- .../test_bank_reconciliation.js | 22 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 ++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js diff --git a/erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js b/erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js new file mode 100644 index 0000000000..f52f6fb431 --- /dev/null +++ b/erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js @@ -0,0 +1,22 @@ +QUnit.module('Account'); + +QUnit.test("test Bank Reconciliation", function(assert) { + assert.expect(0); + let done = assert.async(); + frappe.run_serially([ + () => frappe.set_route('Form', 'Bank Reconciliation'), + () => cur_frm.set_value('bank_account','Cash - FT'), + () => frappe.click_button('Get Payment Entries'), + () => { + for(var i=0;i<=cur_frm.doc.payment_entries.length-1;i++){ + cur_frm.doc.payment_entries[i].clearance_date = frappe.datetime.add_days(frappe.datetime.now_date(), 2); + } + }, + () => {cur_frm.refresh_fields('payment_entries');}, + () => frappe.click_button('Update Clearance Date'), + () => frappe.timeout(0.5), + () => frappe.click_button('Close'), + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index d1dc96cbe1..48b6fb0534 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -113,4 +113,5 @@ erpnext/accounts/doctype/journal_entry/test_journal_entry.js erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js -erpnext/stock/doctype/batch/test_batch.js \ No newline at end of file +erpnext/stock/doctype/batch/test_batch.js +erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js \ No newline at end of file From 7ba2a83182c8aa77db41cc3576ba99b89c6a92f4 Mon Sep 17 00:00:00 2001 From: Utkarsh Goswami Date: Mon, 28 Aug 2017 11:13:16 +0530 Subject: [PATCH 31/39] Tests for Loan (#10557) --- .../employee_loan/test_employee_loan.js | 79 +++++++++++++++++++ .../test_employee_loan_application.js | 68 ++++++++++++++++ .../test_leave_application.js | 4 +- .../hr/doctype/loan_type/test_loan_type.js | 31 ++++++++ erpnext/tests/ui/tests.txt | 3 + 5 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 erpnext/hr/doctype/employee_loan/test_employee_loan.js create mode 100644 erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js create mode 100644 erpnext/hr/doctype/loan_type/test_loan_type.js diff --git a/erpnext/hr/doctype/employee_loan/test_employee_loan.js b/erpnext/hr/doctype/employee_loan/test_employee_loan.js new file mode 100644 index 0000000000..9039339773 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/test_employee_loan.js @@ -0,0 +1,79 @@ + +QUnit.test("Test Loan [HR]", function(assert) { + assert.expect(8); + let done = assert.async(); + let employee_name; + + // To create a loan and check principal,interest and balance amount + let loan_creation = (ename,lname) => { + return frappe.run_serially([ + () => frappe.db.get_value('Employee', {'employee_name': ename}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => frappe.db.get_value('Employee Loan Application', {'loan_type': lname}, 'name'), + (r) => { + // Creating loan for an employee + return frappe.tests.make('Employee Loan', [ + { company: 'Test Company'}, + { posting_date: '2017-08-26'}, + { employee: employee_name}, + { employee_loan_application: r.message.name}, + { disbursement_date: '2018-08-26'}, + { mode_of_payment: 'Cash'}, + { employee_loan_account: 'Temporary Opening - TC'}, + { interest_income_account: 'Service - TC'} + ]); + }, + () => frappe.timeout(3), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(3), + + // Checking if all the amounts are correctly calculated + () => { + assert.ok(cur_frm.get_field('employee_name').value=='Test Employee 1'&& + (cur_frm.get_field('status').value=='Sanctioned'), + 'Loan Sanctioned for correct employee'); + + assert.equal(7270, + cur_frm.get_doc('repayment_schedule').repayment_schedule[0].principal_amount, + 'Principal amount for first instalment is correctly calculated'); + + assert.equal(2333, + cur_frm.get_doc('repayment_schedule').repayment_schedule[0].interest_amount, + 'Interest amount for first instalment is correctly calculated'); + + assert.equal(192730, + cur_frm.get_doc('repayment_schedule').repayment_schedule[0].balance_loan_amount, + 'Balance amount after first instalment is correctly calculated'); + + assert.equal(9479, + cur_frm.get_doc('repayment_schedule').repayment_schedule[23].principal_amount, + 'Principal amount for last instalment is correctly calculated'); + + assert.equal(111, + cur_frm.get_doc('repayment_schedule').repayment_schedule[23].interest_amount, + 'Interest amount for last instalment is correctly calculated'); + + assert.equal(0, + cur_frm.get_doc('repayment_schedule').repayment_schedule[23].balance_loan_amount, + 'Balance amount after last instalment is correctly calculated'); + + }, + () => frappe.set_route('List','Employee Loan','List'), + () => frappe.timeout(2), + + // Checking the submission of Loan + () => { + assert.ok(cur_list.data[0].docstatus==1,'Loan sanctioned and submitted successfully'); + }, + ]); + }; + frappe.run_serially([ + // Creating loan + () => loan_creation('Test Employee 1','Test Loan'), + () => done() + ]); +}); diff --git a/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js new file mode 100644 index 0000000000..72ad915f7d --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js @@ -0,0 +1,68 @@ +QUnit.module('hr'); + +QUnit.test("Test: Employee Loan Application [HR]", function (assert) { + assert.expect(8); + let done = assert.async(); + let employee_name; + + frappe.run_serially([ + // Creation of Loan Application + () => frappe.db.get_value('Employee', {'employee_name': 'Test Employee 1'}, 'name'), + (r) => { + employee_name = r.message.name; + }, + () => { + frappe.tests.make('Employee Loan Application', [ + { company: 'Test Company'}, + { employee: employee_name}, + { employee_name: 'Test Employee 1'}, + { status: 'Approved'}, + { loan_type: 'Test Loan '}, + { loan_amount: 200000}, + { description: 'This is just a test'}, + { repayment_method: 'Repay Over Number of Periods'}, + { repayment_periods: 24}, + { rate_of_interest: 14} + ]); + }, + () => frappe.timeout(6), + () => frappe.click_button('Submit'), + () => frappe.timeout(1), + () => frappe.click_button('Yes'), + () => frappe.timeout(2), + () => { + // To check if all the amounts are correctly calculated + + assert.ok(cur_frm.get_field('employee_name').value == 'Test Employee 1', + 'Application created successfully'); + + assert.ok(cur_frm.get_field('status').value=='Approved', + 'Status of application is correctly set'); + + assert.ok(cur_frm.get_field('loan_type').value=='Test Loan', + 'Application is created for correct Loan Type'); + + assert.ok(cur_frm.get_field('status').value=='Approved', + 'Status of application is correctly set'); + + assert.ok(cur_frm.get_field('repayment_amount').value==9603, + 'Repayment amount is correctly calculated'); + + assert.ok(cur_frm.get_field('total_payable_interest').value==30459, + 'Interest amount is correctly calculated'); + + assert.ok(cur_frm.get_field('total_payable_amount').value==230459, + 'Total payable amount is correctly calculated'); + }, + + () => frappe.set_route('List','Employee Loan Application','List'), + () => frappe.timeout(2), + + // Checking the submission of Loan Application + () => { + assert.ok(cur_list.data[0].docstatus==1,'Loan Application submitted successfully'); + }, + () => frappe.timeout(1), + () => done() + ]); +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.js b/erpnext/hr/doctype/leave_application/test_leave_application.js index 51e8ed623c..6028405c46 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.js +++ b/erpnext/hr/doctype/leave_application/test_leave_application.js @@ -1,7 +1,7 @@ QUnit.module('hr'); QUnit.test("Test: Leave application [HR]", function (assert) { - assert.expect(5); + assert.expect(4); let done = assert.async(); let today_date = frappe.datetime.nowdate(); let leave_date = frappe.datetime.add_days(today_date, 1); // leave for tomorrow @@ -22,8 +22,6 @@ QUnit.test("Test: Leave application [HR]", function (assert) { }, () => frappe.timeout(1), // check calculated total leave days - () => assert.equal("0.5", cur_frm.doc.total_leave_days, - "leave application for half day"), () => assert.ok(!cur_frm.doc.docstatus, "leave application not submitted with status as open"), () => cur_frm.set_value("status", "Approved"), // approve the application [as administrator] diff --git a/erpnext/hr/doctype/loan_type/test_loan_type.js b/erpnext/hr/doctype/loan_type/test_loan_type.js new file mode 100644 index 0000000000..8b5032b04e --- /dev/null +++ b/erpnext/hr/doctype/loan_type/test_loan_type.js @@ -0,0 +1,31 @@ +QUnit.module('hr'); + +QUnit.test("Test: Loan Type [HR]", function (assert) { + assert.expect(3); + let done = assert.async(); + + frappe.run_serially([ + // Loan Type creation + () => { + frappe.tests.make('Loan Type', [ + { loan_name: 'Test Loan'}, + { maximum_loan_amount: 400000}, + { rate_of_interest: 14}, + { description: + 'This is just a test.'} + ]); + }, + () => frappe.timeout(3), + () => frappe.set_route('List','Loan Type','List'), + () => frappe.timeout(2), + + // Checking if the fields are correctly set + () => { + assert.ok(cur_list.data.length==1, 'Loan Type created successfully'); + assert.ok(cur_list.data[0].name=='Test Loan', 'Loan title Correctly set'); + assert.ok(cur_list.data[0].disabled==0, 'Loan enabled'); + }, + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 48b6fb0534..87e557148e 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -73,6 +73,9 @@ erpnext/hr/doctype/expense_claim/test_expense_claim.js erpnext/hr/doctype/training_event/test_training_event.js erpnext/hr/doctype/training_result_employee/test_training_result.js erpnext/hr/doctype/training_feedback/test_training_feedback.js +erpnext/hr/doctype/loan_type/test_loan_type.js +erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.js +erpnext/hr/doctype/employee_loan/test_employee_loan.js erpnext/buying/doctype/supplier/test_supplier.js erpnext/buying/doctype/request_for_quotation/tests/test_request_for_quotation.js erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js From 914b8463b7bcbba7322b00b22b3cd41ff21fbdba Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 28 Aug 2017 11:14:28 +0530 Subject: [PATCH 32/39] [UI Test] UI Test for Payment Request Added: (#10553) --- ...test_sales_invoice_with_payment_request.js | 52 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 1 + 2 files changed, 53 insertions(+) create mode 100644 erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js diff --git a/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js new file mode 100644 index 0000000000..7abfb415ca --- /dev/null +++ b/erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js @@ -0,0 +1,52 @@ +QUnit.module('Sales Invoice'); + +QUnit.test("test sales Invoice with payment request", function(assert) { + assert.expect(4); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Sales Invoice', [ + {customer: 'Test Customer 1'}, + {items: [ + [ + {'qty': 5}, + {'item_code': 'Test Product 1'}, + ] + ]}, + {update_stock:1}, + {customer_address: 'Test1-Billing'}, + {shipping_address_name: 'Test1-Shipping'}, + {contact_person: 'Contact 1-Test Customer 1'}, + {taxes_and_charges: 'TEST In State GST'}, + {tc_name: 'Test Term 1'}, + {terms: 'This is Test'} + ]); + }, + () => cur_frm.save(), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + // get tax details + assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST', "Tax details correct"); + // grand_total Calculated + assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct"); + + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(2), + () => frappe.tests.click_button('Close'), + () => frappe.tests.click_button('Make'), + () => frappe.tests.click_link('Payment Request'), + () => frappe.timeout(0.2), + () => { cur_frm.set_value('print_format','GST Tax Invoice');}, + () => { cur_frm.set_value('email_to','test@gmail.com');}, + () => cur_frm.save(), + () => { + // get payment details + assert.ok(cur_frm.doc.grand_total==590, "grand total Correct"); + }, + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 87e557148e..5723b9e2be 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -82,6 +82,7 @@ erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation.js erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_taxes_and_charges.js erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice.js erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment.js +erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_payment_request.js erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.js erpnext/buying/doctype/supplier_quotation/tests/test_supplier_quotation_for_item_wise_discount.js erpnext/buying/doctype/purchase_order/tests/test_purchase_order.js From 2df0788c11332dba959a2065041e5a4f7c98aa9f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 28 Aug 2017 11:23:33 +0530 Subject: [PATCH 33/39] Validate for negative outstanding reference only if party is Customer or Supplier (#10568) --- erpnext/accounts/doctype/payment_entry/payment_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 92a805f37d..12e46c42d3 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -569,7 +569,7 @@ frappe.ui.form.on('Payment Entry', { }) var allocated_negative_outstanding = 0; - if((frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || + if ((frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee")) { if(total_positive_outstanding_including_order > paid_amount) { @@ -579,7 +579,7 @@ frappe.ui.form.on('Payment Entry', { } var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding; - } else { + } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) { if(paid_amount > total_negative_outstanding) { if(total_negative_outstanding == 0) { frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice", From d44af40de8fe674a0b53784b2b08e624b3ac47b4 Mon Sep 17 00:00:00 2001 From: mbauskar Date: Mon, 28 Aug 2017 12:26:17 +0600 Subject: [PATCH 34/39] bumped to version 8.9.2 --- erpnext/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 16c346baa8..48296e7e33 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from erpnext.hooks import regional_overrides -__version__ = '8.9.1' +__version__ = '8.9.2' def get_default_company(user=None): '''Get default company for user''' From f8e6c449963ad4e295aa83352568b70385860fec Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 28 Aug 2017 15:32:33 +0530 Subject: [PATCH 35/39] [UI Test] Stock Entry for Material Transfer to Manufacture (#10572) --- ...y_for_material_transfer_for_manufacture.js | 34 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js new file mode 100644 index 0000000000..e8b2973c45 --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js @@ -0,0 +1,34 @@ +QUnit.module('Stock'); + +QUnit.test("test material Transfer to manufacture", function(assert) { + assert.expect(3); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Stock Entry', [ + {purpose:'Material Transfer for Manufacture'}, + {from_warehouse:'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {to_warehouse:'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {items: [ + [ + {'item_code': 'Test Product 1'}, + {'qty': 1}, + ] + ]}, + ]); + }, + () => cur_frm.save(), + () => frappe.click_button('Update Rate and Availability'), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + assert.ok(cur_frm.doc.total_outgoing_value==100, " Outgoing Value correct"); + assert.ok(cur_frm.doc.total_incoming_value==100, " Incoming Value correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 5723b9e2be..4e1deb5b4b 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -118,4 +118,5 @@ erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.js erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js erpnext/stock/doctype/batch/test_batch.js -erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js \ No newline at end of file +erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js \ No newline at end of file From 872bebbbf84207dc92e895efebdb0c0cfae98c39 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 28 Aug 2017 15:33:21 +0530 Subject: [PATCH 36/39] [UI Test] Stock Entry for Subcontract (#10574) --- .../tests/test_stock_entry_for_subcontract.js | 34 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 +- 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js new file mode 100644 index 0000000000..131d3ca1de --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js @@ -0,0 +1,34 @@ +QUnit.module('Stock'); + +QUnit.test("test material Transfer to manufacture", function(assert) { + assert.expect(3); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Stock Entry', [ + {purpose:'Subcontract'}, + {from_warehouse:'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {to_warehouse:'Finished Goods - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {items: [ + [ + {'item_code': 'Test Product 1'}, + {'qty': 1}, + ] + ]}, + ]); + }, + () => cur_frm.save(), + () => frappe.click_button('Update Rate and Availability'), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct"); + assert.ok(cur_frm.doc.total_outgoing_value==100, " Outgoing Value correct"); + assert.ok(cur_frm.doc.total_incoming_value==100, " Incoming Value correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 4e1deb5b4b..826b956f38 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -119,4 +119,5 @@ erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js erpnext/stock/doctype/batch/test_batch.js erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js -erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js \ No newline at end of file +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js \ No newline at end of file From 7ed4bfe7ee5ff8bfe1caf5e842a720340266faa3 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Mon, 28 Aug 2017 16:53:25 +0530 Subject: [PATCH 37/39] [UI Test] UI Test for Material Receipt with Serialize Item Added (#10565) * [UI Test] UI Test for Material Receipt with Serialize Item Added * [fix]Codacy fixed --- ...for_material_receipt_for_serialize_item.js | 35 +++++++++++++++++++ erpnext/tests/ui/tests.txt | 3 +- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js new file mode 100644 index 0000000000..ffd06642bf --- /dev/null +++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js @@ -0,0 +1,35 @@ +QUnit.module('Stock'); + +QUnit.test("test material receipt", function(assert) { + assert.expect(2); + let done = assert.async(); + frappe.run_serially([ + () => { + return frappe.tests.make('Stock Entry', [ + {purpose:'Material Receipt'}, + {to_warehouse:'Stores - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, + {items: [ + [ + {'item_code': 'Test Product 4'}, + {'qty': 5}, + {'batch_no':'TEST-BATCH-001'}, + {'serial_no':'Test-Product-001\nTest-Product-002\nTest-Product-003\nTest-Product-004\nTest-Product-005'}, + {'basic_rate':100}, + ] + ]}, + ]); + }, + () => cur_frm.save(), + () => frappe.click_button('Update Rate and Availability'), + () => { + // get_item_details + assert.ok(cur_frm.doc.items[0].item_name=='Test Product 4', "Item name correct"); + assert.ok(cur_frm.doc.total_incoming_value==500, " Incoming Value correct"); + }, + () => frappe.tests.click_button('Submit'), + () => frappe.tests.click_button('Yes'), + () => frappe.timeout(0.3), + () => done() + ]); +}); + diff --git a/erpnext/tests/ui/tests.txt b/erpnext/tests/ui/tests.txt index 826b956f38..653aeecc66 100644 --- a/erpnext/tests/ui/tests.txt +++ b/erpnext/tests/ui/tests.txt @@ -119,5 +119,6 @@ erpnext/accounts/doctype/payment_entry/tests/test_payment_entry.js erpnext/selling/doctype/quotation/tests/test_quotation_submit_cancel_amend.js erpnext/stock/doctype/batch/test_batch.js erpnext/accounts/doctype/bank_reconciliation/test_bank_reconciliation.js +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_receipt_for_serialize_item.js erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_transfer_for_manufacture.js -erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js \ No newline at end of file +erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_subcontract.js From 6ccb6562f13b446610a94cae1a6f0310ab4947fe Mon Sep 17 00:00:00 2001 From: Aditya Hase Date: Mon, 28 Aug 2017 18:17:36 +0530 Subject: [PATCH 38/39] Python 3 compatibility syntax error fixes (#10519) * Use Python 3 style print function * Use 'Exception as e' instead of 'Exception, e' * Unpack tuple arguments explicitly in instead of relying on auto unpacking * Use consistent indentation * Use 0 if stock_frozen_upto_days is None --- .../chart_of_accounts/import_from_openerp.py | 4 +- .../doctype/payment_entry/payment_entry.py | 2 +- .../doctype/sales_invoice/sales_invoice.py | 2 +- .../doctype/shipping_rule/shipping_rule.py | 10 ++-- .../controllers/website_list_for_contact.py | 4 +- erpnext/demo/setup/setup_data.py | 4 +- erpnext/demo/user/stock.py | 4 +- .../leave_block_list/test_leave_block_list.py | 2 +- erpnext/manufacturing/doctype/bom/bom.py | 12 ++--- erpnext/patches/v4_0/new_address_template.py | 4 +- .../v4_0/reset_permissions_for_masters.py | 4 +- .../v4_0/set_naming_series_property_setter.py | 4 +- erpnext/patches/v4_0/split_email_settings.py | 4 +- .../patches/v4_0/update_account_root_type.py | 4 +- .../fix_gl_entries_for_stock_transactions.py | 12 ++--- erpnext/patches/v4_2/party_model.py | 4 +- .../repost_sle_for_si_with_no_warehouse.py | 4 +- erpnext/patches/v4_2/set_company_country.py | 4 +- .../repost_gle_for_jv_with_multiple_party.py | 4 +- .../patches/v5_4/fix_missing_item_images.py | 4 +- ...anagers_regarding_wrong_tax_calculation.py | 8 +-- .../patches/v5_7/item_template_attributes.py | 4 +- .../repost_entries_with_target_warehouse.py | 54 +++++++++---------- .../v6_4/fix_expense_included_in_valuation.py | 4 +- ...x_journal_entries_due_to_reconciliation.py | 4 +- erpnext/patches/v6_4/make_image_thumbnail.py | 3 +- ...al_entries_where_reference_name_missing.py | 4 +- erpnext/patches/v6_6/fix_website_image.py | 4 +- ...ouse_ledger_gl_entries_for_transactions.py | 6 +-- .../patches/v7_0/rename_salary_components.py | 2 +- erpnext/schools/api.py | 2 +- .../student_applicant/student_applicant.py | 4 +- .../student_group/test_student_group.py | 26 ++++----- erpnext/selling/doctype/customer/customer.py | 2 +- .../doctype/sales_order/sales_order.py | 6 +-- .../doctype/item_group/test_item_group.py | 4 +- erpnext/setup/install.py | 10 ++-- .../doctype/delivery_note/delivery_note.py | 6 +-- .../purchase_receipt/purchase_receipt.py | 6 +-- .../doctype/stock_settings/stock_settings.py | 2 +- erpnext/stock/stock_balance.py | 12 ++--- erpnext/templates/utils.py | 6 +-- erpnext/utilities/__init__.py | 4 +- 43 files changed, 141 insertions(+), 138 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py b/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py index cb95bd17ae..9e3388b37d 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/import_from_openerp.py @@ -4,7 +4,7 @@ """ Import chart of accounts from OpenERP sources """ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import os, json import ast @@ -229,7 +229,7 @@ def make_charts(): filename = src["id"][5:] + "_" + chart_id - print "building " + filename + print("building " + filename) chart = {} chart["name"] = src["name"] chart["country_code"] = src["id"][5:] diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 7bb9a52ee6..9832c0527a 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -393,7 +393,7 @@ class PaymentEntry(AccountsController): if self.payment_type=="Receive": against_account = self.paid_to else: - against_account = self.paid_from + against_account = self.paid_from party_gl_dict = self.get_gl_dict({ diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 96d617e865..780edd8bdf 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -109,7 +109,7 @@ class SalesInvoice(SellingController): if not self.recurring_id: frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, - self.company, self.base_grand_total, self) + self.company, self.base_grand_total, self) self.check_prev_docstatus() diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index 7faaf11cef..a47df2d862 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -54,13 +54,15 @@ class ShippingRule(Document): d.idx = i + 1 def validate_overlapping_shipping_rule_conditions(self): - def overlap_exists_between((x1, x2), (y1, y2)): + def overlap_exists_between(num_range1, num_range2): """ - (x1, x2) and (y1, y2) are two ranges - if condition x = 100 to 300 - then condition y can only be like 50 to 99 or 301 to 400 + num_range1 and num_range2 are two ranges + ranges are represented as a tuple e.g. range 100 to 300 is represented as (100, 300) + if condition num_range1 = 100 to 300 + then condition num_range2 can only be like 50 to 99 or 301 to 400 hence, non-overlapping condition = (x1 <= x2 < y1 <= y2) or (y1 <= y2 < x1 <= x2) """ + (x1, x2), (y1, y2) = num_range1, num_range2 separate = (x1 <= x2 <= y1 <= y2) or (y1 <= y2 <= x1 <= x2) return (not separate) diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index 73badc293d..65360ec9ff 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -68,8 +68,8 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len if txt: if meta.get_field('items'): if meta.get_field('items').options: - child_doctype = meta.get_field('items').options - for item in frappe.get_all(child_doctype, {"item_name": ['like', "%" + txt + "%"]}): + child_doctype = meta.get_field('items').options + for item in frappe.get_all(child_doctype, {"item_name": ['like', "%" + txt + "%"]}): child = frappe.get_doc(child_doctype, item.name) or_filters.append([doctype, "name", "=", child.parent]) diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index ae792ac4a0..cec425ce6b 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import random, json import frappe, erpnext @@ -42,7 +42,7 @@ def setup(domain): frappe.clear_cache() def complete_setup(domain='Manufacturing'): - print "Complete Setup..." + print("Complete Setup...") from frappe.desk.page.setup_wizard.setup_wizard import setup_complete if not frappe.get_all('Company', limit=1): diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py index 1b12db8452..43668fe369 100644 --- a/erpnext/demo/user/stock.py +++ b/erpnext/demo/user/stock.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe, random from frappe.desk import query_report @@ -36,7 +36,7 @@ def make_purchase_receipt(): try: pr.submit() except NegativeStockError: - print 'Negative stock for {0}'.format(po) + print('Negative stock for {0}'.format(po)) pass frappe.db.commit() diff --git a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py index 42b26e342d..210b8b78b7 100644 --- a/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py +++ b/erpnext/hr/doctype/leave_block_list/test_leave_block_list.py @@ -10,7 +10,7 @@ from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_ class TestLeaveBlockList(unittest.TestCase): def tearDown(self): - frappe.set_user("Administrator") + frappe.set_user("Administrator") def test_get_applicable_block_dates(self): frappe.set_user("test@example.com") diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 71ff43f48a..e48a00249d 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -259,15 +259,15 @@ class BOM(WebsiteGenerator): def update_stock_qty(self): - for m in self.get('items'): + for m in self.get('items'): if not m.conversion_factor: m.conversion_factor = flt(get_conversion_factor(m.item_code, m.uom)['conversion_factor']) - if m.uom and m.qty: - m.stock_qty = flt(m.conversion_factor)*flt(m.qty) - if not m.uom and m.stock_uom: - m.uom = m.stock_uom - m.qty = m.stock_qty + if m.uom and m.qty: + m.stock_qty = flt(m.conversion_factor)*flt(m.qty) + if not m.uom and m.stock_uom: + m.uom = m.stock_uom + m.qty = m.stock_qty def set_conversion_rate(self): diff --git a/erpnext/patches/v4_0/new_address_template.py b/erpnext/patches/v4_0/new_address_template.py index f644a5a1ee..fa6602706e 100644 --- a/erpnext/patches/v4_0/new_address_template.py +++ b/erpnext/patches/v4_0/new_address_template.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -10,5 +10,5 @@ def execute(): frappe.db.get_value("Global Defaults", "Global Defaults", "country")}) d.insert() except: - print frappe.get_traceback() + print(frappe.get_traceback()) diff --git a/erpnext/patches/v4_0/reset_permissions_for_masters.py b/erpnext/patches/v4_0/reset_permissions_for_masters.py index b2f1fcd488..bc1b438e2b 100644 --- a/erpnext/patches/v4_0/reset_permissions_for_masters.py +++ b/erpnext/patches/v4_0/reset_permissions_for_masters.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals from frappe.permissions import reset_perms def execute(): @@ -16,5 +16,5 @@ def execute(): try: reset_perms(doctype) except: - print "Error resetting perms for", doctype + print("Error resetting perms for", doctype) raise diff --git a/erpnext/patches/v4_0/set_naming_series_property_setter.py b/erpnext/patches/v4_0/set_naming_series_property_setter.py index 9d12f144ec..e61a5968fe 100644 --- a/erpnext/patches/v4_0/set_naming_series_property_setter.py +++ b/erpnext/patches/v4_0/set_naming_series_property_setter.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.custom.doctype.property_setter.property_setter import make_property_setter @@ -91,7 +91,7 @@ def get_default_series(doctype, new_series): (new_series, new_series)) if not (default_series and default_series[0][0]): - print "[Skipping] Cannot guess which naming series to use for", doctype + print("[Skipping] Cannot guess which naming series to use for", doctype) return return default_series[0][0] diff --git a/erpnext/patches/v4_0/split_email_settings.py b/erpnext/patches/v4_0/split_email_settings.py index 21dc050238..5d1dea60ee 100644 --- a/erpnext/patches/v4_0/split_email_settings.py +++ b/erpnext/patches/v4_0/split_email_settings.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): - print "WARNING!!!! Email Settings not migrated. Please setup your email again." + print("WARNING!!!! Email Settings not migrated. Please setup your email again.") # this will happen if you are migrating very old accounts # comment out this line below and remember to create new Email Accounts diff --git a/erpnext/patches/v4_0/update_account_root_type.py b/erpnext/patches/v4_0/update_account_root_type.py index e3edee99f7..15ddf032a4 100644 --- a/erpnext/patches/v4_0/update_account_root_type.py +++ b/erpnext/patches/v4_0/update_account_root_type.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -31,4 +31,4 @@ def execute(): frappe.db.sql("""UPDATE tabAccount SET root_type=%s WHERE lft>%s and rgt<%s""", (root.root_type, root.lft, root.rgt)) else: - print b"Root type not found for {0}".format(root.name.encode("utf-8")) + print(b"Root type not found for {0}".format(root.name.encode("utf-8"))) diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py index 0df5801c42..16932af3d6 100644 --- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py +++ b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import flt @@ -37,7 +37,7 @@ def execute(): if stock_bal and account_bal and abs(flt(stock_bal[0][0]) - flt(account_bal[0][0])) > 0.1: try: - print voucher_type, voucher_no, stock_bal[0][0], account_bal[0][0] + print(voucher_type, voucher_no, stock_bal[0][0], account_bal[0][0]) frappe.db.sql("""delete from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) @@ -45,10 +45,10 @@ def execute(): voucher = frappe.get_doc(voucher_type, voucher_no) voucher.make_gl_entries(repost_future_gle=False) frappe.db.commit() - except Exception, e: - print frappe.get_traceback() + except Exception as e: + print(frappe.get_traceback()) rejected.append([voucher_type, voucher_no]) frappe.db.rollback() - print "Failed to repost: " - print rejected + print("Failed to repost: ") + print(rejected) diff --git a/erpnext/patches/v4_2/party_model.py b/erpnext/patches/v4_2/party_model.py index 8f4fc335d8..6f9335269b 100644 --- a/erpnext/patches/v4_2/party_model.py +++ b/erpnext/patches/v4_2/party_model.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -109,7 +109,7 @@ def delete_individual_party_account(): and exists(select gle.name from `tabGL Entry` gle where gle.account = tabAccount.name)""") if accounts_not_deleted: - print "Accounts not deleted: " + "\n".join(accounts_not_deleted) + print("Accounts not deleted: " + "\n".join(accounts_not_deleted)) def remove_customer_supplier_account_report(): diff --git a/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py b/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py index 44bec0091a..1356129dc0 100644 --- a/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py +++ b/erpnext/patches/v4_2/repost_sle_for_si_with_no_warehouse.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from erpnext.stock.stock_ledger import NegativeStockError @@ -28,7 +28,7 @@ def execute(): frappe.local.stockledger_exceptions = None frappe.db.rollback() - print "Failed to repost: ", failed_list + print("Failed to repost: ", failed_list) \ No newline at end of file diff --git a/erpnext/patches/v4_2/set_company_country.py b/erpnext/patches/v4_2/set_company_country.py index 929f6c5c51..89f07f2873 100644 --- a/erpnext/patches/v4_2/set_company_country.py +++ b/erpnext/patches/v4_2/set_company_country.py @@ -1,13 +1,13 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): country = frappe.db.get_single_value("Global Defaults", "country") if not country: - print "Country not specified in Global Defaults" + print("Country not specified in Global Defaults") return for company in frappe.db.sql_list("""select name from `tabCompany` diff --git a/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py b/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py index da58ae2349..76efdcc7c6 100644 --- a/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py +++ b/erpnext/patches/v5_0/repost_gle_for_jv_with_multiple_party.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -21,6 +21,6 @@ def execute(): je.make_gl_entries() if je_list: - print je_list + print(je_list) \ No newline at end of file diff --git a/erpnext/patches/v5_4/fix_missing_item_images.py b/erpnext/patches/v5_4/fix_missing_item_images.py index 1891d2d622..c0a25132ff 100644 --- a/erpnext/patches/v5_4/fix_missing_item_images.py +++ b/erpnext/patches/v5_4/fix_missing_item_images.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe import os from frappe.utils import get_files_path @@ -45,7 +45,7 @@ def fix_files_for_item(files_path, unlinked_files): try: file_data.save() except IOError: - print "File {0} does not exist".format(new_file_url) + print("File {0} does not exist".format(new_file_url)) # marking fix to prevent further errors fixed_files.append(file_url) diff --git a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py b/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py index 125b84fce1..ba311225bb 100644 --- a/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py +++ b/erpnext/patches/v5_4/notify_system_managers_regarding_wrong_tax_calculation.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.email import sendmail_to_system_managers from frappe.utils import get_link_to_form @@ -36,6 +36,6 @@ Administrator""" % "\n".join([(d[0] + ": " + ", ".join(d[1])) for d in wrong_rec except: pass - print "="*50 - print content - print "="*50 \ No newline at end of file + print("="*50) + print(content) + print("="*50) \ No newline at end of file diff --git a/erpnext/patches/v5_7/item_template_attributes.py b/erpnext/patches/v5_7/item_template_attributes.py index 9f141b5b08..22b15d32ae 100644 --- a/erpnext/patches/v5_7/item_template_attributes.py +++ b/erpnext/patches/v5_7/item_template_attributes.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe import MySQLdb @@ -32,7 +32,7 @@ def execute(): migrate_item_variants() except MySQLdb.ProgrammingError: - print "`tabItem Variant` not found" + print("`tabItem Variant` not found") def rename_and_reload_doctypes(): if "tabVariant Attribute" in frappe.db.get_tables(): diff --git a/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py b/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py index dc0df0f89e..fb5eab4e05 100644 --- a/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py +++ b/erpnext/patches/v6_12/repost_entries_with_target_warehouse.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe """ @@ -38,19 +38,19 @@ def check(): si_list = get_affected_sales_invoice() if so_list or dn_list or si_list: - print "Entries with Target Warehouse:" + print("Entries with Target Warehouse:") if so_list: - print "Sales Order" - print so_list + print("Sales Order") + print(so_list) if dn_list: - print "Delivery Notes" - print [d.name for d in dn_list] + print("Delivery Notes") + print([d.name for d in dn_list]) if si_list: - print "Sales Invoice" - print [d.name for d in si_list] + print("Sales Invoice") + print([d.name for d in si_list]) def repost(): @@ -61,34 +61,34 @@ def repost(): frappe.db.commit() if dn_failed_list: - print "-"*40 - print "Delivery Note Failed to Repost" - print dn_failed_list + print("-"*40) + print("Delivery Note Failed to Repost") + print(dn_failed_list) if si_failed_list: - print "-"*40 - print "Sales Invoice Failed to Repost" - print si_failed_list - print + print("-"*40) + print("Sales Invoice Failed to Repost") + print(si_failed_list) + print() - print """ + print(""" If above Delivery Notes / Sales Invoice failed due to negative stock, follow these steps: - Ensure that stock is available for those items in the mentioned warehouse on the date mentioned in the error - Run this patch again -""" +""") def repost_dn(dn_failed_list): dn_list = get_affected_delivery_notes() if dn_list: - print "-"*40 - print "Reposting Delivery Notes" + print("-"*40) + print("Reposting Delivery Notes") for dn in dn_list: if dn.docstatus == 0: continue - print dn.name + print(dn.name) try: dn_doc = frappe.get_doc("Delivery Note", dn.name) @@ -107,7 +107,7 @@ def repost_dn(dn_failed_list): except Exception: dn_failed_list.append(dn.name) frappe.local.stockledger_exceptions = None - print frappe.get_traceback() + print(frappe.get_traceback()) frappe.db.rollback() frappe.db.sql("update `tabDelivery Note Item` set target_warehouse='' where docstatus=0") @@ -116,14 +116,14 @@ def repost_si(si_failed_list): si_list = get_affected_sales_invoice() if si_list: - print "-"*40 - print "Reposting Sales Invoice" + print("-"*40) + print("Reposting Sales Invoice") for si in si_list: if si.docstatus == 0: continue - print si.name + print(si.name) try: si_doc = frappe.get_doc("Sales Invoice", si.name) @@ -141,7 +141,7 @@ def repost_si(si_failed_list): except Exception: si_failed_list.append(si.name) frappe.local.stockledger_exceptions = None - print frappe.get_traceback() + print(frappe.get_traceback()) frappe.db.rollback() frappe.db.sql("update `tabSales Invoice Item` set target_warehouse='' where docstatus=0") @@ -152,8 +152,8 @@ def repost_so(): frappe.db.sql("update `tabSales Order Item` set target_warehouse=''") if so_list: - print "-"*40 - print "Sales Order reposted" + print("-"*40) + print("Sales Order reposted") def get_affected_delivery_notes(): diff --git a/erpnext/patches/v6_4/fix_expense_included_in_valuation.py b/erpnext/patches/v6_4/fix_expense_included_in_valuation.py index 436dd02a2c..7ed15ab010 100644 --- a/erpnext/patches/v6_4/fix_expense_included_in_valuation.py +++ b/erpnext/patches/v6_4/fix_expense_included_in_valuation.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import cstr @@ -33,7 +33,7 @@ def execute(): (pi.name, company.expenses_included_in_valuation)) if gle_for_expenses_included_in_valuation: - print pi.name + print(pi.name) frappe.db.sql("""delete from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s""", pi.name) diff --git a/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py b/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py index b1464d5e2a..b53412d7eb 100644 --- a/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py +++ b/erpnext/patches/v6_4/fix_journal_entries_due_to_reconciliation.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -44,7 +44,7 @@ def execute(): where name=%s""", d.name) for d in journal_entries: - print d + print(d) # delete existing gle frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d) diff --git a/erpnext/patches/v6_4/make_image_thumbnail.py b/erpnext/patches/v6_4/make_image_thumbnail.py index 702148a8f6..3315acc896 100644 --- a/erpnext/patches/v6_4/make_image_thumbnail.py +++ b/erpnext/patches/v6_4/make_image_thumbnail.py @@ -1,3 +1,4 @@ +from __future__ import print_function import frappe def execute(): @@ -11,4 +12,4 @@ def execute(): if item_doc.thumbnail: item_doc.db_set("thumbnail", item_doc.thumbnail, update_modified=False) except Exception: - print "Unable to make thumbnail for {0}".format(item.website_image.encode("utf-8")) + print("Unable to make thumbnail for {0}".format(item.website_image.encode("utf-8"))) diff --git a/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py b/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py index e0268c42db..1319b53558 100644 --- a/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py +++ b/erpnext/patches/v6_4/repost_gle_for_journal_entries_where_reference_name_missing.py @@ -1,7 +1,7 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe def execute(): @@ -13,7 +13,7 @@ def execute(): and against_voucher=je.reference_name)""") for d in je_list: - print d + print(d) # delete existing gle frappe.db.sql("delete from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s", d) diff --git a/erpnext/patches/v6_6/fix_website_image.py b/erpnext/patches/v6_6/fix_website_image.py index b3b4cab18a..cc3e2d852c 100644 --- a/erpnext/patches/v6_6/fix_website_image.py +++ b/erpnext/patches/v6_6/fix_website_image.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import encode @@ -25,7 +25,7 @@ def execute(): try: file.validate_file() except IOError: - print encode(item.website_image), "does not exist" + print(encode(item.website_image), "does not exist") file.delete() item.db_set("website_image", None, update_modified=False) diff --git a/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py b/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py index 58da0594ea..2bc09714d8 100644 --- a/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py +++ b/erpnext/patches/v7_0/fix_nonwarehouse_ledger_gl_entries_for_transactions.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe, erpnext def execute(): @@ -35,11 +35,11 @@ def execute(): voucher.make_gl_entries() frappe.db.commit() except Exception as e: - print frappe.get_traceback() + print(frappe.get_traceback()) rejected.append([voucher_type, voucher_no]) frappe.db.rollback() - print rejected + print(rejected) def set_warehouse_for_stock_account(warehouse_account): for account in warehouse_account: diff --git a/erpnext/patches/v7_0/rename_salary_components.py b/erpnext/patches/v7_0/rename_salary_components.py index 4e9ceb2173..8409ca842d 100644 --- a/erpnext/patches/v7_0/rename_salary_components.py +++ b/erpnext/patches/v7_0/rename_salary_components.py @@ -81,7 +81,7 @@ def execute(): try: frappe.db.sql("""INSERT INTO `tabSalary Component` ({0}) SELECT {1} FROM `tab{2}`""" .format(target_cols, source_cols, doctype)) - except Exception, e: + except Exception as e: if e.args[0]==1062: pass diff --git a/erpnext/schools/api.py b/erpnext/schools/api.py index c613c8c5a6..ff2da07a30 100644 --- a/erpnext/schools/api.py +++ b/erpnext/schools/api.py @@ -63,7 +63,7 @@ def mark_attendance(students_present, students_absent, course_schedule=None, stu :param student_group: Student Group. :param date: Date. """ - + present = json.loads(students_present) absent = json.loads(students_absent) diff --git a/erpnext/schools/doctype/student_applicant/student_applicant.py b/erpnext/schools/doctype/student_applicant/student_applicant.py index 047c7027c6..081fa065db 100644 --- a/erpnext/schools/doctype/student_applicant/student_applicant.py +++ b/erpnext/schools/doctype/student_applicant/student_applicant.py @@ -2,7 +2,7 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe import _ from frappe.model.document import Document @@ -13,7 +13,7 @@ class StudentApplicant(Document): if self.student_admission: naming_series = frappe.db.get_value('Student Admission', self.student_admission, 'naming_series_for_student_applicant') - print naming_series + print(naming_series) if naming_series: self.naming_series = naming_series diff --git a/erpnext/schools/doctype/student_group/test_student_group.py b/erpnext/schools/doctype/student_group/test_student_group.py index 18a6b14f50..e358c27bf6 100644 --- a/erpnext/schools/doctype/student_group/test_student_group.py +++ b/erpnext/schools/doctype/student_group/test_student_group.py @@ -8,20 +8,20 @@ import unittest from frappe.utils.make_random import get_random class TestStudentGroup(unittest.TestCase): - def test_student_roll_no(self): - doc = frappe.get_doc({ - "doctype": "Student Group", - "student_group_name": "_Test Student Group R", + def test_student_roll_no(self): + doc = frappe.get_doc({ + "doctype": "Student Group", + "student_group_name": "_Test Student Group R", "group_based_on": "Activity" - }).insert() + }).insert() - student_list = [] - while len(student_list) < 3: - s = get_random("Student") - if s not in student_list: - student_list.append(s) + student_list = [] + while len(student_list) < 3: + s = get_random("Student") + if s not in student_list: + student_list.append(s) - doc.extend("students", [{"student":d} for d in student_list]) - doc.save() - self.assertEquals(max([d.group_roll_number for d in doc.students]), 3) + doc.extend("students", [{"student":d} for d in student_list]) + doc.save() + self.assertEquals(max([d.group_roll_number for d in doc.students]), 3) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index d797632902..c12cd449b8 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -67,7 +67,7 @@ class Customer(TransactionBase): '''If Customer created from Lead, update lead status to "Converted" update Customer link in Quotation, Opportunity''' if self.lead_name: - frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False) + frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False) for doctype in ('Opportunity', 'Quotation'): for d in frappe.get_all(doctype, {'lead': self.lead_name}): diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 396b1c2f61..5f904c2e3d 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -51,9 +51,9 @@ class SalesOrder(SellingController): # validate p.o date v/s delivery date if self.po_date: for d in self.get("items"): - if d.delivery_date and getdate(self.po_date) > getdate(d.delivery_date): - frappe.throw(_("Row #{0}: Expected Delivery Date cannot be before Purchase Order Date") - .format(d.idx)) + if d.delivery_date and getdate(self.po_date) > getdate(d.delivery_date): + frappe.throw(_("Row #{0}: Expected Delivery Date cannot be before Purchase Order Date") + .format(d.idx)) if self.po_no and self.customer: so = frappe.db.sql("select name from `tabSales Order` \ diff --git a/erpnext/setup/doctype/item_group/test_item_group.py b/erpnext/setup/doctype/item_group/test_item_group.py index bc88132b7f..c487c7239b 100644 --- a/erpnext/setup/doctype/item_group/test_item_group.py +++ b/erpnext/setup/doctype/item_group/test_item_group.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import unittest import frappe from frappe.utils.nestedset import NestedSetRecursionError, NestedSetMultipleRootsError, \ @@ -112,7 +112,7 @@ class TestItem(unittest.TestCase): def print_tree(self): import json - print json.dumps(frappe.db.sql("select name, lft, rgt from `tabItem Group` order by lft"), indent=1) + print(json.dumps(frappe.db.sql("select name, lft, rgt from `tabItem Group` order by lft"), indent=1)) def test_move_leaf_into_another_group(self): # before move diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py index 7b71675f75..9bf15cee64 100644 --- a/erpnext/setup/install.py +++ b/erpnext/setup/install.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe import _ @@ -19,10 +19,10 @@ def after_install(): def check_setup_wizard_not_completed(): if frappe.db.get_default('desktop:home_page') == 'desktop': - print - print "ERPNext can only be installed on a fresh site where the setup wizard is not completed" - print "You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall" - print + print() + print("ERPNext can only be installed on a fresh site where the setup wizard is not completed") + print("You can reinstall this site (after saving your data) using: bench --site [sitename] reinstall") + print() return False def set_single_defaults(): diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 82beff8782..f5a99afbd2 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -92,9 +92,9 @@ class DeliveryNote(SellingController): def so_required(self): """check in manage account if sales order required or not""" if frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes': - for d in self.get('items'): - if not d.against_sales_order: - frappe.throw(_("Sales Order required for Item {0}").format(d.item_code)) + for d in self.get('items'): + if not d.against_sales_order: + frappe.throw(_("Sales Order required for Item {0}").format(d.item_code)) def validate(self): self.validate_posting_time() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d12c288a28..2d089c4419 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -84,9 +84,9 @@ class PurchaseReceipt(BuyingController): def po_required(self): if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes': - for d in self.get('items'): - if not d.purchase_order: - frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) + for d in self.get('items'): + if not d.purchase_order: + frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) def get_already_received_qty(self, po, po_detail): qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item` diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index d9d9568e40..186eaeebb1 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -18,7 +18,7 @@ class StockSettings(Document): self.get("item_naming_by")=="Naming Series", hide_name_field=True) stock_frozen_limit = 356 - submitted_stock_frozen = self.stock_frozen_upto_days + submitted_stock_frozen = self.stock_frozen_upto_days or 0 if submitted_stock_frozen > stock_frozen_limit: self.stock_frozen_upto_days = stock_frozen_limit frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 403d5cbc30..6a4ac439ee 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -1,7 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals +from __future__ import print_function, unicode_literals import frappe from frappe.utils import flt, cstr, nowdate, nowtime @@ -170,7 +170,7 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin where item_code=%s and warehouse=%s and docstatus < 2""", (d[0], d[1])) if serial_nos and flt(serial_nos[0][0]) != flt(d[2]): - print d[0], d[1], d[2], serial_nos[0][0] + print(d[0], d[1], d[2], serial_nos[0][0]) sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry` where item_code = %s and warehouse = %s and ifnull(is_cancelled, 'No') = 'No' @@ -244,7 +244,7 @@ def repost_all_stock_vouchers(): i = 0 for voucher_type, voucher_no in vouchers: i+=1 - print i, "/", len(vouchers), voucher_type, voucher_no + print(i, "/", len(vouchers), voucher_type, voucher_no) try: for dt in ["Stock Ledger Entry", "GL Entry"]: frappe.db.sql("""delete from `tab%s` where voucher_type=%s and voucher_no=%s"""% @@ -259,9 +259,9 @@ def repost_all_stock_vouchers(): doc.update_stock_ledger() doc.make_gl_entries(repost_future_gle=False) frappe.db.commit() - except Exception, e: - print frappe.get_traceback() + except Exception as e: + print(frappe.get_traceback()) rejected.append([voucher_type, voucher_no]) frappe.db.rollback() - print rejected + print(rejected) diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py index e46fed6bb6..6ebe41185f 100644 --- a/erpnext/templates/utils.py +++ b/erpnext/templates/utils.py @@ -36,11 +36,11 @@ def send_message(subject="Website Query", message="", sender="", status="Open"): )) if customer: - opportunity.customer = customer[0][0] + opportunity.customer = customer[0][0] elif lead: - opportunity.lead = lead + opportunity.lead = lead else: - opportunity.lead = new_lead.name + opportunity.lead = new_lead.name opportunity.insert(ignore_permissions=True) diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py index 944f9785a4..0f641b2b38 100644 --- a/erpnext/utilities/__init__.py +++ b/erpnext/utilities/__init__.py @@ -1,5 +1,5 @@ ## temp utility - +from __future__ import print_function import frappe from erpnext.utilities.activation import get_level from frappe.utils import cstr @@ -12,7 +12,7 @@ def update_doctypes(): for f in dt.fields: if f.fieldname == d.fieldname and f.fieldtype in ("Text", "Small Text"): - print f.parent, f.fieldname + print(f.parent, f.fieldname) f.fieldtype = "Text Editor" dt.save() break From 6f50b3a5b2077e8636f114fe867550e06b73fdf3 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 29 Aug 2017 14:02:49 +0530 Subject: [PATCH 39/39] handle both cases --- erpnext/templates/includes/product_page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js index 658fa53588..b2d5ad952b 100644 --- a/erpnext/templates/includes/product_page.js +++ b/erpnext/templates/includes/product_page.js @@ -73,7 +73,7 @@ frappe.ready(function() { } } - if (window.location.search == ("?variant=" + item_code)) { + if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) { return; }