From e2d8dd0663650439e5132192d0368ca9417e5198 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 22 Jun 2015 19:06:15 +0530 Subject: [PATCH 01/36] added recurring print format to sales/purchase invoice and order --- .../doctype/purchase_invoice/purchase_invoice.js | 1 + .../doctype/purchase_invoice/purchase_invoice.json | 11 ++++++++++- .../accounts/doctype/sales_invoice/sales_invoice.js | 2 -- .../doctype/sales_invoice/sales_invoice.json | 11 ++++++++++- .../doctype/purchase_order/purchase_order.json | 11 ++++++++++- erpnext/controllers/recurring_document.py | 2 +- erpnext/public/js/controllers/transaction.js | 12 ++++++++++++ erpnext/selling/doctype/sales_order/sales_order.js | 2 -- erpnext/selling/doctype/sales_order/sales_order.json | 11 ++++++++++- 9 files changed, 54 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 3e16a312cbc..d404851a187 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -224,3 +224,4 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){ else cur_frm.pformat.print_heading = __("Purchase Invoice"); } + diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index e192e763180..dcbc60521e8 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -926,12 +926,21 @@ "no_copy": 1, "permlevel": 0, "print_hide": 1 + }, + { + "depends_on": "eval:doc.is_recurring==1", + "fieldname": "recurring_print_format", + "fieldtype": "Link", + "label": "Recurring Print Format", + "options": "Print Format", + "permlevel": 0, + "precision": "" } ], "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2015-06-16 16:46:47.308287", + "modified": "2015-06-22 07:30:06.743438", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index b35fa8ae84b..3bb9aa0bac3 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -392,8 +392,6 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) { } } - - cur_frm.set_query("debit_to", function(doc) { return{ filters: [ diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index a021be41d2f..045678dc545 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1227,6 +1227,15 @@ "print_hide": 1, "read_only": 0 }, + { + "depends_on": "eval:doc.is_recurring==1", + "fieldname": "recurring_print_format", + "fieldtype": "Link", + "label": "Recurring Print Format", + "options": "Print Format", + "permlevel": 0, + "precision": "" + }, { "fieldname": "against_income_account", "fieldtype": "Small Text", @@ -1244,7 +1253,7 @@ "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2015-06-16 16:45:06.618286", + "modified": "2015-06-22 06:39:22.072544", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index dc1dfa42bde..f65ba40a92c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -868,12 +868,21 @@ "no_copy": 1, "permlevel": 0, "print_hide": 1 + }, + { + "depends_on": "eval:doc.is_recurring==1", + "fieldname": "recurring_print_format", + "fieldtype": "Link", + "label": "Recurring Print Format", + "options": "Print Format", + "permlevel": 0, + "precision": "" } ], "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2015-06-15 15:38:56.794601", + "modified": "2015-06-22 07:30:36.259753", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py index a46fa324398..8c8af23b58d 100644 --- a/erpnext/controllers/recurring_document.py +++ b/erpnext/controllers/recurring_document.py @@ -124,7 +124,7 @@ def send_notification(new_rv): frappe.sendmail(new_rv.notification_email_address, 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)]) + attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=new_rv.recurring_print_format)]) def notify_errors(doc, doctype, party, owner): from frappe.utils.user import get_system_managers diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index ca0a6cb8d2d..91601a43127 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -37,6 +37,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(this.frm.fields_dict["items"]) { this["items_remove"] = this.calculate_taxes_and_totals; } + + if(this.frm.fields_dict["recurring_print_format"]) { + this.frm.set_query("recurring_print_format", function(doc) { + return{ + filters: [ + ['Print Format', 'doc_type', '=', cur_frm.doctype], + ] + } + }); + } }, onload_post_render: function() { @@ -782,3 +792,5 @@ frappe.ui.form.on(cur_frm.doctype, "discount_amount", function(frm) { cur_frm.cscript.set_dynamic_labels(); cur_frm.cscript.calculate_taxes_and_totals(); }) + + diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 7ede9d45059..7f0b3861e7c 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -183,5 +183,3 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) { cur_frm.email_doc(frappe.boot.notification_settings.sales_order_message); } }; - -; diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index ad62d578d46..59f54fbadcc 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1074,13 +1074,22 @@ "no_copy": 1, "permlevel": 0, "print_hide": 1 + }, + { + "depends_on": "eval:doc.is_recurring==1", + "fieldname": "recurring_print_format", + "fieldtype": "Link", + "label": "Recurring Print Format", + "options": "Print Format", + "permlevel": 0, + "precision": "" } ], "icon": "icon-file-text", "idx": 1, "is_submittable": 1, "issingle": 0, - "modified": "2015-06-15 15:36:38.898462", + "modified": "2015-06-22 07:29:24.379272", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", From a1f1edc7863679c770f2c03b6c58d5ada19dfe7b Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 29 Jun 2015 16:04:12 +0530 Subject: [PATCH 02/36] Typo fixes in Sales Person --- erpnext/selling/page/sales_browser/sales_browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/sales_browser/sales_browser.js b/erpnext/selling/page/sales_browser/sales_browser.js index 3b9e25aa1c7..98d34be40d3 100644 --- a/erpnext/selling/page/sales_browser/sales_browser.js +++ b/erpnext/selling/page/sales_browser/sales_browser.js @@ -122,7 +122,7 @@ erpnext.SalesChart = Class.extend({ if(me.ctype == "Sales Person") { fields.splice(-1, 0, {fieldtype:'Link', fieldname:'employee', label:__('Employee'), - options:'Employee', description: __("Please enter Employee Id of this sales parson")}); + options:'Employee', description: __("Please enter Employee Id of this sales person")}); } // the dialog From b9e5cd0df45a2c7f5b15c1bb666a0ca075f6fb54 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 29 Jun 2015 17:01:32 +0530 Subject: [PATCH 03/36] Fixed lead status not updating on Creation of oppurtunity issue --- erpnext/crm/doctype/lead/lead.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index b523416484d..12de2e58193 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -75,8 +75,7 @@ class Lead(SellingController): return frappe.db.get_value("Customer", {"lead_name": self.name}) def has_opportunity(self): - return frappe.db.get_value("Opportunity", {"lead": self.name, "docstatus": 1, - "status": ["!=", "Lost"]}) + return frappe.db.get_value("Opportunity", {"lead": self.name, "status": ["!=", "Lost"]}) @frappe.whitelist() def make_customer(source_name, target_doc=None): From 11243a4fb4a0a583182f96011fd18786f6f21975 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 29 Jun 2015 18:37:43 +0530 Subject: [PATCH 04/36] Show amount in Journal Entry list view --- erpnext/accounts/doctype/journal_entry/journal_entry.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 6eb395d4861..249fcc46d30 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -53,7 +53,7 @@ "fieldname": "posting_date", "fieldtype": "Date", "in_filter": 1, - "in_list_view": 1, + "in_list_view": 0, "label": "Posting Date", "no_copy": 1, "oldfieldname": "posting_date", @@ -445,7 +445,7 @@ "icon": "icon-file-text", "idx": 1, "is_submittable": 1, - "modified": "2015-04-27 20:32:31.655580", + "modified": "2015-06-29 15:28:12.529019", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", From e06d01e3edbbc77f837737cd70b9daa180c3bb06 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 29 Jun 2015 18:38:27 +0530 Subject: [PATCH 05/36] Delete events via query instead of delete_doc function, to save time --- erpnext/utilities/transaction_base.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 60889c54155..be74aeb920d 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -22,16 +22,21 @@ class TransactionBase(StatusUpdater): self.posting_time = now_datetime().strftime('%H:%M:%S') def add_calendar_event(self, opts, force=False): - if self.contact_by != cstr(self._prev.contact_by) or \ - self.contact_date != cstr(self._prev.contact_date) or force: + if cstr(self.contact_by) != cstr(self._prev.contact_by) or \ + cstr(self.contact_date) != cstr(self._prev.contact_date) or force: self.delete_events() self._add_calendar_event(opts) def delete_events(self): - frappe.delete_doc("Event", frappe.db.sql_list("""select name from `tabEvent` - where ref_type=%s and ref_name=%s""", (self.doctype, self.name)), - ignore_permissions=True) + events = frappe.db.sql_list("""select name from `tabEvent` + where ref_type=%s and ref_name=%s""", (self.doctype, self.name)) + if events: + frappe.db.sql("delete from `tabEvent` where name in (%s)" + .format(", ".join(['%s']*len(events))), tuple(events)) + + frappe.db.sql("delete from `tabEvent Role` where parent in (%s)" + .format(", ".join(['%s']*len(events))), tuple(events)) def _add_calendar_event(self, opts): opts = frappe._dict(opts) From 082016115777be4675e88be5c50512ff729c6d4b Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Mon, 29 Jun 2015 13:58:49 -0400 Subject: [PATCH 06/36] [fix] Item Variant Attribute autocomplete appearance. Fixes #3488, #3515, #3525 --- erpnext/stock/doctype/item/item.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index ed1367e0b61..5da3d35443a 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -7,6 +7,7 @@ frappe.ui.form.on("Item", { onload: function(frm) { var df = frappe.meta.get_docfield("Item Variant", "item_attribute_value"); df.on_make = function(field) { + $(field.input_area).addClass("ui-front"); field.$input.autocomplete({ minLength: 0, minChars: 0, From a0f2510b01889166d6421289c0ce8b3c10a9dbe3 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Tue, 30 Jun 2015 12:36:17 +0530 Subject: [PATCH 07/36] Change currency exchange rate api to fixer.io --- .../doctype/global_defaults/global_defaults.json | 16 +--------------- .../doctype/global_defaults/global_defaults.py | 1 - erpnext/setup/utils.py | 15 ++++++++------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json index 777ae3b78b6..c7330d0c897 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.json +++ b/erpnext/setup/doctype/global_defaults/global_defaults.json @@ -66,20 +66,6 @@ "label": "Disable Rounded Total", "permlevel": 0, "read_only": 0 - }, - { - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "permlevel": 0, - "precision": "" - }, - { - "description": "For automatic exchange rates go to jsonrates.com and signup for an API key", - "fieldname": "jsonrates_api_key", - "fieldtype": "Data", - "label": "jsonrates.com API Key", - "permlevel": 0, - "precision": "" } ], "hide_toolbar": 0, @@ -87,7 +73,7 @@ "idx": 1, "in_create": 1, "issingle": 1, - "modified": "2015-05-07 05:43:49.760061", + "modified": "2015-06-30 03:00:26.420003", "modified_by": "Administrator", "module": "Setup", "name": "Global Defaults", diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index a3fa3a9c5f0..efdc875249f 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -17,7 +17,6 @@ keydict = { 'hide_currency_symbol':'hide_currency_symbol', 'account_url':'account_url', 'disable_rounded_total': 'disable_rounded_total', - 'jsonrates_api_key': 'jsonrates_api_key' } from frappe.model.document import Document diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index cef60799a13..f661edb2b6a 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -60,20 +60,21 @@ def before_tests(): @frappe.whitelist() def get_exchange_rate(from_currency, to_currency): - jsonrates_api_key = frappe.conf.jsonrates_api_key or frappe.db.get_default("jsonrates_api_key") - - if jsonrates_api_key: + try: cache = frappe.cache() key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency) value = cache.get(key) if not value: import requests - response = requests.get("http://jsonrates.com/get/?from={0}&to={1}&apiKey={2}".format(from_currency, - to_currency, jsonrates_api_key)) + response = requests.get("http://api.fixer.io/latest", params={ + "base": from_currency, + "symbols": to_currency + }) # expire in 24 hours - value = response.json().get("rate") + response.raise_for_status() + value = response.json()["rates"][to_currency] cache.setex(key, value, 24 * 60 * 60) return flt(value) - else: + except: exchange = "%s-%s" % (from_currency, to_currency) return flt(frappe.db.get_value("Currency Exchange", exchange, "exchange_rate")) From a25e8ea0bc9c09d9fd817cce4b03183a2c72000a Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 30 Jun 2015 17:15:13 +0530 Subject: [PATCH 08/36] Fixed Issues in Project Task --- erpnext/patches.txt | 4 +++- erpnext/projects/doctype/project/project.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c6472109b16..0f32a6dbf68 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -166,4 +166,6 @@ erpnext.patches.v5_0.portal_fixes erpnext.patches.v5_0.reset_values_in_tools execute:frappe.delete_doc("Page", "users") erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again -erpnext.patches.v5_0.index_on_account_and_gl_entry \ No newline at end of file +erpnext.patches.v5_0.index_on_account_and_gl_entry +execute:frappe.db.sql("""delete from `tabProject Task`""") + diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 05e40388bf9..0ba368d5bad 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -35,6 +35,7 @@ class Project(Document): def validate(self): self.validate_dates() self.sync_tasks() + self.tasks = [] def validate_dates(self): if self.expected_start_date and self.expected_end_date: @@ -70,8 +71,6 @@ class Project(Document): for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}): frappe.delete_doc("Task", t.name) - self.tasks = [] - def update_percent_complete(self): total = frappe.db.sql("""select count(*) from tabTask where project=%s""", self.name)[0][0] From c8cc8b7115033c9e8bd4ce4c919aebd26381ba89 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 20 May 2015 15:49:55 +0530 Subject: [PATCH 09/36] manage variants new doctype created --- erpnext/stock/doctype/item/item.js | 39 +------ erpnext/stock/doctype/item/item.json | 12 +-- erpnext/stock/doctype/item/item.py | 50 ++------- .../stock/doctype/manage_variants/__init__.py | 0 .../manage_variants/manage_variants.js | 44 ++++++++ .../manage_variants/manage_variants.json | 101 ++++++++++++++++++ .../manage_variants/manage_variants.py | 77 +++++++++++++ .../doctype/variant_attribute/__init__.py | 0 .../variant_attribute/variant_attribute.json | 78 ++++++++++++++ .../variant_attribute/variant_attribute.py | 10 ++ .../stock/doctype/variant_item/__init__.py | 0 .../doctype/variant_item/variant_item.json | 90 ++++++++++++++++ .../doctype/variant_item/variant_item.py | 10 ++ 13 files changed, 422 insertions(+), 89 deletions(-) create mode 100644 erpnext/stock/doctype/manage_variants/__init__.py create mode 100644 erpnext/stock/doctype/manage_variants/manage_variants.js create mode 100644 erpnext/stock/doctype/manage_variants/manage_variants.json create mode 100644 erpnext/stock/doctype/manage_variants/manage_variants.py create mode 100644 erpnext/stock/doctype/variant_attribute/__init__.py create mode 100644 erpnext/stock/doctype/variant_attribute/variant_attribute.json create mode 100644 erpnext/stock/doctype/variant_attribute/variant_attribute.py create mode 100644 erpnext/stock/doctype/variant_item/__init__.py create mode 100644 erpnext/stock/doctype/variant_item/variant_item.json create mode 100644 erpnext/stock/doctype/variant_item/variant_item.py diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 5da3d35443a..9db24f143a0 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -5,40 +5,6 @@ frappe.provide("erpnext.item"); frappe.ui.form.on("Item", { onload: function(frm) { - var df = frappe.meta.get_docfield("Item Variant", "item_attribute_value"); - df.on_make = function(field) { - $(field.input_area).addClass("ui-front"); - field.$input.autocomplete({ - minLength: 0, - minChars: 0, - source: function(request, response) { - frappe.call({ - method:"frappe.client.get_list", - args:{ - doctype:"Item Attribute Value", - filters: [ - ["parent","=", field.doc.item_attribute], - ["attribute_value", "like", request.term + "%"] - ], - fields: ["attribute_value"] - }, - callback: function(r) { - response($.map(r.message, function(d) { return d.attribute_value; })); - } - }); - }, - select: function(event, ui) { - field.$input.val(ui.item.value); - field.$input.trigger("change"); - }, - focus: function( event, ui ) { - if(ui.item.action) { - return false; - } - }, - }); - } - erpnext.item.setup_queries(frm); }, @@ -64,6 +30,10 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__("Show Variants"), function() { frappe.set_route("List", "Item", {"variant_of": frm.doc.name}); }, "icon-list", "btn-default"); + frm.add_custom_button(__("Manage Variants"), function() { + frappe.route_options = {"item": frm.doc.name }; + new_doc("Manage Variants"); + }); } if (frm.doc.variant_of) { frm.set_intro(__("This Item is a Variant of {0} (Template). Attributes will be copied over from the template unless 'No Copy' is set", [frm.doc.variant_of]), true); @@ -114,6 +84,7 @@ frappe.ui.form.on("Item", { method: "copy_specification_from_item_group" }); }, + is_stock_item: function(frm) { erpnext.item.toggle_reqd(frm); } diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 8b10319f86e..6ff8ac38675 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -185,16 +185,6 @@ "precision": "", "read_only": 0 }, - { - "depends_on": "has_variants", - "description": "A new variant (Item) will be created for each attribute value combination", - "fieldname": "variants", - "fieldtype": "Table", - "label": "Variants", - "options": "Item Variant", - "permlevel": 0, - "precision": "" - }, { "fieldname": "inventory", "fieldtype": "Section Break", @@ -877,7 +867,7 @@ } ], "icon": "icon-tag", - "idx": 1, + "idx": 1, "max_attachments": 1, "modified": "2015-06-26 17:20:18.204558", "modified_by": "Administrator", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index aa463eee775..77bcb4d668f 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -12,8 +12,6 @@ from frappe.website.doctype.website_slideshow.website_slideshow import get_slide import copy class WarehouseNotSet(frappe.ValidationError): pass -class DuplicateVariant(frappe.ValidationError): pass -class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class Item(WebsiteGenerator): website = frappe._dict( @@ -63,7 +61,6 @@ class Item(WebsiteGenerator): self.cant_change() self.validate_reorder_level() self.validate_warehouse_for_reorder() - self.validate_variants() self.update_item_desc() self.synced_with_hub = 0 @@ -77,7 +74,6 @@ class Item(WebsiteGenerator): invalidate_cache_for_item(self) self.validate_name_with_item_group() self.update_item_price() - self.sync_variants() def get_context(self, context): context["parent_groups"] = get_parent_item_groups(self.item_group) + \ @@ -133,43 +129,6 @@ class Item(WebsiteGenerator): if not matched: frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.")) - def validate_variants(self): - self.validate_variants_are_unique() - self.validate_stock_for_template_must_be_zero() - - def validate_stock_for_template_must_be_zero(self): - if self.has_variants: - stock_in = frappe.db.sql_list("""select warehouse from tabBin - where item_code=%s and ifnull(actual_qty, 0) > 0""", self.name) - if stock_in: - frappe.throw(_("Item Template cannot have stock and varaiants. Please remove stock from warehouses {0}").format(", ".join(stock_in)), - ItemTemplateCannotHaveStock) - - def validate_variants_are_unique(self): - if not self.has_variants: - self.variants = [] - return - - if self.variants: - if self.variant_of: - frappe.throw(_("Item cannot be a variant of a variant")) - - variants, attributes = [], {} - for d in self.variants: - key = (d.item_attribute, d.item_attribute_value) - if key in variants: - frappe.throw(_("{0} {1} is entered more than once in Item Variants table") - .format(d.item_attribute, d.item_attribute_value), DuplicateVariant) - variants.append(key) - - attributes.setdefault(d.item_attribute, [t.attribute_value for t in frappe.db.get_all("Item Attribute Value", - fields=["attribute_value"], filters={"parent": d.item_attribute })]) - - if d.item_attribute_value not in attributes.get(d.item_attribute): - frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.item_attribute_value)) - else: - frappe.throw(_("Please enter atleast one attribute row in Item Variants table")) - def sync_variants(self): variant_item_codes = self.get_variant_item_codes() @@ -460,9 +419,11 @@ class Item(WebsiteGenerator): def update_item_desc(self): if frappe.db.get_value('BOM',self.name, 'description') != self.description: frappe.db.sql("""update `tabBOM` set description = %s where item = %s and docstatus < 2""",(self.description, self.name)) - frappe.db.sql("""update `tabBOM Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name)) - frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name)) - + frappe.db.sql("""update `tabBOM Item` set description = %s where + item_code = %s and docstatus < 2""",(self.description, self.name)) + frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where + item_code = %s and docstatus < 2""",(self.description, self.name)) + def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: end_of_life = frappe.db.get_value("Item", item_code, "end_of_life") @@ -567,3 +528,4 @@ def invalidate_cache_for_item(doc): if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group: invalidate_cache_for(doc, doc.old_item_group) + diff --git a/erpnext/stock/doctype/manage_variants/__init__.py b/erpnext/stock/doctype/manage_variants/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js new file mode 100644 index 00000000000..f579897765d --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -0,0 +1,44 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.ui.form.on("Manage Variants", { + onload: function(frm) { + var df = frappe.meta.get_docfield("Variant Attribute", "attribute_value"); + df.on_make = function(field) { + field.$input.autocomplete({ + minLength: 0, + minChars: 0, + source: function(request, response) { + frappe.call({ + method:"frappe.client.get_list", + args:{ + doctype:"Variant Attribute", + filters: [ + ["parent","=", field.doc.attribute], + ["attribute_value", "like", request.term + "%"] + ], + fields: ["attribute_value"] + }, + callback: function(r) { + response($.map(r.message, function(d) { return d.attribute_value; })); + } + }); + }, + select: function(event, ui) { + field.$input.val(ui.item.value); + field.$input.trigger("change"); + }, + focus: function( event, ui ) { + if(ui.manage_variants.action) { + return false; + } + }, + }); + } + }, + + refresh: function(frm) { + frm.disable_save(); + } + +}); diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json new file mode 100644 index 00000000000..b00266a5cd6 --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -0,0 +1,101 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "creation": "2015-05-19 05:39:59.345901", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "fields": [ + { + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "permlevel": 0, + "precision": "" + }, + { + "allow_on_submit": 0, + "fieldname": "attributes", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Attributes", + "no_copy": 0, + "options": "Variant Attribute", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "fieldname": "generate_combinations", + "fieldtype": "Button", + "label": "Generate Combinations", + "options": "generate_combinations", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "variants", + "fieldtype": "Table", + "label": "Variants", + "options": "Variant Item", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "create_variants", + "fieldtype": "Button", + "label": "Create Variants", + "permlevel": 0, + "precision": "" + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 1, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 1, + "istable": 0, + "modified": "2015-05-20 18:00:48.331950", + "modified_by": "Administrator", + "module": "Stock", + "name": "Manage Variants", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 0, + "role": "Material Master Manager", + "share": 1, + "write": 1 + } + ], + "read_only": 1, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py new file mode 100644 index 00000000000..4169cae243f --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.model.document import Document +import copy + +class DuplicateAttribute(frappe.ValidationError): pass +class ItemTemplateCannotHaveStock(frappe.ValidationError): pass + +class ManageVariants(Document): + + def generate_combinations(self): + self.validate_attributes() + self.validate_template_item() + self.validate_stock_for_template_must_be_zero() + self.validate_attributes_are_unique() + self.get_variant_item_codes() + + def validate_attributes(self): + if not self.attributes: + frappe.throw("Enter atleast one Attribute & its Value in Attribute table.") + + def validate_template_item(self): + template_item = frappe.get_doc("Item", self.item) + if not template_item.has_variants: + frappe.throw(_("Selected Item cannot have Variants.")) + + if template_item.variant_of: + frappe.throw(_("Item cannot be a variant of a variant")) + + def validate_stock_for_template_must_be_zero(self): + stock_in = frappe.db.sql_list("""select warehouse from tabBin + where item_code=%s and ifnull(actual_qty, 0) > 0""", self.item) + if stock_in: + frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ + stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) + + def validate_attributes_are_unique(self): + attributes = [] + for d in self.attributes: + key = (d.attribute, d.attribute_value) + if key in attributes: + frappe.throw(_("{0} {1} is entered more than once in Attributes table") + .format(d.attribute, d.attribute_value), DuplicateAttribute) + attributes.append(key) + + def get_variant_item_codes(self): + """Get all possible suffixes for variants""" + variant_dict = {} + variant_item_codes = [] + + for d in self.attributes: + variant_dict.setdefault(d.attribute, []).append(d.attribute_value) + + all_attributes = [d.name for d in frappe.get_all("Item Attribute", order_by = "priority asc")] + + # sort attributes by their priority + attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes)) + + def add_attribute_suffixes(item_code, my_attributes, attributes): + attr = frappe.get_doc("Item Attribute", attributes[0]) + for value in attr.item_attribute_values: + if value.attribute_value in variant_dict[attr.name]: + _my_attributes = copy.deepcopy(my_attributes) + _my_attributes.append([attr.name, value.attribute_value]) + if len(attributes) > 1: + add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:]) + else: + variant_item_codes.append(item_code + "-" + value.abbr) + + add_attribute_suffixes(self.item, [], attributes) + + print variant_item_codes \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_attribute/__init__.py b/erpnext/stock/doctype/variant_attribute/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/variant_attribute/variant_attribute.json b/erpnext/stock/doctype/variant_attribute/variant_attribute.json new file mode 100644 index 00000000000..5ab3d73239e --- /dev/null +++ b/erpnext/stock/doctype/variant_attribute/variant_attribute.json @@ -0,0 +1,78 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "", + "creation": "2015-05-19 05:12:30.344797", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "attribute", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Attribute", + "no_copy": 0, + "options": "Item Attribute", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "allow_on_submit": 0, + "fieldname": "attribute_value", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Attribute Value", + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "", + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-05-20 06:16:16.803578", + "modified_by": "Administrator", + "module": "Stock", + "name": "Variant Attribute", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_attribute/variant_attribute.py b/erpnext/stock/doctype/variant_attribute/variant_attribute.py new file mode 100644 index 00000000000..9c35732bf2f --- /dev/null +++ b/erpnext/stock/doctype/variant_attribute/variant_attribute.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class VariantAttribute(Document): + pass diff --git a/erpnext/stock/doctype/variant_item/__init__.py b/erpnext/stock/doctype/variant_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json new file mode 100644 index 00000000000..8e1c8621e21 --- /dev/null +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -0,0 +1,90 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "", + "creation": "2015-05-19 05:55:31.155672", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Other", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "varient", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Variant", + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "item_code", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Item Code", + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "", + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "modified": "2015-05-20 18:20:10.555404", + "modified_by": "Administrator", + "module": "Stock", + "name": "Variant Item", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_item/variant_item.py b/erpnext/stock/doctype/variant_item/variant_item.py new file mode 100644 index 00000000000..4aa4ed81a47 --- /dev/null +++ b/erpnext/stock/doctype/variant_item/variant_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class VariantItem(Document): + pass From 333ccd212b4b92e06f19caa5204874e2907d455d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 21 May 2015 16:40:27 +0530 Subject: [PATCH 10/36] variants combination generation logic added --- .../manage_variants/manage_variants.json | 3 +- .../manage_variants/manage_variants.py | 38 +++++++++++------ .../doctype/variant_item/variant_item.json | 42 +------------------ 3 files changed, 30 insertions(+), 53 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index b00266a5cd6..f16910b7f93 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -62,6 +62,7 @@ "fieldname": "create_variants", "fieldtype": "Button", "label": "Create Variants", + "options": "create_variants", "permlevel": 0, "precision": "" } @@ -73,7 +74,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-20 18:00:48.331950", + "modified": "2015-05-21 16:21:33.707125", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 4169cae243f..a583619b62f 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -17,13 +17,14 @@ class ManageVariants(Document): self.validate_attributes() self.validate_template_item() self.validate_stock_for_template_must_be_zero() + self.validate_attribute_values() self.validate_attributes_are_unique() self.get_variant_item_codes() - + def validate_attributes(self): if not self.attributes: frappe.throw("Enter atleast one Attribute & its Value in Attribute table.") - + def validate_template_item(self): template_item = frappe.get_doc("Item", self.item) if not template_item.has_variants: @@ -31,7 +32,7 @@ class ManageVariants(Document): if template_item.variant_of: frappe.throw(_("Item cannot be a variant of a variant")) - + def validate_stock_for_template_must_be_zero(self): stock_in = frappe.db.sql_list("""select warehouse from tabBin where item_code=%s and ifnull(actual_qty, 0) > 0""", self.item) @@ -39,15 +40,24 @@ class ManageVariants(Document): frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) + def validate_attribute_values(self): + attributes = {} + for d in self.attributes: + attributes.setdefault(d.attribute, + [t.attribute_value for t in + frappe.db.get_all("Item Attribute Value", fields=["attribute_value"], filters={"parent": d.attribute })]) + if d.attribute_value not in attributes.get(d.attribute): + frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.attribute_value)) + def validate_attributes_are_unique(self): - attributes = [] - for d in self.attributes: - key = (d.attribute, d.attribute_value) - if key in attributes: - frappe.throw(_("{0} {1} is entered more than once in Attributes table") - .format(d.attribute, d.attribute_value), DuplicateAttribute) - attributes.append(key) - + attributes = [] + for d in self.attributes: + key = (d.attribute, d.attribute_value) + if key in attributes: + frappe.throw(_("{0} {1} is entered more than once in Attributes table") + .format(d.attribute, d.attribute_value), DuplicateAttribute) + attributes.append(key) + def get_variant_item_codes(self): """Get all possible suffixes for variants""" variant_dict = {} @@ -74,4 +84,8 @@ class ManageVariants(Document): add_attribute_suffixes(self.item, [], attributes) - print variant_item_codes \ No newline at end of file + for v in variant_item_codes: + self.append('variants', {"variant": v}) + + def create_variants(self): + pass \ No newline at end of file diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json index 8e1c8621e21..b4a4c0db59c 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -11,7 +11,7 @@ "fields": [ { "allow_on_submit": 0, - "fieldname": "varient", + "fieldname": "variant", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, @@ -23,44 +23,6 @@ "permlevel": 0, "precision": "", "print_hide": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0 - }, - { - "allow_on_submit": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0 - }, - { - "allow_on_submit": 0, - "fieldname": "item_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Item Code", - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, "read_only": 0, "report_hide": 0, "reqd": 1, @@ -76,7 +38,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-20 18:20:10.555404", + "modified": "2015-05-21 16:18:16.605271", "modified_by": "Administrator", "module": "Stock", "name": "Variant Item", From 8fb123b20e25f54c01816172ec4aee2ba9eb931d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 22 May 2015 17:00:58 +0530 Subject: [PATCH 11/36] item variants, creation, deleation and update logic added. logic added to copy changes in template to variants --- erpnext/stock/doctype/item/item.js | 9 +- erpnext/stock/doctype/item/item.json | 41 +++++- erpnext/stock/doctype/item/item.py | 123 +++--------------- .../manage_variants/manage_variants.js | 13 +- .../manage_variants/manage_variants.json | 5 +- .../manage_variants/manage_variants.py | 110 ++++++++++++++-- .../stock_ledger_entry/stock_ledger_entry.py | 2 +- .../doctype/variant_item/variant_item.json | 26 +++- 8 files changed, 204 insertions(+), 125 deletions(-) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 9db24f143a0..d2f1d7527c1 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -30,10 +30,6 @@ frappe.ui.form.on("Item", { frm.add_custom_button(__("Show Variants"), function() { frappe.set_route("List", "Item", {"variant_of": frm.doc.name}); }, "icon-list", "btn-default"); - frm.add_custom_button(__("Manage Variants"), function() { - frappe.route_options = {"item": frm.doc.name }; - new_doc("Manage Variants"); - }); } if (frm.doc.variant_of) { frm.set_intro(__("This Item is a Variant of {0} (Template). Attributes will be copied over from the template unless 'No Copy' is set", [frm.doc.variant_of]), true); @@ -87,6 +83,11 @@ frappe.ui.form.on("Item", { is_stock_item: function(frm) { erpnext.item.toggle_reqd(frm); + }, + + manage_variants: function(frm) { + frappe.route_options = {"item": frm.doc.name }; + frappe.set_route("List", "Manage Variants"); } }); diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 6ff8ac38675..9e7fb3dabec 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -12,7 +12,7 @@ { "fieldname": "name_and_description_section", "fieldtype": "Section Break", - "label": "Name and Description", + "label": "", "no_copy": 0, "oldfieldtype": "Section Break", "options": "icon-flag", @@ -167,16 +167,17 @@ "search_index": 0 }, { - "depends_on": "eval:!!!doc.variant_of", + "depends_on": "eval:!doc.variant_of", "fieldname": "variants_section", "fieldtype": "Section Break", - "label": "Variants", + "label": "Variant", "permlevel": 0, "precision": "" }, { "default": "0", - "description": "Automatically set. If this item has variants, then it cannot be selected in sales orders etc.", + "depends_on": "", + "description": "If this item has variants, then it cannot be selected in sales orders etc.", "fieldname": "has_variants", "fieldtype": "Check", "label": "Has Variants", @@ -185,6 +186,38 @@ "precision": "", "read_only": 0 }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "has_variants", + "fieldname": "manage_variants", + "fieldtype": "Button", + "label": "Manage Variants", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "section_break_20", + "fieldtype": "Section Break", + "permlevel": 0, + "precision": "" + }, + { + "depends_on": "variant_of", + "fieldname": "attributes", + "fieldtype": "Table", + "hidden": 0, + "label": "Attributes", + "no_copy": 1, + "options": "Variant Attribute", + "permlevel": 0, + "precision": "", + "read_only": 0 + }, { "fieldname": "inventory", "fieldtype": "Section Break", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 77bcb4d668f..c907993d653 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -9,7 +9,7 @@ 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 from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow -import copy +from erpnext.stock.doctype.manage_variants.manage_variants import update_variant class WarehouseNotSet(frappe.ValidationError): pass @@ -46,9 +46,6 @@ class Item(WebsiteGenerator): if self.image and not self.website_image: self.website_image = self.image - if self.variant_of: - self.copy_attributes_to_variant(frappe.get_doc("Item", self.variant_of), self) - self.check_warehouse_is_set_for_stock_item() self.check_stock_uom_with_bin() self.add_default_uom_in_conversion_factor_table() @@ -63,6 +60,7 @@ class Item(WebsiteGenerator): self.validate_warehouse_for_reorder() self.update_item_desc() self.synced_with_hub = 0 + self.validate_has_variants() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -74,6 +72,7 @@ class Item(WebsiteGenerator): invalidate_cache_for_item(self) self.validate_name_with_item_group() self.update_item_price() + self.update_variants() def get_context(self, context): context["parent_groups"] = get_parent_item_groups(self.item_group) + \ @@ -129,105 +128,6 @@ class Item(WebsiteGenerator): if not matched: frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.")) - def sync_variants(self): - variant_item_codes = self.get_variant_item_codes() - - # delete missing variants - existing_variants = [d.name for d in frappe.get_all("Item", - filters={"variant_of":self.name})] - - updated, deleted = [], [] - for existing_variant in existing_variants: - if existing_variant not in variant_item_codes: - frappe.delete_doc("Item", existing_variant) - deleted.append(existing_variant) - else: - self.update_variant(existing_variant) - updated.append(existing_variant) - - inserted = [] - for item_code in variant_item_codes: - if item_code not in existing_variants: - self.make_variant(item_code) - inserted.append(item_code) - - if inserted: - frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted))) - - if updated: - frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) - - if deleted: - frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) - - def get_variant_item_codes(self): - """Get all possible suffixes for variants""" - if not self.variants: - return [] - - self.variant_attributes = {} - variant_dict = {} - variant_item_codes = [] - - for d in self.variants: - variant_dict.setdefault(d.item_attribute, []).append(d.item_attribute_value) - - all_attributes = [d.name for d in frappe.get_all("Item Attribute", order_by = "priority asc")] - - # sort attributes by their priority - attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes)) - - def add_attribute_suffixes(item_code, my_attributes, attributes): - attr = frappe.get_doc("Item Attribute", attributes[0]) - for value in attr.item_attribute_values: - if value.attribute_value in variant_dict[attr.name]: - _my_attributes = copy.deepcopy(my_attributes) - _my_attributes.append([attr.name, value.attribute_value]) - if len(attributes) > 1: - add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:]) - else: - variant_item_codes.append(item_code + "-" + value.abbr) - self.variant_attributes[item_code + "-" + value.abbr] = _my_attributes - - add_attribute_suffixes(self.name, [], attributes) - - return variant_item_codes - - def make_variant(self, item_code): - item = frappe.new_doc("Item") - item.item_code = item_code - self.copy_attributes_to_variant(self, item, insert=True) - item.insert() - - def update_variant(self, item_code): - item = frappe.get_doc("Item", item_code) - item.item_code = item_code - self.copy_attributes_to_variant(self, item) - item.save() - - def copy_attributes_to_variant(self, template, variant, insert=False): - from frappe.model import no_value_fields - for field in self.meta.fields: - if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\ - and field.fieldname not in ("item_code", "item_name"): - if variant.get(field.fieldname) != template.get(field.fieldname): - variant.set(field.fieldname, template.get(field.fieldname)) - variant.__dirty = True - - variant.description += "\n" - - if not getattr(template, "variant_attributes", None): - template.get_variant_item_codes() - - for attr in template.variant_attributes[variant.item_code]: - variant.description += "

