[enhancement] make service type product bundle:

This commit is contained in:
Rushabh Mehta
2015-08-03 11:58:23 +05:30
parent 1385f20042
commit 862a2eb975
6 changed files with 174 additions and 58 deletions

View File

@@ -1,21 +1,41 @@
{ {
"allow_copy": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0,
"creation": "2013-06-20 11:53:21", "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", "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, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Master", "document_type": "",
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"fieldname": "basic_section", "fieldname": "basic_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "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", "fieldname": "new_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Parent Item", "label": "Parent Item",
"no_copy": 1, "no_copy": 1,
@@ -23,30 +43,67 @@
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"permlevel": 0, "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.", "description": "List items that form the package.",
"fieldname": "item_section", "fieldname": "item_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "", "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", "fieldname": "items",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "Items", "label": "Items",
"no_copy": 0,
"oldfieldname": "sales_bom_items", "oldfieldname": "sales_bom_items",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Product Bundle Item", "options": "Product Bundle Item",
"permlevel": 0, "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", "icon": "icon-sitemap",
"idx": 1, "idx": 1,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 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", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Product Bundle", "name": "Product Bundle",
@@ -54,14 +111,20 @@
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
@@ -69,31 +132,44 @@
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock User", "role": "Stock User",
"set_user_permissions": 0,
"share": 0,
"submit": 0, "submit": 0,
"write": 0 "write": 0
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Sales User", "role": "Sales User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
} }
] ],
"read_only": 0,
"read_only_onload": 0
} }

View File

@@ -13,17 +13,9 @@ class ProductBundle(Document):
self.name = self.new_item_code self.name = self.new_item_code
def validate(self): def validate(self):
self.validate_main_item()
from erpnext.utilities.transaction_base import validate_uom_is_integer from erpnext.utilities.transaction_base import validate_uom_is_integer
validate_uom_is_integer(self, "uom", "qty") 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): def get_item_details(self, name):
det = frappe.db.sql("""select description, stock_uom from `tabItem` det = frappe.db.sql("""select description, stock_uom from `tabItem`
where name = %s""", name) 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 from erpnext.controllers.queries import get_match_cond
return frappe.db.sql("""select name, item_name, description from tabItem return frappe.db.sql("""select name, item_name, description from tabItem
where is_stock_item=0 and is_sales_item=1 where name not in (select name from `tabProduct Bundle`)
and name not in (select name from `tabProduct Bundle`) and %s like %s and %s like %s %s limit %s, %s""" % (searchfield, "%s",
%s limit %s, %s""" % (searchfield, "%s",
get_match_cond(doctype),"%s", "%s"), get_match_cond(doctype),"%s", "%s"),
("%%%s%%" % txt, start, page_len)) ("%%%s%%" % txt, start, page_len))

View File

@@ -6,3 +6,20 @@ from __future__ import unicode_literals
import frappe import frappe
test_records = frappe.get_test_records('Product Bundle') 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

View File

@@ -39,9 +39,8 @@ class SalesOrder(SellingController):
for d in self.get('items'): for d in self.get('items'):
check_list.append(cstr(d.item_code)) check_list.append(cstr(d.item_code))
if (frappe.db.get_value("Item", d.item_code, "is_stock_item")==1 or if frappe.db.get_value("Item", d.item_code, "is_stock_item") and not d.warehouse:
self.has_product_bundle(d.item_code)) and not d.warehouse: frappe.throw(_("Delivery warehouse required for stock item {0}").format(d.item_code))
frappe.throw(_("Reserved warehouse required for stock item {0}").format(d.item_code))
# used for production plan # used for production plan
d.transaction_date = self.transaction_date d.transaction_date = self.transaction_date

View File

@@ -224,6 +224,22 @@ class TestSalesOrder(unittest.TestCase):
self.assertRaises(frappe.CancelledLinkError, dn.submit) 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): def make_sales_order(**args):
so = frappe.new_doc("Sales Order") so = frappe.new_doc("Sales Order")
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -12,6 +12,23 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
test_ignore = ["BOM"] test_ignore = ["BOM"]
test_dependencies = ["Warehouse"] 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): class TestItem(unittest.TestCase):
def get_item(self, idx): def get_item(self, idx):
item_code = test_records[idx].get("item_code") item_code = test_records[idx].get("item_code")