From 9c339145b213db5ab3b7e8ad758affc54a4b39ea Mon Sep 17 00:00:00 2001 From: Makarand Bauskar Date: Fri, 29 Sep 2017 15:02:51 +0530 Subject: [PATCH 1/2] [Enhance] Custom notification messages for subscription documents (#10970) * [minor] configurable subscription email message and subject for notification * [minor] added description for subject field --- .../doctype/subscription/subscription.json | 99 ++++++++++++++++++- .../doctype/subscription/subscription.py | 27 +++-- 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json index 85779533ea..902b06290e 100644 --- a/erpnext/accounts/doctype/subscription/subscription.json +++ b/erpnext/accounts/doctype/subscription/subscription.json @@ -439,7 +439,7 @@ "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, + "collapsible": 1, "columns": 0, "fieldname": "notification", "fieldtype": "Section Break", @@ -495,6 +495,38 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval: doc.notify_by_email", + "description": "To add dynamic subject, use jinja tags like\n\n
New {{ doc.doctype }} #{{ doc.name }}
", + "fieldname": "subject", + "fieldtype": "Data", + "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": "Subject", + "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, @@ -593,6 +625,69 @@ "bold": 0, "collapsible": 1, "columns": 0, + "depends_on": "eval:doc.notify_by_email", + "fieldname": "section_break_20", + "fieldtype": "Section Break", + "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": "Message", + "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, + "columns": 0, + "default": "Please find attached {{ doc.doctype }} #{{ doc.name }}", + "fieldname": "message", + "fieldtype": "Text", + "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": "Message", + "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": 1, + "columns": 0, + "depends_on": "eval: !doc.__islocal", "fieldname": "section_break_16", "fieldtype": "Section Break", "hidden": 0, @@ -690,7 +785,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-14 12:09:38.471458", + "modified": "2017-09-28 18:27:48.522098", "modified_by": "Administrator", "module": "Accounts", "name": "Subscription", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index c9df7d461e..b7ea96f0ce 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -7,6 +7,7 @@ import frappe import calendar from frappe import _ from frappe.desk.form import assign_to +from frappe.utils.jinja import validate_template from dateutil.relativedelta import relativedelta from frappe.utils.user import get_system_managers from frappe.utils import cstr, getdate, split_emails, add_days, today @@ -20,6 +21,9 @@ class Subscription(Document): self.validate_next_schedule_date() self.validate_email_id() + validate_template(self.subject or "") + validate_template(self.message or "") + def before_submit(self): self.set_next_schedule_date() @@ -114,7 +118,7 @@ def create_documents(data, schedule_date): doc = make_new_document(data, schedule_date) if data.notify_by_email and data.recipients: print_format = data.print_format or "Standard" - send_notification(doc, print_format, data.recipients) + send_notification(doc, data, print_format=print_format) frappe.db.commit() except Exception: @@ -174,14 +178,25 @@ def get_next_date(dt, mcount, day=None): return dt -def send_notification(new_rv, print_format='Standard', recipients=None): +def send_notification(new_rv, subscription_doc, print_format='Standard'): """Notify concerned persons about recurring document generation""" print_format = print_format - frappe.sendmail(recipients, - subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name), - message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name), - attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)]) + if not subscription_doc.subject: + subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name) + elif "{" in subscription_doc.subject: + subject = frappe.render_template(subscription_doc.subject, {'doc': new_rv}) + + if not subscription_doc.message: + message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name) + elif "{" in subscription_doc.message: + message = frappe.render_template(subscription_doc.message, {'doc': new_rv}) + + attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, + file_name=new_rv.name, print_format=print_format)] + + frappe.sendmail(subscription_doc.recipients, + subject=subject, message=message, attachments=attachments) def notify_errors(doc, doctype, party, owner, name): recipients = get_system_managers(only_name=True) From 945f5027484a5a86119578fb69879ce62877c69f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 29 Sep 2017 15:11:50 +0530 Subject: [PATCH 2/2] Fixes for updating item variant from template (#10975) * Fixes for updating item variant from template * More fixes for test cases --- erpnext/controllers/item_variant.py | 16 +++++++++++++--- erpnext/projects/doctype/project/project.py | 2 +- erpnext/stock/doctype/item/test_item.py | 2 ++ .../item_variant_settings.js | 2 +- .../item_variant_settings.py | 2 +- .../doctype/stock_entry/test_stock_entry.py | 5 +++-- .../stock/doctype/warehouse/test_records.json | 7 ------- erpnext/utilities/transaction_base.py | 2 +- 8 files changed, 22 insertions(+), 16 deletions(-) diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py index 5b5cd9e690..e5564404ec 100644 --- a/erpnext/controllers/item_variant.py +++ b/erpnext/controllers/item_variant.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cstr, flt -import json +import json, copy class ItemVariantExistsError(frappe.ValidationError): pass class InvalidItemAttributeValueError(frappe.ValidationError): pass @@ -175,18 +175,28 @@ def copy_attributes_to_variant(item, variant): # copy non no-copy fields exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", - "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate", "variant_based_on"] + "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"] if item.variant_based_on=='Manufacturer': # don't copy manufacturer values if based on part no exclude_fields += ['manufacturer', 'manufacturer_part_no'] allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields = ['field_name'])] + if "variant_based_on" not in allow_fields: + allow_fields.append("variant_based_on") for field in item.meta.fields: # "Table" is part of `no_value_field` but we shouldn't ignore tables if (field.reqd or field.fieldname in allow_fields) and field.fieldname not in exclude_fields: if variant.get(field.fieldname) != item.get(field.fieldname): - variant.set(field.fieldname, item.get(field.fieldname)) + if field.fieldtype == "Table": + variant.set(field.fieldname, []) + for d in item.get(field.fieldname): + row = copy.deepcopy(d) + if row.get("name"): + row.name = None + variant.append(field.fieldname, row) + else: + variant.set(field.fieldname, item.get(field.fieldname)) variant.variant_of = item.name variant.has_variants = 0 diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 9f4c2b9c35..460ddc6210 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -61,7 +61,7 @@ class Project(Document): self.send_welcome_email() def validate_project_name(self): - if frappe.db.exists("Project", self.project_name): + if self.get("__islocal") and frappe.db.exists("Project", self.project_name): frappe.throw(_("Project {0} already exists").format(self.project_name)) def validate_dates(self): diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 34e3af6102..c3f399a536 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -120,6 +120,8 @@ class TestItem(unittest.TestCase): self.assertRaises(ItemVariantExistsError, variant.save) def test_copy_fields_from_template_to_variants(self): + frappe.delete_doc_if_exists("Item", "_Test Variant Item-XL", force=1) + fields = [{'field_name': 'item_group'}, {'field_name': 'is_stock_item'}] allow_fields = [d.get('field_name') for d in fields] set_item_variant_settings(fields) diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js index df78572dcb..24f7e31a0c 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js @@ -5,7 +5,7 @@ frappe.ui.form.on('Item Variant Settings', { setup: function(frm) { const allow_fields = []; const exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", - "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate", "variant_based_on"]; + "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]; frappe.model.with_doctype('Item', () => { frappe.get_meta('Item').fields.forEach(d => { diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py index 0c6acd4290..678de1a9ba 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py @@ -12,7 +12,7 @@ class ItemVariantSettings(Document): fields = frappe.get_meta('Item').fields exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", "show_variant_in_website", "standard_rate", "opening_stock", "image", "description", - "variant_of", "valuation_rate", "description", "variant_based_on", + "variant_of", "valuation_rate", "description", "website_image", "thumbnail", "website_specifiations", "web_long_description"] for d in fields: diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 4bcbcc4b6f..0aecb78ddd 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -11,7 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \ from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation -from erpnext.stock.doctype.item.test_item import set_item_variant_settings +from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make_item_variant from frappe.tests.test_permissions import set_user_permission_doctypes from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.accounts.doctype.account.test_account import get_inventory_account @@ -46,6 +46,7 @@ class TestStockEntry(unittest.TestCase): make_stock_entry(item_code=item_code, target=warehouse, qty=1, basic_rate=10) sle = get_sle(item_code = item_code, warehouse = warehouse)[0] + self.assertEqual([[1, 10]], frappe.safe_eval(sle.stock_queue)) # negative qty @@ -74,7 +75,6 @@ class TestStockEntry(unittest.TestCase): frappe.db.set_default("allow_negative_stock", 0) def test_auto_material_request(self): - from erpnext.stock.doctype.item.test_item import make_item_variant make_item_variant() self._test_auto_material_request("_Test Item") self._test_auto_material_request("_Test Item", material_request_type="Transfer") @@ -82,6 +82,7 @@ class TestStockEntry(unittest.TestCase): def test_auto_material_request_for_variant(self): fields = [{'field_name': 'reorder_levels'}] set_item_variant_settings(fields) + make_item_variant() template = frappe.get_doc("Item", "_Test Variant Item") if not template.reorder_levels: diff --git a/erpnext/stock/doctype/warehouse/test_records.json b/erpnext/stock/doctype/warehouse/test_records.json index af3bd231fc..014cf3e501 100644 --- a/erpnext/stock/doctype/warehouse/test_records.json +++ b/erpnext/stock/doctype/warehouse/test_records.json @@ -13,13 +13,6 @@ "warehouse_name": "_Test Scrap Warehouse", "is_group": 0 }, - { - "company": "_Test Company", - "create_account_under": "Stock Assets - _TC", - "doctype": "Warehouse", - "warehouse_name": "_Test Warehouse", - "is_group": 0 - }, { "company": "_Test Company", "create_account_under": "Fixed Assets - _TC", diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 0e3a4f9525..65310aa9e3 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -25,7 +25,7 @@ class TransactionBase(StatusUpdater): if not getattr(self, 'set_posting_time', None): now = now_datetime() self.posting_date = now.strftime('%Y-%m-%d') - self.posting_time = now.strftime('%H:%M:%S') + self.posting_time = now.strftime('%H:%M:%S.%f') def add_calendar_event(self, opts, force=False): if cstr(self.contact_by) != cstr(self._prev.contact_by) or \