From 831207ff54319e5d31e85658788f6139a7bd3eeb Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 14:15:48 +0530 Subject: [PATCH 01/10] incoming rate for sales return as per delivery note outgoing rate --- stock/doctype/stock_entry/stock_entry.js | 3 +- stock/doctype/stock_entry/stock_entry.py | 48 ++++++++++++++----- .../stock_reconciliation.txt | 7 +-- stock/utils.py | 41 +++++++++++----- 4 files changed, 70 insertions(+), 29 deletions(-) diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js index bb55622453..6613ec43b3 100644 --- a/stock/doctype/stock_entry/stock_entry.js +++ b/stock/doctype/stock_entry/stock_entry.js @@ -226,7 +226,8 @@ cur_frm.cscript.s_warehouse = function(doc, cdt, cdn) { 'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse), 'transfer_qty' : d.transfer_qty, 'serial_no' : d.serial_no, - 'bom_no' : d.bom_no + 'bom_no' : d.bom_no, + 'qty' : d.s_warehouse ? -1* d.qty : d.qty } get_server_fields('get_warehouse_details', JSON.stringify(args), 'mtn_details', doc, cdt, cdn, 1); diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index eaf796655d..12e69e6e94 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -25,6 +25,8 @@ from webnotes.model.code import get_obj from webnotes import msgprint, _ from stock.utils import get_incoming_rate from stock.stock_ledger import get_previous_sle +import json + sql = webnotes.conn.sql @@ -157,23 +159,46 @@ class DocType(TransactionBase): def get_stock_and_rate(self): """get stock and incoming rate on posting date""" for d in getlist(self.doclist, 'mtn_details'): - args = { + args = webnotes._dict({ "item_code": d.item_code, "warehouse": d.s_warehouse or d.t_warehouse, "posting_date": self.doc.posting_date, "posting_time": self.doc.posting_time, - "qty": d.transfer_qty, + "qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty, "serial_no": d.serial_no, - "bom_no": d.bom_no - } + "bom_no": d.bom_no, + }) # get actual stock at source warehouse d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0 # get incoming rate - if not flt(d.incoming_rate): - d.incoming_rate = get_incoming_rate(args) + if not flt(d.incoming_rate) or self.doc.purpose == "Sales Return": + d.incoming_rate = self.get_incoming_rate(args) d.amount = flt(d.qty) * flt(d.incoming_rate) + + def get_incoming_rate(self, args): + if self.doc.purpose == "Sales Return" and \ + (self.doc.delivery_note_no or self.doc.sales_invoice_no): + sle = webnotes.conn.sql("""select name, posting_date, posting_time, + actual_qty, stock_value from `tabStock Ledger Entry` + where voucher_type = %s and voucher_no = %s and + item_code = %s and ifnull(is_cancelled, 'No') = 'No' limit 1""", + ((self.doc.delivery_note_no and "Delivery Note" or "Sales Invoice"), + self.doc.delivery_note_no or self.doc.sales_invoice_no, args.item_code), as_dict=1) + if sle: + args.update({ + "posting_date": sle[0].posting_date, + "posting_time": sle[0].posting_time, + "sle": sle[0].name + }) + previous_sle = get_previous_sle(args) + incoming_rate = (flt(sle[0].stock_value) - flt(previous_sle.get("stock_value"))) / \ + flt(sle[0].actual_qty) + else: + incoming_rate = get_incoming_rate(args) + + return incoming_rate def validate_incoming_rate(self): for d in getlist(self.doclist, 'mtn_details'): @@ -264,8 +289,7 @@ class DocType(TransactionBase): pro_obj.doc.save() def get_item_details(self, arg): - import json - arg, actual_qty, in_rate = json.loads(arg), 0, 0 + arg = json.loads(arg) item = sql("""select stock_uom, description, item_name from `tabItem` where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00' @@ -305,16 +329,16 @@ class DocType(TransactionBase): return ret def get_warehouse_details(self, args): - import json - args, actual_qty, in_rate = json.loads(args), 0, 0 + args = json.loads(args) args.update({ "posting_date": self.doc.posting_date, - "posting_time": self.doc.posting_time + "posting_time": self.doc.posting_time, }) + args = webnotes._dict(args) ret = { "actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0, - "incoming_rate" : get_incoming_rate(args) + "incoming_rate" : self.get_incoming_rate(args) } return ret diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.txt b/stock/doctype/stock_reconciliation/stock_reconciliation.txt index 58384cc42f..145d6fa9ad 100644 --- a/stock/doctype/stock_reconciliation/stock_reconciliation.txt +++ b/stock/doctype/stock_reconciliation/stock_reconciliation.txt @@ -2,9 +2,9 @@ { "owner": "Administrator", "docstatus": 0, - "creation": "2013-01-14 15:14:40", + "creation": "2013-01-15 12:28:57", "modified_by": "Administrator", - "modified": "2013-01-15 12:25:13" + "modified": "2013-01-16 13:59:28" }, { "allow_attach": 0, @@ -13,6 +13,7 @@ "search_fields": "posting_date", "module": "Stock", "doctype": "DocType", + "read_only_onload": 0, "autoname": "SR/.######", "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.", "allow_email": 1, @@ -33,7 +34,7 @@ "read": 1, "cancel": 1, "name": "__common__", - "amend": 0, + "amend": 1, "create": 1, "doctype": "DocPerm", "submit": 1, diff --git a/stock/utils.py b/stock/utils.py index a65406beb0..e05e07aa38 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -78,12 +78,11 @@ def get_incoming_rate(args): valuation_method = get_valuation_method(args.get("item_code")) previous_sle = get_previous_sle(args) if valuation_method == 'FIFO': - # get rate based on the last item value? - if args.get("qty"): - if not previous_sle: - return 0.0 - stock_queue = json.loads(previous_sle.get('stock_queue', '[]')) - in_rate = stock_queue and get_fifo_rate(stock_queue) or 0 + if not previous_sle: + return 0.0 + previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]')) + in_rate = previous_stock_queue and \ + get_fifo_rate(previous_stock_queue, args.get("qty")) or 0 elif valuation_method == 'Moving Average': in_rate = previous_sle.get('valuation_rate') or 0 return in_rate @@ -104,13 +103,29 @@ def get_valuation_method(item_code): val_method = get_defaults().get('valuation_method', 'FIFO') return val_method -def get_fifo_rate(stock_queue): - """get FIFO (average) Rate from Stack""" - if not stock_queue: - return 0.0 - - total = sum(f[0] for f in stock_queue) - return total and sum(f[0] * f[1] for f in stock_queue) / flt(total) or 0.0 +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 + else: + outgoing_cost = 0 + qty_to_pop = abs(qty) + while qty_to_pop: + batch = previous_stock_queue[0] + if 0 < batch[0] <= qty_to_pop: + # if batch qty > 0 + # not enough or exactly same qty in current batch, clear batch + outgoing_cost += flt(batch[0]) * flt(batch[1]) + qty_to_pop -= batch[0] + previous_stock_queue.pop(0) + else: + # all from current batch + outgoing_cost += flt(qty_to_pop) * flt(batch[1]) + batch[0] -= qty_to_pop + qty_to_pop = 0 + + return outgoing_cost / abs(qty) def get_valid_serial_nos(sr_nos, qty=0, item_code=''): """split serial nos, validate and return list of valid serial nos""" From d11e9d66195eeb6e9f91ac58b1438437d5cf9df4 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 16:51:58 +0530 Subject: [PATCH 02/10] removed time_to_ampm and time_to_hhmm function --- .../production_planning_tool.py | 90 ++++++++++++++++--- .../production_planning_tool.txt | 48 ++++++---- .../maintenance_visit_list.js | 5 +- utilities/page/calendar/calendar.js | 13 +-- 4 files changed, 113 insertions(+), 43 deletions(-) diff --git a/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/manufacturing/doctype/production_planning_tool/production_planning_tool.py index adc80918e1..fa08789b91 100644 --- a/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -16,7 +16,7 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import cstr, flt, nowdate, get_defaults +from webnotes.utils import cstr, flt, nowdate from webnotes.model.doc import addchild, Document from webnotes.model.wrapper import getlist from webnotes.model.code import get_obj @@ -210,7 +210,7 @@ class DocType: "wip_warehouse" : "", "fg_warehouse" : "", "status" : "Draft", - "fiscal_year" : get_defaults()["fiscal_year"] + "fiscal_year" : webnotes.conn.get_default("fiscal_year") } return bom_dict, item_dict @@ -239,18 +239,22 @@ class DocType: return self.get_csv() def get_raw_materials(self, bom_dict): - """ Get raw materials considering sub-assembly items """ + """ Get raw materials considering sub-assembly items + { + "item_code": [qty_required, description, stock_uom] + } + """ for bom in bom_dict: if self.doc.use_multi_level_bom: # get all raw materials with sub assembly childs fl_bom_items = sql(""" select item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty, - description, stock_uom + description, stock_uom, min_order_qty from ( select distinct fb.name, fb.description, fb.item_code, - fb.qty_consumed_per_unit, fb.stock_uom + fb.qty_consumed_per_unit, fb.stock_uom, it.min_order_qty from `tabBOM Explosion Item` fb,`tabItem` it where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' @@ -263,18 +267,21 @@ class DocType: # Get all raw materials considering SA items as raw materials, # so no childs of SA items fl_bom_items = sql(""" - select item_code, ifnull(sum(qty_consumed_per_unit), 0) * '%s', - description, stock_uom - from `tabBOM Item` - where parent = '%s' and docstatus < 2 + select bom_item.item_code, + ifnull(sum(bom_item.qty_consumed_per_unit), 0) * %s, + bom_item.description, bom_item.stock_uom, item.min_order_qty + from `tabBOM Item` bom_item, tabItem item + where bom_item.parent = %s and bom_item.docstatus < 2 + and bom_item.item_code = item.name group by item_code - """ % (flt(bom_dict[bom]), bom)) + """, (flt(bom_dict[bom]), bom)) self.make_items_dict(fl_bom_items) def make_items_dict(self, item_list): for i in item_list: - self.item_dict[i[0]] = [(flt(self.item_dict.get(i[0], [0])[0]) + flt(i[1])), i[2], i[3]] + self.item_dict[i[0]] = [(flt(self.item_dict.get(i[0], [0])[0]) + flt(i[1])), + i[2], i[3], i[4]] def get_csv(self): @@ -291,4 +298,63 @@ class DocType: if item_qty: item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty]) - return item_list \ No newline at end of file + return item_list + + def raise_purchase_request(self): + def _get_projected_qty(items): + item_projected_qty = webnotes.conn.sql("""select item_code, sum(projected_qty) + from `tabBin` where item_code in (%s) group by item_code""" % + (", ".join(["%s"]*len(items)),), tuple(items)) + + return dict(item_projected_qty) + + item_dict = self.get_raw_materials() + item_projected_qty = _get_projected_qty(item_dict.keys()) + + from accounts.utils import get_fiscal_year + fiscal_year = get_fiscal_year(nowdate()) + + items_to_be_requested = [] + for item in item_dict: + if flt(item_dict[item][0]) > item_projected_qty[item]: + # shortage + requested_qty = flt(item_dict[item][0]) - item_projected_qty[item] + # comsider minimum order qty + requested_qty = requested_qty > flt(item_dict[item][3]) and \ + requested_qty or flt(item_dict[item][3]) + items_to_be_requested.append({ + "item_code": item, + "qty": requested_qty, + "description": item_dict[item][1], + "stock_uom": item_dict[item][2] + }) + webnotes.errprint(items_to_be_requested) + self.insert_purchase_request(items_to_be_requested, fiscal_year) + + def insert_purchase_request(self, items, fiscal_year): + for item in items: + item_wrapper = webnotes.model_wrapper("Item", args.item_code) + pr = [ + { + "doctype": "Purchase Request", + "naming_series": "IDT", + "transaction_date": nowdate(), + "status": "Draft", + "company": self.doc.company, + "fiscal_year": fiscal_year, + "requested_by": webnotes.session.user, + "remark": "Automatically raised from Production Planning Tool" + }, + { + "doctype": "Purchase Request Item", + "item_code": item.item_code, + "item_name": item_wrapper.doc.item_name, + "description": item.description, + "uom": item.stock_uom, + "item_group": item_wrapper.doc.item_group, + "brand": item_wrapper.doc.brand, + "qty": item.qty, + + + } + ] \ No newline at end of file diff --git a/manufacturing/doctype/production_planning_tool/production_planning_tool.txt b/manufacturing/doctype/production_planning_tool/production_planning_tool.txt index dd7acfdb14..7eb0a2da11 100644 --- a/manufacturing/doctype/production_planning_tool/production_planning_tool.txt +++ b/manufacturing/doctype/production_planning_tool/production_planning_tool.txt @@ -2,9 +2,9 @@ { "owner": "jai@webnotestech.com", "docstatus": 0, - "creation": "2012-12-14 10:15:16", + "creation": "2013-01-16 14:48:56", "modified_by": "Administrator", - "modified": "2012-12-14 11:37:40" + "modified": "2013-01-16 15:46:26" }, { "read_only": 1, @@ -28,8 +28,10 @@ "parent": "Production Planning Tool", "read": 1, "create": 1, + "submit": 0, "doctype": "DocPerm", "write": 1, + "report": 0, "parenttype": "DocType", "permlevel": 0, "parentfield": "permissions" @@ -68,9 +70,9 @@ { "doctype": "DocField", "label": "Company", + "reqd": 1, "fieldname": "company", "fieldtype": "Link", - "reqd": 1, "options": "Company" }, { @@ -154,10 +156,19 @@ "fieldtype": "Section Break", "options": "Simple" }, + { + "description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.", + "default": "1", + "doctype": "DocField", + "label": "Use Multi-Level BOM", + "reqd": 0, + "fieldname": "use_multi_level_bom", + "fieldtype": "Check" + }, { "doctype": "DocField", "width": "50%", - "fieldname": "column_break5", + "fieldname": "cb5", "fieldtype": "Column Break" }, { @@ -170,18 +181,9 @@ }, { "doctype": "DocField", - "width": "50%", - "fieldname": "column_break6", - "fieldtype": "Column Break" - }, - { - "description": "If checked, BOM for sub-assembly items will be considered for getting raw materials. Otherwise, all sub-assembly items will be treated as a raw material.", - "default": "1", - "doctype": "DocField", - "label": "Use Multi-Level BOM", - "fieldname": "use_multi_level_bom", - "fieldtype": "Check", - "reqd": 0 + "fieldname": "sb5", + "fieldtype": "Section Break", + "options": "Simple" }, { "description": "Download a report containing all raw materials with their latest inventory status", @@ -191,8 +193,18 @@ "fieldtype": "Button" }, { - "role": "System Manager", - "doctype": "DocPerm" + "doctype": "DocField", + "width": "50%", + "fieldname": "column_break6", + "fieldtype": "Column Break" + }, + { + "description": "Raise Purchase Request automatically for items which are \"Out of Stock\" considering already requested, already ordered qty and minimum order qty", + "doctype": "DocField", + "label": "Raise Purchase Request", + "fieldname": "raise_purchase_request", + "fieldtype": "Button", + "options": "raise_purchase_request" }, { "role": "Manufacturing User", diff --git a/support/doctype/maintenance_visit/maintenance_visit_list.js b/support/doctype/maintenance_visit/maintenance_visit_list.js index 654f455fd0..6dc5daccb3 100644 --- a/support/doctype/maintenance_visit/maintenance_visit_list.js +++ b/support/doctype/maintenance_visit/maintenance_visit_list.js @@ -13,15 +13,12 @@ wn.doclistviews['Maintenance Visit'] = wn.views.ListView.extend({ ]); this.stats = this.stats.concat(['completion_status', 'company']); - //this.show_hide_check_column(); }, prepare_data: function(data) { this._super(data); data.mntc_date = wn.datetime.str_to_user(data.mntc_date); - data.mntc_time = wn.datetime.time_to_ampm(data.mntc_time); - data.date_time = "on " + data.mntc_date + " at " + - data.mntc_time[0] + ":" + data.mntc_time[1] + " " + data.mntc_time[2]; + data.date_time = "on " + data.mntc_date + " at " + data.mntc_time; data.customer_name = data.customer_name + " " + data.date_time; data.completion_status = data.completion_status + (data.maintenance_type ? " [" + data.maintenance_type + "]": ""); diff --git a/utilities/page/calendar/calendar.js b/utilities/page/calendar/calendar.js index d62dc68a86..5e59f1f4c8 100644 --- a/utilities/page/calendar/calendar.js +++ b/utilities/page/calendar/calendar.js @@ -94,13 +94,11 @@ Calendar.prototype.show_event = function(ev, cal_ev) { d.onshow = function() { // heading var c = me.selected_date; - var tmp = time_to_ampm(this.ev.event_hour); - tmp = tmp[0]+':'+tmp[1]+' '+tmp[2]; this.widgets['Heading'].innerHTML = '
' + erpnext.calendar.weekdays[c.getDay()] + ', ' + c.getDate() + ' ' + month_list_full[c.getMonth()] + ' ' + c.getFullYear() - + ' - '+tmp+'
'; + + ' - '+this.ev.event_hour+''; // set this.widgets['Description'].value = cstr(this.ev.description); @@ -175,7 +173,7 @@ Calendar.prototype.add_event = function() { ev = locals['Event'][ev]; ev.event_date = dateutil.obj_to_str(this.selected_date); - ev.event_hour = this.selected_hour+':00'; + ev.event_hour = this.selected_hour+':00:00'; ev.event_type = 'Private'; this.show_event(ev); @@ -447,8 +445,7 @@ Calendar.DayView.prototype.create_table = function() { for(var j=0;j<2;j++) { var cell = r.insertCell(j); if(j==0) { - var tmp = time_to_ampm((i)+':00'); - cell.innerHTML = tmp[0]+':'+tmp[1]+' '+tmp[2]; + cell.innerHTML = i+':00:00'; $w(cell, '10%'); } else { cell.viewunit = new Calendar.DayViewUnit(cell); @@ -510,9 +507,7 @@ Calendar.WeekView.prototype.create_table = function() { for(var j=0;j<8;j++) { var cell = r.insertCell(j); if(j==0) { - var tmp = time_to_ampm(i+':00'); - cell.innerHTML = tmp[0]+':'+tmp[1]+' '+tmp[2]; - + cell.innerHTML = i+':00:00'; $w(cell, '10%'); } else { cell.viewunit = new Calendar.WeekViewUnit(cell); From 6b1f21d1bb4053362aaa3a31915ececb0409589e Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 16 Jan 2013 17:17:17 +0530 Subject: [PATCH 03/10] fixes in get_incoming_rate --- stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stock/utils.py b/stock/utils.py index e05e07aa38..e7702a89fd 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -82,7 +82,7 @@ def get_incoming_rate(args): return 0.0 previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]')) in_rate = previous_stock_queue and \ - get_fifo_rate(previous_stock_queue, args.get("qty")) or 0 + get_fifo_rate(previous_stock_queue, args.get("qty") or 0) or 0 elif valuation_method == 'Moving Average': in_rate = previous_sle.get('valuation_rate') or 0 return in_rate From 64d7c4b2d56db4698ab1dd590f755a7e6bb0b215 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2013 12:22:59 +0530 Subject: [PATCH 04/10] fixes in get_incoming_rate --- stock/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stock/utils.py b/stock/utils.py index e7702a89fd..46dd9d01a9 100644 --- a/stock/utils.py +++ b/stock/utils.py @@ -111,7 +111,7 @@ def get_fifo_rate(previous_stock_queue, qty): else: outgoing_cost = 0 qty_to_pop = abs(qty) - while qty_to_pop: + while qty_to_pop and previous_stock_queue: batch = previous_stock_queue[0] if 0 < batch[0] <= qty_to_pop: # if batch qty > 0 @@ -124,8 +124,8 @@ def get_fifo_rate(previous_stock_queue, qty): outgoing_cost += flt(qty_to_pop) * flt(batch[1]) batch[0] -= qty_to_pop qty_to_pop = 0 - - return outgoing_cost / abs(qty) + # if queue gets blank and qty_to_pop remaining, get average rate of full queue + return outgoing_cost / abs(qty) - qty_to_pop def get_valid_serial_nos(sr_nos, qty=0, item_code=''): """split serial nos, validate and return list of valid serial nos""" From 35b6d94c0660c82c2db43c5346621ea7580acb62 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2013 12:31:09 +0530 Subject: [PATCH 05/10] automatic purchase request from production planning tool --- .../production_plan_item.txt | 4 +- .../production_planning_tool.py | 125 ++++++++++-------- .../production_planning_tool.txt | 11 +- 3 files changed, 84 insertions(+), 56 deletions(-) diff --git a/manufacturing/doctype/production_plan_item/production_plan_item.txt b/manufacturing/doctype/production_plan_item/production_plan_item.txt index 6ae2a04f5a..2cb2a59f8b 100644 --- a/manufacturing/doctype/production_plan_item/production_plan_item.txt +++ b/manufacturing/doctype/production_plan_item/production_plan_item.txt @@ -2,9 +2,9 @@ { "owner": "Administrator", "docstatus": 0, - "creation": "2012-12-20 18:14:55", + "creation": "2013-01-16 14:48:56", "modified_by": "Administrator", - "modified": "2012-12-24 19:37:34" + "modified": "2013-01-17 11:21:46" }, { "istable": 1, diff --git a/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/manufacturing/doctype/production_planning_tool/production_planning_tool.py index fa08789b91..a6c8458f3f 100644 --- a/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -16,7 +16,7 @@ from __future__ import unicode_literals import webnotes -from webnotes.utils import cstr, flt, nowdate +from webnotes.utils import cstr, flt, cint, nowdate, add_days from webnotes.model.doc import addchild, Document from webnotes.model.wrapper import getlist from webnotes.model.code import get_obj @@ -275,7 +275,6 @@ class DocType: and bom_item.item_code = item.name group by item_code """, (flt(bom_dict[bom]), bom)) - self.make_items_dict(fl_bom_items) def make_items_dict(self, item_list): @@ -301,60 +300,82 @@ class DocType: return item_list def raise_purchase_request(self): - def _get_projected_qty(items): - item_projected_qty = webnotes.conn.sql("""select item_code, sum(projected_qty) - from `tabBin` where item_code in (%s) group by item_code""" % - (", ".join(["%s"]*len(items)),), tuple(items)) - - return dict(item_projected_qty) + """ + Raise Purchase Request if projected qty is less than qty required + Requested qty should be shortage qty considering minimum order qty + """ + if not self.doc.purchase_request_for_warehouse: + webnotes.msgprint("Please enter Warehouse for which Purchase Request will be raised", + raise_exception=1) - item_dict = self.get_raw_materials() - item_projected_qty = _get_projected_qty(item_dict.keys()) + bom_dict = self.get_distinct_items_and_boms()[0] + self.get_raw_materials(bom_dict) + item_projected_qty = self.get_projected_qty() from accounts.utils import get_fiscal_year - fiscal_year = get_fiscal_year(nowdate()) + fiscal_year = get_fiscal_year(nowdate())[0] - items_to_be_requested = [] - for item in item_dict: - if flt(item_dict[item][0]) > item_projected_qty[item]: + items_to_be_requested = webnotes._dict() + for item in self.item_dict: + if flt(self.item_dict[item][0]) > item_projected_qty[item]: # shortage - requested_qty = flt(item_dict[item][0]) - item_projected_qty[item] + requested_qty = flt(self.item_dict[item][0]) - item_projected_qty[item] # comsider minimum order qty - requested_qty = requested_qty > flt(item_dict[item][3]) and \ - requested_qty or flt(item_dict[item][3]) - items_to_be_requested.append({ - "item_code": item, - "qty": requested_qty, - "description": item_dict[item][1], - "stock_uom": item_dict[item][2] - }) - webnotes.errprint(items_to_be_requested) - self.insert_purchase_request(items_to_be_requested, fiscal_year) + requested_qty = requested_qty > flt(self.item_dict[item][3]) and \ + requested_qty or flt(self.item_dict[item][3]) + items_to_be_requested[item] = requested_qty - def insert_purchase_request(self, items, fiscal_year): - for item in items: - item_wrapper = webnotes.model_wrapper("Item", args.item_code) - pr = [ - { - "doctype": "Purchase Request", - "naming_series": "IDT", - "transaction_date": nowdate(), - "status": "Draft", - "company": self.doc.company, - "fiscal_year": fiscal_year, - "requested_by": webnotes.session.user, - "remark": "Automatically raised from Production Planning Tool" - }, - { - "doctype": "Purchase Request Item", - "item_code": item.item_code, - "item_name": item_wrapper.doc.item_name, - "description": item.description, - "uom": item.stock_uom, - "item_group": item_wrapper.doc.item_group, - "brand": item_wrapper.doc.brand, - "qty": item.qty, - - - } - ] \ No newline at end of file + self.insert_purchase_request(items_to_be_requested, fiscal_year) + + def get_projected_qty(self): + items = self.item_dict.keys() + item_projected_qty = webnotes.conn.sql("""select item_code, sum(projected_qty) + from `tabBin` where item_code in (%s) group by item_code""" % + (", ".join(["%s"]*len(items)),), tuple(items)) + + return dict(item_projected_qty) + + def insert_purchase_request(self, items_to_be_requested, fiscal_year): + purchase_request_list = [] + if items_to_be_requested: + for item in items_to_be_requested: + item_wrapper = webnotes.model_wrapper("Item", item) + pr_doclist = [ + { + "doctype": "Purchase Request", + "__islocal": 1, + "naming_series": "IDT", + "transaction_date": nowdate(), + "status": "Draft", + "company": self.doc.company, + "fiscal_year": fiscal_year, + "requested_by": webnotes.session.user, + "remark": "Automatically raised from Production Planning Tool" + }, + { + "doctype": "Purchase Request Item", + "__islocal": 1, + "parentfield": "indent_details", + "item_code": item, + "item_name": item_wrapper.doc.item_name, + "description": item_wrapper.doc.description, + "uom": item_wrapper.doc.stock_uom, + "item_group": item_wrapper.doc.item_group, + "brand": item_wrapper.doc.brand, + "qty": items_to_be_requested[item], + "schedule_date": add_days(nowdate(), cint(item_wrapper.doc.lead_time_days)), + "warehouse": self.doc.purchase_request_for_warehouse + } + ] + pr_wrapper = webnotes.model_wrapper(pr_doclist) + pr_wrapper.ignore_permissions = 1 + pr_wrapper.submit() + purchase_request_list.append(pr_wrapper.doc.name) + + if purchase_request_list: + pur_req = ["""%s""" % \ + (p, p) for p in purchase_request_list] + webnotes.msgprint("Following Purchase Request created successfully: \n%s" % + "\n".join(pur_req)) + else: + webnotes.msgprint("Nothing to request") \ No newline at end of file diff --git a/manufacturing/doctype/production_planning_tool/production_planning_tool.txt b/manufacturing/doctype/production_planning_tool/production_planning_tool.txt index 7eb0a2da11..d4a0d94774 100644 --- a/manufacturing/doctype/production_planning_tool/production_planning_tool.txt +++ b/manufacturing/doctype/production_planning_tool/production_planning_tool.txt @@ -4,7 +4,7 @@ "docstatus": 0, "creation": "2013-01-16 14:48:56", "modified_by": "Administrator", - "modified": "2013-01-16 15:46:26" + "modified": "2013-01-17 11:39:55" }, { "read_only": 1, @@ -199,7 +199,14 @@ "fieldtype": "Column Break" }, { - "description": "Raise Purchase Request automatically for items which are \"Out of Stock\" considering already requested, already ordered qty and minimum order qty", + "doctype": "DocField", + "label": "Purchase Request For Warehouse", + "fieldname": "purchase_request_for_warehouse", + "fieldtype": "Link", + "options": "Warehouse" + }, + { + "description": "Items to be requested which are \"Out of Stock\" considering all warehouses based on projected qty and minimum order qty", "doctype": "DocField", "label": "Raise Purchase Request", "fieldname": "raise_purchase_request", From feee2520933ca46e5934eef91d1a9776f95c32fe Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2013 13:03:30 +0530 Subject: [PATCH 06/10] fixes in website page_name for item --- stock/doctype/item/item.py | 2 +- website/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stock/doctype/item/item.py b/stock/doctype/item/item.py index 38ff797291..cca814ff08 100644 --- a/stock/doctype/item/item.py +++ b/stock/doctype/item/item.py @@ -77,7 +77,7 @@ class DocType: def update_website(self): from website.utils import update_page_name - update_page_name(self.doc, self.doc.item_name) + update_page_name(self.doc, self.doc.name + " " + self.doc.item_name) from website.helpers.product import invalidate_cache_for invalidate_cache_for(self.doc.item_group) diff --git a/website/utils.py b/website/utils.py index 96fbdc8dc9..4dafd08c05 100644 --- a/website/utils.py +++ b/website/utils.py @@ -97,7 +97,7 @@ def page_name(title): import re name = title.lower() name = re.sub('[~!@#$%^&*()<>,."\']', '', name) - return '-'.join(name.split()[:8]) + return '-'.join(name.split()) def update_page_name(doc, title): """set page_name and check if it is unique""" From 80c681294ca12b17e399054e98930ab82fd2554a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2013 13:24:51 +0530 Subject: [PATCH 07/10] fixes in website page naming --- website/utils.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/website/utils.py b/website/utils.py index 4dafd08c05..6963b76f5a 100644 --- a/website/utils.py +++ b/website/utils.py @@ -97,7 +97,14 @@ def page_name(title): import re name = title.lower() name = re.sub('[~!@#$%^&*()<>,."\']', '', name) - return '-'.join(name.split()) + name = re.sub('[:/]', '-', name) + + name = '-'.join(name.split()) + + # replace repeating hyphens + name = re.sub(r"(-)\1+", r"\1", name) + + return name def update_page_name(doc, title): """set page_name and check if it is unique""" From 1b4f56c57f6ee646f4148e428e926c725bad3fb6 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2013 17:01:51 +0530 Subject: [PATCH 08/10] fixes in c-form --- accounts/doctype/c_form/c_form.py | 80 +++++++++++----------- stock/doctype/packing_slip/packing_slip.py | 5 +- stock/stock_ledger.py | 11 ++- 3 files changed, 45 insertions(+), 51 deletions(-) diff --git a/accounts/doctype/c_form/c_form.py b/accounts/doctype/c_form/c_form.py index a1cc9e8373..bff68f7c1c 100644 --- a/accounts/doctype/c_form/c_form.py +++ b/accounts/doctype/c_form/c_form.py @@ -18,11 +18,7 @@ from __future__ import unicode_literals import webnotes from webnotes.utils import flt, getdate from webnotes.model.doc import make_autoname -from webnotes.model.wrapper import getlist, copy_doclist -from webnotes import msgprint - -sql = webnotes.conn.sql - +from webnotes.model.wrapper import getlist class DocType: def __init__(self,d,dl): @@ -31,53 +27,57 @@ class DocType: def autoname(self): self.doc.name = make_autoname(self.doc.naming_series + '.#####') + def validate(self): + """Validate invoice that c-form is applicable + and no other c-form is received for that""" + + for d in getlist(self.doclist, 'invoice_details'): + inv = webnotes.conn.sql("""select c_form_applicable, c_form_no from + `tabSales Invoice` where name = %s""", d.invoice_no) + + if not inv: + webnotes.msgprint("Invoice: %s is not exists in the system, please check." % + d.invoice_no, raise_exception=1) + + elif inv[0][0] != 'Yes': + webnotes.msgprint("C-form is not applicable for Invoice: %s" % + d.invoice_no, raise_exception=1) + + elif inv[0][1] and inv[0][1] != self.doc.name: + webnotes.msgprint("""Invoice %s is tagged in another C-form: %s. + If you want to change C-form no for this invoice, + please remove invoice no from the previous c-form and then try again""" % + (d.invoice_no, inv[0][1]), raise_exception=1) def on_update(self): """ Update C-Form No on invoices""" - - if len(getlist(self.doclist, 'invoice_details')): - inv = "'" + "', '".join([d.invoice_no for d in getlist(self.doclist, 'invoice_details')]) + "'" - sql("""update `tabSales Invoice` set c_form_no = '%s', modified ='%s' - where name in (%s)"""%(self.doc.name, self.doc.modified, inv)) - sql("""update `tabSales Invoice` set c_form_no = '', modified = %s where name not - in (%s) and ifnull(c_form_no, '') = %s""", (self.doc.modified, self.doc.name, inv)) + inv = [d.invoice_no for d in getlist(self.doclist, 'invoice_details')] + if inv: + webnotes.conn.sql("""update `tabSales Invoice` set c_form_no=%s, modified=%s + where name in (%s)""" % ('%s', '%s', ', '.join(['%s'] * len(inv))), + tuple([self.doc.name, self.doc.modified] + inv), debug=1) + + webnotes.conn.sql("""update `tabSales Invoice` set c_form_no = '', modified = %s + where name not in (%s) and ifnull(c_form_no, '') = %s""" % + ('%s', ', '.join(['%s'*len(inv)]), '%s'), + tuple([self.doc.modified] + inv + [self.doc.name]), debug=1) else: - msgprint("Please enter atleast 1 invoice in the table below", raise_exception=1) + webnotes.msgprint("Please enter atleast 1 invoice in the table", raise_exception=1) - self.calculate_total_invoiced_amount() + self.set_total_invoiced_amount() - def calculate_total_invoiced_amount(self): - total = 0 - for d in getlist(self.doclist, 'invoice_details'): - total += flt(d.grand_total) + def set_total_invoiced_amount(self): + total = sum([flt(d.total) for d in getlist(self.doclist, 'invoice_details')]) webnotes.conn.set(self.doc, 'total_invoiced_amount', total) - def get_invoice_details(self, invoice_no): """ Pull details from invoices for referrence """ - inv = sql("""select posting_date, territory, net_total, grand_total from - `tabSales Invoice` where name = %s""", invoice_no) - ret = { + inv = webnotes.conn.sql("""select posting_date, territory, net_total, grand_total + from `tabSales Invoice` where name = %s""", invoice_no) + return { 'invoice_date' : inv and getdate(inv[0][0]).strftime('%Y-%m-%d') or '', 'territory' : inv and inv[0][1] or '', 'net_total' : inv and flt(inv[0][2]) or '', 'grand_total' : inv and flt(inv[0][3]) or '' - } - return ret - - - def validate_invoice(self): - """Validate invoice that c-form is applicable and no other c-form is - received for that""" - - for d in getlist(self.doclist, 'invoice_details'): - inv = sql("""select c_form_applicable, c_form_no from - `tabSales Invoice` where name = %s""", invoice_no) - if not inv: - msgprint("Invoice: %s is not exists in the system, please check." % d.invoice_no, raise_exception=1) - elif inv[0][0] != 'Yes': - msgprint("C-form is not applicable for Invoice: %s" % d.invoice_no, raise_exception=1) - elif inv[0][1] and inv[0][1] != self.doc.name: - msgprint("""Invoice %s is tagged in another C-form: %s. \nIf you want to change C-form no for this invoice, - please remove invoice no from the previous c-form and then try again""" % (d.invoice_no, inv[0][1]), raise_exception=1) + } \ No newline at end of file diff --git a/stock/doctype/packing_slip/packing_slip.py b/stock/doctype/packing_slip/packing_slip.py index 7ef7d367a0..b90b0e6c76 100644 --- a/stock/doctype/packing_slip/packing_slip.py +++ b/stock/doctype/packing_slip/packing_slip.py @@ -117,7 +117,7 @@ class DocType: from `tabDelivery Note Item` dni where parent=%s and item_code in (%s) group by item_code""" % ("%s", ", ".join(["%s"]*len(items))), - tuple([self.doc.delivery_note] + items), as_dict=1, debug=1) + tuple([self.doc.delivery_note] + items), as_dict=1) ps_item_qty = dict([[d.item_code, d.qty] for d in self.doclist]) @@ -170,9 +170,6 @@ class DocType: webnotes.msgprint("Invalid new packed quantity for item %s. \ Please try again or contact support@erpnext.com" % item['item_code'], raise_exception=1) - delivery_note_item = webnotes.conn.get_value("Delivery Note Item", { - "parent": self.doc.delivery_note, "item_code": item["item_code"]}) - webnotes.conn.sql("""\ UPDATE `tabDelivery Note Item` SET packed_qty = %s diff --git a/stock/stock_ledger.py b/stock/stock_ledger.py index ffb6692df9..26f03c6478 100644 --- a/stock/stock_ledger.py +++ b/stock/stock_ledger.py @@ -54,16 +54,13 @@ def update_entries_after(args, verbose=1): if not validate_negative_stock(qty_after_transaction, sle): qty_after_transaction += flt(sle.actual_qty) continue - + if sle.serial_no: - valuation_rate = get_serialized_values(qty_after_transaction, sle, - valuation_rate) + valuation_rate = get_serialized_values(qty_after_transaction, sle, valuation_rate) elif valuation_method == "Moving Average": - valuation_rate = get_moving_average_values(qty_after_transaction, sle, - valuation_rate) + valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate) else: - valuation_rate = get_fifo_values(qty_after_transaction, sle, - stock_queue) + valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue) qty_after_transaction += flt(sle.actual_qty) From 144ba186d3f2deacf7a38639dff7fa864dc591ed Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2013 17:20:08 +0530 Subject: [PATCH 09/10] fixes in c-form --- accounts/doctype/c_form/c_form.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/doctype/c_form/c_form.py b/accounts/doctype/c_form/c_form.py index bff68f7c1c..1bea635c92 100644 --- a/accounts/doctype/c_form/c_form.py +++ b/accounts/doctype/c_form/c_form.py @@ -59,7 +59,7 @@ class DocType: webnotes.conn.sql("""update `tabSales Invoice` set c_form_no = '', modified = %s where name not in (%s) and ifnull(c_form_no, '') = %s""" % - ('%s', ', '.join(['%s'*len(inv)]), '%s'), + ('%s', ', '.join(['%s']*len(inv)), '%s'), tuple([self.doc.modified] + inv + [self.doc.name]), debug=1) else: webnotes.msgprint("Please enter atleast 1 invoice in the table", raise_exception=1) From a81010a5b31e202abd1dcbe77c43f0c8b9be8e49 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 17 Jan 2013 17:25:55 +0530 Subject: [PATCH 10/10] removed debug mode --- accounts/doctype/c_form/c_form.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/accounts/doctype/c_form/c_form.py b/accounts/doctype/c_form/c_form.py index 1bea635c92..4575653699 100644 --- a/accounts/doctype/c_form/c_form.py +++ b/accounts/doctype/c_form/c_form.py @@ -55,12 +55,12 @@ class DocType: if inv: webnotes.conn.sql("""update `tabSales Invoice` set c_form_no=%s, modified=%s where name in (%s)""" % ('%s', '%s', ', '.join(['%s'] * len(inv))), - tuple([self.doc.name, self.doc.modified] + inv), debug=1) + tuple([self.doc.name, self.doc.modified] + inv)) webnotes.conn.sql("""update `tabSales Invoice` set c_form_no = '', modified = %s where name not in (%s) and ifnull(c_form_no, '') = %s""" % ('%s', ', '.join(['%s']*len(inv)), '%s'), - tuple([self.doc.modified] + inv + [self.doc.name]), debug=1) + tuple([self.doc.modified] + inv + [self.doc.name])) else: webnotes.msgprint("Please enter atleast 1 invoice in the table", raise_exception=1)