diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.calculate.md b/docs/dev/custom_script_examples/docs.dev.custom_script.calculate.md new file mode 100644 index 00000000000..6744e2b6f16 --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.calculate.md @@ -0,0 +1,24 @@ +--- +{ + "_label": "Calculate Incentive for Sales Team" +} +--- +Can be used in any Sales Transaction with **Sales Team** Table: + + + cur_frm.cscript.custom_validate = function(doc) { + // calculate incentives for each person on the deal + total_incentive = 0 + $.each(wn.model.get("Sales Team", {parent:doc.name}), function(i, d) { + + // calculate incentive + var incentive_percent = 2; + if(doc.grand_total > 400) incentive_percent = 4; + + // actual incentive + d.incentives = flt(doc.grand_total) * incentive_percent / 100; + total_incentive += flt(d.incentives) + }); + + doc.total_incentive = total_incentive; + } \ No newline at end of file diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.fetch.md b/docs/dev/custom_script_examples/docs.dev.custom_script.fetch.md new file mode 100644 index 00000000000..d0414fbeca9 --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.fetch.md @@ -0,0 +1,20 @@ +--- +{ + "_label": "Custom Script: Fetch Values from Master" +} +--- +To pull a value of a link on selection, use the `add_fetch` method. + + add_fetch(link_fieldname, source_fieldname, target_fieldname) + +### Example + +You create Custom Field **VAT ID** (`vat_id`) in **Customer** and **Sales Invoice** and want to make sure this value gets updated every time you select a Customer in a Sales Invoice. + +Then in the Sales Invoice Custom Script, add this line: + + cur_frm.add_fetch('customer','vat_id','vat_id') + + +--- +See: [How to create a custom script](!docs.dev.custom_script.html) \ No newline at end of file diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.item_code.md b/docs/dev/custom_script_examples/docs.dev.custom_script.item_code.md new file mode 100644 index 00000000000..22e75c943cb --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.item_code.md @@ -0,0 +1,35 @@ +--- +{ + "_label": "Generate Item Code based on Custom Logic" +} +--- +Add this in the Custom Script of **Item**, so that the new Item Code is generated just before the a new Item is saved. + + cur_frm.cscript.custom_validate = function(doc) { + // clear item_code (name is from item_code) + doc.item_code = ""; + + // first 2 characters based on item_group + switch(doc.item_group) { + case "Test A": + doc.item_code = "TA"; + break; + case "Test B": + doc.item_code = "TB"; + break; + default: + doc.item_code = "XX"; + } + + // add next 2 characters based on brand + switch(doc.brand) { + case "Brand A": + doc.item_code += "BA"; + break; + case "Brand B": + doc.item_code += "BB"; + break; + default: + doc.item_code += "BX"; + } + } \ No newline at end of file diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.read_only.md b/docs/dev/custom_script_examples/docs.dev.custom_script.read_only.md new file mode 100644 index 00000000000..9da3e858f30 --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.read_only.md @@ -0,0 +1,13 @@ +--- +{ + "_label": "Make an Item read-only after Saving" +} +--- +Use the method `cur_frm.set_df_property` to update the field's display. + +In this script we also use the `__islocal` property of the doc to check if the document has been saved atleast once or is never saved. If `__islocal` is `1`, then the document has never been saved. + + cur_frm.cscript.custom_refresh = function(doc) { + // use the __islocal value of doc, to check if the doc is saved or not + cur_frm.set_df_property("myfield", "read_only", doc.__islocal ? 0 : 1); + } \ No newline at end of file diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.validate.md b/docs/dev/custom_script_examples/docs.dev.custom_script.validate.md new file mode 100644 index 00000000000..a22851cd269 --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.validate.md @@ -0,0 +1,12 @@ +--- +{ + "_label": "Date Validation: Do not allow past dates in a date field" +} +--- + + cur_frm.cscript.custom_validate = function(doc) { + if (doc.from_date < get_today()) { + msgprint("You can not select past date in From Date"); + validated = false; + } + } \ No newline at end of file diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.validate1.md b/docs/dev/custom_script_examples/docs.dev.custom_script.validate1.md new file mode 100644 index 00000000000..b12c61fa4a4 --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.validate1.md @@ -0,0 +1,12 @@ +--- +{ + "_label": "Restrict Purpose of Stock Entry" +} +--- + + cur_frm.cscript.custom_validate = function(doc) { + if(user=="user1@example.com" && doc.purpose!="Material Receipt") { + msgprint("You are only allowed Material Receipt"); + validated = false; + } + } \ No newline at end of file diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.validate2.md b/docs/dev/custom_script_examples/docs.dev.custom_script.validate2.md new file mode 100644 index 00000000000..05d662e67c9 --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.validate2.md @@ -0,0 +1,22 @@ +--- +{ + "_label": "Restrict User Based on Child Record (Warehouse)" +} +--- + + // restrict certain warehouse to Material Manager + cur_frm.cscript.custom_validate = function(doc) { + if(user_roles.indexOf("Material Manager")==-1) { + + var restricted_in_source = wn.model.get("Stock Entry Detail", + {parent:cur_frm.doc.name, s_warehouse:"Restricted"}); + + var restricted_in_target = wn.model.get("Stock Entry Detail", + {parent:cur_frm.doc.name, t_warehouse:"Restricted"}) + + if(restricted_in_source.length || restricted_in_target.length) { + msgprint("Only Material Manager can make entry in Restricted Warehouse"); + validated = false; + } + } + } \ No newline at end of file diff --git a/docs/dev/custom_script_examples/docs.dev.custom_script.validate3.md b/docs/dev/custom_script_examples/docs.dev.custom_script.validate3.md new file mode 100644 index 00000000000..629f1c186da --- /dev/null +++ b/docs/dev/custom_script_examples/docs.dev.custom_script.validate3.md @@ -0,0 +1,17 @@ +--- +{ + "_label": "Restrict Cancel Rights based on Certain Order Value" +} +--- +Add a handler to `custom_before_cancel` event: + + cur_frm.cscript.custom_before_cancel = function(doc) { + if (user_roles.indexOf("Accounts User")!=-1 && user_roles.indexOf("Accounts Manager")==-1 + && user_roles.indexOf("System Manager")==-1) { + if (flt(doc.grand_total) > 10000) { + msgprint("You can not cancel this transaction, because grand total \ + is greater than 10000"); + validated = false; + } + } + } diff --git a/docs/dev/docs.dev.client_script.md b/docs/dev/docs.dev.client_script.md deleted file mode 100644 index fc82f9d5ff7..00000000000 --- a/docs/dev/docs.dev.client_script.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -{ - "_label": "Client Scripts: Custoimzing ERPNext" - -} ---- - - - - diff --git a/docs/dev/docs.dev.custom_script.md b/docs/dev/docs.dev.custom_script.md new file mode 100644 index 00000000000..ad506374735 --- /dev/null +++ b/docs/dev/docs.dev.custom_script.md @@ -0,0 +1,31 @@ +--- +{ + "_label": "Custom Script Examples", + "_toc": [ + "docs.dev.custom_script.fetch", + "docs.dev.custom_script.validate", + "docs.dev.custom_script.validate1", + "docs.dev.custom_script.validate2", + "docs.dev.custom_script.validate3", + "docs.dev.custom_script.read_only", + "docs.dev.custom_script.calculate", + "docs.dev.custom_script.item_code" + ] +} +--- +### How to Create a Custom Script + +Create a Custom Script (you must have System Manager role for this): + +1. Got to: Setup > Custom Script > New Custom Script +1. Select the DocType in which you want to add the Custom Script + +--- +### Notes + +1. Server Custom Scripts are only available for the Administrator. +1. Client Custom Scripts are in Javascript and Server Custom Scripts are in Python. +1. For testing, make sure to go to Tools > Clear Cache and refresh after updating a Custom Script. + + + diff --git a/docs/dev/docs.dev.md b/docs/dev/docs.dev.md index 285f1767c7f..15afea8e25a 100644 --- a/docs/dev/docs.dev.md +++ b/docs/dev/docs.dev.md @@ -6,7 +6,7 @@ "docs.dev.quickstart", "docs.dev.framework", "docs.dev.modules", - "docs.dev.client_script", + "docs.dev.custom_script", "docs.dev.api", "docs.dev.translate", "docs.dev.docs" diff --git a/home/__init__.py b/home/__init__.py index 9667efd2128..086179813ae 100644 --- a/home/__init__.py +++ b/home/__init__.py @@ -88,4 +88,5 @@ def update_feed(controller, method=None): if method in ['on_update', 'on_submit']: subject, color = feed_dict.get(doc.doctype, [None, None]) if subject: - make_feed('', doc.doctype, doc.name, doc.owner, subject % doc.fields, color) + from webnotes.utils import encode_dict + make_feed('', doc.doctype, doc.name, doc.owner, subject % encode_dict(doc.fields.copy()), color) diff --git a/hr/doctype/expense_claim/expense_claim.js b/hr/doctype/expense_claim/expense_claim.js index ad3dacebda6..65c557947f8 100644 --- a/hr/doctype/expense_claim/expense_claim.js +++ b/hr/doctype/expense_claim/expense_claim.js @@ -27,8 +27,8 @@ erpnext.hr.ExpenseClaimController = wn.ui.form.Controller.extend({ var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries'); d1.credit = cur_frm.doc.total_sanctioned_amount; if(r.message) { - d1.account = r.message[0].account; - d1.balance = r.message[0].balance; + d1.account = r.message.account; + d1.balance = r.message.balance; } loaddoc('Journal Voucher', jv.name); diff --git a/patches/patch_list.py b/patches/patch_list.py index 320614a23de..ff28031a8f6 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -259,4 +259,5 @@ patch_list = [ "execute:webnotes.bean('Style Settings').save() #2013-08-20", "patches.september_2013.p01_fix_buying_amount_gl_entries", "patches.september_2013.p01_update_communication", + "patches.september_2013.p02_fix_serial_no_status", ] \ No newline at end of file diff --git a/patches/september_2013/p02_fix_serial_no_status.py b/patches/september_2013/p02_fix_serial_no_status.py new file mode 100644 index 00000000000..714cd7a3778 --- /dev/null +++ b/patches/september_2013/p02_fix_serial_no_status.py @@ -0,0 +1,30 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import webnotes + +def execute(): + stock_entries = webnotes.conn.sql("""select ste_item.serial_no, ste.name + from `tabStock Entry Detail` ste_item, `tabStock Entry` ste + where ste.name = ste_item.parent + and ifnull(ste_item.serial_no, '') != '' + and ste.purpose='Material Transfer' + and ste.modified>='2013-08-14' + order by ste.posting_date desc, ste.posting_time desc, ste.name desc""", as_dict=1) + + for d in stock_entries: + serial_nos = d.serial_no.split("\n") + for sr in serial_nos: + serial_no = sr.strip() + if serial_no: + serial_bean = webnotes.bean("Serial No", serial_no) + if serial_bean.doc.status == "Not Available": + latest_sle = webnotes.conn.sql("""select voucher_no from `tabStock Ledger Entry` + where item_code=%s and warehouse=%s and serial_no like %s + order by name desc limit 1""", (serial_bean.doc.item_code, + serial_bean.doc.warehouse, "%%%s%%" % serial_no)) + + if latest_sle and latest_sle[0][0] == d.name: + serial_bean.doc.status = "Available" + serial_bean.save() \ No newline at end of file diff --git a/startup/boot.py b/startup/boot.py index 79f0a7bd04a..ce446e9ecfa 100644 --- a/startup/boot.py +++ b/startup/boot.py @@ -16,7 +16,9 @@ def boot_session(bootinfo): if webnotes.session['user']!='Guest': bootinfo['letter_heads'] = get_letter_heads() - + + load_country_and_currency(bootinfo) + import webnotes.model.doctype bootinfo['notification_settings'] = webnotes.doc("Notification Control", "Notification Control").get_values() @@ -36,7 +38,15 @@ def boot_session(bootinfo): bootinfo['docs'] += webnotes.conn.sql("""select name, default_currency, cost_center from `tabCompany`""", as_dict=1, update={"doctype":":Company"}) - + +def load_country_and_currency(bootinfo): + if bootinfo.control_panel.country and \ + webnotes.conn.exists("Country", bootinfo.control_panel.country): + bootinfo["docs"] += [webnotes.doc("Country", bootinfo.control_panel.country)] + + bootinfo["docs"] += webnotes.conn.sql("""select * from tabCurrency + where ifnull(enabled,0)=1""", as_dict=1, update={"doctype":":Currency"}) + def get_letter_heads(): """load letter heads with startup""" import webnotes diff --git a/startup/event_handlers.py b/startup/event_handlers.py index e807797a325..fa08962fc2e 100644 --- a/startup/event_handlers.py +++ b/startup/event_handlers.py @@ -64,6 +64,12 @@ def check_if_expired(): webnotes.response['message'] = 'Account Expired' raise webnotes.AuthenticationError +def on_build(): + from website.helpers.make_web_include_files import make + make() + + from home.page.latest_updates import latest_updates + latest_updates.make() def comment_added(doc): """add comment to feed""" diff --git a/stock/doctype/serial_no/serial_no.py b/stock/doctype/serial_no/serial_no.py index e7018a2bebc..1feab02cf57 100644 --- a/stock/doctype/serial_no/serial_no.py +++ b/stock/doctype/serial_no/serial_no.py @@ -52,9 +52,7 @@ class DocType(StockController): webnotes.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError) if not self.via_stock_ledger and warehouse != self.doc.warehouse: webnotes.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError) - - if not self.doc.warehouse and self.doc.status=="Available": - self.doc.status = "Not Available" + def validate_item(self): """ diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index e3fc67e0fc9..6a61461ac53 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -343,10 +343,14 @@ class DocType(StockController): def update_stock_ledger(self, is_cancelled=0): self.values = [] for d in getlist(self.doclist, 'mtn_details'): - if cstr(d.s_warehouse): + if cstr(d.s_warehouse) and not is_cancelled: self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled) + if cstr(d.t_warehouse): self.add_to_values(d, cstr(d.t_warehouse), flt(d.transfer_qty), is_cancelled) + + if cstr(d.s_warehouse) and is_cancelled: + self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled) get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values, self.doc.amended_from and 'Yes' or 'No') diff --git a/stock/doctype/stock_entry/test_stock_entry.py b/stock/doctype/stock_entry/test_stock_entry.py index b9b32306d8b..3beb61ddc83 100644 --- a/stock/doctype/stock_entry/test_stock_entry.py +++ b/stock/doctype/stock_entry/test_stock_entry.py @@ -648,6 +648,9 @@ class TestStockEntry(unittest.TestCase): self.assertTrue(webnotes.conn.exists("Serial No", "ABCD")) self.assertTrue(webnotes.conn.exists("Serial No", "EFGH")) + se.cancel() + self.assertFalse(webnotes.conn.get_value("Serial No", "ABCD", "warehouse")) + def test_serial_no_not_exists(self): se = webnotes.bean(copy=test_records[0]) se.doc.purpose = "Material Issue" @@ -699,6 +702,9 @@ class TestStockEntry(unittest.TestCase): se.insert() se.submit() self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC") + + se.cancel() + self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse - _TC") def test_serial_warehouse_error(self): make_serialized_item() @@ -720,7 +726,6 @@ class TestStockEntry(unittest.TestCase): serial_no = get_serial_nos(se.doclist[1].serial_no)[0] self.assertFalse(webnotes.conn.get_value("Serial No", serial_no, "warehouse")) - self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "status"), "Not Available") def make_serialized_item(): se = webnotes.bean(copy=test_records[0]) diff --git a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 58fc828f2de..2f365383fdf 100644 --- a/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -119,13 +119,14 @@ class DocType(DocListController): if self.doc.actual_qty < 0: if sr.doc.warehouse!=self.doc.warehouse: - webnotes.throw(_("Warehouse does not belong to Item") + \ - (": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoWarehouseError) + webnotes.throw(_("Serial No") + ": " + serial_no + + _(" does not belong to Warehouse") + ": " + self.doc.warehouse, + SerialNoWarehouseError) if self.doc.voucher_type in ("Delivery Note", "Sales Invoice") \ and sr.doc.status != "Available": - webnotes.throw(_("Serial No status must be 'Available' to Deliver") + \ - ": " + serial_no, SerialNoStatusError) + webnotes.throw(_("Serial No status must be 'Available' to Deliver") + + ": " + serial_no, SerialNoStatusError) sr.doc.warehouse = None diff --git a/website/helpers/make_web_include_files.py b/website/helpers/make_web_include_files.py index 7ff3d6ad998..a9df23bb1c5 100644 --- a/website/helpers/make_web_include_files.py +++ b/website/helpers/make_web_include_files.py @@ -3,14 +3,14 @@ import os import webnotes -import webnotes.webutils def make(): + from startup.webutils import get_home_page if not webnotes.conn: webnotes.connect() - home_page = webnotes.webutils.get_home_page() + home_page = get_home_page() fname = 'js/wn-web.js' if os.path.basename(os.path.abspath('.'))!='public':