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
This commit is contained in:
Rushabh Mehta 2017-11-14 16:36:25 +05:30 committed by GitHub
parent d5c6416d91
commit cc1262c36d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 201 additions and 39 deletions

View File

@ -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",
},
]
},

View File

@ -4,3 +4,4 @@ item-variants
purchase-details
reorder
item-valuation-fifo-and-moving-average
item-settings

View File

@ -4,4 +4,12 @@ You can set default settings for your stock related transactions here.
<img class="screenshot" alt="Stock Settings" src="/docs/assets/img/stock/stock-settings.png">
### 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}

View File

@ -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

View File

@ -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:

View File

@ -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",

View File

@ -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
}

View File

@ -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"))
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)

View File

@ -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()
]);
});

View File

@ -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 = '<p><span style="font-size: 12px;">Drawing No. 07-xxx-PO132<br></span><span style="font-size: 12px;">1800 x 1685 x 750<br></span><span style="font-size: 12px;">All parts made of Marine Ply<br></span><span style="font-size: 12px;">Top w/ Corian dd<br></span><span style="font-size: 12px;">CO, CS, VIP Day Cabin</span></p>'
)).insert()
settings = frappe.get_single('Stock Settings')
settings.clean_description_html = 1
settings.save()
item.reload()
self.assertEquals(item.description, '<p>Drawing No. 07-xxx-PO132<br>1800 x 1685 x 750<br>All parts made of Marine Ply<br>Top w/ Corian dd<br>CO, CS, VIP Day Cabin</p>')
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 = '<p><span style="font-size: 12px;">Drawing No. 07-xxx-PO132<br></span><span style="font-size: 12px;">1800 x 1685 x 750<br></span><span style="font-size: 12px;">All parts made of Marine Ply<br></span><span style="font-size: 12px;">Top w/ Corian dd<br></span><span style="font-size: 12px;">CO, CS, VIP Day Cabin</span></p>'
)).insert()
self.assertEquals(item.description, '<p>Drawing No. 07-xxx-PO132<br>1800 x 1685 x 750<br>All parts made of Marine Ply<br>Top w/ Corian dd<br>CO, CS, VIP Day Cabin</p>')
item.delete()