From 862a2eb975517c4fc78e8298a04076d72df6cec1 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 3 Aug 2015 11:58:23 +0530 Subject: [PATCH 1/3] [enhancement] make service type product bundle: --- .../product_bundle/product_bundle.json | 92 +++++++++++++++++-- .../doctype/product_bundle/product_bundle.py | 13 +-- .../product_bundle/test_product_bundle.py | 19 +++- .../doctype/sales_order/sales_order.py | 5 +- .../doctype/sales_order/test_sales_order.py | 86 ++++++++++------- erpnext/stock/doctype/item/test_item.py | 17 ++++ 6 files changed, 174 insertions(+), 58 deletions(-) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.json b/erpnext/selling/doctype/product_bundle/product_bundle.json index 3f4e2965d6f..a1be948691a 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.json +++ b/erpnext/selling/doctype/product_bundle/product_bundle.json @@ -1,21 +1,41 @@ { + "allow_copy": 0, "allow_import": 1, + "allow_rename": 0, "creation": "2013-06-20 11:53:21", + "custom": 0, "description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials", "docstatus": 0, "doctype": "DocType", - "document_type": "Master", + "document_type": "", "fields": [ { + "allow_on_submit": 0, "fieldname": "basic_section", "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { - "description": "The Item that represents the Package. This Item must have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\"", + "allow_on_submit": 0, + "description": "", "fieldname": "new_item_code", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Parent Item", "no_copy": 1, @@ -23,30 +43,67 @@ "oldfieldtype": "Data", "options": "Item", "permlevel": 0, - "reqd": 1 + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "description": "List items that form the package.", "fieldname": "item_section", "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "items", "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Items", + "no_copy": 0, "oldfieldname": "sales_bom_items", "oldfieldtype": "Table", "options": "Product Bundle Item", "permlevel": 0, - "reqd": 1 + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], + "hide_heading": 0, + "hide_toolbar": 0, "icon": "icon-sitemap", "idx": 1, + "in_create": 0, + "in_dialog": 0, "is_submittable": 0, - "modified": "2015-07-13 05:28:28.140327", + "issingle": 0, + "istable": 0, + "modified": "2015-08-03 11:23:26.263254", "modified_by": "Administrator", "module": "Selling", "name": "Product Bundle", @@ -54,14 +111,20 @@ "permissions": [ { "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, "create": 1, "delete": 1, "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Stock Manager", + "set_user_permissions": 0, "share": 1, "submit": 0, "write": 1 @@ -69,31 +132,44 @@ { "amend": 0, "apply_user_permissions": 1, + "cancel": 0, "create": 0, "delete": 0, "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Stock User", + "set_user_permissions": 0, + "share": 0, "submit": 0, "write": 0 }, { "amend": 0, "apply_user_permissions": 1, + "cancel": 0, "create": 1, "delete": 1, "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales User", + "set_user_permissions": 0, "share": 1, "submit": 0, "write": 1 } - ] + ], + "read_only": 0, + "read_only_onload": 0 } \ No newline at end of file diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index 8c95a45bf3a..fdf6b76db08 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -13,17 +13,9 @@ class ProductBundle(Document): self.name = self.new_item_code def validate(self): - self.validate_main_item() - from erpnext.utilities.transaction_base import validate_uom_is_integer validate_uom_is_integer(self, "uom", "qty") - def validate_main_item(self): - """main item must have Is Stock Item as No and Is Sales Item as Yes""" - if not frappe.db.sql("""select name from tabItem where name=%s and - is_stock_item = 0 and is_sales_item = 1""", self.new_item_code): - frappe.throw(_("Parent Item {0} must be not Stock Item and must be a Sales Item").format(self.new_item_code)) - def get_item_details(self, name): det = frappe.db.sql("""select description, stock_uom from `tabItem` where name = %s""", name) @@ -36,8 +28,7 @@ def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond return frappe.db.sql("""select name, item_name, description from tabItem - where is_stock_item=0 and is_sales_item=1 - and name not in (select name from `tabProduct Bundle`) and %s like %s - %s limit %s, %s""" % (searchfield, "%s", + where name not in (select name from `tabProduct Bundle`) + and %s like %s %s limit %s, %s""" % (searchfield, "%s", get_match_cond(doctype),"%s", "%s"), ("%%%s%%" % txt, start, page_len)) diff --git a/erpnext/selling/doctype/product_bundle/test_product_bundle.py b/erpnext/selling/doctype/product_bundle/test_product_bundle.py index 8c5fe12e1be..39b17f368de 100644 --- a/erpnext/selling/doctype/product_bundle/test_product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/test_product_bundle.py @@ -5,4 +5,21 @@ from __future__ import unicode_literals import frappe -test_records = frappe.get_test_records('Product Bundle') \ No newline at end of file +test_records = frappe.get_test_records('Product Bundle') + +def make_product_bundle(parent, items): + if frappe.db.exists("Product Bundle", parent): + return frappe.get_doc("Product Bundle", parent) + + product_bundle = frappe.get_doc({ + "doctype": "Product Bundle", + "parent_item": parent, + "new_item_code": parent + }) + + for item in items: + product_bundle.append("items", {"item_code": item, "qty": 1}) + + product_bundle.insert() + + return product_bundle diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b92e934fa38..109034d14c9 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -39,9 +39,8 @@ class SalesOrder(SellingController): for d in self.get('items'): check_list.append(cstr(d.item_code)) - if (frappe.db.get_value("Item", d.item_code, "is_stock_item")==1 or - self.has_product_bundle(d.item_code)) and not d.warehouse: - frappe.throw(_("Reserved warehouse required for stock item {0}").format(d.item_code)) + if frappe.db.get_value("Item", d.item_code, "is_stock_item") and not d.warehouse: + frappe.throw(_("Delivery warehouse required for stock item {0}").format(d.item_code)) # used for production plan d.transaction_date = self.transaction_date diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 59e58b0c787..d4d5f92b595 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -80,7 +80,7 @@ class TestSalesOrder(unittest.TestCase): def test_reserved_qty_for_partial_delivery(self): existing_reserved_qty = get_reserved_qty() - + so = make_sales_order() self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10) @@ -91,7 +91,7 @@ class TestSalesOrder(unittest.TestCase): so.load_from_db() so.stop_sales_order() self.assertEqual(get_reserved_qty(), existing_reserved_qty) - + # unstop so so.load_from_db() so.unstop_sales_order() @@ -99,7 +99,7 @@ class TestSalesOrder(unittest.TestCase): dn.cancel() self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10) - + # cancel so.load_from_db() so.cancel() @@ -108,9 +108,9 @@ class TestSalesOrder(unittest.TestCase): def test_reserved_qty_for_over_delivery(self): # set over-delivery tolerance frappe.db.set_value('Item', "_Test Item", 'tolerance', 50) - + existing_reserved_qty = get_reserved_qty() - + so = make_sales_order() self.assertEqual(get_reserved_qty(), existing_reserved_qty + 10) @@ -124,39 +124,39 @@ class TestSalesOrder(unittest.TestCase): def test_reserved_qty_for_partial_delivery_with_packing_list(self): existing_reserved_qty_item1 = get_reserved_qty("_Test Item") existing_reserved_qty_item2 = get_reserved_qty("_Test Item Home Desktop 100") - + so = make_sales_order(item_code="_Test Product Bundle Item") - + self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50) - self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), + self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2 + 20) - + dn = create_dn_against_so(so.name) - + self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 25) - self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), + self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2 + 10) # stop so so.load_from_db() so.stop_sales_order() - + self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1) self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2) # unstop so so.load_from_db() so.unstop_sales_order() - + self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 25) - self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), + self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2 + 10) dn.cancel() self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50) - self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), + self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2 + 20) - + so.load_from_db() so.cancel() self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1) @@ -165,25 +165,25 @@ class TestSalesOrder(unittest.TestCase): def test_reserved_qty_for_over_delivery_with_packing_list(self): # set over-delivery tolerance frappe.db.set_value('Item', "_Test Product Bundle Item", 'tolerance', 50) - + existing_reserved_qty_item1 = get_reserved_qty("_Test Item") existing_reserved_qty_item2 = get_reserved_qty("_Test Item Home Desktop 100") - + so = make_sales_order(item_code="_Test Product Bundle Item") - + self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50) - self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), + self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2 + 20) - + dn = create_dn_against_so(so.name, 15) - + self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1) - self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), + self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2) - + dn.cancel() self.assertEqual(get_reserved_qty("_Test Item"), existing_reserved_qty_item1 + 50) - self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), + self.assertEqual(get_reserved_qty("_Test Item Home Desktop 100"), existing_reserved_qty_item2 + 20) def test_warehouse_user(self): @@ -201,7 +201,7 @@ class TestSalesOrder(unittest.TestCase): frappe.set_user("test@example.com") - so = make_sales_order(company="_Test Company 1", + so = make_sales_order(company="_Test Company 1", warehouse="_Test Warehouse 2 - _TC1", do_not_save=True) so.conversion_rate = 0.02 so.plc_conversion_rate = 0.02 @@ -216,14 +216,30 @@ class TestSalesOrder(unittest.TestCase): def test_block_delivery_note_against_cancelled_sales_order(self): so = make_sales_order() - + dn = make_delivery_note(so.name) dn.insert() - + so.cancel() - + self.assertRaises(frappe.CancelledLinkError, dn.submit) - + + def test_service_type_product_bundle(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + + make_item("_Test Service Product Bundle", {"is_stock_item": 0, "is_sales_item": 1}) + make_item("_Test Service Product Bundle Item 1", {"is_stock_item": 0, "is_sales_item": 1}) + make_item("_Test Service Product Bundle Item 2", {"is_stock_item": 0, "is_sales_item": 1}) + + make_product_bundle("_Test Service Product Bundle", + ["_Test Service Product Bundle Item 1", "_Test Service Product Bundle Item 2"]) + + so = make_sales_order(item_code = "_Test Service Product Bundle") + + self.assertTrue("_Test Service Product Bundle Item 1" in [d.item_code for d in so.packed_items]) + self.assertTrue("_Test Service Product Bundle Item 2" in [d.item_code for d in so.packed_items]) + def make_sales_order(**args): so = frappe.new_doc("Sales Order") args = frappe._dict(args) @@ -246,12 +262,12 @@ def make_sales_order(**args): so.insert() if not args.do_not_submit: so.submit() - + return so - + def create_dn_against_so(so, delivered_qty=0): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) - + dn = make_delivery_note(so) dn.get("items")[0].qty = delivered_qty or 5 dn.insert() @@ -261,5 +277,5 @@ def create_dn_against_so(so, delivered_qty=0): def get_reserved_qty(item_code="_Test Item", warehouse="_Test Warehouse - _TC"): return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "reserved_qty")) - -test_dependencies = ["Currency Exchange"] \ No newline at end of file + +test_dependencies = ["Currency Exchange"] diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index f299c4a75c8..0fb01ac1172 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -12,6 +12,23 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry test_ignore = ["BOM"] test_dependencies = ["Warehouse"] +def make_item(item_code, properties=None): + if frappe.db.exists("Item", item_code): + return frappe.get_doc("Item", item_code) + + item = frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "description": item_code, + "item_group": "Products" + }) + + if properties: + item.update(properties) + item.insert() + return item + class TestItem(unittest.TestCase): def get_item(self, idx): item_code = test_records[idx].get("item_code") From a208c5681317458351c683f416d22abd985d404d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 3 Aug 2015 13:18:54 +0530 Subject: [PATCH 2/3] [enhancement] auto insert item price if missing, #3533 --- .../current/auto_insert_price_list_rate.md | 1 + erpnext/change_log/current/product_bundle.md | 1 + erpnext/patches.txt | 2 +- erpnext/public/js/controllers/transaction.js | 16 +- .../doctype/sales_order/test_sales_order.py | 33 +++ .../selling_settings/selling_settings.json | 171 +++++++++++- .../setup/page/setup_wizard/setup_wizard.py | 1 + .../stock_settings/stock_settings.json | 252 ++++++++++++++++-- erpnext/stock/get_item_details.py | 18 ++ 9 files changed, 458 insertions(+), 37 deletions(-) create mode 100644 erpnext/change_log/current/auto_insert_price_list_rate.md create mode 100644 erpnext/change_log/current/product_bundle.md diff --git a/erpnext/change_log/current/auto_insert_price_list_rate.md b/erpnext/change_log/current/auto_insert_price_list_rate.md new file mode 100644 index 00000000000..6ea6689a6bb --- /dev/null +++ b/erpnext/change_log/current/auto_insert_price_list_rate.md @@ -0,0 +1 @@ +- Automatically insert Price List Rate in Price List if added in transaction if permission exists and allowed from Stock Settings diff --git a/erpnext/change_log/current/product_bundle.md b/erpnext/change_log/current/product_bundle.md new file mode 100644 index 00000000000..56c1716d89f --- /dev/null +++ b/erpnext/change_log/current/product_bundle.md @@ -0,0 +1 @@ +- Product Bundle now allowed for all Items (stock or non-stock) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 761f1a94b36..b2d8068eeaf 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -187,4 +187,4 @@ execute:frappe.db.sql("update `tabLeave Type` set include_holiday=0") erpnext.patches.v5_4.set_root_and_report_type erpnext.patches.v5_4.notify_system_managers_regarding_wrong_tax_calculation erpnext.patches.v5_4.fix_invoice_outstanding -execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' where voucher_type = 'Stock Reconciliation' and ifnull(qty_after_transaction, 0) = 0") \ No newline at end of file +execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' where voucher_type = 'Stock Reconciliation' and ifnull(qty_after_transaction, 0) = 0") diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 5ba3651fc6f..ba10702bd98 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -36,7 +36,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(this.frm.fields_dict["items"]) { this["items_remove"] = this.calculate_taxes_and_totals; } - + if(this.frm.fields_dict["recurring_print_format"]) { this.frm.set_query("recurring_print_format", function(doc) { return{ @@ -46,7 +46,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }); } - + if(this.frm.fields_dict["return_against"]) { this.frm.set_query("return_against", function(doc) { var filters = { @@ -56,13 +56,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ }; if (me.frm.fields_dict["customer"] && doc.customer) filters["customer"] = doc.customer; if (me.frm.fields_dict["supplier"] && doc.supplier) filters["supplier"] = doc.supplier; - + return { filters: filters } }); } - + }, onload_post_render: function() { @@ -275,7 +275,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ posting_date: function() { var me = this; if (this.frm.doc.posting_date) { - if ((this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.customer) || + if ((this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.customer) || (this.frm.doc.doctype == "Purchase Invoice" && this.frm.doc.supplier)) { return frappe.call({ method: "erpnext.accounts.party.get_due_date", @@ -284,7 +284,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ "party_type": me.frm.doc.doctype == "Sales Invoice" ? "Customer" : "Supplier", "party": me.frm.doc.doctype == "Sales Invoice" ? me.frm.doc.customer : me.frm.doc.supplier, "company": me.frm.doc.company - }, + }, callback: function(r, rt) { if(r.message) { me.frm.set_value("due_date", r.message); @@ -301,7 +301,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ get_company_currency: function() { return erpnext.get_currency(this.frm.doc.company); }, - + contact_person: function() { erpnext.utils.get_contact_details(this.frm); }, @@ -373,7 +373,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ plc_conversion_rate: function() { if(this.frm.doc.price_list_currency === this.get_company_currency()) { this.frm.set_value("plc_conversion_rate", 1.0); - } else if(this.frm.doc.price_list_currency === this.frm.doc.currency + } else if(this.frm.doc.price_list_currency === this.frm.doc.currency && this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 && cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) { this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate); diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index d4d5f92b595..1ceda1246ef 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -240,6 +240,37 @@ class TestSalesOrder(unittest.TestCase): self.assertTrue("_Test Service Product Bundle Item 1" in [d.item_code for d in so.packed_items]) self.assertTrue("_Test Service Product Bundle Item 2" in [d.item_code for d in so.packed_items]) + def test_auto_insert_price(self): + from erpnext.stock.doctype.item.test_item import make_item + make_item("_Test Item for Auto Price List", {"is_stock_item": 0, "is_sales_item": 1}) + frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1) + + item_price = frappe.db.get_value("Item Price", {"price_list": "_Test Price List", + "item_code": "_Test Item for Auto Price List"}) + if item_price: + frappe.delete_doc("Item Price", item_price) + + make_sales_order(item_code = "_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100) + + self.assertEquals(frappe.db.get_value("Item Price", + {"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}, "price_list_rate"), 100) + + + # do not update price list + frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0) + + item_price = frappe.db.get_value("Item Price", {"price_list": "_Test Price List", + "item_code": "_Test Item for Auto Price List"}) + if item_price: + frappe.delete_doc("Item Price", item_price) + + make_sales_order(item_code = "_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100) + + self.assertEquals(frappe.db.get_value("Item Price", + {"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}, "price_list_rate"), None) + + frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1) + def make_sales_order(**args): so = frappe.new_doc("Sales Order") args = frappe._dict(args) @@ -250,6 +281,8 @@ def make_sales_order(**args): so.customer = args.customer or "_Test Customer" so.delivery_date = add_days(so.transaction_date, 10) so.currency = args.currency or "INR" + if args.selling_price_list: + so.selling_price_list = args.selling_price_list so.append("items", { "item_code": args.item or args.item_code or "_Test Item", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index aafc31e3133..fb239d61ff0 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -1,103 +1,250 @@ { + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, "creation": "2013-06-25 10:25:16", + "custom": 0, "description": "Settings for Selling Module", "docstatus": 0, "doctype": "DocType", "document_type": "Other", "fields": [ { + "allow_on_submit": 0, "default": "Customer Name", "fieldname": "cust_master_name", "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Customer Naming By", + "no_copy": 0, "options": "Customer Name\nNaming Series", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "campaign_naming_by", "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Campaign Naming By", + "no_copy": 0, "options": "Campaign Name\nNaming Series", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "description": "", "fieldname": "customer_group", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Default Customer Group", + "no_copy": 0, "options": "Customer Group", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "description": "", "fieldname": "territory", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Default Territory", + "no_copy": 0, "options": "Territory", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "selling_price_list", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Default Price List", + "no_copy": 0, "options": "Price List", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "column_break_5", "fieldtype": "Column Break", - "permlevel": 0 + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "so_required", "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Sales Order Required", + "no_copy": 0, "options": "No\nYes", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "dn_required", "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Delivery Note Required", + "no_copy": 0, "options": "No\nYes", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "maintain_same_sales_rate", "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Maintain Same Rate Throughout Sales Cycle", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "editable_price_list_rate", "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Allow user to edit Price List Rate in transactions", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], + "hide_heading": 0, + "hide_toolbar": 0, "icon": "icon-cog", "idx": 1, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, "issingle": 1, - "modified": "2015-02-05 05:11:46.384538", + "istable": 0, + "modified": "2015-08-03 12:59:51.829458", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", "owner": "Administrator", "permissions": [ { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, "create": 1, + "delete": 0, "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, "permlevel": 0, "print": 1, "read": 1, + "report": 0, "role": "System Manager", + "set_user_permissions": 0, "share": 1, + "submit": 0, "write": 1 } - ] + ], + "read_only": 0, + "read_only_onload": 0 } \ No newline at end of file diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.py b/erpnext/setup/page/setup_wizard/setup_wizard.py index 2734172978e..cb59a821d1a 100644 --- a/erpnext/setup/page/setup_wizard/setup_wizard.py +++ b/erpnext/setup/page/setup_wizard/setup_wizard.py @@ -220,6 +220,7 @@ def set_defaults(args): stock_settings.valuation_method = "FIFO" stock_settings.stock_uom = _("Nos") stock_settings.auto_indent = 1 + stock_settings.auto_insert_price_list_rate_if_missing = 1 stock_settings.save() selling_settings = frappe.get_doc("Selling Settings") diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 4907e45c968..a6dc6d485dd 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -1,123 +1,343 @@ { + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, "creation": "2013-06-24 16:37:54", + "custom": 0, "description": "Settings", "docstatus": 0, "doctype": "DocType", "fields": [ { + "allow_on_submit": 0, "default": "Item Code", "fieldname": "item_naming_by", "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Item Naming By", + "no_copy": 0, "options": "Item Code\nNaming Series", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "description": "", "fieldname": "item_group", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Default Item Group", + "no_copy": 0, "options": "Item Group", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "stock_uom", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Default Stock UOM", + "no_copy": 0, "options": "UOM", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, + "fieldname": "auto_insert_price_list_rate_if_missing", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Auto insert Price List rate if missing", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", - "permlevel": 0 + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "valuation_method", "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Default Valuation Method", + "no_copy": 0, "options": "FIFO\nMoving Average", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.", "fieldname": "tolerance", "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Allowance Percent", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "allow_negative_stock", "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, "in_list_view": 1, "label": "Allow Negative Stock", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "auto_material_request", "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Auto Material Request", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "auto_indent", "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Raise Material Request when stock reaches re-order level", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "reorder_email_notify", "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Notify by Email on creation of automatic Material Request", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "freeze_stock_entries", "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Freeze Stock Entries", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "stock_frozen_upto", "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Stock Frozen Upto", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "stock_frozen_upto_days", "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Freeze Stocks Older Than [Days]", - "permlevel": 0 + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 }, { + "allow_on_submit": 0, "fieldname": "stock_auth_role", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, "label": "Role Allowed to edit frozen stock", + "no_copy": 0, "options": "Role", - "permlevel": 0 + "permlevel": 0, + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], + "hide_heading": 0, + "hide_toolbar": 0, "icon": "icon-cog", "idx": 1, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, "issingle": 1, - "modified": "2015-07-13 05:28:23.839277", + "istable": 0, + "modified": "2015-08-03 13:00:36.082986", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", "owner": "Administrator", "permissions": [ { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, "create": 1, + "delete": 0, "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, "permlevel": 0, "print": 1, "read": 1, + "report": 0, "role": "Stock Manager", + "set_user_permissions": 0, "share": 1, + "submit": 0, "write": 1 } - ] + ], + "read_only": 0, + "read_only_onload": 0 } \ No newline at end of file diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index faf3d98c0f8..c45439a90b3 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -214,6 +214,8 @@ def get_price_list_rate(args, item_doc, out): price_list_rate = get_price_list_rate_for(args, item_doc.variant_of) if not price_list_rate: + if args.price_list and args.rate: + insert_item_price(args) return {} out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \ @@ -224,6 +226,22 @@ def get_price_list_rate(args, item_doc, out): out.update(get_last_purchase_details(item_doc.name, args.parent, args.conversion_rate)) +def insert_item_price(args): + """Insert Item Price if Price List and Price List Rate are specified and currency is the same""" + if frappe.db.get_value("Price List", args.price_list, "currency") == args.currency \ + and cint(frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")): + if frappe.has_permission("Item Price", "write"): + item_price = frappe.get_doc({ + "doctype": "Item Price", + "price_list": args.price_list, + "item_code": args.item_code, + "currency": args.currency, + "price_list_rate": args.rate + }) + item_price.insert() + frappe.msgprint("Item Price added for {0} in Price List {1}".format(args.item_code, + args.price_list)) + def get_price_list_rate_for(args, item_code): return frappe.db.get_value("Item Price", {"price_list": args.price_list, "item_code": item_code}, "price_list_rate") From 5bd394278d2f08cd1971120160ebbd66a75be62d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 3 Aug 2015 15:09:48 +0530 Subject: [PATCH 3/3] [fix] test-case, warehouse mandatory for mix type product bundle --- .../doctype/product_bundle/product_bundle.py | 8 +++++- .../product_bundle/test_product_bundle.py | 1 - .../doctype/sales_order/sales_order.py | 15 +++++++++-- .../doctype/sales_order/test_sales_order.py | 23 ++++++++++++++--- erpnext/setup/doctype/company/company.py | 25 ++++++++++--------- erpnext/setup/utils.py | 3 +++ erpnext/stock/doctype/item/test_item.py | 8 +++++- 7 files changed, 63 insertions(+), 20 deletions(-) diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.py b/erpnext/selling/doctype/product_bundle/product_bundle.py index fdf6b76db08..2949c5cddfe 100644 --- a/erpnext/selling/doctype/product_bundle/product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/product_bundle.py @@ -13,9 +13,15 @@ class ProductBundle(Document): self.name = self.new_item_code def validate(self): + self.validate_main_item() from erpnext.utilities.transaction_base import validate_uom_is_integer validate_uom_is_integer(self, "uom", "qty") + def validate_main_item(self): + """Validates, main Item is not a stock item""" + if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"): + frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code)) + def get_item_details(self, name): det = frappe.db.sql("""select description, stock_uom from `tabItem` where name = %s""", name) @@ -28,7 +34,7 @@ def get_new_item_code(doctype, txt, searchfield, start, page_len, filters): from erpnext.controllers.queries import get_match_cond return frappe.db.sql("""select name, item_name, description from tabItem - where name not in (select name from `tabProduct Bundle`) + where is_stock_item=0 and name not in (select name from `tabProduct Bundle`) and %s like %s %s limit %s, %s""" % (searchfield, "%s", get_match_cond(doctype),"%s", "%s"), ("%%%s%%" % txt, start, page_len)) diff --git a/erpnext/selling/doctype/product_bundle/test_product_bundle.py b/erpnext/selling/doctype/product_bundle/test_product_bundle.py index 39b17f368de..85a2b209f64 100644 --- a/erpnext/selling/doctype/product_bundle/test_product_bundle.py +++ b/erpnext/selling/doctype/product_bundle/test_product_bundle.py @@ -13,7 +13,6 @@ def make_product_bundle(parent, items): product_bundle = frappe.get_doc({ "doctype": "Product Bundle", - "parent_item": parent, "new_item_code": parent }) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 109034d14c9..1c057151791 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -15,6 +15,8 @@ form_grid_templates = { "items": "templates/form_grid/item_grid.html" } +class WarehouseRequired(frappe.ValidationError): pass + class SalesOrder(SellingController): def validate_mandatory(self): # validate transaction date v/s delivery date @@ -39,8 +41,11 @@ class SalesOrder(SellingController): for d in self.get('items'): check_list.append(cstr(d.item_code)) - if frappe.db.get_value("Item", d.item_code, "is_stock_item") and not d.warehouse: - frappe.throw(_("Delivery warehouse required for stock item {0}").format(d.item_code)) + if (frappe.db.get_value("Item", d.item_code, "is_stock_item")==1 or + (self.has_product_bundle(d.item_code) and self.product_bundle_has_stock_item(d.item_code))) \ + and not d.warehouse: + frappe.throw(_("Delivery warehouse required for stock item {0}").format(d.item_code), + WarehouseRequired) # used for production plan d.transaction_date = self.transaction_date @@ -52,6 +57,12 @@ class SalesOrder(SellingController): if len(unique_chk_list) != len(check_list): frappe.msgprint(_("Warning: Same item has been entered multiple times.")) + def product_bundle_has_stock_item(self, product_bundle): + """Returns true if product bundle has stock item""" + ret = len(frappe.db.sql("""select i.name from tabItem i, `tabProduct Bundle Item` pbi + where pbi.parent = %s and pbi.item_code = i.name and i.is_stock_item = 1""", product_bundle)) + return ret + def validate_sales_mntc_quotation(self): for d in self.get('items'): if d.prevdoc_docname: diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 1ceda1246ef..8841202ab3c 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -6,7 +6,7 @@ from frappe.utils import flt, add_days import frappe.permissions import unittest from erpnext.selling.doctype.sales_order.sales_order \ - import make_material_request, make_delivery_note, make_sales_invoice + import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired class TestSalesOrder(unittest.TestCase): def tearDown(self): @@ -235,11 +235,24 @@ class TestSalesOrder(unittest.TestCase): make_product_bundle("_Test Service Product Bundle", ["_Test Service Product Bundle Item 1", "_Test Service Product Bundle Item 2"]) - so = make_sales_order(item_code = "_Test Service Product Bundle") + so = make_sales_order(item_code = "_Test Service Product Bundle", warehouse=None) self.assertTrue("_Test Service Product Bundle Item 1" in [d.item_code for d in so.packed_items]) self.assertTrue("_Test Service Product Bundle Item 2" in [d.item_code for d in so.packed_items]) + def test_mix_type_product_bundle(self): + from erpnext.stock.doctype.item.test_item import make_item + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + + make_item("_Test Mix Product Bundle", {"is_stock_item": 0, "is_sales_item": 1}) + make_item("_Test Mix Product Bundle Item 1", {"is_stock_item": 1, "is_sales_item": 1}) + make_item("_Test Mix Product Bundle Item 2", {"is_stock_item": 0, "is_sales_item": 1}) + + make_product_bundle("_Test Mix Product Bundle", + ["_Test Mix Product Bundle Item 1", "_Test Mix Product Bundle Item 2"]) + + self.assertRaises(WarehouseRequired, make_sales_order, item_code = "_Test Mix Product Bundle", warehouse="") + def test_auto_insert_price(self): from erpnext.stock.doctype.item.test_item import make_item make_item("_Test Item for Auto Price List", {"is_stock_item": 0, "is_sales_item": 1}) @@ -284,13 +297,17 @@ def make_sales_order(**args): if args.selling_price_list: so.selling_price_list = args.selling_price_list + if "warehouse" not in args: + args.warehouse = "_Test Warehouse - _TC" + so.append("items", { "item_code": args.item or args.item_code or "_Test Item", - "warehouse": args.warehouse or "_Test Warehouse - _TC", + "warehouse": args.warehouse, "qty": args.qty or 10, "rate": args.rate or 100, "conversion_factor": 1.0, }) + if not args.do_not_save: so.insert() if not args.do_not_submit: diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index f1f7cc4a5c1..5660d892cc1 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -30,7 +30,7 @@ class Company(Document): self.abbr = self.abbr.strip() if self.get('__islocal') and len(self.abbr) > 5: frappe.throw(_("Abbreviation cannot have more than 5 characters")) - + if not self.abbr.strip(): frappe.throw(_("Abbr can not be blank or space")) @@ -70,7 +70,8 @@ class Company(Document): frappe.clear_cache() def install_country_fixtures(self): - if os.path.exists(os.path.join(os.path.dirname(__file__), "fixtures", self.country.lower())): + path = os.path.join(os.path.dirname(__file__), "fixtures", self.country.lower()) + if os.path.exists(path.encode("utf-8")): frappe.get_attr("erpnext.setup.doctype.company.fixtures.{0}.install".format(self.country.lower()))(self) def create_default_warehouses(self): @@ -183,7 +184,7 @@ class Company(Document): accounts = frappe.db.sql_list("select name from tabAccount where company=%s", self.name) cost_centers = frappe.db.sql_list("select name from `tabCost Center` where company=%s", self.name) warehouses = frappe.db.sql_list("select name from tabWarehouse where company=%s", self.name) - + rec = frappe.db.sql("SELECT name from `tabGL Entry` where company = %s", self.name) if not rec: # delete Account @@ -202,21 +203,21 @@ class Company(Document): frappe.db.sql("""delete from `tabWarehouse` where company=%s""", self.name) frappe.defaults.clear_default("company", value=self.name) - + # clear default accounts, warehouses from item for f in ["default_warehouse", "website_warehouse"]: - frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)""" + frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)""" % (f, f, ', '.join(['%s']*len(warehouses))), tuple(warehouses)) - - frappe.db.sql("""delete from `tabItem Reorder` where warehouse in (%s)""" + + frappe.db.sql("""delete from `tabItem Reorder` where warehouse in (%s)""" % ', '.join(['%s']*len(warehouses)), tuple(warehouses)) - + for f in ["income_account", "expense_account"]: - frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)""" + frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)""" % (f, f, ', '.join(['%s']*len(accounts))), tuple(accounts)) - + for f in ["selling_cost_center", "buying_cost_center"]: - frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)""" + frappe.db.sql("""update tabItem set %s=NULL where %s in (%s)""" % (f, f, ', '.join(['%s']*len(cost_centers))), tuple(cost_centers)) # reset default company @@ -229,7 +230,7 @@ def replace_abbr(company, old, new): new = new.strip() if not new: frappe.throw(_("Abbr can not be blank or space")) - + frappe.only_for("System Manager") frappe.db.set_value("Company", company, "abbr", new) diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index f661edb2b6a..5480613b4eb 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -56,6 +56,9 @@ def before_tests(): frappe.db.sql("delete from `tabLeave Application`") frappe.db.sql("delete from `tabSalary Slip`") frappe.db.sql("delete from `tabItem Price`") + + frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0) + frappe.db.commit() @frappe.whitelist() diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 0fb01ac1172..510c0d1b483 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -26,7 +26,13 @@ def make_item(item_code, properties=None): if properties: item.update(properties) - item.insert() + + + if item.is_stock_item and not item.default_warehouse: + item.default_warehouse = "_Test Warehouse - _TC" + + item.insert() + return item class TestItem(unittest.TestCase):