Merge pull request #3908 from rmehta/text-editor
[fix] changed text > text editor for description fields in tables
diff --git a/erpnext/__version__.py b/erpnext/__version__.py
index 99604dc..90d02c9 100644
--- a/erpnext/__version__.py
+++ b/erpnext/__version__.py
@@ -1,2 +1,2 @@
from __future__ import unicode_literals
-__version__ = '5.6.4'
+__version__ = '5.7.0'
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index d12b2d7..0b74948 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -232,45 +232,24 @@
test_recurring_document(self, test_records)
def test_total_purchase_cost_for_project(self):
- purchase_invoice = frappe.new_doc('Purchase Invoice')
- purchase_invoice.update({
- "credit_to": "_Test Payable - _TC",
- "supplier": "_Test Supplier",
- "company": "_Test Company",
- "items": [
- {
- "rate": 500,
- "qty": 1,
- "project_name": "_Test Project",
- "item_code": "_Test Item Home Desktop 100",
- "expense_account": "_Test Account Cost for Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC"
- },
- {
- "rate": 1500,
- "qty": 1,
- "project_name": "_Test Project",
- "item_code": "_Test Item Home Desktop 200",
- "expense_account": "_Test Account Cost for Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC"
- }
- ]
- })
- purchase_invoice.save()
- purchase_invoice.submit()
- self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 2000)
+ existing_purchase_cost = frappe.db.sql("""select sum(ifnull(base_net_amount, 0))
+ from `tabPurchase Invoice Item` where project_name = '_Test Project' and docstatus=1""")
+ existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
+
+ pi = make_purchase_invoice(currency="USD", conversion_rate=60, project_name="_Test Project")
+ self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+ existing_purchase_cost + 15000)
- purchase_invoice1 = frappe.copy_doc(purchase_invoice)
- purchase_invoice1.save()
- purchase_invoice1.submit()
+ pi1 = make_purchase_invoice(qty=10, project_name="_Test Project")
+ self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+ existing_purchase_cost + 15500)
- self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 4000)
+ pi1.cancel()
+ self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"),
+ existing_purchase_cost + 15000)
- purchase_invoice1.cancel()
- self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 2000)
-
- purchase_invoice.cancel()
- self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 0)
+ pi.cancel()
+ self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), existing_purchase_cost)
def test_return_purchase_invoice(self):
set_perpetual_inventory()
@@ -308,6 +287,7 @@
pi.company = args.company or "_Test Company"
pi.supplier = args.supplier or "_Test Supplier"
pi.currency = args.currency or "INR"
+ pi.conversion_rate = args.conversion_rate or 1
pi.is_return = args.is_return
pi.return_against = args.return_against
@@ -318,7 +298,9 @@
"rate": args.rate or 50,
"conversion_factor": 1.0,
"serial_no": args.serial_no,
- "stock_uom": "_Test UOM"
+ "stock_uom": "_Test UOM",
+ "cost_center": "_Test Cost Center - _TC",
+ "project_name": args.project_name
})
if not args.do_not_save:
pi.insert()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index dd42f45..734684f 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -83,7 +83,7 @@
cur_frm.meta.default_print_format = cur_frm.pos_print_format;
}
} else {
- if(cur_frm.meta.cur_frm.meta._default_print_format) {
+ if(cur_frm.meta._default_print_format) {
cur_frm.meta.default_print_format = cur_frm.meta._default_print_format;
cur_frm.meta._default_print_format = null;
}
diff --git a/erpnext/change_log/current/item_variants.md b/erpnext/change_log/current/item_variants.md
deleted file mode 100644
index 268b595..0000000
--- a/erpnext/change_log/current/item_variants.md
+++ /dev/null
@@ -1,6 +0,0 @@
-- Make Variants added to Item Master
-- Added ability to specify Range of Numeric Values in Attribute Master
-- Fixed an issue in filtering Templates in Item List View
-- Added Patch to fetch Item Template Attributes
-- Allow Editing variant attribute in Variant. Add validation to check duplication.
-- Removed Manage Variants
\ No newline at end of file
diff --git a/erpnext/change_log/current/pos.md b/erpnext/change_log/current/pos.md
deleted file mode 100644
index 9faf82d..0000000
--- a/erpnext/change_log/current/pos.md
+++ /dev/null
@@ -1,2 +0,0 @@
-- Set default Print Format for Point-of-Sale (POS) via **POS Profile**
-- Option to automatically print after submitting POS **Sales Invoice**
diff --git a/erpnext/change_log/v5/v5_7_0.md b/erpnext/change_log/v5/v5_7_0.md
new file mode 100644
index 0000000..c76eb60
--- /dev/null
+++ b/erpnext/change_log/v5/v5_7_0.md
@@ -0,0 +1,7 @@
+- **Item Variants**
+ - Removed **Manage Variants** tool
+ - Use **Make Variant** button in the Item form to create new variants based on a template Item
+ - Added ability to specify Range of Numeric Values in Item Attribute
+- Set default Print Format for Point-of-Sale (POS) via **POS Profile**
+- Option to automatically print after submitting POS **Sales Invoice**
+- **Communication** added to CRM module
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 1609b87..0fdbda1 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -27,7 +27,7 @@
"""
app_icon = "icon-th"
app_color = "#e74c3c"
-app_version = "5.6.4"
+app_version = "5.7.0"
github_link = "https://github.com/frappe/erpnext"
error_report_email = "support@erpnext.com"
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 87a5551..551518e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -192,3 +192,4 @@
execute:frappe.db.sql("update `tabProduction Order` pro set description = (select description from tabItem where name=pro.production_item) where ifnull(description, '') = ''")
erpnext.patches.v5_7.item_template_attributes
erpnext.patches.v4_2.repost_reserved_qty #2015-08-17
+erpnext.patches.v5_4.update_purchase_cost_against_project
diff --git a/erpnext/patches/v5_4/update_purchase_cost_against_project.py b/erpnext/patches/v5_4/update_purchase_cost_against_project.py
new file mode 100644
index 0000000..3a02eb7
--- /dev/null
+++ b/erpnext/patches/v5_4/update_purchase_cost_against_project.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ for p in frappe.get_all("Project"):
+ purchase_cost = frappe.db.sql("""select sum(ifnull(base_net_amount, 0))
+ from `tabPurchase Invoice Item` where project_name = %s and docstatus=1""", p.name)
+ purchase_cost = purchase_cost and purchase_cost[0][0] or 0
+
+ frappe.db.set_value("Project", p.name, "total_purchase_cost", purchase_cost)
\ No newline at end of file
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 6ebafdb..de1c3f8 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -25,13 +25,13 @@
"description": task.description,
"task_id": task.name
})
-
+
def __setup__(self):
self.onload()
-
+
def get_tasks(self):
return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc")
-
+
def validate(self):
self.validate_dates()
self.sync_tasks()
@@ -74,7 +74,7 @@
for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}):
frappe.delete_doc("Task", t.name)
task_added_or_deleted = True
-
+
if task_added_or_deleted:
self.update_project()
@@ -87,12 +87,12 @@
if total:
completed = frappe.db.sql("""select count(*) from tabTask where
project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0]
-
+
self.percent_complete = flt(completed) / total * 100
def update_costing(self):
- total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount,
- sum(total_billing_amount) as billing_amount, sum(total_expense_claim) as expense_claim,
+ total_cost = frappe.db.sql("""select sum(ifnull(total_costing_amount, 0)) as costing_amount,
+ sum(ifnull(total_billing_amount, 0)) as billing_amount, sum(ifnull(total_expense_claim, 0)) as expense_claim,
min(act_start_date) as start_date, max(act_end_date) as end_date, sum(actual_time) as time
from `tabTask` where project = %s""", self.name, as_dict=1)[0]
@@ -105,10 +105,12 @@
self.gross_margin = flt(total_cost.billing_amount) - flt(total_cost.costing_amount)
if self.total_billing_amount:
self.per_gross_margin = (self.gross_margin / flt(self.total_billing_amount)) *100
-
+
def update_purchase_costing(self):
- self.total_purchase_cost = frappe.db.sql("""select sum(amount) as cost
- from `tabPurchase Invoice Item` where project_name = %s and docstatus=1 """, self.name, as_dict=1)[0].cost or 0
+ total_purchase_cost = frappe.db.sql("""select sum(ifnull(base_net_amount, 0))
+ from `tabPurchase Invoice Item` where project_name = %s and docstatus=1""", self.name)
+
+ self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0
@frappe.whitelist()
def get_cost_center_name(project_name):
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 20a064b..cd3d295 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -14,6 +14,7 @@
class WarehouseNotSet(frappe.ValidationError): pass
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
class ItemVariantExistsError(frappe.ValidationError): pass
+class InvalidItemAttributeValueError(frappe.ValidationError): pass
class Item(WebsiteGenerator):
website = frappe._dict(
@@ -351,9 +352,11 @@
frappe.throw(_("Please specify Attribute Value for attribute {0}").format(d.attribute))
args[d.attribute] = d.attribute_value
- variant = get_variant(self.variant_of, args)
- if variant and variant != self.name:
- frappe.throw(_("Item variant {0} exists with same attributes").format(variant), ItemVariantExistsError)
+ if self.get("__islocal"):
+ # test this during insert because naming is based on item_code and we cannot use condition like self.name != variant
+ variant = get_variant(self.variant_of, args)
+ if variant:
+ frappe.throw(_("Item variant {0} exists with same attributes").format(variant), ItemVariantExistsError)
def validate_end_of_life(item_code, end_of_life=None, verbose=1):
if not end_of_life:
@@ -510,8 +513,8 @@
filters={"parent": ["in", args.keys()]}):
(attribute_values.setdefault(t.parent, [])).append(t.attribute_value)
- numeric_attributes = [t.name for t in frappe.get_list("Item Attribute", filters={"numeric_values":1,
- "parent": ["in", args.keys()]})]
+ numeric_attributes = frappe._dict((t.name, t) for t in frappe.get_list("Item Attribute", filters={"numeric_values":1,
+ "name": ["in", args.keys()]}, fields=["name", "from_range", "to_range", "increment"]))
template_item = frappe.get_doc("Item", item)
template_item_attributes = frappe._dict((d.attribute, d) for d in template_item.attributes)
@@ -519,18 +522,19 @@
for attribute, value in args.items():
if attribute in numeric_attributes:
- template_attribute = template_item_attributes[attribute]
+ numeric_attribute = numeric_attributes[attribute]
- if template_attribute.increment == 0:
+ from_range = numeric_attribute.from_range
+ to_range = numeric_attribute.to_range
+ increment = numeric_attribute.increment
+
+ if increment == 0:
# defensive validation to prevent ZeroDivisionError
frappe.throw(_("Increment for Attribute {0} cannot be 0").format(attribute))
- from_range = template_attribute.from_range
- to_range = template_attribute.to_range
- increment = template_attribute.increment
if not ( (from_range <= flt(value) <= to_range) and (flt(value) - from_range) % increment == 0 ):
- frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}").format(attribute, from_range, to_range, increment))
+ frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3}").format(attribute, from_range, to_range, increment), InvalidItemAttributeValueError)
elif value not in attribute_values[attribute]:
frappe.throw(_("Value {0} for Attribute {1} does not exist in the list of valid Item Attribute Values").format(
@@ -538,7 +542,7 @@
def find_variant(item, args):
conditions = ["""(iv_attribute.attribute="{0}" and iv_attribute.attribute_value="{1}")"""\
- .format(frappe.db.escape(key), frappe.db.escape(value)) for key, value in args.items()]
+ .format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
conditions = " or ".join(conditions)
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 428bd37..2ba9647 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -6,7 +6,8 @@
import frappe
from frappe.test_runner import make_test_records
-from erpnext.stock.doctype.item.item import WarehouseNotSet, ItemTemplateCannotHaveStock, create_variant, ItemVariantExistsError
+from erpnext.stock.doctype.item.item import (WarehouseNotSet, ItemTemplateCannotHaveStock, create_variant,
+ ItemVariantExistsError, InvalidItemAttributeValueError)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
test_ignore = ["BOM"]
@@ -100,21 +101,52 @@
self.assertEquals(value, details.get(key))
def test_make_item_variant(self):
- if frappe.db.exists("Item", "_Test Variant Item-L"):
- frappe.delete_doc("Item", "_Test Variant Item-L")
- frappe.delete_doc("Item", "_Test Variant Item-L2")
+ frappe.delete_doc_if_exists("Item", "_Test Variant Item-L")
- variant = create_variant("_Test Variant Item", """{"Test Size": "Large"}""")
- variant.item_code = "_Test Variant Item-L"
- variant.item_name = "_Test Variant Item-L"
+ variant = create_variant("_Test Variant Item", {"Test Size": "Large"})
variant.save()
# doing it again should raise error
- variant = create_variant("_Test Variant Item", """{"Test Size": "Large"}""")
- variant.item_code = "_Test Variant Item-L2"
- variant.item_name = "_Test Variant Item-L2"
+ variant = create_variant("_Test Variant Item", {"Test Size": "Large"})
self.assertRaises(ItemVariantExistsError, variant.save)
+ def test_make_item_variant_with_numeric_values(self):
+ # cleanup
+ frappe.delete_doc_if_exists("Item", "_Test Numeric Template Item")
+ frappe.delete_doc_if_exists("Item", "_Test Numeric Variant-L-1.5")
+ frappe.delete_doc_if_exists("Item Attribute", "Test Item Length")
+
+ # make item attribute
+ frappe.get_doc({
+ "doctype": "Item Attribute",
+ "attribute_name": "Test Item Length",
+ "numeric_values": 1,
+ "from_range": 0.0,
+ "to_range": 100.0,
+ "increment": 0.5
+ }).insert()
+
+ # make template item
+ make_item("_Test Numeric Template Item", {
+ "attributes": [
+ {"attribute": "Test Size"},
+ {"attribute": "Test Item Length"}
+ ],
+ "default_warehouse": "_Test Warehouse - _TC"
+ })
+
+ variant = create_variant("_Test Numeric Template Item", {"Test Size": "Large", "Test Item Length": 1.1})
+ self.assertEquals(variant.item_code, None)
+ variant.item_code = "_Test Numeric Variant-L-1.1"
+ variant.item_name = "_Test Numeric Variant Large 1.1m"
+ self.assertRaises(InvalidItemAttributeValueError, variant.save)
+
+ variant = create_variant("_Test Numeric Template Item", {"Test Size": "Large", "Test Item Length": 1.5})
+ self.assertEquals(variant.item_code, None)
+ variant.item_code = "_Test Numeric Variant-L-1.5"
+ variant.item_name = "_Test Numeric Variant Large 1.5m"
+ variant.save()
+
def make_item_variant():
if not frappe.db.exists("Item", "_Test Variant Item-S"):
variant = create_variant("_Test Variant Item", """{"Test Size": "Small"}""")
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index a82a3a8..8310288 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -6,6 +6,8 @@
from frappe.model.document import Document
from frappe import _
+class ItemAttributeIncrementError(frappe.ValidationError): pass
+
class ItemAttribute(Document):
def validate(self):
self.validate_numeric()
@@ -15,14 +17,14 @@
def validate_numeric(self):
if self.numeric_values:
self.set("item_attribute_values", [])
- if not self.from_range or not self.to_range:
+ if self.from_range is None or self.to_range is None:
frappe.throw(_("Please specify from/to range"))
- elif self.from_range > self.to_range:
- frappe.throw(_("From Range cannot be greater than to Range"))
+ elif self.from_range >= self.to_range:
+ frappe.throw(_("From Range has to be less than To Range"))
if not self.increment:
- frappe.throw(_("Increment cannot be 0"))
+ frappe.throw(_("Increment cannot be 0"), ItemAttributeIncrementError)
else:
self.from_range = self.to_range = self.increment = 0
diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
index 31b3b0a..6357b52 100644
--- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
@@ -6,5 +6,25 @@
test_records = frappe.get_test_records('Item Attribute')
+from erpnext.stock.doctype.item_attribute.item_attribute import ItemAttributeIncrementError
+
class TestItemAttribute(unittest.TestCase):
- pass
+ def setUp(self):
+ if frappe.db.exists("Item Attribute", "_Test_Length"):
+ frappe.delete_doc("Item Attribute", "_Test_Length")
+
+ def test_numeric_item_attribute(self):
+ item_attribute = frappe.get_doc({
+ "doctype": "Item Attribute",
+ "attribute_name": "_Test_Length",
+ "numeric_values": 1,
+ "from_range": 0.0,
+ "to_range": 100.0,
+ "increment": 0
+ })
+
+ self.assertRaises(ItemAttributeIncrementError, item_attribute.save)
+
+ item_attribute.increment = 0.5
+ item_attribute.save()
+
diff --git a/setup.py b/setup.py
index c624e0e..5c92ec0 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
-version = "5.6.4"
+version = "5.7.0"
with open("requirements.txt", "r") as f:
install_requires = f.readlines()