From cc1262c36dfc292891d563d978ec8f692344abbf Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 14 Nov 2017 16:36:25 +0530 Subject: [PATCH] Add option to clean description html in item description. (#11565) * [fix] add item settings * [docs] Item Settings * [fix] enqueue in item_settings.py * [refactor] move to stock settings * [refactor] move to stock settings --- erpnext/config/stock.py | 29 +------ .../docs/user/manual/en/stock/item/index.txt | 1 + .../manual/en/stock/setup/stock-settings.md | 8 ++ erpnext/patches.txt | 1 + erpnext/stock/doctype/item/item.py | 7 ++ .../item_variant_settings.json | 6 +- .../stock_settings/stock_settings.json | 79 ++++++++++++++++++- .../doctype/stock_settings/stock_settings.py | 32 ++++++-- .../stock_settings/test_stock_settings.js | 23 ++++++ .../stock_settings/test_stock_settings.py | 54 +++++++++++++ 10 files changed, 201 insertions(+), 39 deletions(-) create mode 100644 erpnext/stock/doctype/stock_settings/test_stock_settings.js create mode 100644 erpnext/stock/doctype/stock_settings/test_stock_settings.py diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py index d6b18fdec0..cb17deb9bf 100644 --- a/erpnext/config/stock.py +++ b/erpnext/config/stock.py @@ -9,22 +9,18 @@ def get_data(): { "type": "doctype", "name": "Stock Entry", - "description": _("Record item movement."), }, { "type": "doctype", "name": "Delivery Note", - "description": _("Shipments to customers."), }, { "type": "doctype", "name": "Purchase Receipt", - "description": _("Goods received from Suppliers."), }, { "type": "doctype", "name": "Material Request", - "description": _("Requests for items."), }, ] }, @@ -69,17 +65,14 @@ def get_data(): { "type": "doctype", "name": "Item", - "description": _("All Products or Services."), }, { "type": "doctype", "name": "Product Bundle", - "description": _("Bundle items at time of sale."), }, { "type": "doctype", "name": "Price List", - "description": _("Price List master.") }, { "type": "doctype", @@ -87,30 +80,24 @@ def get_data(): "icon": "fa fa-sitemap", "label": _("Item Group"), "link": "Tree/Item Group", - "description": _("Tree of Item Groups."), }, { "type": "doctype", "name": "Item Price", - "description": _("Multiple Item prices."), "route": "Report/Item Price" }, { "type": "doctype", "name": "Shipping Rule", - "description": _("Rules for adding shipping costs.") }, { "type": "doctype", "name": "Pricing Rule", - "description": _("Rules for applying pricing and discount.") }, { "type": "doctype", "name": "Item Variant Settings", - "description": _("Item Variant Settings."), }, - ] }, { @@ -119,17 +106,14 @@ def get_data(): { "type": "doctype", "name": "Serial No", - "description": _("Single unit of an Item."), }, { "type": "doctype", "name": "Batch", - "description": _("Batch (lot) of an Item."), }, { "type": "doctype", "name": "Installation Note", - "description": _("Installation record for a Serial No.") }, { "type": "report", @@ -155,22 +139,18 @@ def get_data(): { "type": "doctype", "name": "Stock Reconciliation", - "description": _("Upload stock balance via csv.") }, { "type": "doctype", "name": "Packing Slip", - "description": _("Split Delivery Note into packages.") }, { "type": "doctype", "name": "Quality Inspection", - "description": _("Incoming quality inspection.") }, { "type": "doctype", "name": "Landed Cost Voucher", - "description": _("Update additional costs to calculate landed cost of items"), } ] }, @@ -181,28 +161,27 @@ def get_data(): { "type": "doctype", "name": "Stock Settings", - "description": _("Default settings for stock transactions.") }, { "type": "doctype", "name": "Warehouse", - "description": _("Where items are stored."), }, { "type": "doctype", "name": "UOM", "label": _("Unit of Measure") + " (UOM)", - "description": _("e.g. Kg, Unit, Nos, m") }, { "type": "doctype", "name": "Item Attribute", - "description": _("Attributes for Item Variants. e.g Size, Color etc."), }, { "type": "doctype", "name": "Brand", - "description": _("Brand master.") + }, + { + "type": "doctype", + "name": "Item Variant Settings", }, ] }, diff --git a/erpnext/docs/user/manual/en/stock/item/index.txt b/erpnext/docs/user/manual/en/stock/item/index.txt index 4e66875741..db5fa0ad16 100644 --- a/erpnext/docs/user/manual/en/stock/item/index.txt +++ b/erpnext/docs/user/manual/en/stock/item/index.txt @@ -4,3 +4,4 @@ item-variants purchase-details reorder item-valuation-fifo-and-moving-average +item-settings diff --git a/erpnext/docs/user/manual/en/stock/setup/stock-settings.md b/erpnext/docs/user/manual/en/stock/setup/stock-settings.md index 32562c6c6e..96d1627a59 100644 --- a/erpnext/docs/user/manual/en/stock/setup/stock-settings.md +++ b/erpnext/docs/user/manual/en/stock/setup/stock-settings.md @@ -4,4 +4,12 @@ You can set default settings for your stock related transactions here. Stock Settings +### Clean HTML Description + +Usually descriptions are copy-pasted from a website or Word / PDF file and they contain a lot of embedded style. This messes up the Print view of your invoices or quotes. + +To fix this, you can check "Convert Item Description to Clean HTML" in Stock Settings. This will ensure that when you save your Items, their descriptions will be cleaned up. + +If you want control your description views and llow any HTML to be embedded, you can uncheck this property. + {next} \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index fb4e0baade..1b86bb13ff 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -23,6 +23,7 @@ execute:frappe.reload_doc('selling', 'doctype', 'customer') # 2014-01-29 execute:frappe.reload_doc('buying', 'doctype', 'supplier') # 2014-01-29 execute:frappe.reload_doc('accounts', 'doctype', 'asset_category') execute:frappe.reload_doc('accounts', 'doctype', 'pricing_rule') +execute:frappe.reload_doc('stock', 'doctype', 'item_settings') erpnext.patches.v4_0.map_charge_to_taxes_and_charges execute:frappe.reload_doc('support', 'doctype', 'newsletter') # 2014-01-31 execute:frappe.reload_doc('hr', 'doctype', 'employee') # 2014-02-03 diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index d6c4b7d1db..13c0027193 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -9,6 +9,7 @@ import itertools from frappe import msgprint, _ from frappe.utils import (cstr, flt, cint, getdate, now_datetime, formatdate, strip, get_timestamp, random_string) +from frappe.utils.html_utils import clean_html from frappe.website.website_generator import WebsiteGenerator from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups from frappe.website.render import clear_cache @@ -82,6 +83,7 @@ class Item(WebsiteGenerator): self.description = self.item_name self.validate_uom() + self.validate_description() self.add_default_uom_in_conversion_factor_table() self.validate_conversion_factor() self.validate_item_type() @@ -114,6 +116,11 @@ class Item(WebsiteGenerator): self.update_item_price() self.update_template_item() + def validate_description(self): + '''Clean HTML description if set''' + if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')): + self.description = clean_html(self.description) + def add_price(self, price_list=None): '''Add a new price''' if not price_list: diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json index 8bdd46c72d..8ad73d19d5 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.json @@ -47,7 +47,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "If enabled then system will not update the fields of variants from the template but will copy the data of below mentioned fields while making new variant", + "description": "Fields will be copied over only at time of creation.", "fieldname": "do_not_update_variants", "fieldtype": "Check", "hidden": 0, @@ -57,7 +57,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Do not Update Variants", + "label": "Do not update variants on save", "length": 0, "no_copy": 0, "permlevel": 0, @@ -144,7 +144,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-11-08 11:38:12.821404", + "modified": "2017-11-14 15:54:12.190518", "modified_by": "Administrator", "module": "Stock", "name": "Item Variant Settings", diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json index 4801d40be2..16726be05d 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.json +++ b/erpnext/stock/doctype/stock_settings/stock_settings.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "beta": 0, @@ -11,6 +12,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -22,6 +24,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Item Naming By", @@ -40,6 +43,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -51,6 +55,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Default Item Group", @@ -69,6 +74,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -79,6 +85,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, "label": "Default Stock UOM", @@ -97,6 +104,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -107,6 +115,7 @@ "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 Warehouse", @@ -126,6 +135,38 @@ "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "clean_description_html", + "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": "Convert Item Description to Clean HTML", + "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, @@ -136,6 +177,7 @@ "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, @@ -152,6 +194,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -162,6 +205,7 @@ "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 Valuation Method", @@ -180,6 +224,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -191,6 +236,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Limit Percent", @@ -208,6 +254,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -219,6 +266,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Show Barcode Field", @@ -237,6 +285,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -247,6 +296,7 @@ "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, @@ -264,6 +314,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -274,6 +325,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Auto insert Price List rate if missing", @@ -292,6 +344,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -302,6 +355,7 @@ "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 Negative Stock", @@ -319,6 +373,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -329,6 +384,7 @@ "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, @@ -346,6 +402,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -357,6 +414,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Automatically Set Serial Nos based on FIFO", @@ -375,6 +433,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -385,6 +444,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Auto Material Request", @@ -402,6 +462,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -412,6 +473,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Raise Material Request when stock reaches re-order level", @@ -429,6 +491,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -439,6 +502,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Notify by Email on creation of automatic Material Request", @@ -456,6 +520,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -466,6 +531,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Freeze Stock Entries", @@ -483,6 +549,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -493,6 +560,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Stock Frozen Upto", @@ -510,6 +578,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -520,6 +589,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Freeze Stocks Older Than [Days]", @@ -537,6 +607,7 @@ "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -547,6 +618,7 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Role Allowed to edit frozen stock", @@ -565,18 +637,18 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "icon": "icon-cog", "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-12-16 02:18:58.187847", + "modified": "2017-11-14 16:19:50.274518", "modified_by": "Administrator", "module": "Stock", "name": "Stock Settings", @@ -592,7 +664,6 @@ "export": 0, "if_owner": 0, "import": 0, - "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -607,6 +678,8 @@ "quick_entry": 1, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_order": "ASC", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py index 186eaeebb1..89ece33b08 100644 --- a/erpnext/stock/doctype/stock_settings/stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/stock_settings.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from frappe.utils.html_utils import clean_html class StockSettings(Document): def validate(self): @@ -26,19 +27,34 @@ class StockSettings(Document): # show/hide barcode field frappe.make_property_setter({'fieldname': 'barcode', 'property': 'hidden', 'value': 0 if self.show_barcode_field else 1}) - + self.cant_change_valuation_method() - + self.validate_clean_description_html() + def cant_change_valuation_method(self): db_valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method") - + if db_valuation_method and db_valuation_method != self.valuation_method: - # check if there are any stock ledger entries against items + # check if there are any stock ledger entries against items # which does not have it's own valuation method sle = frappe.db.sql("""select name from `tabStock Ledger Entry` sle - where exists(select name from tabItem - where name=sle.item_code and (valuation_method is null or valuation_method='')) + where exists(select name from tabItem + where name=sle.item_code and (valuation_method is null or valuation_method='')) limit 1 """) - + if sle: - frappe.throw(_("Can't change valuation method, as there are transactions against some items which does not have it's own valuation method")) \ No newline at end of file + frappe.throw(_("Can't change valuation method, as there are transactions against some items which does not have it's own valuation method")) + + def validate_clean_description_html(self): + if int(self.clean_description_html or 0) \ + and not int(self.db_get('clean_description_html') or 0): + # changed to text + frappe.enqueue('erpnext.stock.doctype.stock_settings.stock_settings.clean_all_descriptions', now=frappe.flags.in_test) + + +def clean_all_descriptions(): + for item in frappe.get_all('Item', ['name', 'description']): + if item.description: + clean_description = clean_html(item.description) + if item.description != clean_description: + frappe.db.set_value('Item', item.name, 'description', clean_description) diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.js b/erpnext/stock/doctype/stock_settings/test_stock_settings.js new file mode 100644 index 0000000000..57d9fc61aa --- /dev/null +++ b/erpnext/stock/doctype/stock_settings/test_stock_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: Stock Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Stock Settings + () => frappe.tests.make('Stock Settings', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.py b/erpnext/stock/doctype/stock_settings/test_stock_settings.py new file mode 100644 index 0000000000..43c0c57142 --- /dev/null +++ b/erpnext/stock/doctype/stock_settings/test_stock_settings.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestStockSettings(unittest.TestCase): + def setUp(self): + settings = frappe.get_single('Stock Settings') + settings.clean_description_html = 0 + settings.save() + + frappe.delete_doc('Item', 'Item for description test') + + def tearDown(self): + settings = frappe.get_single('Stock Settings') + settings.clean_description_html = 1 + settings.save() + + def test_settings(self): + item = frappe.get_doc(dict( + doctype = 'Item', + item_code = 'Item for description test', + item_group = 'Products', + description = '

Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin

' + )).insert() + + settings = frappe.get_single('Stock Settings') + settings.clean_description_html = 1 + settings.save() + + item.reload() + + self.assertEquals(item.description, '

Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin

') + + item.delete() + + def test_clean_html(self): + settings = frappe.get_single('Stock Settings') + settings.clean_description_html = 1 + settings.save() + + item = frappe.get_doc(dict( + doctype = 'Item', + item_code = 'Item for description test', + item_group = 'Products', + description = '

Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin

' + )).insert() + + self.assertEquals(item.description, '

Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin

') + + item.delete()