" + attr[0] + ": " + attr[1] + "

" - - variant.item_name = self.item_name + variant.item_code[len(self.name):] - - variant.variant_of = template.name - variant.has_variants = 0 - variant.show_in_website = 0 - def update_template_tables(self): template = frappe.get_doc("Item", self.variant_of) @@ -320,7 +220,8 @@ class Item(WebsiteGenerator): vals.has_batch_no != self.has_batch_no or cstr(vals.valuation_method) != cstr(self.valuation_method)): if self.check_if_sle_exists() == "exists": - frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'")) + frappe.throw(_("As there are existing stock transactions for this item, \ + you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'")) def validate_reorder_level(self): if cint(self.apply_warehouse_wise_reorder_level): @@ -423,6 +324,20 @@ class Item(WebsiteGenerator): item_code = %s and docstatus < 2""",(self.description, self.name)) frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name)) + + def update_variants(self): + if self.has_variants: + updated = [] + variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name }) + for d in variants: + update_variant(self.item_code, d) + updated.append(d.item_code) + frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) + + def validate_has_variants(self): + if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"): + if frappe.db.exists("Item", {"variant_of": self.name}): + frappe.throw("Item has variants.") def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index f579897765d..992e2a9df1a 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -12,7 +12,7 @@ frappe.ui.form.on("Manage Variants", { frappe.call({ method:"frappe.client.get_list", args:{ - doctype:"Variant Attribute", + doctype:"Item Attribute Value", filters: [ ["parent","=", field.doc.attribute], ["attribute_value", "like", request.term + "%"] @@ -39,6 +39,17 @@ frappe.ui.form.on("Manage Variants", { refresh: function(frm) { frm.disable_save(); + }, + + item:function(frm) { + return frappe.call({ + method: "get_item_details", + doc:frm.doc, + callback: function(r) { + refresh_field('attributes'); + refresh_field('variants'); + } + }) } }); diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index f16910b7f93..bcad045c075 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -14,7 +14,8 @@ "label": "Item", "options": "Item", "permlevel": 0, - "precision": "" + "precision": "", + "reqd": 1 }, { "allow_on_submit": 0, @@ -74,7 +75,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-21 16:21:33.707125", + "modified": "2015-05-27 04:43:52.051367", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index a583619b62f..8b96ff9dd27 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -7,12 +7,17 @@ import frappe from frappe import _ from frappe.model.document import Document import copy +import json class DuplicateAttribute(frappe.ValidationError): pass class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class ManageVariants(Document): - + + def get_item_details(self): + self.get_attributes() + self.get_variants() + def generate_combinations(self): self.validate_attributes() self.validate_template_item() @@ -20,6 +25,31 @@ class ManageVariants(Document): self.validate_attribute_values() self.validate_attributes_are_unique() self.get_variant_item_codes() + + def create_variants(self): + self.sync_variants() + + def get_attributes(self): + attributes = {} + self.set('attributes', []) + for d in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` as attribute, + `tabItem` as item where attribute.parent= item.name and item.variant_of = %s""", self.item, as_dict=1): + attributes.setdefault(d.attribute, []).append(d.attribute_value) + for d in attributes: + attribute_values = set(attributes[d]) + for value in attribute_values: + self.append('attributes',{"attribute": d, "attribute_value": value}) + + def get_variants(self): + self.set('variants', []) + variants = [d.name for d in frappe.get_all("Item", + filters={"variant_of":self.item})] + for d in variants: + variant_attributes, attributes = "", [] + for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): + variant_attributes += attribute[1] + " " + attributes.append([attribute[0], attribute[1]]) + self.append('variants',{"variant": d, "variant_attributes": variant_attributes, "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: @@ -61,7 +91,7 @@ class ManageVariants(Document): def get_variant_item_codes(self): """Get all possible suffixes for variants""" variant_dict = {} - variant_item_codes = [] + self.set('variants', []) for d in self.attributes: variant_dict.setdefault(d.attribute, []).append(d.attribute_value) @@ -80,12 +110,76 @@ class ManageVariants(Document): if len(attributes) > 1: add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:]) else: - variant_item_codes.append(item_code + "-" + value.abbr) + variant_attributes = "" + for d in _my_attributes: + variant_attributes += d[1] + " " + self.append('variants', {"variant": item_code + "-" + value.abbr, + "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes}) add_attribute_suffixes(self.item, [], attributes) - for v in variant_item_codes: - self.append('variants', {"variant": v}) - - def create_variants(self): - pass \ No newline at end of file + def sync_variants(self): + variant_item_codes = [] + for v in self.variants: + variant_item_codes.append(v.variant) + + existing_variants = [d.name for d in frappe.get_all("Item", + filters={"variant_of":self.item})] + + inserted, updated, deleted = [], [], [] + for existing_variant in existing_variants: + if existing_variant not in variant_item_codes: + frappe.delete_doc("Item", existing_variant) + deleted.append(existing_variant) + + for item_code in variant_item_codes: + if item_code not in existing_variants: + make_variant(self.item, item_code, self.variants) + inserted.append(item_code) + else: + update_variant(self.item, existing_variant, self.variants) + updated.append(existing_variant) + + if inserted: + frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted))) + + if updated: + frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) + + if deleted: + frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) + +def make_variant(item, variant_code, variant_attribute): + variant = frappe.new_doc("Item") + variant.item_code = variant_code + template = frappe.get_doc("Item", item) + copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + variant.insert() + +def update_variant(item, variant_code, variant_attribute=None): + variant = frappe.get_doc("Item", variant_code) + template = frappe.get_doc("Item", item) + copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + variant.save() + +def copy_attributes_to_variant(template, variant, variant_attribute=None, insert=False): + from frappe.model import no_value_fields + for field in template.meta.fields: + if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\ + and field.fieldname not in ("item_code", "item_name"): + if variant.get(field.fieldname) != template.get(field.fieldname): + variant.set(field.fieldname, template.get(field.fieldname)) + variant.item_name = template.item_name + variant.item_code[len(template.name):] + variant.variant_of = template.name + variant.has_variants = 0 + variant.show_in_website = 0 + if variant_attribute: + for d in variant_attribute: + if d.variant == variant.item_code: + variant.attributes= [] + for a in json.loads(d.attributes): + variant.append('attributes', {"attribute": a[0], "attribute_value": a[1]}) + if variant.attributes: + variant.description += "\n" + for d in variant.attributes: + variant.description += "

" + d.attribute + ": " + d.attribute_value + "

" \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index de6b3a6950f..74fd4d11407 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.utils import flt, getdate, add_days, formatdate from frappe.model.document import Document from datetime import date -from erpnext.stock.doctype.item.item import ItemTemplateCannotHaveStock +from erpnext.stock.doctype.manage_variants.manage_variants import ItemTemplateCannotHaveStock class StockFreezeError(frappe.ValidationError): pass diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json index b4a4c0db59c..9f104827208 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -28,6 +28,30 @@ "reqd": 1, "search_index": 0, "set_only_once": 0 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "variant_attributes", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Variant Attributes", + "permlevel": 0, + "precision": "", + "read_only": 1 + }, + { + "fieldname": "attributes", + "fieldtype": "Text", + "hidden": 1, + "label": "attributes", + "permlevel": 0, + "precision": "", + "read_only": 1 } ], "hide_heading": 0, @@ -38,7 +62,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-21 16:18:16.605271", + "modified": "2015-05-28 04:58:20.495616", "modified_by": "Administrator", "module": "Stock", "name": "Variant Item", From bd9745ba729235416b5da8616a447ba0da123328 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Fri, 29 May 2015 17:15:55 +0530 Subject: [PATCH 12/36] Rename Item Variant from Manage Variants feature Added. --- .../manage_variants/manage_variants.js | 9 ++- .../manage_variants/manage_variants.json | 10 +-- .../manage_variants/manage_variants.py | 61 ++++++++++++++----- .../doctype/variant_item/variant_item.json | 4 +- 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 992e2a9df1a..5388cc2a924 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -39,8 +39,14 @@ frappe.ui.form.on("Manage Variants", { refresh: function(frm) { frm.disable_save(); + frm.page.set_primary_action(__("Create Variants"), function() { + frappe.call({ + method: "create_variants", + doc:frm.doc + }) + }); }, - + item:function(frm) { return frappe.call({ method: "get_item_details", @@ -51,5 +57,4 @@ frappe.ui.form.on("Manage Variants", { } }) } - }); diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index bcad045c075..1ad938d05be 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -58,14 +58,6 @@ "options": "Variant Item", "permlevel": 0, "precision": "" - }, - { - "fieldname": "create_variants", - "fieldtype": "Button", - "label": "Create Variants", - "options": "create_variants", - "permlevel": 0, - "precision": "" } ], "hide_heading": 0, @@ -75,7 +67,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-27 04:43:52.051367", + "modified": "2015-05-28 06:18:03.238411", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 8b96ff9dd27..834b9ef9d93 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -15,8 +15,10 @@ class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class ManageVariants(Document): def get_item_details(self): - self.get_attributes() - self.get_variants() + self.clear_tables() + if self.item: + self.get_attributes() + self.get_variants() def generate_combinations(self): self.validate_attributes() @@ -29,6 +31,10 @@ class ManageVariants(Document): def create_variants(self): self.sync_variants() + def clear_tables(self): + self.set('attributes', []) + self.set('variants', []) + def get_attributes(self): attributes = {} self.set('attributes', []) @@ -41,15 +47,14 @@ class ManageVariants(Document): self.append('attributes',{"attribute": d, "attribute_value": value}) def get_variants(self): - self.set('variants', []) variants = [d.name for d in frappe.get_all("Item", filters={"variant_of":self.item})] for d in variants: variant_attributes, attributes = "", [] for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): - variant_attributes += attribute[1] + " " + variant_attributes += attribute[1] + " | " attributes.append([attribute[0], attribute[1]]) - self.append('variants',{"variant": d, "variant_attributes": variant_attributes, "attributes": json.dumps(attributes)}) + self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -2], "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: @@ -112,33 +117,50 @@ class ManageVariants(Document): else: variant_attributes = "" for d in _my_attributes: - variant_attributes += d[1] + " " + variant_attributes += d[1] + " | " self.append('variants', {"variant": item_code + "-" + value.abbr, - "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes}) - + "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -2]}) add_attribute_suffixes(self.item, [], attributes) def sync_variants(self): variant_item_codes = [] + item_variants_attributes = {} + inserted, updated, renamed_old, renamed_new, deleted = [], [], [], [], [] + for v in self.variants: variant_item_codes.append(v.variant) existing_variants = [d.name for d in frappe.get_all("Item", filters={"variant_of":self.item})] + + for d in existing_variants: + attributes = [] + for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): + attributes.append([attribute[0], attribute[1]]) + item_variants_attributes.setdefault(d, []).append(attributes) - inserted, updated, deleted = [], [], [] for existing_variant in existing_variants: if existing_variant not in variant_item_codes: - frappe.delete_doc("Item", existing_variant) - deleted.append(existing_variant) + att = item_variants_attributes[existing_variant][0] + for variant in self.variants: + if sorted(json.loads(variant.attributes) ,key=lambda x: x[0]) == \ + sorted(att ,key=lambda x: x[0]): + rename_variant(existing_variant, variant.variant) + renamed_old.append(existing_variant) + renamed_new.append(variant.variant) + + if existing_variant not in renamed_old: + delete_variant(existing_variant) + deleted.append(existing_variant) for item_code in variant_item_codes: if item_code not in existing_variants: - make_variant(self.item, item_code, self.variants) - inserted.append(item_code) + if item_code not in renamed_new: + make_variant(self.item, item_code, self.variants) + inserted.append(item_code) else: - update_variant(self.item, existing_variant, self.variants) - updated.append(existing_variant) + update_variant(self.item, item_code, self.variants) + updated.append(item_code) if inserted: frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted))) @@ -146,6 +168,9 @@ class ManageVariants(Document): if updated: frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) + if renamed_old: + frappe.msgprint(_("Item Variants {0} renamed").format(", ".join(renamed_old))) + if deleted: frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) @@ -162,6 +187,12 @@ def update_variant(item, variant_code, variant_attribute=None): copy_attributes_to_variant(template, variant, variant_attribute, insert=True) variant.save() +def rename_variant(old_variant_code, new_variant_code): + frappe.rename_doc("Item", old_variant_code, new_variant_code) + +def delete_variant(variant_code): + frappe.delete_doc("Item", variant_code) + def copy_attributes_to_variant(template, variant, variant_attribute=None, insert=False): from frappe.model import no_value_fields for field in template.meta.fields: diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/variant_item/variant_item.json index 9f104827208..13c46ac0f77 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/variant_item/variant_item.json @@ -47,7 +47,7 @@ { "fieldname": "attributes", "fieldtype": "Text", - "hidden": 1, + "hidden": 0, "label": "attributes", "permlevel": 0, "precision": "", @@ -62,7 +62,7 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-28 04:58:20.495616", + "modified": "2015-05-29 02:09:33.622631", "modified_by": "Administrator", "module": "Stock", "name": "Variant Item", From fed431f9085c8bb1725a7a7387f9dde64fe5d94b Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 1 Jun 2015 17:05:37 +0530 Subject: [PATCH 13/36] Code Fixes in Manage Variants --- .../doctype/manage_variants/manage_variants.js | 7 +------ .../doctype/manage_variants/manage_variants.py | 14 +++++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 5388cc2a924..9e6fff78b8d 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -27,12 +27,7 @@ frappe.ui.form.on("Manage Variants", { select: function(event, ui) { field.$input.val(ui.item.value); field.$input.trigger("change"); - }, - focus: function( event, ui ) { - if(ui.manage_variants.action) { - return false; - } - }, + } }); } }, diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 834b9ef9d93..913c2d8136a 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -125,7 +125,7 @@ class ManageVariants(Document): def sync_variants(self): variant_item_codes = [] item_variants_attributes = {} - inserted, updated, renamed_old, renamed_new, deleted = [], [], [], [], [] + inserted, updated, old_variant_name, new_variant_name, deleted = [], [], [], [], [] for v in self.variants: variant_item_codes.append(v.variant) @@ -146,16 +146,16 @@ class ManageVariants(Document): if sorted(json.loads(variant.attributes) ,key=lambda x: x[0]) == \ sorted(att ,key=lambda x: x[0]): rename_variant(existing_variant, variant.variant) - renamed_old.append(existing_variant) - renamed_new.append(variant.variant) + old_variant_name.append(existing_variant) + new_variant_name.append(variant.variant) - if existing_variant not in renamed_old: + if existing_variant not in old_variant_name: delete_variant(existing_variant) deleted.append(existing_variant) for item_code in variant_item_codes: if item_code not in existing_variants: - if item_code not in renamed_new: + if item_code not in new_variant_name: make_variant(self.item, item_code, self.variants) inserted.append(item_code) else: @@ -168,8 +168,8 @@ class ManageVariants(Document): if updated: frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) - if renamed_old: - frappe.msgprint(_("Item Variants {0} renamed").format(", ".join(renamed_old))) + if old_variant_name: + frappe.msgprint(_("Item Variants {0} renamed").format(", ".join(old_variant_name))) if deleted: frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted))) From 90c66b19986091db954eaa5a0f7b0baa18bb1161 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 4 Jun 2015 15:42:38 +0530 Subject: [PATCH 14/36] test cases added --- .../projects/doctype/project/test_project.py | 1 + erpnext/stock/doctype/item/item.py | 10 ++++ erpnext/stock/doctype/item/test_item.py | 60 +++---------------- erpnext/stock/doctype/item/test_records.json | 5 -- .../manage_variants/manage_variants.py | 15 +---- .../manage_variants/test_manage_variants.py | 48 +++++++++++++++ .../stock_ledger_entry/stock_ledger_entry.py | 2 +- 7 files changed, 70 insertions(+), 71 deletions(-) create mode 100644 erpnext/stock/doctype/manage_variants/test_manage_variants.py diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index f69ce808248..f1be54492f5 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -5,3 +5,4 @@ from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Project') +test_ignore = ["Sales Order"] diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index c907993d653..22e6b212f59 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -12,6 +12,7 @@ from frappe.website.doctype.website_slideshow.website_slideshow import get_slide from erpnext.stock.doctype.manage_variants.manage_variants import update_variant class WarehouseNotSet(frappe.ValidationError): pass +class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class Item(WebsiteGenerator): website = frappe._dict( @@ -61,6 +62,7 @@ class Item(WebsiteGenerator): self.update_item_desc() self.synced_with_hub = 0 self.validate_has_variants() + self.validate_stock_for_template_must_be_zero() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -338,6 +340,14 @@ class Item(WebsiteGenerator): if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"): if frappe.db.exists("Item", {"variant_of": self.name}): frappe.throw("Item has variants.") + + def validate_stock_for_template_must_be_zero(self): + if self.has_variants: + stock_in = frappe.db.sql_list("""select warehouse from tabBin + where item_code=%s and ifnull(actual_qty, 0) > 0""", self.name) + if stock_in: + frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ + stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 02ff714bdaf..9cf3c077b41 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -6,7 +6,7 @@ import unittest import frappe from frappe.test_runner import make_test_records -from erpnext.stock.doctype.item.item import WarehouseNotSet, DuplicateVariant, ItemTemplateCannotHaveStock +from erpnext.stock.doctype.item.item import WarehouseNotSet, ItemTemplateCannotHaveStock from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry test_ignore = ["BOM"] @@ -20,60 +20,14 @@ class TestItem(unittest.TestCase): item.insert() else: item = frappe.get_doc("Item", item_code) - return item - - def test_duplicate_variant(self): - item = frappe.copy_doc(test_records[11]) - item.append("variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"}) - self.assertRaises(DuplicateVariant, item.insert) - + def test_template_cannot_have_stock(self): - item = self.get_item(10) - - se = make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, incoming_rate=1) - - item.has_variants = 1 - item.append("variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"}) - - self.assertRaises(ItemTemplateCannotHaveStock, item.save) - - def test_variant_item_codes(self): - item = self.get_item(11) - - variants = ['_Test Variant Item-S', '_Test Variant Item-M', '_Test Variant Item-L'] - self.assertEqual(item.get_variant_item_codes(), variants) - for v in variants: - self.assertTrue(frappe.db.get_value("Item", {"variant_of": item.name, "name": v})) - - item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Red"}) - item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Blue"}) - item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Green"}) - - self.assertEqual(item.get_variant_item_codes(), ['_Test Variant Item-S-R', - '_Test Variant Item-S-G', '_Test Variant Item-S-B', - '_Test Variant Item-M-R', '_Test Variant Item-M-G', - '_Test Variant Item-M-B', '_Test Variant Item-L-R', - '_Test Variant Item-L-G', '_Test Variant Item-L-B']) - - self.assertEqual(item.variant_attributes['_Test Variant Item-L-R'], [['Test Size', 'Large'], ['Test Colour', 'Red']]) - self.assertEqual(item.variant_attributes['_Test Variant Item-S-G'], [['Test Size', 'Small'], ['Test Colour', 'Green']]) - - # check stock entry cannot be made - def test_stock_entry_cannot_be_made_for_template(self): - item = self.get_item(11) - - se = frappe.new_doc("Stock Entry") - se.purpose = "Material Receipt" - se.append("items", { - "item_code": item.name, - "t_warehouse": "Stores - _TC", - "qty": 1, - "incoming_rate": 1 - }) - se.insert() - self.assertRaises(ItemTemplateCannotHaveStock, se.submit) - + item = self.get_item(10) + se = make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, incoming_rate=1) + item.has_variants = 1 + self.assertRaises(ItemTemplateCannotHaveStock, item.save) + def test_default_warehouse(self): item = frappe.copy_doc(test_records[0]) item.is_stock_item = "Yes" diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json index dc095c6b14b..5a02c6bde56 100644 --- a/erpnext/stock/doctype/item/test_records.json +++ b/erpnext/stock/doctype/item/test_records.json @@ -273,11 +273,6 @@ "item_name": "_Test Variant Item", "stock_uom": "_Test UOM", "has_variants": 1, - "variants": [ - {"item_attribute": "Test Size", "item_attribute_value": "Small"}, - {"item_attribute": "Test Size", "item_attribute_value": "Medium"}, - {"item_attribute": "Test Size", "item_attribute_value": "Large"} - ], "apply_warehouse_wise_reorder_level": 1, "reorder_levels": [ { diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 913c2d8136a..8b1a13b0a17 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -10,7 +10,6 @@ import copy import json class DuplicateAttribute(frappe.ValidationError): pass -class ItemTemplateCannotHaveStock(frappe.ValidationError): pass class ManageVariants(Document): @@ -23,7 +22,6 @@ class ManageVariants(Document): def generate_combinations(self): self.validate_attributes() self.validate_template_item() - self.validate_stock_for_template_must_be_zero() self.validate_attribute_values() self.validate_attributes_are_unique() self.get_variant_item_codes() @@ -54,7 +52,7 @@ class ManageVariants(Document): for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): variant_attributes += attribute[1] + " | " attributes.append([attribute[0], attribute[1]]) - self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -2], "attributes": json.dumps(attributes)}) + self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -3], "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: @@ -64,17 +62,10 @@ class ManageVariants(Document): template_item = frappe.get_doc("Item", self.item) if not template_item.has_variants: frappe.throw(_("Selected Item cannot have Variants.")) - + if template_item.variant_of: frappe.throw(_("Item cannot be a variant of a variant")) - def validate_stock_for_template_must_be_zero(self): - stock_in = frappe.db.sql_list("""select warehouse from tabBin - where item_code=%s and ifnull(actual_qty, 0) > 0""", self.item) - if stock_in: - frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ - stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) - def validate_attribute_values(self): attributes = {} for d in self.attributes: @@ -119,7 +110,7 @@ class ManageVariants(Document): for d in _my_attributes: variant_attributes += d[1] + " | " self.append('variants', {"variant": item_code + "-" + value.abbr, - "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -2]}) + "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -3]}) add_attribute_suffixes(self.item, [], attributes) def sync_variants(self): diff --git a/erpnext/stock/doctype/manage_variants/test_manage_variants.py b/erpnext/stock/doctype/manage_variants/test_manage_variants.py new file mode 100644 index 00000000000..e3624a29ad7 --- /dev/null +++ b/erpnext/stock/doctype/manage_variants/test_manage_variants.py @@ -0,0 +1,48 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import unittest +import frappe + +from erpnext.stock.doctype.manage_variants.manage_variants import DuplicateAttribute + +class TestManageVariants(unittest.TestCase): + def test_variant_item_codes(self): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.update({ + "item": "_Test Variant Item", + "attributes": [ + { + "attribute": "Test Size", + "attribute_value": "Small" + }, + { + "attribute": "Test Size", + "attribute_value": "Large" + } + ] + }) + manage_variant.generate_combinations() + self.assertEqual(manage_variant.variants[0].variant, "_Test Variant Item-S") + self.assertEqual(manage_variant.variants[1].variant, "_Test Variant Item-L") + + self.assertEqual(manage_variant.variants[0].variant_attributes, "Small") + self.assertEqual(manage_variant.variants[1].variant_attributes, "Large") + + def test_attributes_are_unique(self): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.update({ + "item": "_Test Variant Item", + "attributes": [ + { + "attribute": "Test Size", + "attribute_value": "Small" + }, + { + "attribute": "Test Size", + "attribute_value": "Small" + } + ] + }) + self.assertRaises(DuplicateAttribute, manage_variant.generate_combinations) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 74fd4d11407..de6b3a6950f 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -8,7 +8,7 @@ from frappe import _ from frappe.utils import flt, getdate, add_days, formatdate from frappe.model.document import Document from datetime import date -from erpnext.stock.doctype.manage_variants.manage_variants import ItemTemplateCannotHaveStock +from erpnext.stock.doctype.item.item import ItemTemplateCannotHaveStock class StockFreezeError(frappe.ValidationError): pass From 203cb10ef759ceb9ba4a255ffe036c4beaaebb70 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 4 Jun 2015 17:07:04 +0530 Subject: [PATCH 15/36] fixes in test cases --- erpnext/stock/doctype/manage_variants/test_manage_variants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/manage_variants/test_manage_variants.py b/erpnext/stock/doctype/manage_variants/test_manage_variants.py index e3624a29ad7..9952ba9a2b4 100644 --- a/erpnext/stock/doctype/manage_variants/test_manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/test_manage_variants.py @@ -29,6 +29,7 @@ class TestManageVariants(unittest.TestCase): self.assertEqual(manage_variant.variants[0].variant_attributes, "Small") self.assertEqual(manage_variant.variants[1].variant_attributes, "Large") + manage_variant.create_variants() def test_attributes_are_unique(self): manage_variant = frappe.new_doc("Manage Variants") From c761fefb780d1b00412cb2db0603ec7855ea6b76 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 4 Jun 2015 17:37:26 +0530 Subject: [PATCH 16/36] more fixes in test records --- .../stock/doctype/stock_entry/test_stock_entry.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index ff0b272fd52..8114befa2f7 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -72,6 +72,18 @@ class TestStockEntry(unittest.TestCase): self._test_auto_material_request("_Test Item") def test_auto_material_request_for_variant(self): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.update({ + "item": "_Test Variant Item", + "attributes": [ + { + "attribute": "Test Size", + "attribute_value": "Small" + } + ] + }) + manage_variant.generate_combinations() + manage_variant.create_variants() self._test_auto_material_request("_Test Variant Item-S") def _test_auto_material_request(self, item_code): From 3f2604eff69c29c4bcb28a409821f4ea3948a797 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Mon, 29 Jun 2015 15:06:46 +0530 Subject: [PATCH 17/36] Patch for Item Variants --- erpnext/patches.txt | 2 +- erpnext/patches/v5_0/item_variants.py | 11 +++++++++++ erpnext/stock/doctype/item/item.json | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v5_0/item_variants.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0f32a6dbf68..80965ece06b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -168,4 +168,4 @@ execute:frappe.delete_doc("Page", "users") erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again erpnext.patches.v5_0.index_on_account_and_gl_entry execute:frappe.db.sql("""delete from `tabProject Task`""") - +erpnext.patches.v5_0.item_variants diff --git a/erpnext/patches/v5_0/item_variants.py b/erpnext/patches/v5_0/item_variants.py new file mode 100644 index 00000000000..fd4e4bbe0c5 --- /dev/null +++ b/erpnext/patches/v5_0/item_variants.py @@ -0,0 +1,11 @@ +import frappe + +def execute(): + frappe.reload_doctype("Item") + for d in frappe.get_list("Item", filters={"has_variants":1}): + manage_variant = frappe.new_doc("Manage Variants") + manage_variant.item = d.name + manage_variant.attributes = frappe.db.sql("select item_attribute as attribute, item_attribute_value as attribute_value \ + from `tabItem Variant` where parent = %s", d.name, as_dict=1) + manage_variant.generate_combinations() + manage_variant.create_variants() \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 9e7fb3dabec..a43b70467d0 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -902,7 +902,7 @@ "icon": "icon-tag", "idx": 1, "max_attachments": 1, - "modified": "2015-06-26 17:20:18.204558", + "modified": "2015-06-29 17:20:18.204558", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 724fd824192ce2c7ccbc4dec17efa14d846d6763 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 30 Jun 2015 12:52:39 +0530 Subject: [PATCH 18/36] Fixes in Manage Variants --- erpnext/patches/v5_0/item_variants.py | 3 ++- .../doctype/manage_variants/manage_variants.json | 12 ++++++++++-- .../__init__.py | 0 .../manage_variants_item.json} | 6 +++--- .../manage_variants_item.py} | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) rename erpnext/stock/doctype/{variant_item => manage_variants_item}/__init__.py (100%) rename erpnext/stock/doctype/{variant_item/variant_item.json => manage_variants_item/manage_variants_item.json} (93%) rename erpnext/stock/doctype/{variant_item/variant_item.py => manage_variants_item/manage_variants_item.py} (88%) diff --git a/erpnext/patches/v5_0/item_variants.py b/erpnext/patches/v5_0/item_variants.py index fd4e4bbe0c5..6e4db194324 100644 --- a/erpnext/patches/v5_0/item_variants.py +++ b/erpnext/patches/v5_0/item_variants.py @@ -8,4 +8,5 @@ def execute(): manage_variant.attributes = frappe.db.sql("select item_attribute as attribute, item_attribute_value as attribute_value \ from `tabItem Variant` where parent = %s", d.name, as_dict=1) manage_variant.generate_combinations() - manage_variant.create_variants() \ No newline at end of file + manage_variant.create_variants() + frappe.delete_doc("doctype", "Item Variant") \ No newline at end of file diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index 1ad938d05be..055fc274423 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -17,6 +17,13 @@ "precision": "", "reqd": 1 }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Item Variant Attributes", + "permlevel": 0, + "precision": "" + }, { "allow_on_submit": 0, "fieldname": "attributes", @@ -48,6 +55,7 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break", + "label": "Item Variants", "permlevel": 0, "precision": "" }, @@ -55,7 +63,7 @@ "fieldname": "variants", "fieldtype": "Table", "label": "Variants", - "options": "Variant Item", + "options": "Manage Variants Item", "permlevel": 0, "precision": "" } @@ -67,7 +75,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-05-28 06:18:03.238411", + "modified": "2015-06-30 03:18:13.787883", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/variant_item/__init__.py b/erpnext/stock/doctype/manage_variants_item/__init__.py similarity index 100% rename from erpnext/stock/doctype/variant_item/__init__.py rename to erpnext/stock/doctype/manage_variants_item/__init__.py diff --git a/erpnext/stock/doctype/variant_item/variant_item.json b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.json similarity index 93% rename from erpnext/stock/doctype/variant_item/variant_item.json rename to erpnext/stock/doctype/manage_variants_item/manage_variants_item.json index 13c46ac0f77..a8bb61d5e48 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.json +++ b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.json @@ -47,7 +47,7 @@ { "fieldname": "attributes", "fieldtype": "Text", - "hidden": 0, + "hidden": 1, "label": "attributes", "permlevel": 0, "precision": "", @@ -62,10 +62,10 @@ "is_submittable": 0, "issingle": 0, "istable": 1, - "modified": "2015-05-29 02:09:33.622631", + "modified": "2015-06-30 03:19:07.548196", "modified_by": "Administrator", "module": "Stock", - "name": "Variant Item", + "name": "Manage Variants Item", "name_case": "", "owner": "Administrator", "permissions": [], diff --git a/erpnext/stock/doctype/variant_item/variant_item.py b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.py similarity index 88% rename from erpnext/stock/doctype/variant_item/variant_item.py rename to erpnext/stock/doctype/manage_variants_item/manage_variants_item.py index 4aa4ed81a47..800888a864d 100644 --- a/erpnext/stock/doctype/variant_item/variant_item.py +++ b/erpnext/stock/doctype/manage_variants_item/manage_variants_item.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -class VariantItem(Document): +class ManageVariantsItem(Document): pass From 274dd4ada05248c94d06dbe84cb1a8dbc0d4404d Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Tue, 30 Jun 2015 13:09:43 +0530 Subject: [PATCH 19/36] Fixed Manage Variants Attribute autocomplete appearance. --- erpnext/stock/doctype/manage_variants/manage_variants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 9e6fff78b8d..82a3da7f4d9 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -5,6 +5,7 @@ frappe.ui.form.on("Manage Variants", { onload: function(frm) { var df = frappe.meta.get_docfield("Variant Attribute", "attribute_value"); df.on_make = function(field) { + $(field.input_area).addClass("ui-front"); field.$input.autocomplete({ minLength: 0, minChars: 0, From 0988440fd979c01c5ac6fab774c576af58ea0fbe Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 1 Jul 2015 12:50:52 +0530 Subject: [PATCH 20/36] Fixes for Manage Variants --- erpnext/patches/v5_0/item_variants.py | 12 ++++-- erpnext/stock/doctype/item/item.js | 2 +- erpnext/stock/doctype/item/item.py | 4 +- .../manage_variants/manage_variants.js | 2 +- .../manage_variants/manage_variants.json | 6 +-- .../manage_variants/manage_variants.py | 38 +++++++++---------- 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/erpnext/patches/v5_0/item_variants.py b/erpnext/patches/v5_0/item_variants.py index 6e4db194324..62e9ac9e1f0 100644 --- a/erpnext/patches/v5_0/item_variants.py +++ b/erpnext/patches/v5_0/item_variants.py @@ -2,11 +2,15 @@ import frappe def execute(): frappe.reload_doctype("Item") + for dt in ["manage_variants", "manage_variants_item", "variant_attribute"]: + frappe.reload_doc("stock", "doctype", dt) + for d in frappe.get_list("Item", filters={"has_variants":1}): manage_variant = frappe.new_doc("Manage Variants") - manage_variant.item = d.name + manage_variant.item_code = d.name manage_variant.attributes = frappe.db.sql("select item_attribute as attribute, item_attribute_value as attribute_value \ from `tabItem Variant` where parent = %s", d.name, as_dict=1) - manage_variant.generate_combinations() - manage_variant.create_variants() - frappe.delete_doc("doctype", "Item Variant") \ No newline at end of file + if manage_variant.attributes: + manage_variant.generate_combinations() + manage_variant.create_variants() + frappe.delete_doc("DocType", "Item Variant") \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index d2f1d7527c1..58b1adb8db7 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -86,7 +86,7 @@ frappe.ui.form.on("Item", { }, manage_variants: function(frm) { - frappe.route_options = {"item": frm.doc.name }; + frappe.route_options = {"item_code": frm.doc.name }; frappe.set_route("List", "Manage Variants"); } }); diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 22e6b212f59..a24fc0476ad 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -332,14 +332,14 @@ class Item(WebsiteGenerator): updated = [] variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name }) for d in variants: - update_variant(self.item_code, d) + update_variant(self.name, d) updated.append(d.item_code) frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated))) def validate_has_variants(self): if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"): if frappe.db.exists("Item", {"variant_of": self.name}): - frappe.throw("Item has variants.") + frappe.throw(_("Item has variants.")) def validate_stock_for_template_must_be_zero(self): if self.has_variants: diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.js b/erpnext/stock/doctype/manage_variants/manage_variants.js index 82a3da7f4d9..ba5c46e8eb9 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.js +++ b/erpnext/stock/doctype/manage_variants/manage_variants.js @@ -43,7 +43,7 @@ frappe.ui.form.on("Manage Variants", { }); }, - item:function(frm) { + item_code:function(frm) { return frappe.call({ method: "get_item_details", doc:frm.doc, diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.json b/erpnext/stock/doctype/manage_variants/manage_variants.json index 055fc274423..7c61620b083 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.json +++ b/erpnext/stock/doctype/manage_variants/manage_variants.json @@ -9,9 +9,9 @@ "document_type": "", "fields": [ { - "fieldname": "item", + "fieldname": "item_code", "fieldtype": "Link", - "label": "Item", + "label": "Item Code", "options": "Item", "permlevel": 0, "precision": "", @@ -75,7 +75,7 @@ "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2015-06-30 03:18:13.787883", + "modified": "2015-06-30 13:40:59.946655", "modified_by": "Administrator", "module": "Stock", "name": "Manage Variants", diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 8b1a13b0a17..0131deb0de5 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -15,7 +15,7 @@ class ManageVariants(Document): def get_item_details(self): self.clear_tables() - if self.item: + if self.item_code: self.get_attributes() self.get_variants() @@ -37,7 +37,7 @@ class ManageVariants(Document): attributes = {} self.set('attributes', []) for d in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` as attribute, - `tabItem` as item where attribute.parent= item.name and item.variant_of = %s""", self.item, as_dict=1): + `tabItem` as item where attribute.parent= item.name and item.variant_of = %s""", self.item_code, as_dict=1): attributes.setdefault(d.attribute, []).append(d.attribute_value) for d in attributes: attribute_values = set(attributes[d]) @@ -46,24 +46,25 @@ class ManageVariants(Document): def get_variants(self): variants = [d.name for d in frappe.get_all("Item", - filters={"variant_of":self.item})] + filters={"variant_of":self.item_code})] + data = frappe.db.sql("""select parent, attribute, attribute_value from `tabVariant Attribute`""", as_dict=1) for d in variants: variant_attributes, attributes = "", [] - for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d): - variant_attributes += attribute[1] + " | " - attributes.append([attribute[0], attribute[1]]) + for attribute in data: + if attribute.parent == d: + variant_attributes += attribute.attribute_value + " | " + attributes.append([attribute.attribute, attribute.attribute_value]) self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -3], "attributes": json.dumps(attributes)}) def validate_attributes(self): if not self.attributes: - frappe.throw("Enter atleast one Attribute & its Value in Attribute table.") + frappe.throw(_("Enter atleast one Attribute & its Value in Attribute table.")) def validate_template_item(self): - template_item = frappe.get_doc("Item", self.item) - if not template_item.has_variants: + if not frappe.db.get_value("Item", self.item_code, "has_variants"): frappe.throw(_("Selected Item cannot have Variants.")) - if template_item.variant_of: + if frappe.db.get_value("Item", self.item_code, "variant_of"): frappe.throw(_("Item cannot be a variant of a variant")) def validate_attribute_values(self): @@ -111,7 +112,7 @@ class ManageVariants(Document): variant_attributes += d[1] + " | " self.append('variants', {"variant": item_code + "-" + value.abbr, "attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -3]}) - add_attribute_suffixes(self.item, [], attributes) + add_attribute_suffixes(self.item_code, [], attributes) def sync_variants(self): variant_item_codes = [] @@ -122,7 +123,7 @@ class ManageVariants(Document): variant_item_codes.append(v.variant) existing_variants = [d.name for d in frappe.get_all("Item", - filters={"variant_of":self.item})] + filters={"variant_of":self.item_code})] for d in existing_variants: attributes = [] @@ -147,10 +148,10 @@ class ManageVariants(Document): for item_code in variant_item_codes: if item_code not in existing_variants: if item_code not in new_variant_name: - make_variant(self.item, item_code, self.variants) + make_variant(self.item_code, item_code, self.variants) inserted.append(item_code) else: - update_variant(self.item, item_code, self.variants) + update_variant(self.item_code, item_code, self.variants) updated.append(item_code) if inserted: @@ -168,14 +169,12 @@ class ManageVariants(Document): def make_variant(item, variant_code, variant_attribute): variant = frappe.new_doc("Item") variant.item_code = variant_code - template = frappe.get_doc("Item", item) - copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + copy_attributes_to_variant(item, variant, variant_attribute, insert=True) variant.insert() def update_variant(item, variant_code, variant_attribute=None): variant = frappe.get_doc("Item", variant_code) - template = frappe.get_doc("Item", item) - copy_attributes_to_variant(template, variant, variant_attribute, insert=True) + copy_attributes_to_variant(item, variant, variant_attribute, insert=True) variant.save() def rename_variant(old_variant_code, new_variant_code): @@ -184,7 +183,8 @@ def rename_variant(old_variant_code, new_variant_code): def delete_variant(variant_code): frappe.delete_doc("Item", variant_code) -def copy_attributes_to_variant(template, variant, variant_attribute=None, insert=False): +def copy_attributes_to_variant(item, variant, variant_attribute=None, insert=False): + template = frappe.get_doc("Item", item) from frappe.model import no_value_fields for field in template.meta.fields: if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\ From 64fbe955c7ab255ebbf6652a27f5486bf741da8c Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 1 Jul 2015 14:04:00 +0530 Subject: [PATCH 21/36] Modified Date changed for Item Doctype --- erpnext/stock/doctype/item/item.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index a43b70467d0..acf2764e936 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -902,7 +902,7 @@ "icon": "icon-tag", "idx": 1, "max_attachments": 1, - "modified": "2015-06-29 17:20:18.204558", + "modified": "2015-07-01 17:20:18.204558", "modified_by": "Administrator", "module": "Stock", "name": "Item", From fe9cd1d8755ebe263990ec8a6e24cc5ee128bfba Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Wed, 1 Jul 2015 14:41:30 +0530 Subject: [PATCH 22/36] More Fixes in Manage Varients --- erpnext/stock/doctype/manage_variants/manage_variants.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/manage_variants/manage_variants.py b/erpnext/stock/doctype/manage_variants/manage_variants.py index 0131deb0de5..b6784d3a2e3 100644 --- a/erpnext/stock/doctype/manage_variants/manage_variants.py +++ b/erpnext/stock/doctype/manage_variants/manage_variants.py @@ -69,10 +69,10 @@ class ManageVariants(Document): def validate_attribute_values(self): attributes = {} + for t in frappe.db.get_all("Item Attribute Value", fields=["parent", "attribute_value"]): + attributes.setdefault(t.parent, []).append(t.attribute_value) + for d in self.attributes: - attributes.setdefault(d.attribute, - [t.attribute_value for t in - frappe.db.get_all("Item Attribute Value", fields=["attribute_value"], filters={"parent": d.attribute })]) if d.attribute_value not in attributes.get(d.attribute): frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.attribute_value)) From bc799885d044ff1ada3e72af77953070144c1b1e Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 2 Jul 2015 12:44:16 +0530 Subject: [PATCH 23/36] [fix] bom client script --- erpnext/manufacturing/doctype/bom/bom.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 65ecfd8b90b..f0eebb1a2a2 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -61,7 +61,7 @@ var get_bom_material_detail= function(doc, cdt, cdn) { var d = locals[cdt][cdn]; if (d.item_code) { return frappe.call({ - doc: cur_frm.doc, + doc: doc, method: "get_bom_material_detail", args: { 'item_code': d.item_code, @@ -234,5 +234,3 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) { cur_frm.cscript.image = function() { refresh_field("image_view"); } - - From 9c15ef903d608f7c671a37a08fdfcd0370e50f63 Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 2 Jul 2015 13:09:57 +0530 Subject: [PATCH 24/36] Image feild added to Sales Invoice & Purchase Invoice --- .../purchase_invoice_item.json | 19 ++++++- .../sales_invoice_item.json | 19 ++++++- erpnext/patches.txt | 1 + .../v5_0/update_item_desc_in_invoice.py | 50 +++++++++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v5_0/update_item_desc_in_invoice.py diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 1280cc0c1e6..ce6ac7aa3b6 100755 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -49,6 +49,23 @@ "read_only": 0, "width": "300px" }, + { + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "permlevel": 0, + "precision": "", + "print_hide": 1 + }, { "fieldname": "quantity_and_rate", "fieldtype": "Section Break", @@ -452,7 +469,7 @@ ], "idx": 1, "istable": 1, - "modified": "2015-06-02 14:18:56.294949", + "modified": "2015-07-02 03:00:44.496683", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 4ace40a672a..6dcd59137a6 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -68,6 +68,23 @@ "reqd": 1, "width": "200px" }, + { + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image", + "permlevel": 0, + "precision": "" + }, + { + "fieldname": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "permlevel": 0, + "precision": "", + "print_hide": 1 + }, { "fieldname": "quantity_and_rate", "fieldtype": "Section Break", @@ -505,7 +522,7 @@ ], "idx": 1, "istable": 1, - "modified": "2015-06-02 14:18:45.176726", + "modified": "2015-07-02 02:59:08.413213", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 80965ece06b..725e020888c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -169,3 +169,4 @@ erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again erpnext.patches.v5_0.index_on_account_and_gl_entry execute:frappe.db.sql("""delete from `tabProject Task`""") erpnext.patches.v5_0.item_variants +erpnext.patches.v5_0.update_item_desc_in_invoice \ No newline at end of file diff --git a/erpnext/patches/v5_0/update_item_desc_in_invoice.py b/erpnext/patches/v5_0/update_item_desc_in_invoice.py new file mode 100644 index 00000000000..b7a071cbfea --- /dev/null +++ b/erpnext/patches/v5_0/update_item_desc_in_invoice.py @@ -0,0 +1,50 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from frappe.website.utils import find_first_image +from frappe.utils import cstr +import re + +def execute(): + item_details = frappe._dict() + for d in frappe.db.sql("select name, description, image from `tabItem`", as_dict=1): + description = cstr(d.description).strip() + item_details.setdefault(d.name, frappe._dict({ + "description": description, + "image": d.image + })) + + + dt_list= ["Sales Invoice Item","Purchase Invoice Item"] + for dt in dt_list: + frappe.reload_doctype(dt) + records = frappe.db.sql("""select name, item_code, description from `tab{0}` + where description is not null """.format(dt), as_dict=1) + + count = 1 + for d in records: + if d.item_code and item_details.get(d.item_code) \ + and cstr(d.description) == item_details.get(d.item_code).description: + desc = item_details.get(d.item_code).description + image = item_details.get(d.item_code).image + else: + desc, image = extract_image_and_description(cstr(d.description)) + + if not image: + image = item_details.get(d.item_code).image + + frappe.db.sql("""update `tab{0}` set description = %s, image = %s + where name = %s """.format(dt), (desc, image, d.name)) + + count += 1 + if count % 500 == 0: + frappe.db.commit() + + +def extract_image_and_description(data): + image_url = find_first_image(data) + desc = data + for tag in ("img", "table", "tr", "td"): + desc = re.sub("\]*\>".format(tag), "", desc) + return desc, image_url \ No newline at end of file From 885c70984d3f0c4a744a162e0638ea1e59afdb60 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 1 Jul 2015 16:48:03 +0530 Subject: [PATCH 25/36] [fix] Always show Sales invoice in Gross profit report --- erpnext/accounts/report/gross_profit/gross_profit.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 716cc3d4f62..9b688109d97 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -175,16 +175,15 @@ class GrossProfitGenerator(object): else: if row.update_stock or row.dn_detail: + parenttype, parent, item_row = row.parenttype, row.parent, row.item_row if row.dn_detail: - row.parenttype = "Delivery Note" - row.parent = row.delivery_note - row.item_row = row.dn_detail + parenttype, parent, item_row = "Delivery Note", row.delivery_note, row.dn_detail my_sle = self.sle.get((item_code, row.warehouse)) for i, sle in enumerate(my_sle): # find the stock valution rate from stock ledger entry - if sle.voucher_type == row.parenttype and row.parent == sle.voucher_no and \ - sle.voucher_detail_no == row.item_row: + if sle.voucher_type == parenttype and parent == sle.voucher_no and \ + sle.voucher_detail_no == item_row: previous_stock_value = len(my_sle) > i+1 and \ flt(my_sle[i+1].stock_value) or 0.0 return previous_stock_value - flt(sle.stock_value) From f92465981c97f2a31aec943bf5aae504f04d489a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 1 Jul 2015 16:48:47 +0530 Subject: [PATCH 26/36] datetime issue fixed in maintenance schedule --- .../doctype/maintenance_schedule/maintenance_schedule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py index 23989507ac9..0ecc9c0c948 100644 --- a/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import add_days, getdate, cint +from frappe.utils import add_days, getdate, cint, cstr from frappe import throw, _ from erpnext.utilities.transaction_base import TransactionBase, delete_events @@ -73,7 +73,7 @@ class MaintenanceSchedule(TransactionBase): "owner": email_map[d.sales_person] or self.owner, "subject": description, "description": description, - "starts_on": key["scheduled_date"] + " 10:00:00", + "starts_on": cstr(key["scheduled_date"]) + " 10:00:00", "event_type": "Private", "ref_type": self.doctype, "ref_name": self.name From fdd0db3459dd0a0cc55833e9d071d67d88cbc4ec Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 1 Jul 2015 16:53:19 +0530 Subject: [PATCH 27/36] Update project completion percentage and costing after syncing task --- erpnext/projects/doctype/project/project.py | 18 ++++++++++++++---- erpnext/projects/doctype/task/task.py | 11 ++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 0ba368d5bad..6ebafdba59e 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -46,6 +46,8 @@ class Project(Document): """sync tasks and remove table""" if self.flags.dont_sync_tasks: return + + task_added_or_deleted = False task_names = [] for t in self.tasks: if t.task_id: @@ -53,6 +55,7 @@ class Project(Document): else: task = frappe.new_doc("Task") task.project = self.name + task_added_or_deleted = True task.update({ "subject": t.title, @@ -70,15 +73,22 @@ class Project(Document): # delete 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() + + def update_project(self): + self.update_percent_complete() + self.update_costing() def update_percent_complete(self): - total = frappe.db.sql("""select count(*) from tabTask where project=%s""", - self.name)[0][0] + total = frappe.db.sql("""select count(*) from tabTask where project=%s""", self.name)[0][0] if total: completed = frappe.db.sql("""select count(*) from tabTask where project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0] - frappe.db.set_value("Project", self.name, "percent_complete", - int(float(completed) / total * 100)) + + self.percent_complete = flt(completed) / total * 100 def update_costing(self): total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount, diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index f5541cc0e5e..7229203395c 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -43,15 +43,8 @@ class Task(Document): def on_update(self): self.check_recursion() self.reschedule_dependent_tasks() - self.update_percentage() self.update_project() - def update_percentage(self): - """update percent complete in project""" - if self.project and not self.flags.from_project: - project = frappe.get_doc("Project", self.project) - project.run_method("update_percent_complete") - def update_total_expense_claim(self): self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` where project = %s and task = %s and approval_status = "Approved" and docstatus=1""",(self.project, self.name)) @@ -70,10 +63,10 @@ class Task(Document): self.act_end_date= tl.end_date def update_project(self): - if self.project and frappe.db.exists("Project", self.project): + if self.project and not self.flags.from_project: project = frappe.get_doc("Project", self.project) project.flags.dont_sync_tasks = True - project.update_costing() + project.update_project() project.save() def check_recursion(self): From 53bd62eafcb374b7ceb23271080b9ff367fac27e Mon Sep 17 00:00:00 2001 From: Neil Trini Lasrado Date: Thu, 2 Jul 2015 11:58:52 +0530 Subject: [PATCH 28/36] Validation changed for Item Template cannot have Stock --- erpnext/stock/doctype/item/item.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a24fc0476ad..ccb0fab9a1d 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -344,10 +344,10 @@ class Item(WebsiteGenerator): def validate_stock_for_template_must_be_zero(self): if self.has_variants: stock_in = frappe.db.sql_list("""select warehouse from tabBin - where item_code=%s and ifnull(actual_qty, 0) > 0""", self.name) + where item_code=%s and (ifnull(actual_qty, 0) > 0 or ifnull(ordered_qty, 0) > 0 + or ifnull(reserved_qty, 0) > 0 or ifnull(indented_qty, 0) > 0 or ifnull(planned_qty, 0) > 0)""", self.name) if stock_in: - frappe.throw(_("Item Template cannot have stock and varaiants. Please remove \ - stock from warehouses {0}").format(", ".join(stock_in)), ItemTemplateCannotHaveStock) + frappe.throw(_("Item Template cannot have stock or Open Sales/Purchase/Production Orders."), ItemTemplateCannotHaveStock) def validate_end_of_life(item_code, end_of_life=None, verbose=1): if not end_of_life: From 6b35ea873bbf48349ce0352e9b31cbd55b5fa0ae Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 1 Jul 2015 12:25:18 +0530 Subject: [PATCH 29/36] [fix] against account in general ledger will show party --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 21 ++++++++++- .../doctype/journal_entry/journal_entry.py | 12 +++--- .../purchase_invoice/purchase_invoice.py | 16 ++++---- .../doctype/sales_invoice/sales_invoice.py | 10 ++--- erpnext/patches.txt | 3 +- erpnext/patches/v5_1/__init__.py | 0 erpnext/patches/v5_1/fix_against_account.py | 37 +++++++++++++++++++ 7 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 erpnext/patches/v5_1/__init__.py create mode 100644 erpnext/patches/v5_1/fix_against_account.py diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 138cf23d381..3d306fb8d5f 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -127,7 +127,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga against_voucher_amount = flt(frappe.db.sql(""" select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s - and account = %s and ifnull(party_type, '')=%s and ifnull(party, '')=%s + and account = %s and ifnull(party_type, '')=%s and ifnull(party, '')=%s and ifnull(against_voucher, '') = ''""", (against_voucher, account, cstr(party_type), cstr(party)))[0][0]) @@ -158,3 +158,22 @@ def validate_frozen_account(account, adv_adj=None): frappe.throw(_("Account {0} is frozen").format(account)) elif frozen_accounts_modifier not in frappe.get_roles(): frappe.throw(_("Not authorized to edit frozen Account {0}").format(account)) + +def update_against_account(voucher_type, voucher_no): + entries = frappe.db.get_all("GL Entry", + filters={"voucher_type": voucher_type, "voucher_no": voucher_no}, + fields=["name", "party", "against", "debit", "credit", "account"]) + + accounts_debited, accounts_credited = [], [] + for d in entries: + if flt(d.debit > 0): accounts_debited.append(d.party or d.account) + if flt(d.credit) > 0: accounts_credited.append(d.party or d.account) + + for d in entries: + if flt(d.debit > 0): + new_against = ", ".join(list(set(accounts_credited))) + if flt(d.credit > 0): + new_against = ", ".join(list(set(accounts_debited))) + + if d.against != new_against: + frappe.db.set_value("GL Entry", d.name, "against", new_against) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 7bf6c56dc52..489d4889a11 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -81,7 +81,7 @@ class JournalEntry(AccountsController): frappe.throw(_("Row {0}: Party Type and Party is only applicable against Receivable / Payable account").format(d.idx)) def check_credit_limit(self): - customers = list(set([d.party for d in self.get("accounts") + customers = list(set([d.party for d in self.get("accounts") if d.party_type=="Customer" and d.party and flt(d.debit) > 0])) if customers: from erpnext.selling.doctype.customer.customer import check_credit_limit @@ -243,8 +243,8 @@ class JournalEntry(AccountsController): def set_against_account(self): accounts_debited, accounts_credited = [], [] for d in self.get("accounts"): - if flt(d.debit > 0): accounts_debited.append(d.account) - if flt(d.credit) > 0: accounts_credited.append(d.account) + if flt(d.debit > 0): accounts_debited.append(d.party or d.account) + if flt(d.credit) > 0: accounts_credited.append(d.party or d.account) for d in self.get("accounts"): if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited))) @@ -274,9 +274,9 @@ class JournalEntry(AccountsController): r.append(_('Reference #{0} dated {1}').format(self.cheque_no, formatdate(self.cheque_date))) else: msgprint(_("Please enter Reference date"), raise_exception=frappe.MandatoryError) - + company_currency = get_company_currency(self.company) - + for d in self.get('accounts'): if d.against_invoice and d.credit: r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = company_currency), \ @@ -426,7 +426,7 @@ class JournalEntry(AccountsController): def validate_expense_claim(self): for d in self.accounts: if d.against_expense_claim: - sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim", + sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim", d.against_expense_claim, ("total_sanctioned_amount", "total_amount_reimbursed")) pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount) if d.debit > pending_amount: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 660b221e7bd..50a79ec01be 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -164,7 +164,7 @@ class PurchaseInvoice(BuyingController): elif item.expense_account not in against_accounts: # if no auto_accounting_for_stock or not a stock item against_accounts.append(item.expense_account) - + self.against_expense_account = ",".join(against_accounts) def po_required(self): @@ -271,7 +271,7 @@ class PurchaseInvoice(BuyingController): gl_entries.append( self.get_gl_dict({ "account": tax.account_head, - "against": self.credit_to, + "against": self.supplier, "debit": tax.add_deduct_tax == "Add" and tax.base_tax_amount_after_discount_amount or 0, "credit": tax.add_deduct_tax == "Deduct" and tax.base_tax_amount_after_discount_amount or 0, "remarks": self.remarks, @@ -295,7 +295,7 @@ class PurchaseInvoice(BuyingController): gl_entries.append( self.get_gl_dict({ "account": item.expense_account, - "against": self.credit_to, + "against": self.supplier, "debit": item.base_net_amount, "remarks": self.remarks, "cost_center": item.cost_center @@ -315,7 +315,7 @@ class PurchaseInvoice(BuyingController): gl_entries.append( self.get_gl_dict({ "account": stock_received_but_not_billed, - "against": self.credit_to, + "against": self.supplier, "debit": flt(item.item_tax_amount, self.precision("item_tax_amount", item)), "remarks": self.remarks or "Accounting Entry for Stock" }) @@ -341,7 +341,7 @@ class PurchaseInvoice(BuyingController): self.get_gl_dict({ "account": expenses_included_in_valuation, "cost_center": cost_center, - "against": self.credit_to, + "against": self.supplier, "credit": applicable_amount, "remarks": self.remarks or "Accounting Entry for Stock" }) @@ -355,7 +355,7 @@ class PurchaseInvoice(BuyingController): gl_entries.append( self.get_gl_dict({ "account": self.write_off_account, - "against": self.credit_to, + "against": self.supplier, "credit": flt(self.write_off_amount), "remarks": self.remarks, "cost_center": self.write_off_cost_center @@ -374,7 +374,7 @@ class PurchaseInvoice(BuyingController): self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.make_gl_entries_on_cancel() self.update_project() - + def update_project(self): project_list = [] for d in self.items: @@ -384,7 +384,7 @@ class PurchaseInvoice(BuyingController): project.update_purchase_costing() project.save() project_list.append(d.project_name) - + def validate_supplier_invoice(self): if self.bill_date: if getdate(self.bill_date) > getdate(self.posting_date): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 87f723d4f82..604370b3262 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -503,7 +503,7 @@ class SalesInvoice(SellingController): gl_entries.append( self.get_gl_dict({ "account": tax.account_head, - "against": self.debit_to, + "against": self.customer, "credit": flt(tax.base_tax_amount_after_discount_amount), "remarks": self.remarks, "cost_center": tax.cost_center @@ -517,7 +517,7 @@ class SalesInvoice(SellingController): gl_entries.append( self.get_gl_dict({ "account": item.income_account, - "against": self.debit_to, + "against": self.customer, "credit": item.base_net_amount, "remarks": self.remarks, "cost_center": item.cost_center @@ -548,7 +548,7 @@ class SalesInvoice(SellingController): gl_entries.append( self.get_gl_dict({ "account": self.cash_bank_account, - "against": self.debit_to, + "against": self.customer, "debit": self.paid_amount, "remarks": self.remarks, }) @@ -572,7 +572,7 @@ class SalesInvoice(SellingController): gl_entries.append( self.get_gl_dict({ "account": self.write_off_account, - "against": self.debit_to, + "against": self.customer, "debit": self.write_off_amount, "remarks": self.remarks, "cost_center": self.write_off_cost_center @@ -587,7 +587,7 @@ def get_list_context(context=None): @frappe.whitelist() def get_bank_cash_account(mode_of_payment, company): - account = frappe.db.get_value("Mode of Payment Account", + account = frappe.db.get_value("Mode of Payment Account", {"parent": mode_of_payment, "company": company}, "default_account") if not account: frappe.msgprint(_("Please set default Cash or Bank account in Mode of Payment {0}").format(mode_of_payment)) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 725e020888c..b21d31b5c86 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -169,4 +169,5 @@ erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again erpnext.patches.v5_0.index_on_account_and_gl_entry execute:frappe.db.sql("""delete from `tabProject Task`""") erpnext.patches.v5_0.item_variants -erpnext.patches.v5_0.update_item_desc_in_invoice \ No newline at end of file +erpnext.patches.v5_0.update_item_desc_in_invoice +erpnext.patches.v5_1.fix_against_account diff --git a/erpnext/patches/v5_1/__init__.py b/erpnext/patches/v5_1/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/patches/v5_1/fix_against_account.py b/erpnext/patches/v5_1/fix_against_account.py new file mode 100644 index 00000000000..f0b57d9b591 --- /dev/null +++ b/erpnext/patches/v5_1/fix_against_account.py @@ -0,0 +1,37 @@ +from __future__ import unicode_literals + +import frappe + +from erpnext.accounts.doctype.gl_entry.gl_entry import update_against_account + +def execute(): + from_date = "2015-01-01" + + for doc in frappe.get_all("Journal Entry", + filters={"creation": (">", from_date), "docstatus": "1"}): + + # update in gl_entry + update_against_account("Journal Entry", doc.name) + + # update in jv + doc = frappe.get_doc("Journal Entry", doc.name) + doc.set_against_account() + doc.db_update() + + for doc in frappe.get_all("Sales Invoice", + filters={"creation": (">", from_date), "docstatus": "1"}, + fields=["name", "customer"]): + + frappe.db.sql("""update `tabGL Entry` set against=%s + where voucher_type='Sales Invoice' and voucher_no=%s + and credit > 0 and ifnull(party, '')=''""", + (doc.customer, doc.name)) + + for doc in frappe.get_all("Purchase Invoice", + filters={"creation": (">", from_date), "docstatus": "1"}, + fields=["name", "supplier"]): + + frappe.db.sql("""update `tabGL Entry` set against=%s + where voucher_type='Purchase Invoice' and voucher_no=%s + and debit > 0 and ifnull(party, '')=''""", + (doc.supplier, doc.name)) From 0b0ec3536c151fdbf8fe83533ee82531fcfbdc20 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 1 Jul 2015 12:28:37 +0530 Subject: [PATCH 30/36] [change-log] added --- erpnext/change_log/current/v5_1_0.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 erpnext/change_log/current/v5_1_0.md diff --git a/erpnext/change_log/current/v5_1_0.md b/erpnext/change_log/current/v5_1_0.md new file mode 100644 index 00000000000..852fa075bd6 --- /dev/null +++ b/erpnext/change_log/current/v5_1_0.md @@ -0,0 +1 @@ +- Against account in General Ledger will show Party instead of Account (which is not useful) From 2cd02af80aa82899f660048d0986558e82702bdd Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 1 Jul 2015 12:29:41 +0530 Subject: [PATCH 31/36] [fix] patch date --- erpnext/patches/v5_1/fix_against_account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v5_1/fix_against_account.py b/erpnext/patches/v5_1/fix_against_account.py index f0b57d9b591..a62c15b7d19 100644 --- a/erpnext/patches/v5_1/fix_against_account.py +++ b/erpnext/patches/v5_1/fix_against_account.py @@ -5,7 +5,7 @@ import frappe from erpnext.accounts.doctype.gl_entry.gl_entry import update_against_account def execute(): - from_date = "2015-01-01" + from_date = "2015-05-01" for doc in frappe.get_all("Journal Entry", filters={"creation": (">", from_date), "docstatus": "1"}): From 7580723ab38b46a1ae85ed94bb5e0d2f80a0427b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 2 Jul 2015 14:41:27 +0530 Subject: [PATCH 32/36] Recurring docs should not consider Stopped documents and should be scheduled for hourly --- erpnext/controllers/recurring_document.py | 8 +++++--- erpnext/hooks.py | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/recurring_document.py b/erpnext/controllers/recurring_document.py index 8c8af23b58d..f9ab50965c5 100644 --- a/erpnext/controllers/recurring_document.py +++ b/erpnext/controllers/recurring_document.py @@ -33,11 +33,13 @@ def manage_recurring_documents(doctype, next_date=None, commit=True): next_date = next_date or nowdate() date_field = date_field_map[doctype] + + condition = " and ifnull(status, '') != 'Stopped'" if doctype in ("Sales Order", "Purchase Order") else "" recurring_documents = frappe.db.sql("""select name, recurring_id - from `tab{}` where ifnull(is_recurring, 0)=1 - and docstatus=1 and next_date='{}' - and next_date <= ifnull(end_date, '2199-12-31')""".format(doctype, next_date)) + from `tab{0}` where ifnull(is_recurring, 0)=1 + and docstatus=1 and next_date=%s + and next_date <= ifnull(end_date, '2199-12-31') {1}""".format(doctype, condition), next_date) exception_list = [] for ref_document, recurring_id in recurring_documents: diff --git a/erpnext/hooks.py b/erpnext/hooks.py index d9262d483cf..f84951b1c38 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -78,8 +78,10 @@ doc_events = { } scheduler_events = { + "hourly": [ + "erpnext.controllers.recurring_document.create_recurring_documents" + ], "daily": [ - "erpnext.controllers.recurring_document.create_recurring_documents", "erpnext.stock.reorder_item.reorder_item", "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.support.doctype.issue.issue.auto_close_tickets", From 5105f909d6a499db7e3b039b344973565fad26ef Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 2 Jul 2015 15:09:18 +0530 Subject: [PATCH 33/36] [fix] show item description in form grid based on in_list_view --- .../doctype/sales_order_item/sales_order_item.json | 4 ++-- .../stock_entry_detail/stock_entry_detail.json | 4 ++-- .../templates/form_grid/includes/visible_cols.html | 2 ++ erpnext/templates/form_grid/item_grid.html | 11 ++++------- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index b4a9c0a7ca8..8012bba7af4 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -58,7 +58,7 @@ "fieldname": "description", "fieldtype": "Small Text", "in_filter": 1, - "in_list_view": 1, + "in_list_view": 0, "label": "Description", "oldfieldname": "description", "oldfieldtype": "Small Text", @@ -498,7 +498,7 @@ ], "idx": 1, "istable": 1, - "modified": "2015-05-27 02:47:15.134435", + "modified": "2015-07-02 05:37:29.289574", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index fb654c20db8..a373185c238 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -100,7 +100,7 @@ { "fieldname": "description", "fieldtype": "Text", - "in_list_view": 1, + "in_list_view": 0, "label": "Description", "oldfieldname": "description", "oldfieldtype": "Text", @@ -344,7 +344,7 @@ ], "idx": 1, "istable": 1, - "modified": "2015-02-25 04:31:21.801200", + "modified": "2015-07-02 05:32:56.511570", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/templates/form_grid/includes/visible_cols.html b/erpnext/templates/form_grid/includes/visible_cols.html index c26f2793379..caa13667db2 100644 --- a/erpnext/templates/form_grid/includes/visible_cols.html +++ b/erpnext/templates/form_grid/includes/visible_cols.html @@ -9,5 +9,7 @@ {%= doc.get_formatted(df.fieldname) %} + {% } else if (df.fieldname==="description") { %} +

{{ doc.description }}

{% } %} {% }); %} diff --git a/erpnext/templates/form_grid/item_grid.html b/erpnext/templates/form_grid/item_grid.html index d2db463dcb5..197cef461bf 100644 --- a/erpnext/templates/form_grid/item_grid.html +++ b/erpnext/templates/form_grid/item_grid.html @@ -1,4 +1,6 @@ -{% var visible_columns = row.get_visible_columns(["item_code", "qty", "rate", "amount", "stock_uom", "uom", "discount_percentage", "schedule_date", "warehouse", "against_sales_order", "sales_order"]); %} +{% var visible_columns = row.get_visible_columns(["item_code", "qty", "rate", "amount", + "stock_uom", "uom", "discount_percentage", "schedule_date", "warehouse", + "against_sales_order", "sales_order"]); %} {% if(!doc) { %}
@@ -48,12 +50,7 @@ {% if(doc.item_name != doc.item_code && in_list(visible_column_fieldnames, "item_name")) { %}
{%= doc.item_name %}{% } %} - - {% if((doc.description != doc.item_code != doc.item_name) && - in_list(visible_column_fieldnames, "description")) { %} -
- Description: - {%= doc.get_formatted("description") %}{% } %} + {% include "templates/form_grid/includes/visible_cols.html" %}
From dffd7c3889fba8b3622746b1e5902de204af9161 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 2 Jul 2015 15:21:13 +0530 Subject: [PATCH 34/36] Change log --- erpnext/change_log/current/v5_1_0.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/change_log/current/v5_1_0.md b/erpnext/change_log/current/v5_1_0.md index 852fa075bd6..9a350025f44 100644 --- a/erpnext/change_log/current/v5_1_0.md +++ b/erpnext/change_log/current/v5_1_0.md @@ -1 +1,8 @@ +- Item variants is now manageable via dedicated tool **Manage Variants**. To learn about it, check https://manual.erpnext.com/contents/stock/item/item-variants - Against account in General Ledger will show Party instead of Account (which is not useful) +- Print format for recurring documents can be set by the users +- Recurring documents won't be created for Stopped Sales / Purchase Orders. +- Lead status will be changed to 'Opportunity' when Lead converted to Opportunity +- Amount in Journal Entry list view +- Currency exchange rate is now automatically fetched from fixer.io, instead of jsonrates.com +- Item image is now available in Sales / Purchase Invoice \ No newline at end of file From 784b49e54ef37890d3587e14ad8d67d6d61c2892 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 2 Jul 2015 15:27:36 +0530 Subject: [PATCH 35/36] change log --- erpnext/change_log/{current => }/v5_1_0.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename erpnext/change_log/{current => }/v5_1_0.md (100%) diff --git a/erpnext/change_log/current/v5_1_0.md b/erpnext/change_log/v5_1_0.md similarity index 100% rename from erpnext/change_log/current/v5_1_0.md rename to erpnext/change_log/v5_1_0.md From bc176b6bf88b31da310d403a807221b8a05d6e86 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 2 Jul 2015 15:58:49 +0600 Subject: [PATCH 36/36] bumped to version 5.1.0 --- erpnext/__version__.py | 2 +- erpnext/hooks.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/__version__.py b/erpnext/__version__.py index b0b41d353db..5069b2fe32c 100644 --- a/erpnext/__version__.py +++ b/erpnext/__version__.py @@ -1,2 +1,2 @@ from __future__ import unicode_literals -__version__ = '5.0.29' +__version__ = '5.1.0' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index f84951b1c38..8ee63feaf36 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -5,7 +5,7 @@ app_publisher = "Frappe Technologies Pvt. Ltd. and Contributors" app_description = "Open Source Enterprise Resource Planning for Small and Midsized Organizations" app_icon = "icon-th" app_color = "#e74c3c" -app_version = "5.0.29" +app_version = "5.1.0" error_report_email = "support@erpnext.com" diff --git a/setup.py b/setup.py index ea74fa83051..0694a82ce12 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = "5.0.29" +version = "5.1.0" with open("requirements.txt", "r") as f: install_requires = f.readlines()