From 38265efc391039c6adf7ae7a2b93a39bb31abd8b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 21 Oct 2014 20:23:39 +0530 Subject: [PATCH 01/22] Minor fix --- erpnext/stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index b444e84193c..889c30ced34 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -100,7 +100,7 @@ def get_fifo_rate(previous_stock_queue, qty): """get FIFO (average) Rate from Queue""" if qty >= 0: total = sum(f[0] for f in previous_stock_queue) - return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0 + return sum(flt(f[0]) * flt(f[1]) for f in previous_stock_queue) / flt(total) if total else 0.0 else: available_qty_for_outgoing, outgoing_cost = 0, 0 qty_to_pop = abs(qty) From 23d7919865f9bc6a84141da4428021175921cc2d Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 22 Oct 2014 13:06:26 +0530 Subject: [PATCH 02/22] Stock ageing report fix --- erpnext/stock/report/stock_ageing/stock_ageing.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index fb5e9f9bfd3..b0cd6c29206 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -42,13 +42,12 @@ def get_columns(): def get_fifo_queue(filters): item_details = {} - prev_qty = 0.0 for d in get_stock_ledger_entries(filters): item_details.setdefault(d.name, {"details": d, "fifo_queue": []}) fifo_queue = item_details[d.name]["fifo_queue"] if d.voucher_type == "Stock Reconciliation": - d.actual_qty = flt(d.qty_after_transaction) - flt(prev_qty) + d.actual_qty = flt(d.qty_after_transaction) - flt(item_details[d.name].get("qty_after_transaction", 0)) if d.actual_qty > 0: fifo_queue.append([d.actual_qty, d.posting_date]) @@ -66,7 +65,7 @@ def get_fifo_queue(filters): batch[0] -= qty_to_pop qty_to_pop = 0 - prev_qty = d.qty_after_transaction + item_details[d.name]["qty_after_transaction"] = d.qty_after_transaction return item_details From 16e05c321c53dfd2fe77955c7b3558bfaf537073 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 22 Oct 2014 13:10:43 +0530 Subject: [PATCH 03/22] Removed stock ageing grid report --- erpnext/stock/page/stock_ageing/README.md | 1 - erpnext/stock/page/stock_ageing/__init__.py | 1 - .../stock/page/stock_ageing/stock_ageing.js | 183 ------------------ .../stock/page/stock_ageing/stock_ageing.json | 23 --- 4 files changed, 208 deletions(-) delete mode 100644 erpnext/stock/page/stock_ageing/README.md delete mode 100644 erpnext/stock/page/stock_ageing/__init__.py delete mode 100644 erpnext/stock/page/stock_ageing/stock_ageing.js delete mode 100644 erpnext/stock/page/stock_ageing/stock_ageing.json diff --git a/erpnext/stock/page/stock_ageing/README.md b/erpnext/stock/page/stock_ageing/README.md deleted file mode 100644 index e8597b21942..00000000000 --- a/erpnext/stock/page/stock_ageing/README.md +++ /dev/null @@ -1 +0,0 @@ -Average "age" of an Item in a particular Warehouse based on First-in-first-out (FIFO). \ No newline at end of file diff --git a/erpnext/stock/page/stock_ageing/__init__.py b/erpnext/stock/page/stock_ageing/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/erpnext/stock/page/stock_ageing/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/stock/page/stock_ageing/stock_ageing.js b/erpnext/stock/page/stock_ageing/stock_ageing.js deleted file mode 100644 index f7740101ca0..00000000000 --- a/erpnext/stock/page/stock_ageing/stock_ageing.js +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - - -frappe.pages['stock-ageing'].onload = function(wrapper) { - frappe.ui.make_app_page({ - parent: wrapper, - title: __('Stock Ageing'), - single_column: true - }); - - new erpnext.StockAgeing(wrapper); - - - wrapper.appframe.add_module_icon("Stock") - -} - -frappe.require("assets/erpnext/js/stock_grid_report.js"); - -erpnext.StockAgeing = erpnext.StockGridReport.extend({ - init: function(wrapper) { - this._super({ - title: __("Stock Ageing"), - page: wrapper, - parent: $(wrapper).find('.layout-main'), - appframe: wrapper.appframe, - doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Item Group", "Brand", "Serial No"], - }) - }, - setup_columns: function() { - this.columns = [ - {id: "name", name: __("Item"), field: "name", width: 300, - link_formatter: { - open_btn: true, - doctype: '"Item"' - }}, - {id: "item_name", name: __("Item Name"), field: "item_name", - width: 100, formatter: this.text_formatter}, - {id: "description", name: __("Description"), field: "description", - width: 200, formatter: this.text_formatter}, - {id: "brand", name: __("Brand"), field: "brand", width: 100}, - {id: "average_age", name: __("Average Age"), field: "average_age", - formatter: this.currency_formatter}, - {id: "earliest", name: __("Earliest"), field: "earliest", - formatter: this.currency_formatter}, - {id: "latest", name: __("Latest"), field: "latest", - formatter: this.currency_formatter}, - {id: "stock_uom", name: "UOM", field: "stock_uom", width: 100}, - ]; - }, - filters: [ - {fieldtype:"Select", label: __("Warehouse"), link:"Warehouse", - default_value: "Select Warehouse..."}, - {fieldtype:"Select", label: __("Brand"), link:"Brand", - default_value: "Select Brand...", filter: function(val, item, opts) { - return val == opts.default_value || item.brand == val; - }, link_formatter: {filter_input: "brand"}}, - {fieldtype:"Select", label: __("Plot By"), - options: ["Average Age", "Earliest", "Latest"]}, - {fieldtype:"Date", label: __("To Date")}, - {fieldtype:"Button", label: __("Refresh"), icon:"icon-refresh icon-white"}, - {fieldtype:"Button", label: __("Reset Filters"), icon: "icon-filter"} - ], - setup_filters: function() { - var me = this; - this._super(); - this.trigger_refresh_on_change(["warehouse", "plot_by", "brand"]); - this.show_zero_check(); - }, - init_filter_values: function() { - this._super(); - this.filter_inputs.to_date.val(dateutil.obj_to_user(new Date())); - }, - prepare_data: function() { - var me = this; - - if(!this.data) { - me._data = frappe.report_dump.data["Item"]; - me.item_by_name = me.make_name_map(me._data); - } - - this.data = [].concat(this._data); - - this.serialized_buying_rates = this.get_serialized_buying_rates(); - - $.each(this.data, function(i, d) { - me.reset_item_values(d); - }); - - this.prepare_balances(); - - // filter out brand - this.data = $.map(this.data, function(d) { - return me.apply_filter(d, "brand") ? d : null; - }); - - // filter out rows with zero values - this.data = $.map(this.data, function(d) { - return me.apply_zero_filter(null, d, null, me) ? d : null; - }); - }, - prepare_balances: function() { - var me = this; - var to_date = dateutil.str_to_obj(this.to_date); - var data = frappe.report_dump.data["Stock Ledger Entry"]; - - this.item_warehouse = {}; - - for(var i=0, j=data.length; i to_date) - break; - } - } - - $.each(me.data, function(i, item) { - var full_fifo_stack = []; - if(me.is_default("warehouse")) { - $.each(me.item_warehouse[item.name] || {}, function(i, wh) { - full_fifo_stack = full_fifo_stack.concat(wh.fifo_stack || []) - }); - } else { - full_fifo_stack = me.get_item_warehouse(me.warehouse, item.name).fifo_stack || []; - } - - var age_qty = total_qty = 0.0; - var min_age = max_age = null; - - $.each(full_fifo_stack, function(i, batch) { - var batch_age = dateutil.get_diff(me.to_date, batch[2]); - age_qty += batch_age * batch[0]; - total_qty += batch[0]; - max_age = Math.max(max_age, batch_age); - if(min_age===null) min_age=batch_age; - else min_age = Math.min(min_age, batch_age); - }); - - item.average_age = total_qty.toFixed(2)==0.0 ? 0 - : (age_qty / total_qty).toFixed(2); - item.earliest = max_age || 0.0; - item.latest = min_age || 0.0; - }); - - this.data = this.data.sort(function(a, b) { - var sort_by = me.plot_by.replace(" ", "_").toLowerCase(); - return b[sort_by] - a[sort_by]; - }); - }, - get_plot_data: function() { - var data = []; - var me = this; - - data.push({ - label: me.plot_by, - data: $.map(me.data, function(item, idx) { - return [[idx+1, item[me.plot_by.replace(" ", "_").toLowerCase() ]]] - }), - bars: {show: true}, - }); - - return data.length ? data : false; - }, - get_plot_options: function() { - var me = this; - return { - grid: { hoverable: true, clickable: true }, - xaxis: { - ticks: $.map(me.data, function(item, idx) { return [[idx+1, item.name]] }), - max: 15 - }, - series: { downsample: { threshold: 1000 } } - } - } -}); \ No newline at end of file diff --git a/erpnext/stock/page/stock_ageing/stock_ageing.json b/erpnext/stock/page/stock_ageing/stock_ageing.json deleted file mode 100644 index 34c259e46d7..00000000000 --- a/erpnext/stock/page/stock_ageing/stock_ageing.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "creation": "2012-09-21 20:15:14.000000", - "docstatus": 0, - "doctype": "Page", - "icon": "icon-table", - "idx": 1, - "modified": "2013-07-11 14:44:08.000000", - "modified_by": "Administrator", - "module": "Stock", - "name": "stock-ageing", - "owner": "Administrator", - "page_name": "stock-ageing", - "roles": [ - { - "role": "Analytics" - }, - { - "role": "Material Manager" - } - ], - "standard": "Yes", - "title": "Stock Ageing" -} \ No newline at end of file From 16343683d9d0bfa021aaa5b76721126909250c46 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Wed, 22 Oct 2014 14:49:17 +0600 Subject: [PATCH 04/22] bumped to version 4.9.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 0d53216d5be..316191657ca 100644 --- a/erpnext/__version__.py +++ b/erpnext/__version__.py @@ -1 +1 @@ -__version__ = '4.8.0' +__version__ = '4.9.0' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index febf4b0a498..a1a98f66722 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -4,7 +4,7 @@ app_publisher = "Web Notes 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 = "4.8.0" +app_version = "4.9.0" error_report_email = "support@erpnext.com" diff --git a/setup.py b/setup.py index 5c7fe9a77dc..c95b14d8c03 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = "4.8.0" +version = "4.9.0" with open("requirements.txt", "r") as f: install_requires = f.readlines() From 14e1d20df3db4c4ee57405947478e57e5b9dfcdf Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Wed, 22 Oct 2014 16:55:20 +0530 Subject: [PATCH 05/22] Change position of 'To Time' field for better UX --- .../projects/doctype/time_log/time_log.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/projects/doctype/time_log/time_log.json b/erpnext/projects/doctype/time_log/time_log.json index ee34da04f50..0eed1fc9af2 100644 --- a/erpnext/projects/doctype/time_log/time_log.json +++ b/erpnext/projects/doctype/time_log/time_log.json @@ -1,5 +1,5 @@ { - "allow_import": 1, + "allow_import": 1, "autoname": "naming_series:", "creation": "2013-04-03 16:38:41", "description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.", @@ -25,14 +25,6 @@ "read_only": 0, "reqd": 1 }, - { - "fieldname": "hours", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Hours", - "permlevel": 0, - "read_only": 0 - }, { "fieldname": "to_time", "fieldtype": "Datetime", @@ -42,6 +34,14 @@ "read_only": 0, "reqd": 1 }, + { + "fieldname": "hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Hours", + "permlevel": 0, + "read_only": 0 + }, { "fieldname": "column_break_3", "fieldtype": "Column Break", @@ -151,7 +151,7 @@ "icon": "icon-time", "idx": 1, "is_submittable": 1, - "modified": "2014-08-04 05:23:15.740050", + "modified": "2014-10-22 16:53:26.993828", "modified_by": "Administrator", "module": "Projects", "name": "Time Log", From 10b155a4865ba6342a80c0bc646ef1644ef1243b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 23 Oct 2014 13:18:59 +0530 Subject: [PATCH 06/22] cost center based on project --- erpnext/selling/sales_common.js | 24 +++++++++++--------- erpnext/utilities/doctype/address/address.py | 2 ++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 66e935ab6fd..fd16258d6f5 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -587,16 +587,18 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ }); frappe.ui.form.on(cur_frm.doctype,"project_name", function(frm) { - frappe.call({ - method:'erpnext.projects.doctype.project.project.get_cost_center_name' , - args: { project_name: frm.doc.project_name }, - callback: function(r, rt) { - if(!r.exc) { - $.each(frm.doc[cur_frm.cscript.fname] || [], function(i, row) { - frappe.model.set_value(row.doctype, row.name, "cost_center", r.message); - msgprint(__("Cost Center For Item with Item Code '"+row.item_name+"' has been Changed to "+ r.message)); - }) + if(in_list(["Delivery Note", "Sales Invoice"], frm.doc.doctype)) { + frappe.call({ + method:'erpnext.projects.doctype.project.project.get_cost_center_name' , + args: { project_name: frm.doc.project_name }, + callback: function(r, rt) { + if(!r.exc) { + $.each(frm.doc[cur_frm.cscript.fname] || [], function(i, row) { + frappe.model.set_value(row.doctype, row.name, "cost_center", r.message); + msgprint(__("Cost Center For Item with Item Code '"+row.item_name+"' has been Changed to "+ r.message)); + }) + } } - } - }) + }) + } }) diff --git a/erpnext/utilities/doctype/address/address.py b/erpnext/utilities/doctype/address/address.py index 8fd5cb1dea2..172ce23d66e 100644 --- a/erpnext/utilities/doctype/address/address.py +++ b/erpnext/utilities/doctype/address/address.py @@ -52,6 +52,8 @@ class Address(Document): @frappe.whitelist() def get_address_display(address_dict): + if not address_dict: + return if not isinstance(address_dict, dict): address_dict = frappe.db.get_value("Address", address_dict, "*", as_dict=True) or {} From daed0b655a4b3ad2081c5ac63ed6653399dad2a1 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Mon, 27 Oct 2014 09:56:36 +0600 Subject: [PATCH 07/22] bumped to version 4.9.1 --- 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 316191657ca..84fddef10e6 100644 --- a/erpnext/__version__.py +++ b/erpnext/__version__.py @@ -1 +1 @@ -__version__ = '4.9.0' +__version__ = '4.9.1' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a1a98f66722..6f4a41ed6e0 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -4,7 +4,7 @@ app_publisher = "Web Notes 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 = "4.9.0" +app_version = "4.9.1" error_report_email = "support@erpnext.com" diff --git a/setup.py b/setup.py index c95b14d8c03..5bdcc94cf0d 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = "4.9.0" +version = "4.9.1" with open("requirements.txt", "r") as f: install_requires = f.readlines() From b5c56f6cea6441ce0b88a5e01cb3ec6107ade15c Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Mon, 27 Oct 2014 09:41:25 +0530 Subject: [PATCH 08/22] [minor] add port to test site --- test_sites/test_site/site_config.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test_sites/test_site/site_config.json b/test_sites/test_site/site_config.json index 12007b87ecd..1b388a08563 100644 --- a/test_sites/test_site/site_config.json +++ b/test_sites/test_site/site_config.json @@ -2,5 +2,6 @@ "db_name": "test_frappe", "db_password": "test_frappe", "admin_password": "admin", + "host_name": "http://localhost:8888", "mute_emails": 1 } From 4e61536f50725a674e8d56826779b079ea94d59c Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Wed, 22 Oct 2014 17:43:07 +0530 Subject: [PATCH 09/22] Fix Customer Issue error on click event of Serial No. field --- .../support/doctype/customer_issue/customer_issue.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/customer_issue/customer_issue.js b/erpnext/support/doctype/customer_issue/customer_issue.js index 67a265dd027..b12003a583e 100644 --- a/erpnext/support/doctype/customer_issue/customer_issue.js +++ b/erpnext/support/doctype/customer_issue/customer_issue.js @@ -51,9 +51,14 @@ cur_frm.fields_dict['serial_no'].get_query = function(doc, cdt, cdn) { ['Serial No', 'docstatus', '!=', 2], ['Serial No', 'status', '=', "Delivered"] ]; - if(doc.item_code) cond = ['Serial No', 'item_code', '=', doc.item_code]; - if(doc.customer) cond = ['Serial No', 'customer', '=', doc.customer]; - filter.push(cond); + if(doc.item_code) { + cond = ['Serial No', 'item_code', '=', doc.item_code]; + filter.push(cond); + } + if(doc.customer) { + cond = ['Serial No', 'customer', '=', doc.customer]; + filter.push(cond); + } return{ filters:filter } From ee212e7bb560eff127917e63bc1fb6a98bfb2f3c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 29 Oct 2014 14:25:49 +0530 Subject: [PATCH 10/22] [fix] Floating point issue fixed in payment reconciliation --- .../doctype/payment_reconciliation/payment_reconciliation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index c6a2b056dae..a18ee435e16 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -96,13 +96,14 @@ class PaymentReconciliation(Document): payment_amount = payment_amount[0][0] if payment_amount else 0 - if d.invoice_amount > payment_amount: + if d.invoice_amount - payment_amount > 0.005: non_reconciled_invoices.append({ 'voucher_no': d.voucher_no, 'voucher_type': d.voucher_type, 'posting_date': d.posting_date, 'invoice_amount': flt(d.invoice_amount), - 'outstanding_amount': d.invoice_amount - payment_amount}) + 'outstanding_amount': flt(d.invoice_amount - payment_amount, 2) + }) self.add_invoice_entries(non_reconciled_invoices) From dda239fd49c716f2f4c1b380724c871ceec6b58a Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Thu, 30 Oct 2014 14:52:17 +0600 Subject: [PATCH 11/22] bumped to version 4.9.2 --- 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 84fddef10e6..be4c11176dd 100644 --- a/erpnext/__version__.py +++ b/erpnext/__version__.py @@ -1 +1 @@ -__version__ = '4.9.1' +__version__ = '4.9.2' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 6f4a41ed6e0..9a701100f4e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -4,7 +4,7 @@ app_publisher = "Web Notes 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 = "4.9.1" +app_version = "4.9.2" error_report_email = "support@erpnext.com" diff --git a/setup.py b/setup.py index 5bdcc94cf0d..4abed79d16b 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = "4.9.1" +version = "4.9.2" with open("requirements.txt", "r") as f: install_requires = f.readlines() From 28913b97b04f9ce25a346acbf811aa4370122f07 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 31 Oct 2014 14:46:07 +0530 Subject: [PATCH 12/22] [fix] Null issue fixed in stock analytics report --- erpnext/public/js/stock_analytics.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/public/js/stock_analytics.js b/erpnext/public/js/stock_analytics.js index a86d7ade950..091836f95e3 100644 --- a/erpnext/public/js/stock_analytics.js +++ b/erpnext/public/js/stock_analytics.js @@ -131,6 +131,7 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({ if(me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) { var item = me.item_by_name[sl.item_code]; + if(item.closing_qty_value==undefined) item.closing_qty_value = 0; if(me.value_or_qty!="Quantity") { var wh = me.get_item_warehouse(sl.warehouse, sl.item_code); From 6e06357dc59e9857909d82510dabe6759e4150f8 Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Fri, 31 Oct 2014 12:36:33 +0530 Subject: [PATCH 13/22] Hide totals on checking print_without_amount field in DN --- erpnext/stock/doctype/delivery_note/delivery_note.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 079b7fc110a..e87a233b9c9 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -52,11 +52,15 @@ class DeliveryNote(SellingController): else: df.delete_key("__print_hide") - toggle_print_hide(self.meta, "currency") - item_meta = frappe.get_meta("Delivery Note Item") - for fieldname in ("rate", "amount", "price_list_rate", "discount_percentage"): - toggle_print_hide(item_meta, fieldname) + print_hide_fields = { + "parent": ["grand_total_export", "rounded_total_export", "in_words_export", "currency", "net_total_export"], + "items": ["rate", "amount", "price_list_rate", "discount_percentage"] + } + + for key, fieldname in print_hide_fields.items(): + for f in fieldname: + toggle_print_hide(self.meta if key == "parent" else item_meta, f) def get_portal_page(self): return "shipment" if self.docstatus==1 else None From f5804438bb14c45b4c46bef6143fdcfa1f35d9ae Mon Sep 17 00:00:00 2001 From: ankitjavalkarwork Date: Mon, 3 Nov 2014 12:23:55 +0530 Subject: [PATCH 14/22] Auto set default bank/cash account on select of Bank/Cash Voucher in JV --- .../doctype/journal_voucher/journal_voucher.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/journal_voucher/journal_voucher.js b/erpnext/accounts/doctype/journal_voucher/journal_voucher.js index 9174873406d..4ba4dff32f1 100644 --- a/erpnext/accounts/doctype/journal_voucher/journal_voucher.js +++ b/erpnext/accounts/doctype/journal_voucher/journal_voucher.js @@ -213,10 +213,11 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) { return; var update_jv_details = function(doc, r) { - $.each(r.message, function(i, d) { - var jvdetail = frappe.model.add_child(doc, "Journal Voucher Detail", "entries"); - jvdetail.account = d.account; - jvdetail.balance = d.balance; + var jvdetail = frappe.model.add_child(doc, "Journal Voucher Detail", "entries"); + $.each(r, function(i, d) { + var row = frappe.model.add_child(doc, "Journal Voucher Detail", "entries"); + row.account = d.account; + row.balance = d.balance; }); refresh_field("entries"); } @@ -231,7 +232,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) { }, callback: function(r) { if(r.message) { - update_jv_details(doc, r); + update_jv_details(doc, [r.message]); } } }) @@ -245,7 +246,7 @@ cur_frm.cscript.voucher_type = function(doc, cdt, cdn) { callback: function(r) { frappe.model.clear_table(doc, "entries"); if(r.message) { - update_jv_details(doc, r); + update_jv_details(doc, r.message); } cur_frm.set_value("is_opening", "Yes") } From 5288bdeabb59d016d4a4f78c8e76cf6764abbe88 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 3 Nov 2014 15:08:21 +0530 Subject: [PATCH 15/22] Minor fix --- erpnext/controllers/stock_controller.py | 4 ++-- erpnext/stock/stock_ledger.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 0f8eca4a7cc..0a9adc0e050 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -175,7 +175,7 @@ class StockController(AccountsController): _(self.doctype), self.name, item.get("item_code"))) def get_sl_entries(self, d, args): - sl_dict = { + sl_dict = frappe._dict({ "item_code": d.get("item_code", None), "warehouse": d.get("warehouse", None), "posting_date": self.posting_date, @@ -192,7 +192,7 @@ class StockController(AccountsController): "serial_no": d.get("serial_no"), "project": d.get("project_name"), "is_cancelled": self.docstatus==2 and "Yes" or "No" - } + }) sl_dict.update(args) return sl_dict diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index edbdb1aa065..eae1bf68bf7 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -27,7 +27,7 @@ def make_sl_entries(sl_entries, is_amended=None): if sle.get('is_cancelled') == 'Yes': sle['actual_qty'] = -flt(sle['actual_qty']) - if sle.get("actual_qty") or sle.voucher_type=="Stock Reconciliation": + if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation": sle_id = make_entry(sle) args = sle.copy() From 3c8838816d8b96822462c14dd26e4b917ceb2845 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 3 Nov 2014 15:49:29 +0530 Subject: [PATCH 16/22] Check credit limit only if customer debited --- erpnext/accounts/doctype/journal_voucher/journal_voucher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_voucher/journal_voucher.py b/erpnext/accounts/doctype/journal_voucher/journal_voucher.py index c6299edbc92..7a833f76274 100644 --- a/erpnext/accounts/doctype/journal_voucher/journal_voucher.py +++ b/erpnext/accounts/doctype/journal_voucher/journal_voucher.py @@ -385,7 +385,7 @@ class JournalVoucher(AccountsController): for d in self.get("entries"): master_type, master_name = frappe.db.get_value("Account", d.account, ["master_type", "master_name"]) - if master_type == "Customer" and master_name: + if master_type == "Customer" and master_name and flt(d.debit) > 0: super(JournalVoucher, self).check_credit_limit(d.account) def get_balance(self): From b9ce1f590be7a4584b69e9edf7dc2298a11c23d0 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Mon, 3 Nov 2014 16:34:29 +0600 Subject: [PATCH 17/22] bumped to version 4.9.3 --- 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 be4c11176dd..3fe011fff5c 100644 --- a/erpnext/__version__.py +++ b/erpnext/__version__.py @@ -1 +1 @@ -__version__ = '4.9.2' +__version__ = '4.9.3' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9a701100f4e..25be39d76c1 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -4,7 +4,7 @@ app_publisher = "Web Notes 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 = "4.9.2" +app_version = "4.9.3" error_report_email = "support@erpnext.com" diff --git a/setup.py b/setup.py index 4abed79d16b..0e13a53ee90 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages import os -version = "4.9.2" +version = "4.9.3" with open("requirements.txt", "r") as f: install_requires = f.readlines() From fad0d566f941edc2f9d984913ff7cab835d2fb46 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 4 Nov 2014 11:26:49 +0530 Subject: [PATCH 18/22] Payment receipt voucher print format fixed --- .../payment_receipt_voucher/payment_receipt_voucher.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json index 2944b4b986c..5c0a72a8def 100755 --- a/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json +++ b/erpnext/accounts/print_format/payment_receipt_voucher/payment_receipt_voucher.json @@ -3,9 +3,9 @@ "doc_type": "Journal Voucher", "docstatus": 0, "doctype": "Print Format", - "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n
\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Payment Receipt Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Received On\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Received From\"), doc.pay_to_recd_from),\n (_(\"Amount\"), \"\" + doc.total_amount + \"
\" + (doc.total_amount_in_words or \"\") + \"
\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n
\n
\n
{{ value }}
\n
\n\n {%- endfor -%}\n\n
\n
\n

\n {{ _(\"For\") }} {{ doc.company }},
\n
\n
\n
\n {{ _(\"Authorized Signatory\") }}\n

\n
\n\n", + "html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n
\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Payment Receipt Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Received On\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Received From\"), doc.pay_to_recd_from),\n (_(\"Amount\"), \"\" + doc.total_amount or 0 + \"
\" + (doc.total_amount_in_words or \"\") + \"
\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n
\n
\n
{{ value }}
\n
\n\n {%- endfor -%}\n\n
\n
\n

\n {{ _(\"For\") }} {{ doc.company }},
\n
\n
\n
\n {{ _(\"Authorized Signatory\") }}\n

\n
\n\n", "idx": 1, - "modified": "2014-08-29 15:55:34.248384", + "modified": "2014-11-04 11:25:57.560873", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Receipt Voucher", From 4acd431b9279452eaa83dfdf4a079cbfb488a93e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 4 Nov 2014 15:32:31 +0530 Subject: [PATCH 19/22] Requested qty calculation logic --- erpnext/hooks.py | 4 +- .../material_request/material_request.py | 111 ++++++------------ .../material_request/test_material_request.py | 69 +++++++---- erpnext/utilities/repost_stock.py | 10 +- 4 files changed, 92 insertions(+), 102 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 25be39d76c1..2d7d4e52ca5 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -47,8 +47,8 @@ doc_events = { "on_update": "erpnext.home.make_comment_feed" }, "Stock Entry": { - "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_qty", - "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_qty" + "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", + "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty" }, "User": { "validate": "erpnext.hr.doctype.employee.employee.validate_employee_role", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index eeda2ce6133..cb9552d1fcd 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -79,30 +79,9 @@ class MaterialRequest(BuyingController): # NOTE: Since Item BOM and FG quantities are combined, using current data, it cannot be validated # Though the creation of Material Request from a Production Plan can be rethought to fix this - def update_bin(self, is_submit, is_stopped): - """ Update Quantity Requested for Purchase in Bin for Material Request of type 'Purchase'""" - - from erpnext.stock.utils import update_bin - for d in self.get('indent_details'): - if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes": - if not d.warehouse: - frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code)) - - qty =flt(d.qty) - if is_stopped: - qty = (d.qty > d.ordered_qty) and flt(flt(d.qty) - flt(d.ordered_qty)) or 0 - - args = { - "item_code": d.item_code, - "warehouse": d.warehouse, - "indented_qty": (is_submit and 1 or -1) * flt(qty), - "posting_date": self.transaction_date - } - update_bin(args) - def on_submit(self): frappe.db.set(self, 'status', 'Submitted') - self.update_bin(is_submit = 1, is_stopped = 0) + self.update_requested_qty() def check_modified_date(self): mod_db = frappe.db.sql("""select modified from `tabMaterial Request` where name = %s""", @@ -115,23 +94,18 @@ class MaterialRequest(BuyingController): def update_status(self, status): self.check_modified_date() - self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1) + self.update_requested_qty() frappe.db.set(self, 'status', cstr(status)) frappe.msgprint(_("Status updated to {0}").format(_(status))) def on_cancel(self): - # Step 1:=> Get Purchase Common Obj pc_obj = frappe.get_doc('Purchase Common') - # Step 2:=> Check for stopped status pc_obj.check_for_stopped_status(self.doctype, self.name) - - # Step 3:=> Check if Purchase Order has been submitted against current Material Request pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Order', docname = self.name, detail_doctype = 'Purchase Order Item') - # Step 4:=> Update Bin - self.update_bin(is_submit = 0, is_stopped = (cstr(self.status) == 'Stopped') and 1 or 0) - # Step 5:=> Set Status + self.update_requested_qty() + frappe.db.set(self,'status','Cancelled') def update_completed_qty(self, mr_items=None): @@ -162,56 +136,47 @@ class MaterialRequest(BuyingController): self.per_ordered = flt((per_ordered / flt(len(item_doclist))) * 100.0, 2) frappe.db.set_value(self.doctype, self.name, "per_ordered", self.per_ordered) -def update_completed_qty(doc, method): - if doc.doctype == "Stock Entry": + def update_requested_qty(self, mr_item_rows=None): + """update requested qty (before ordered_qty is updated)""" + from erpnext.stock.utils import get_bin + + def _update_requested_qty(item_code, warehouse): + requested_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0)) + from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr + where mr_item.item_code=%s and mr_item.warehouse=%s + and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) + + bin_doc = get_bin(item_code, warehouse) + bin_doc.indented_qty = flt(requested_qty[0][0]) if requested_qty else 0 + bin_doc.save() + + item_wh_list = [] + for d in self.get("indent_details"): + if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \ + and frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse: + item_wh_list.append([d.item_code, d.warehouse]) + + for item_code, warehouse in item_wh_list: + _update_requested_qty(item_code, warehouse) + +def update_completed_and_requested_qty(stock_entry, method): + if stock_entry.doctype == "Stock Entry": material_request_map = {} - for d in doc.get("mtn_details"): + for d in stock_entry.get("mtn_details"): if d.material_request: material_request_map.setdefault(d.material_request, []).append(d.material_request_item) - for mr_name, mr_items in material_request_map.items(): - mr_obj = frappe.get_doc("Material Request", mr_name) + for mr, mr_item_rows in material_request_map.items(): + if mr and mr_item_rows: + mr_obj = frappe.get_doc("Material Request", mr) - if mr_obj.status in ["Stopped", "Cancelled"]: - frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr_obj.name), - frappe.InvalidStatusError) + if mr_obj.status in ["Stopped", "Cancelled"]: + frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError) - _update_requested_qty(doc, mr_obj, mr_items) - - # update ordered percentage and qty - mr_obj.update_completed_qty(mr_items) - -def _update_requested_qty(doc, mr_obj, mr_items): - """update requested qty (before ordered_qty is updated)""" - from erpnext.stock.utils import update_bin - for mr_item_name in mr_items: - mr_item = mr_obj.get("indent_details", {"name": mr_item_name}) - se_detail = doc.get("mtn_details", {"material_request": mr_obj.name, - "material_request_item": mr_item_name}) - - if mr_item and se_detail: - mr_item = mr_item[0] - se_detail = se_detail[0] - mr_item.ordered_qty = flt(mr_item.ordered_qty) - mr_item.qty = flt(mr_item.qty) - se_detail.transfer_qty = flt(se_detail.transfer_qty) - - if se_detail.docstatus == 2 and mr_item.ordered_qty > mr_item.qty \ - and se_detail.transfer_qty == mr_item.ordered_qty: - add_indented_qty = mr_item.qty - elif se_detail.docstatus == 1 and \ - mr_item.ordered_qty + se_detail.transfer_qty > mr_item.qty: - add_indented_qty = mr_item.qty - mr_item.ordered_qty - else: - add_indented_qty = se_detail.transfer_qty - - update_bin({ - "item_code": se_detail.item_code, - "warehouse": se_detail.t_warehouse, - "indented_qty": (se_detail.docstatus==2 and 1 or -1) * add_indented_qty, - "posting_date": doc.posting_date, - }) + mr_obj.update_completed_qty(mr_item_rows) + mr_obj.update_requested_qty(mr_item_rows) def set_missing_values(source, target_doc): target_doc.run_method("set_missing_values") diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 04b7793b994..ab1d3ccd95e 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -58,12 +58,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(se.doctype, "Stock Entry") self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details"))) - def _test_requested_qty(self, qty1, qty2): - self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100", - "warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty1) - self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 200", - "warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty2) - def _insert_stock_entry(self, qty1, qty2): se = frappe.get_doc({ "company": "_Test Company", @@ -103,7 +97,8 @@ class TestMaterialRequest(unittest.TestCase): se.submit() def test_completed_qty_for_purchase(self): - frappe.db.sql("""delete from `tabBin`""") + existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") # submit material request of type Purchase mr = frappe.copy_doc(test_records[0]) @@ -115,8 +110,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) - # map a purchase order from erpnext.stock.doctype.material_request.material_request import make_purchase_order po_doc = make_purchase_order(mr.name) @@ -149,7 +142,12 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.per_ordered, 50) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5) - self._test_requested_qty(27.0, 1.5) + + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 27.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 1.5) po.cancel() # check if per complete is as expected @@ -158,11 +156,15 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, None) self.assertEquals(mr.get("indent_details")[1].ordered_qty, None) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_completed_qty_for_transfer(self): - frappe.db.sql("""delete from `tabBin`""") - frappe.db.sql("""delete from `tabStock Ledger Entry`""") + existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") # submit material request of type Purchase mr = frappe.copy_doc(test_records[0]) @@ -175,7 +177,11 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) from erpnext.stock.doctype.material_request.material_request import make_stock_entry @@ -226,7 +232,11 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5) - self._test_requested_qty(27.0, 1.5) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 27.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 1.5) # check if per complete is as expected for Stock Entry cancelled se.cancel() @@ -235,11 +245,15 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_completed_qty_for_over_transfer(self): - frappe.db.sql("""delete from `tabBin`""") - frappe.db.sql("""delete from `tabStock Ledger Entry`""") + existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") # submit material request of type Purchase mr = frappe.copy_doc(test_records[0]) @@ -252,8 +266,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) - # map a stock entry from erpnext.stock.doctype.material_request.material_request import make_stock_entry @@ -297,7 +309,12 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.per_ordered, 100) self.assertEquals(mr.get("indent_details")[0].ordered_qty, 60.0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 3.0) - self._test_requested_qty(0.0, 0.0) + + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2) # check if per complete is as expected for Stock Entry cancelled se.cancel() @@ -306,7 +323,11 @@ class TestMaterialRequest(unittest.TestCase): self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0) self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0) - self._test_requested_qty(54.0, 3.0) + current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") + current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") + + self.assertEquals(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEquals(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_incorrect_mapping_of_stock_entry(self): # submit material request of type Purchase @@ -348,5 +369,9 @@ class TestMaterialRequest(unittest.TestCase): mr.company = "_Test Company 1" self.assertRaises(InvalidWarehouseCompany, mr.insert) + def _get_requested_qty(self, item_code, warehouse): + return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty")) + + test_dependencies = ["Currency Exchange"] test_records = frappe.get_test_records('Material Request') diff --git a/erpnext/utilities/repost_stock.py b/erpnext/utilities/repost_stock.py index f1ba1798cee..b63493e1307 100644 --- a/erpnext/utilities/repost_stock.py +++ b/erpnext/utilities/repost_stock.py @@ -93,11 +93,11 @@ def get_reserved_qty(item_code, warehouse): return flt(reserved_qty[0][0]) if reserved_qty else 0 def get_indented_qty(item_code, warehouse): - indented_qty = frappe.db.sql("""select sum(pr_item.qty - ifnull(pr_item.ordered_qty, 0)) - from `tabMaterial Request Item` pr_item, `tabMaterial Request` pr - where pr_item.item_code=%s and pr_item.warehouse=%s - and pr_item.qty > ifnull(pr_item.ordered_qty, 0) and pr_item.parent=pr.name - and pr.status!='Stopped' and pr.docstatus=1""", (item_code, warehouse)) + indented_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0)) + from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr + where mr_item.item_code=%s and mr_item.warehouse=%s + and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) return flt(indented_qty[0][0]) if indented_qty else 0 From 941a965af4a62881bfe78758cc995377201afb54 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 4 Nov 2014 15:33:12 +0530 Subject: [PATCH 20/22] Ordered qty calculation logic --- .../purchase_common/purchase_common.py | 25 +----- .../doctype/purchase_order/purchase_order.py | 87 +++++++++---------- .../purchase_order/test_purchase_order.py | 26 ++++-- .../purchase_receipt/purchase_receipt.py | 35 +++----- 4 files changed, 75 insertions(+), 98 deletions(-) diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.py b/erpnext/buying/doctype/purchase_common/purchase_common.py index 68a6e93e687..2cf8673dc55 100644 --- a/erpnext/buying/doctype/purchase_common/purchase_common.py +++ b/erpnext/buying/doctype/purchase_common/purchase_common.py @@ -3,15 +3,13 @@ from __future__ import unicode_literals import frappe - -from frappe.utils import cstr, flt +from frappe.utils import flt from frappe import _ from erpnext.stock.doctype.item.item import get_last_purchase_details from erpnext.controllers.buying_controller import BuyingController class PurchaseCommon(BuyingController): - def update_last_purchase_rate(self, obj, is_submit): """updates last_purchase_rate in item table for each item""" @@ -123,27 +121,6 @@ class PurchaseCommon(BuyingController): else: chk_dupl_itm.append(f) - def get_qty(self, curr_doctype, ref_tab_fname, ref_tab_dn, ref_doc_tname, transaction, curr_parent_name): - # Get total Quantities of current doctype (eg. PR) except for qty of this transaction - #------------------------------ - # please check as UOM changes from Material Request - Purchase Order ,so doing following else uom should be same . - # i.e. in PO uom is NOS then in PR uom should be NOS - # but if in Material Request uom KG it can change in PO - - get_qty = (transaction == 'Material Request - Purchase Order') and 'qty * conversion_factor' or 'qty' - qty = frappe.db.sql("""select sum(%s) from `tab%s` where %s = %s and - docstatus = 1 and parent != %s""" % (get_qty, curr_doctype, ref_tab_fname, '%s', '%s'), - (ref_tab_dn, curr_parent_name)) - qty = qty and flt(qty[0][0]) or 0 - - # get total qty of ref doctype - #-------------------- - max_qty = frappe.db.sql("""select qty from `tab%s` where name = %s - and docstatus = 1""" % (ref_doc_tname, '%s'), ref_tab_dn) - max_qty = max_qty and flt(max_qty[0][0]) or 0 - - return cstr(qty)+'~~~'+cstr(max_qty) - def check_for_stopped_status(self, doctype, docname): stopped = frappe.db.sql("""select name from `tab%s` where name = %s and status = 'Stopped'""" % (doctype, '%s'), docname) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 0bfd3e558b7..9b473c19bdd 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -96,50 +96,45 @@ class PurchaseOrder(BuyingController): check_list.append(d.prevdoc_docname) pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname) + def update_requested_qty(self): + material_request_map = {} + for d in self.get("po_details"): + if d.prevdoc_doctype and d.prevdoc_doctype == "Material Request" and d.prevdoc_detail_docname: + material_request_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname) - def update_bin(self, is_submit, is_stopped = 0): - from erpnext.stock.utils import update_bin - pc_obj = frappe.get_doc('Purchase Common') - for d in self.get('po_details'): - #1. Check if is_stock_item == 'Yes' - if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes": - # this happens when item is changed from non-stock to stock item - if not d.warehouse: - continue + for mr, mr_item_rows in material_request_map.items(): + if mr and mr_item_rows: + mr_obj = frappe.get_doc("Material Request", mr) - ind_qty, po_qty = 0, flt(d.qty) * flt(d.conversion_factor) - if is_stopped: - po_qty = flt(d.qty) > flt(d.received_qty) and \ - flt( flt(flt(d.qty) - flt(d.received_qty))*flt(d.conversion_factor)) or 0 + if mr_obj.status in ["Stopped", "Cancelled"]: + frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError) - # No updates in Material Request on Stop / Unstop - if cstr(d.prevdoc_doctype) == 'Material Request' and not is_stopped: - # get qty and pending_qty of prevdoc - curr_ref_qty = pc_obj.get_qty(d.doctype, 'prevdoc_detail_docname', - d.prevdoc_detail_docname, 'Material Request Item', - 'Material Request - Purchase Order', self.name) - max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \ - flt(curr_ref_qty.split('~~~')[0]), 0 + mr_obj.update_requested_qty(mr_item_rows) - if flt(qty) + flt(po_qty) > flt(max_qty): - curr_qty = flt(max_qty) - flt(qty) - # special case as there is no restriction - # for Material Request - Purchase Order - curr_qty = curr_qty > 0 and curr_qty or 0 - else: - curr_qty = flt(po_qty) + def update_ordered_qty(self, po_item_rows=None): + """update requested qty (before ordered_qty is updated)""" + from erpnext.stock.utils import get_bin - ind_qty = -flt(curr_qty) + def _update_ordered_qty(item_code, warehouse): + ordered_qty = frappe.db.sql(""" + select sum((po_item.qty - ifnull(po_item.received_qty, 0))*po_item.conversion_factor) + from `tabPurchase Order Item` po_item, `tabPurchase Order` po + where po_item.item_code=%s and po_item.warehouse=%s + and po_item.qty > ifnull(po_item.received_qty, 0) and po_item.parent=po.name + and po.status!='Stopped' and po.docstatus=1""", (item_code, warehouse)) - # Update ordered_qty and indented_qty in bin - args = { - "item_code": d.item_code, - "warehouse": d.warehouse, - "ordered_qty": (is_submit and 1 or -1) * flt(po_qty), - "indented_qty": (is_submit and 1 or -1) * flt(ind_qty), - "posting_date": self.transaction_date - } - update_bin(args) + bin_doc = get_bin(item_code, warehouse) + bin_doc.ordered_qty = flt(ordered_qty[0][0]) if ordered_qty else 0 + bin_doc.save() + + item_wh_list = [] + for d in self.get("po_details"): + if (not po_item_rows or d.name in po_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \ + and frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse: + item_wh_list.append([d.item_code, d.warehouse]) + + for item_code, warehouse in item_wh_list: + _update_ordered_qty(item_code, warehouse) def check_modified_date(self): mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s", @@ -152,13 +147,11 @@ class PurchaseOrder(BuyingController): def update_status(self, status): self.check_modified_date() - # step 1:=> Set Status frappe.db.set(self,'status',cstr(status)) - # step 2:=> Update Bin - self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1) + self.update_requested_qty() + self.update_ordered_qty() - # step 3:=> Acknowledge user msgprint(_("Status of {0} {1} is now {2}").format(self.doctype, self.name, status)) def on_submit(self): @@ -167,7 +160,8 @@ class PurchaseOrder(BuyingController): purchase_controller = frappe.get_doc("Purchase Common") self.update_prevdoc_status() - self.update_bin(is_submit = 1, is_stopped = 0) + self.update_requested_qty() + self.update_ordered_qty() frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.grand_total) @@ -192,8 +186,13 @@ class PurchaseOrder(BuyingController): throw(_("Purchase Invoice {0} is already submitted").format(", ".join(submitted))) frappe.db.set(self,'status','Cancelled') + self.update_prevdoc_status() - self.update_bin( is_submit = 0, is_stopped = 0) + + # Must be called after updating ordered qty in Material Request + self.update_requested_qty() + self.update_ordered_qty() + pc_obj.update_last_purchase_rate(self, is_submit = 0) def on_update(self): diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 842409f7152..fc31a9a4b03 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -29,8 +29,7 @@ class TestPurchaseOrder(unittest.TestCase): frappe.get_doc(pr).insert() def test_ordered_qty(self): - frappe.db.sql("delete from tabBin") - + existing_ordered_qty = self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC") from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt po = frappe.copy_doc(test_records[0]).insert() @@ -43,8 +42,7 @@ class TestPurchaseOrder(unittest.TestCase): po.get("po_details")[0].item_code = "_Test Item" po.submit() - self.assertEquals(frappe.db.get_value("Bin", {"item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC"}, "ordered_qty"), 10) + self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 10) pr = make_purchase_receipt(po.name) @@ -56,8 +54,9 @@ class TestPurchaseOrder(unittest.TestCase): pr.insert() pr.submit() - self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 6.0) + po.load_from_db() + self.assertEquals(po.get("po_details")[0].received_qty, 4) + self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 6) frappe.db.set_value('Item', '_Test Item', 'tolerance', 50) @@ -68,8 +67,16 @@ class TestPurchaseOrder(unittest.TestCase): pr1.insert() pr1.submit() - self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item", - "warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 0.0) + po.load_from_db() + self.assertEquals(po.get("po_details")[0].received_qty, 12) + self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty) + + pr1.load_from_db() + pr1.cancel() + + po.load_from_db() + self.assertEquals(po.get("po_details")[0].received_qty, 4) + self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 6) def test_make_purchase_invoice(self): from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice @@ -111,6 +118,9 @@ class TestPurchaseOrder(unittest.TestCase): from erpnext.controllers.tests.test_recurring_document import test_recurring_document test_recurring_document(self, test_records) + def _get_ordered_qty(self, item_code, warehouse): + return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "ordered_qty")) + test_dependencies = ["BOM", "Item Price"] diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 13495a03777..f38ee5d5deb 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -8,7 +8,6 @@ from frappe.utils import cstr, flt, cint from frappe import _ import frappe.defaults -from erpnext.stock.utils import update_bin from erpnext.controllers.buying_controller import BuyingController @@ -157,29 +156,19 @@ class PurchaseReceipt(BuyingController): self.make_sl_entries(sl_entries) def update_ordered_qty(self): - stock_items = self.get_stock_items() + po_map = {} for d in self.get("purchase_receipt_details"): - if d.item_code in stock_items and d.warehouse \ - and cstr(d.prevdoc_doctype) == 'Purchase Order': + if d.prevdoc_doctype and d.prevdoc_doctype == "Purchase Order" and d.prevdoc_detail_docname: + po_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname) - already_received_qty = self.get_already_received_qty(d.prevdoc_docname, - d.prevdoc_detail_docname) - po_qty, ordered_warehouse = self.get_po_qty_and_warehouse(d.prevdoc_detail_docname) + for po, po_item_rows in po_map.items(): + if po and po_item_rows: + po_obj = frappe.get_doc("Purchase Order", po) - if not ordered_warehouse: - frappe.throw(_("Warehouse is missing in Purchase Order")) + if po_obj.status in ["Stopped", "Cancelled"]: + frappe.throw(_("Material Request {0} is cancelled or stopped").format(po), frappe.InvalidStatusError) - if already_received_qty + d.qty > po_qty: - ordered_qty = - (po_qty - already_received_qty) * flt(d.conversion_factor) - else: - ordered_qty = - flt(d.qty) * flt(d.conversion_factor) - - update_bin({ - "item_code": d.item_code, - "warehouse": ordered_warehouse, - "posting_date": self.posting_date, - "ordered_qty": flt(ordered_qty) if self.docstatus==1 else -flt(ordered_qty) - }) + po_obj.update_ordered_qty(po_item_rows) def get_already_received_qty(self, po, po_detail): qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item` @@ -265,11 +254,13 @@ class PurchaseReceipt(BuyingController): frappe.db.set(self,'status','Cancelled') - self.update_ordered_qty() - self.update_stock_ledger() self.update_prevdoc_status() + + # Must be called after updating received qty in PO + self.update_ordered_qty() + pc_obj.update_last_purchase_rate(self, 0) self.make_gl_entries_on_cancel() From 9114c268575543fa8d342ae7caba7fbc1e765e83 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 4 Nov 2014 15:33:41 +0530 Subject: [PATCH 21/22] Patch to recalculate requested qty and ordered qty for all items --- erpnext/patches.txt | 1 + .../v4_2/update_requested_and_ordered_qty.py | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 erpnext/patches/v4_2/update_requested_and_ordered_qty.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d75a99c3f0a..d9a6b649f1d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -87,3 +87,4 @@ execute:frappe.delete_doc("DocType", "Purchase Request") execute:frappe.delete_doc("DocType", "Purchase Request Item") erpnext.patches.v4_2.recalculate_bom_cost erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions +erpnext.patches.v4_2.update_requested_and_ordered_qty diff --git a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py new file mode 100644 index 00000000000..e44133e6a27 --- /dev/null +++ b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py @@ -0,0 +1,24 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty, get_ordered_qty + + count=0 + for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse from + (select item_code, warehouse from tabBin + union + select item_code, warehouse from `tabStock Ledger Entry`) a"""): + try: + count += 1 + update_bin_qty(item_code, warehouse, { + "indented_qty": get_indented_qty(item_code, warehouse), + "ordered_qty": get_ordered_qty(item_code, warehouse) + }) + if count / 200 == 0: + frappe.db.commit() + except: + frappe.db.rollback() From 5086ef249978ec9b6d81493705d94ecd1a3dd9a9 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 5 Nov 2014 12:33:29 +0530 Subject: [PATCH 22/22] Update update_requested_and_ordered_qty.py --- erpnext/patches/v4_2/update_requested_and_ordered_qty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py index e44133e6a27..d6cabd8c236 100644 --- a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py +++ b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py @@ -18,7 +18,7 @@ def execute(): "indented_qty": get_indented_qty(item_code, warehouse), "ordered_qty": get_ordered_qty(item_code, warehouse) }) - if count / 200 == 0: + if count % 200 == 0: frappe.db.commit() except: frappe.db.rollback()