Merge branch 'stock_reco' of github.com:webnotes/erpnext into stock_reco
diff --git a/patches/january_2013/stock_reconciliation_patch.py b/patches/january_2013/stock_reconciliation_patch.py
index 9d72f58..1203595 100644
--- a/patches/january_2013/stock_reconciliation_patch.py
+++ b/patches/january_2013/stock_reconciliation_patch.py
@@ -4,6 +4,7 @@
webnotes.reload_doc("stock", "doctype", "stock_ledger_entry")
rename_fields()
+ move_remarks_to_comments()
store_stock_reco_json()
def rename_fields():
@@ -13,6 +14,21 @@
webnotes.conn.sql("""update `tab%s` set `%s`=`%s`""" %
(doctype, new_fieldname, old_fieldname))
+def move_remarks_to_comments():
+ from webnotes.utils import get_fullname
+ result = webnotes.conn.sql("""select name, remark, modified_by from `tabStock Reconciliation`
+ where ifnull(remark, '')!=''""")
+ fullname_map = {}
+ for reco, remark, modified_by in result:
+ webnotes.model_wrapper([{
+ "doctype": "Comment",
+ "comment": remark,
+ "comment_by": modified_by,
+ "comment_by_fullname": fullname_map.setdefault(modified_by, get_fullname(modified_by)),
+ "comment_doctype": "Stock Reconciliation",
+ "comment_docname": reco
+ }]).insert()
+
def store_stock_reco_json():
import os
import json
@@ -40,6 +56,7 @@
with open(stock_reco_file_path, "r") as open_reco_file:
content = open_reco_file.read()
content = read_csv_content(content)
- webnotes.conn.set_value("Stock Reconciliation", reco, "reconciliation_json",
- json.dumps(content, separators=(',', ': ')))
+ reconciliation_json = json.dumps(content, separators=(',', ': '))
+ webnotes.conn.sql("""update `tabStock Reconciliation`
+ set reconciliation_json=%s where name=%s""", (reconciliation_json, name))
\ No newline at end of file
diff --git a/public/js/stock_controller.js b/public/js/stock_controller.js
new file mode 100644
index 0000000..d3511e1
--- /dev/null
+++ b/public/js/stock_controller.js
@@ -0,0 +1,32 @@
+// ERPNext - web based ERP (http://erpnext.com)
+// Copyright (C) 2012 Web Notes Technologies Pvt Ltd
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+wn.provide("erpnext.stock");
+
+erpnext.stock.StockController = erpnext.utils.Controller.extend({
+ show_stock_ledger: function() {
+ var me = this;
+ this.frm.add_custom_button("Show Stock Ledger", function() {
+ var args = {
+ voucher_no: cur_frm.doc.name,
+ from_date: wn.datetime.str_to_user(cur_frm.doc.posting_date),
+ to_date: wn.datetime.str_to_user(cur_frm.doc.posting_date)
+ };
+ wn.set_route('stock-ledger',
+ $.map(args, function(val, key) { return key+"="+val; }).join("&&"));
+ }, "icon-bar-chart");
+ }
+});
\ No newline at end of file
diff --git a/stock/doctype/bin/bin.py b/stock/doctype/bin/bin.py
index c473a6c..19ce8f9 100644
--- a/stock/doctype/bin/bin.py
+++ b/stock/doctype/bin/bin.py
@@ -31,18 +31,34 @@
self.doc = doc
self.doclist = doclist
+ def validate(self):
+ if not self.doc.stock_uom:
+ self.doc.stock_uom = webnotes.conn.get_value('Item', self.doc.item_code, 'stock_uom')
+
+ if not self.doc.warehouse_type:
+ self.doc.warehouse_type = webnotes.conn.get_value("Warehouse", self.doc.warehouse,
+ "warehouse_type")
+
+ self.validate_mandatory()
+
+ self.doc.projected_qty = flt(self.doc.actual_qty) + flt(self.doc.ordered_qty) + \
+ flt(self.doc.indented_qty) + flt(self.doc.planned_qty) - flt(self.doc.reserved_qty)
+
+ def validate_mandatory(self):
+ qf = ['actual_qty', 'reserved_qty', 'ordered_qty', 'indented_qty']
+ for f in qf:
+ if (not self.doc.fields.has_key(f)) or (not self.doc.fields[f]):
+ self.doc.fields[f] = 0.0
+
def update_stock(self, args):
- from stock.stock_ledger import update_entries_after
- if not args.get("posting_date"):
- posting_date = nowdate()
-
self.update_qty(args)
- if (flt(args.get("actual_qty")) < 0 or flt(args.get("reserved_qty")) > 0) \
- and args.get("is_cancelled") == 'No' and args.get("is_amended")=='No':
- self.reorder_item(args.get("voucher_type"), args.get("voucher_no"))
-
if args.get("actual_qty"):
+ from stock.stock_ledger import update_entries_after
+
+ if not args.get("posting_date"):
+ posting_date = nowdate()
+
# update valuation and qty after transaction for post dated entry
update_entries_after({
"item_code": self.doc.item_code,
@@ -53,8 +69,8 @@
def update_qty(self, args):
# update the stock values (for current quantities)
- self.doc.actual_qty = flt(self.doc.actual_qty) + flt(args.get("actual_qty", 0))
- self.doc.ordered_qty = flt(self.doc.ordered_qty) + flt(args.get("ordered_qty", 0))
+ self.doc.actual_qty = flt(self.doc.actual_qty) + flt(args.get("actual_qty"))
+ self.doc.ordered_qty = flt(self.doc.ordered_qty) + flt(args.get("ordered_qty"))
self.doc.reserved_qty = flt(self.doc.reserved_qty) + flt(args.get("reserved_qty"))
self.doc.indented_qty = flt(self.doc.indented_qty) + flt(args.get("indented_qty"))
self.doc.planned_qty = flt(self.doc.planned_qty) + flt(args.get("planned_qty"))
@@ -63,6 +79,10 @@
flt(self.doc.indented_qty) + flt(self.doc.planned_qty) - flt(self.doc.reserved_qty)
self.doc.save()
+
+ if (flt(args.get("actual_qty")) < 0 or flt(args.get("reserved_qty")) > 0) \
+ and args.get("is_cancelled") == 'No' and args.get("is_amended")=='No':
+ self.reorder_item(args.get("voucher_type"), args.get("voucher_no"))
def get_first_sle(self):
sle = sql("""
@@ -75,111 +95,6 @@
""", (self.doc.item_code, self.doc.warehouse), as_dict=1)
return sle and sle[0] or None
-
-
- # def get_serialized_inventory_values(self, val_rate, in_rate, opening_qty, \
- # actual_qty, is_cancelled, serial_nos):
- # """
- # get serialized inventory values
- # """
- # if flt(in_rate) < 0: # wrong incoming rate
- # in_rate = val_rate
- # elif flt(in_rate) == 0 or flt(actual_qty) < 0:
- # # In case of delivery/stock issue, get average purchase rate
- # # of serial nos of current entry
- # in_rate = flt(sql("""select ifnull(avg(purchase_rate), 0)
- # from `tabSerial No` where name in (%s)""" % (serial_nos))[0][0])
- #
- # if in_rate and val_rate == 0: # First entry
- # val_rate = in_rate
- # # val_rate is same as previous entry if val_rate is negative
- # # Otherwise it will be calculated as per moving average
- # elif opening_qty + actual_qty > 0 and ((opening_qty * val_rate) + \
- # (actual_qty * in_rate)) > 0:
- # val_rate = ((opening_qty *val_rate) + (actual_qty * in_rate)) / \
- # (opening_qty + actual_qty)
- # return val_rate, in_rate
- #
- # def get_moving_average_inventory_values(self, val_rate, in_rate, opening_qty, actual_qty, is_cancelled):
- # if flt(in_rate) == 0 or flt(actual_qty) < 0:
- # # In case of delivery/stock issue in_rate = 0 or wrong incoming rate
- # in_rate = val_rate
- #
- # # val_rate is same as previous entry if :
- # # 1. actual qty is negative(delivery note / stock entry)
- # # 2. cancelled entry
- # # 3. val_rate is negative
- # # Otherwise it will be calculated as per moving average
- # if actual_qty > 0 and (opening_qty + actual_qty) > 0 and is_cancelled == 'No' \
- # and ((opening_qty * val_rate) + (actual_qty * in_rate)) > 0:
- # opening_qty = opening_qty > 0 and opening_qty or 0
- # val_rate = ((opening_qty *val_rate) + (actual_qty * in_rate)) / \
- # (opening_qty + actual_qty)
- # elif (opening_qty + actual_qty) <= 0:
- # val_rate = 0
- # return val_rate, in_rate
- #
- # def get_fifo_inventory_values(self, in_rate, actual_qty):
- # # add batch to fcfs balance
- # if actual_qty > 0:
- # self.fcfs_bal.append([flt(actual_qty), flt(in_rate)])
- #
- # # remove from fcfs balance
- # else:
- # incoming_cost = 0
- # withdraw = flt(abs(actual_qty))
- # while withdraw:
- # if not self.fcfs_bal:
- # break # nothing in store
- #
- # batch = self.fcfs_bal[0]
- #
- # if batch[0] <= withdraw:
- # # not enough or exactly same qty in current batch, clear batch
- # incoming_cost += flt(batch[1])*flt(batch[0])
- # withdraw -= batch[0]
- # self.fcfs_bal.pop(0)
- #
- #
- # else:
- # # all from current batch
- # incoming_cost += flt(batch[1])*flt(withdraw)
- # batch[0] -= withdraw
- # withdraw = 0
- #
- # in_rate = incoming_cost / flt(abs(actual_qty))
- #
- # fcfs_val = sum([flt(d[0])*flt(d[1]) for d in self.fcfs_bal])
- # fcfs_qty = sum([flt(d[0]) for d in self.fcfs_bal])
- # val_rate = fcfs_qty and fcfs_val / fcfs_qty or 0
- #
- # return val_rate, in_rate
- #
- # def get_valuation_rate(self, val_method, serial_nos, val_rate, in_rate, stock_val, cqty, s):
- # if serial_nos:
- # val_rate, in_rate = self.get_serialized_inventory_values( \
- # val_rate, in_rate, opening_qty = cqty, actual_qty = s['actual_qty'], \
- # is_cancelled = s['is_cancelled'], serial_nos = serial_nos)
- # elif val_method == 'Moving Average':
- # val_rate, in_rate = self.get_moving_average_inventory_values( \
- # val_rate, in_rate, opening_qty = cqty, actual_qty = s['actual_qty'], \
- # is_cancelled = s['is_cancelled'])
- # elif val_method == 'FIFO':
- # val_rate, in_rate = self.get_fifo_inventory_values(in_rate, \
- # actual_qty = s['actual_qty'])
- # return val_rate, in_rate
-
- # def get_stock_value(self, val_method, cqty, val_rate, serial_nos):
- # if serial_nos:
- # stock_val = flt(val_rate) * flt(cqty)
- # elif val_method == 'Moving Average':
- # stock_val = flt(cqty) > 0 and flt(val_rate) * flt(cqty) or 0
- # elif val_method == 'FIFO':
- # stock_val = sum([flt(d[0])*flt(d[1]) for d in self.fcfs_bal])
- # return stock_val
-
-
-
def reorder_item(self,doc_type,doc_name):
""" Reorder item if stock reaches reorder level"""
@@ -246,12 +161,3 @@
msg="""A Purchase Request has been raised
for item %s: %s on %s """ % (doc_type, doc_name, nowdate())
sendmail(email_list, subject='Auto Purchase Request Generation Notification', msg = msg)
-
- def validate(self):
- self.validate_mandatory()
-
- def validate_mandatory(self):
- qf = ['actual_qty', 'reserved_qty', 'ordered_qty', 'indented_qty']
- for f in qf:
- if (not self.doc.fields.has_key(f)) or (not self.doc.fields[f]):
- self.doc.fields[f] = 0.0
diff --git a/stock/doctype/stock_entry/stock_entry.js b/stock/doctype/stock_entry/stock_entry.js
index a6d233e..bb55622 100644
--- a/stock/doctype/stock_entry/stock_entry.js
+++ b/stock/doctype/stock_entry/stock_entry.js
@@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+wn.require("public/app/js/stock_controller.js");
wn.provide("erpnext.stock");
-erpnext.stock.StockEntry = erpnext.utils.Controller.extend({
+erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
onload_post_render: function() {
this._super();
if(this.frm.doc.__islocal && (this.frm.doc.production_order || this.frm.doc.bom_no)
@@ -30,8 +31,9 @@
this._super();
this.toggle_related_fields(this.frm.doc);
this.toggle_enable_bom();
- if (this.frm.doc.docstatus==1) this.frm.add_custom_button("Show Stock Ledger",
- this.show_stock_ledger)
+ if (this.frm.doc.docstatus==1) {
+ this.show_stock_ledger();
+ }
},
on_submit: function() {
@@ -108,16 +110,6 @@
}
}
-cur_frm.cscript.show_stock_ledger = function() {
- var args = {
- voucher_no: cur_frm.doc.name,
- from_date: wn.datetime.str_to_user(cur_frm.doc.posting_date),
- to_date: wn.datetime.str_to_user(cur_frm.doc.posting_date)
- };
- wn.set_route('stock-ledger',
- $.map(args, function(val, key) { return key+"="+val; }).join("&&"));
-}
-
cur_frm.cscript.delivery_note_no = function(doc,cdt,cdn){
if(doc.delivery_note_no) get_server_fields('get_cust_values','','',doc,cdt,cdn,1);
}
diff --git a/stock/doctype/stock_entry/stock_entry.txt b/stock/doctype/stock_entry/stock_entry.txt
index d3b39c3..76a8d42 100644
--- a/stock/doctype/stock_entry/stock_entry.txt
+++ b/stock/doctype/stock_entry/stock_entry.txt
@@ -2,27 +2,27 @@
{
"owner": "Administrator",
"docstatus": 0,
- "creation": "2012-12-19 12:29:07",
+ "creation": "2012-12-24 18:32:32",
"modified_by": "Administrator",
- "modified": "2012-12-19 18:09:15"
+ "modified": "2013-01-11 11:54:51"
},
{
- "is_submittable": 1,
"in_create": 0,
+ "is_submittable": 1,
"allow_print": 0,
"search_fields": "transfer_date, from_warehouse, to_warehouse, purpose, remarks",
"module": "Stock",
- "autoname": "naming_series:",
+ "doctype": "DocType",
"read_only_onload": 0,
"in_dialog": 0,
+ "issingle": 0,
"allow_attach": 0,
"read_only": 0,
"allow_email": 0,
"hide_heading": 0,
- "issingle": 0,
+ "autoname": "naming_series:",
"name": "__common__",
"allow_rename": 0,
- "doctype": "DocType",
"max_attachments": 0,
"hide_toolbar": 0,
"allow_copy": 0
@@ -47,6 +47,7 @@
"doctype": "DocType"
},
{
+ "print_width": "50%",
"oldfieldtype": "Column Break",
"doctype": "DocField",
"width": "50%",
@@ -93,6 +94,7 @@
"in_filter": 1
},
{
+ "print_width": "50%",
"oldfieldtype": "Column Break",
"doctype": "DocField",
"width": "50%",
@@ -146,7 +148,7 @@
},
{
"print_hide": 1,
- "no_copy": 0,
+ "no_copy": 1,
"oldfieldtype": "Link",
"allow_on_submit": 0,
"doctype": "DocField",
@@ -170,7 +172,7 @@
},
{
"print_hide": 1,
- "no_copy": 0,
+ "no_copy": 1,
"oldfieldtype": "Link",
"allow_on_submit": 0,
"doctype": "DocField",
@@ -279,7 +281,7 @@
{
"print_hide": 1,
"depends_on": "eval:doc.purpose==\"Sales Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 1,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -298,7 +300,7 @@
{
"print_hide": 1,
"depends_on": "eval:doc.purpose==\"Purchase Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 1,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -349,6 +351,7 @@
},
{
"print_hide": 1,
+ "no_copy": 1,
"depends_on": "eval:doc.purpose==\"Sales Return\"",
"doctype": "DocField",
"label": "Sales Invoice No",
@@ -369,7 +372,7 @@
{
"print_hide": 1,
"depends_on": "eval:doc.purpose==\"Purchase Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 0,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -388,7 +391,7 @@
{
"print_hide": 0,
"depends_on": "eval:doc.purpose==\"Purchase Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 0,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -406,7 +409,7 @@
{
"print_hide": 0,
"depends_on": "eval:doc.purpose==\"Purchase Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 0,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -424,7 +427,7 @@
{
"print_hide": 1,
"depends_on": "eval:doc.purpose==\"Sales Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 0,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -443,7 +446,7 @@
{
"print_hide": 0,
"depends_on": "eval:doc.purpose==\"Sales Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 0,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -461,7 +464,7 @@
{
"print_hide": 0,
"depends_on": "eval:doc.purpose==\"Sales Return\"",
- "no_copy": 0,
+ "no_copy": 1,
"search_index": 0,
"allow_on_submit": 0,
"doctype": "DocField",
@@ -485,6 +488,7 @@
"permlevel": 0
},
{
+ "print_width": "50%",
"doctype": "DocField",
"width": "50%",
"fieldname": "col4",
@@ -539,6 +543,7 @@
"in_filter": 1
},
{
+ "print_width": "50%",
"doctype": "DocField",
"width": "50%",
"fieldname": "col5",
@@ -601,16 +606,23 @@
"permlevel": 1
},
{
+ "amend": 0,
"create": 0,
"doctype": "DocPerm",
+ "submit": 0,
"write": 1,
"role": "Manufacturing User",
+ "cancel": 0,
"permlevel": 2
},
{
+ "amend": 0,
+ "create": 0,
"doctype": "DocPerm",
+ "submit": 0,
"write": 1,
"role": "Manufacturing Manager",
+ "cancel": 0,
"permlevel": 2
},
{
@@ -624,8 +636,12 @@
"permlevel": 0
},
{
+ "amend": 0,
+ "create": 0,
"doctype": "DocPerm",
+ "submit": 0,
"role": "Manufacturing User",
+ "cancel": 0,
"permlevel": 1
},
{
@@ -639,8 +655,12 @@
"permlevel": 0
},
{
+ "amend": 0,
+ "create": 0,
"doctype": "DocPerm",
+ "submit": 0,
"role": "Manufacturing Manager",
+ "cancel": 0,
"permlevel": 1
},
{
diff --git a/stock/doctype/stock_entry_detail/stock_entry_detail.txt b/stock/doctype/stock_entry_detail/stock_entry_detail.txt
index 6926c9a..a6b9521 100644
--- a/stock/doctype/stock_entry_detail/stock_entry_detail.txt
+++ b/stock/doctype/stock_entry_detail/stock_entry_detail.txt
@@ -2,9 +2,9 @@
{
"owner": "Administrator",
"docstatus": 0,
- "creation": "2012-12-18 13:47:41",
+ "creation": "2012-12-20 14:31:18",
"modified_by": "Administrator",
- "modified": "2012-12-18 17:08:52"
+ "modified": "2013-01-11 11:59:10"
},
{
"istable": 1,
@@ -26,6 +26,7 @@
"doctype": "DocType"
},
{
+ "no_copy": 1,
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Source Warehouse",
@@ -37,6 +38,7 @@
"in_filter": 1
},
{
+ "no_copy": 1,
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Target Warehouse",
@@ -61,6 +63,7 @@
"in_filter": 1
},
{
+ "print_width": "300px",
"oldfieldtype": "Text",
"doctype": "DocField",
"label": "Description",
diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.js b/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 1e64965..62bc69f 100644
--- a/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -13,9 +13,11 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+wn.require("public/app/js/stock_controller.js");
wn.provide("erpnext.stock");
-erpnext.stock.StockReconciliation = erpnext.utils.Controller.extend({
+erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
refresh: function() {
if(this.frm.doc.docstatus===0) {
this.show_download_template();
@@ -23,22 +25,37 @@
if(this.frm.doc.reconciliation_json) {
this.frm.set_intro("You can submit this Stock Reconciliation.");
} else {
- this.frm.set_intro("Download the template, fill in data and \
- upload it.");
+ this.frm.set_intro("Download the Template, fill appropriate data and \
+ attach the modified file.");
}
+ } else if(this.frm.doc.docstatus == 1) {
+ this.frm.set_intro("Cancelling this Stock Reconciliation will nullify it's effect.");
+ this.show_stock_ledger();
+ } else {
+ this.frm.set_intro("");
}
- if(this.frm.doc.reconciliation_json) {
- this.show_reconciliation_data();
- this.show_download_reconciliation_data();
- }
+ this.show_reconciliation_data();
+ this.show_download_reconciliation_data();
},
show_download_template: function() {
var me = this;
this.frm.add_custom_button("Download Template", function() {
this.title = "Stock Reconcilation Template";
- wn.tools.downloadify([["Item Code", "Warehouse", "Quantity", "Valuation Rate"]], null,
- this);
+ wn.tools.downloadify([["Stock Reconciliation"],
+ ["----"],
+ ["Stock Reconciliation can be used to update the stock on a particular date,"
+ + " usually as per physical inventory."],
+ ["When submitted, the system creates difference entries"
+ + " to set the given stock and valuation on this date."],
+ ["It can also be used to create opening stock entries and to fix stock value."],
+ ["----"],
+ ["Notes:"],
+ ["Item Code and Warehouse should already exist."],
+ ["You can update either Quantity or Valuation Rate or both."],
+ ["If no change in either Quantity or Valuation Rate, leave the cell blank."],
+ ["----"],
+ ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]], null, this);
return false;
}, "icon-download");
},
@@ -59,22 +76,25 @@
$wrapper.find(".dit-progress-area").toggle(false);
me.frm.set_value("reconciliation_json", JSON.stringify(r));
me.show_reconciliation_data();
+ me.frm.save();
}
});
},
show_download_reconciliation_data: function() {
var me = this;
- this.frm.add_custom_button("Download Reconcilation Data", function() {
- this.title = "Stock Reconcilation Data";
- wn.tools.downloadify(JSON.parse(me.frm.doc.reconciliation_json), null, this);
- return false;
- }, "icon-download");
+ if(this.frm.doc.reconciliation_json) {
+ this.frm.add_custom_button("Download Reconcilation Data", function() {
+ this.title = "Stock Reconcilation Data";
+ wn.tools.downloadify(JSON.parse(me.frm.doc.reconciliation_json), null, this);
+ return false;
+ }, "icon-download");
+ }
},
show_reconciliation_data: function() {
+ var $wrapper = $(cur_frm.fields_dict.reconciliation_html.wrapper).empty();
if(this.frm.doc.reconciliation_json) {
- var $wrapper = $(cur_frm.fields_dict.reconciliation_html.wrapper).empty();
var reconciliation_data = JSON.parse(this.frm.doc.reconciliation_json);
var _make = function(data, header) {
@@ -92,14 +112,14 @@
return result;
};
- var $reconciliation_table = $("<div style='overflow-x: scroll;'>\
+ var $reconciliation_table = $("<div style='overflow-x: auto;'>\
<table class='table table-striped table-bordered'>\
<thead>" + _make([reconciliation_data[0]], true) + "</thead>\
<tbody>" + _make(reconciliation_data.splice(1)) + "</tbody>\
</table>\
</div>").appendTo($wrapper);
}
- }
+ },
});
cur_frm.cscript = new erpnext.stock.StockReconciliation({frm: cur_frm});
\ No newline at end of file
diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.py b/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 725bb5f..3a8ffcd 100644
--- a/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -23,6 +23,9 @@
from stock.stock_ledger import update_entries_after
class DocType(DocListController):
+ def setup(self):
+ self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]
+
def validate(self):
self.validate_data()
@@ -34,17 +37,22 @@
def validate_data(self):
data = json.loads(self.doc.reconciliation_json)
- if data[0] != ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]:
+ if self.head_row not in data:
msgprint(_("""Hey! You seem to be using the wrong template. \
Click on 'Download Template' button to get the correct template."""),
raise_exception=1)
+
+ # remove the help part and save the json
+ if data.index(self.head_row) != 0:
+ data = data[data.index(self.head_row):]
+ self.doc.reconciliation_json = json.dumps(data)
def _get_msg(row_num, msg):
return _("Row # ") + ("%d: " % (row_num+2)) + _(msg)
self.validation_messages = []
item_warehouse_combinations = []
- for row_num, row in enumerate(data[1:]):
+ for row_num, row in enumerate(data[data.index(self.head_row)+1:]):
# find duplicates
if [row[0], row[1]] in item_warehouse_combinations:
self.validation_messages.append(_get_msg(row_num, "Duplicate entry"))
@@ -111,7 +119,7 @@
row_template = ["item_code", "warehouse", "qty", "valuation_rate"]
data = json.loads(self.doc.reconciliation_json)
- for row_num, row in enumerate(data[1:]):
+ for row_num, row in enumerate(data[data.index(self.head_row)+1:]):
row = webnotes._dict(zip(row_template, row))
previous_sle = get_previous_sle({
"item_code": row.item_code,
@@ -148,18 +156,19 @@
if change_in_qty:
# if change in qty, irrespective of change in rate
incoming_rate = _get_incoming_rate(flt(row.qty), flt(row.valuation_rate),
- flt(previous_sle.qty_after_transaction),
- flt(previous_sle.valuation_rate))
+ flt(previous_sle.get("qty_after_transaction")),
+ flt(previous_sle.get("valuation_rate")))
self.insert_entries({"actual_qty": change_in_qty,
"incoming_rate": incoming_rate}, row)
- elif change_in_rate and previous_sle.qty_after_transaction >= 0:
+ elif change_in_rate and flt(previous_sle.get("qty_after_transaction")) >= 0:
# if no change in qty, but change in rate
# and positive actual stock before this reconciliation
- incoming_rate = _get_incoming_rate(flt(previous_sle.qty_after_transaction)+1,
- flt(row.valuation_rate), flt(previous_sle.qty_after_transaction),
- flt(previous_sle.valuation_rate))
+ incoming_rate = _get_incoming_rate(
+ flt(previous_sle.get("qty_after_transaction"))+1, flt(row.valuation_rate),
+ flt(previous_sle.get("qty_after_transaction")),
+ flt(previous_sle.get("valuation_rate")))
# +1 entry
self.insert_entries({"actual_qty": 1, "incoming_rate": incoming_rate}, row)
@@ -169,7 +178,7 @@
def sle_for_fifo(self, row, previous_sle, change_in_qty, change_in_rate):
"""Insert Stock Ledger Entries for FIFO valuation"""
- previous_stock_queue = json.loads(previous_sle.stock_queue or "[]")
+ previous_stock_queue = json.loads(previous_sle.get("stock_queue") or "[]")
previous_stock_qty = sum((batch[0] for batch in previous_stock_queue))
previous_stock_value = sum((batch[0] * batch[1] for batch in \
previous_stock_queue))
@@ -181,9 +190,11 @@
"incoming_rate": flt(row.valuation_rate)}, row)
# Make reverse entry
- self.insert_entries({"actual_qty": -1 * previous_stock_qty,
- "incoming_rate": previous_stock_qty < 0 and \
- flt(row.valuation_rate) or 0}, row)
+ if previous_stock_qty:
+ self.insert_entries({"actual_qty": -1 * previous_stock_qty,
+ "incoming_rate": previous_stock_qty < 0 and \
+ flt(row.valuation_rate) or 0}, row)
+
if change_in_qty:
if row.valuation_rate == "":
@@ -213,13 +224,17 @@
"voucher_type": self.doc.doctype,
"voucher_no": self.doc.name,
"company": webnotes.conn.get_default("company"),
- "is_cancelled": "No"
+ "is_cancelled": "No",
}
args.update(opts)
+ # create stock ledger entry
sle_wrapper = webnotes.model_wrapper([args]).insert()
+
+ # update bin
+ webnotes.get_obj('Warehouse', row.warehouse).update_bin(args)
- update_entries_after(args)
+ # update_entries_after(args)
return sle_wrapper
diff --git a/stock/doctype/stock_reconciliation/stock_reconciliation.txt b/stock/doctype/stock_reconciliation/stock_reconciliation.txt
index 272bf99..ddd7e08 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-09 11:24:35",
+ "creation": "2013-01-11 12:04:17",
"modified_by": "Administrator",
- "modified": "2013-01-10 19:26:28"
+ "modified": "2013-01-11 15:36:21"
},
{
"allow_attach": 0,
@@ -29,11 +29,18 @@
"parentfield": "fields"
},
{
- "name": "__common__",
"parent": "Stock Reconciliation",
"read": 1,
"doctype": "DocPerm",
+ "cancel": 1,
+ "name": "__common__",
+ "amend": 1,
+ "create": 1,
+ "submit": 1,
+ "write": 1,
"parenttype": "DocType",
+ "role": "Material Manager",
+ "permlevel": 0,
"parentfield": "permissions"
},
{
@@ -78,22 +85,6 @@
"fieldtype": "Column Break"
},
{
- "read_only": 0,
- "oldfieldtype": "Text",
- "doctype": "DocField",
- "label": "Remark",
- "oldfieldname": "remark",
- "fieldname": "remark",
- "fieldtype": "Text"
- },
- {
- "depends_on": "eval:doc.docstatus===0",
- "doctype": "DocField",
- "label": "Upload",
- "fieldname": "sb1",
- "fieldtype": "Section Break"
- },
- {
"read_only": 1,
"print_hide": 1,
"doctype": "DocField",
@@ -102,6 +93,7 @@
"fieldtype": "HTML"
},
{
+ "depends_on": "reconciliation_json",
"doctype": "DocField",
"label": "Reconciliation Data",
"fieldname": "sb2",
@@ -127,32 +119,6 @@
"hidden": 1
},
{
- "amend": 0,
- "create": 1,
- "doctype": "DocPerm",
- "submit": 1,
- "write": 1,
- "cancel": 1,
- "role": "Material Manager",
- "permlevel": 0
- },
- {
- "amend": 0,
- "create": 0,
- "doctype": "DocPerm",
- "submit": 0,
- "write": 0,
- "cancel": 0,
- "role": "Material Manager",
- "permlevel": 1
- },
- {
- "create": 1,
- "doctype": "DocPerm",
- "submit": 1,
- "write": 1,
- "cancel": 1,
- "role": "System Manager",
- "permlevel": 0
+ "doctype": "DocPerm"
}
]
\ No newline at end of file
diff --git a/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index fadc3b4..224d70e 100644
--- a/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -36,18 +36,19 @@
webnotes.conn.rollback()
def test_reco_for_fifo(self):
- # [[qty, valuation_rate, posting_date, posting_time]]
+ # [[qty, valuation_rate, posting_date, posting_time, expected_stock_value, bin_qty]]
input_data = [
- [50, 1000, "2012-12-26", "12:00", 50000],
- [5, 1000, "2012-12-26", "12:00", 5000],
- [15, 1000, "2012-12-26", "12:00", 15000],
- [25, 900, "2012-12-26", "12:00", 22500],
- [20, 500, "2012-12-26", "12:00", 10000],
- [50, 1000, "2013-01-01", "12:00", 50000],
- [5, 1000, "2013-01-01", "12:00", 5000],
- ["", 1000, "2012-12-26", "12:05", 15000],
- [20, "", "2012-12-26", "12:05", 16000],
- [10, 2000, "2012-12-26", "12:10", 20000]
+ [50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
+ [5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
+ [15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
+ [25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
+ [20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
+ [50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
+ [5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
+ ["", 1000, "2012-12-26", "12:05", 15000, 10, 12000],
+ [20, "", "2012-12-26", "12:05", 16000, 15, 18000],
+ [10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
+ [1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
]
for d in input_data:
@@ -60,14 +61,19 @@
and posting_date = %s and posting_time = %s order by name desc limit 1""",
(d[2], d[3]))
- # stock_value = sum([v[0]*v[1] for v in json.loads(res and res[0][0] or "[]")])
self.assertEqual(res and flt(res[0][0]) or 0, d[4])
+ bin = webnotes.conn.sql("""select actual_qty, stock_value from `tabBin`
+ where item_code = 'Android Jack D' and warehouse = 'Default Warehouse'""")
+
+ self.assertEqual(bin and [flt(bin[0][0]), flt(bin[0][1])] or [], [d[5], d[6]])
+
+
self.tearDown()
self.setUp()
- def test_reco_for_moving_average(self):
+ def atest_reco_for_moving_average(self):
# [[qty, valuation_rate, posting_date, posting_time]]
input_data = [
[50, 1000, "2012-12-26", "12:00", 50000],
@@ -79,7 +85,8 @@
[5, 1000, "2013-01-01", "12:00", 5000],
["", 1000, "2012-12-26", "12:05", 15000],
[20, "", "2012-12-26", "12:05", 18000],
- [10, 2000, "2012-12-26", "12:10", 20000]
+ [10, 2000, "2012-12-26", "12:10", 20000],
+ [1, 1000, "2012-12-01", "00:00", 1000],
]
for d in input_data:
@@ -96,7 +103,7 @@
self.tearDown()
self.setUp()
-
+
def submit_stock_reconciliation(self, qty, rate, posting_date, posting_time):
return webnotes.model_wrapper([{
"doctype": "Stock Reconciliation",
@@ -168,12 +175,4 @@
},
]
- # pprint(webnotes.conn.sql("""select * from `tabBin` where item_code='Android Jack D'
- # and warehouse='Default Warehouse'""", as_dict=1))
-
- webnotes.get_obj("Stock Ledger").update_stock(existing_ledgers)
-
- # pprint(webnotes.conn.sql("""select * from `tabBin` where item_code='Android Jack D'
- # and warehouse='Default Warehouse'""", as_dict=1))
-
-
\ No newline at end of file
+ webnotes.get_obj("Stock Ledger").update_stock(existing_ledgers)
\ No newline at end of file
diff --git a/stock/doctype/warehouse/warehouse.py b/stock/doctype/warehouse/warehouse.py
index 775f0d0..8d6065c 100644
--- a/stock/doctype/warehouse/warehouse.py
+++ b/stock/doctype/warehouse/warehouse.py
@@ -35,15 +35,13 @@
warehouse = %s", (item_code, warehouse))
bin = bin and bin[0][0] or ''
if not bin:
- bin = Document('Bin')
- bin.item_code = item_code
- bin.stock_uom = webnotes.conn.get_value('Item', item_code, 'stock_uom')
- bin.warehouse = warehouse
- bin.warehouse_type = webnotes.conn.get_value("Warehouse", warehouse, "warehouse_type")
- bin_obj = get_obj(doc=bin)
- bin_obj.validate()
- bin.save(1)
- bin = bin.name
+ bin_wrapper = webnotes.model_wrapper([{
+ "doctype": "Bin",
+ "item_code": item_code,
+ "warehouse": warehouse,
+ }]).insert()
+
+ bin_obj = bin_wrapper.make_obj()
else:
bin_obj = get_obj('Bin', bin)
return bin_obj
diff --git a/stock/stock_ledger.py b/stock/stock_ledger.py
index f88ea5d..3cad355 100644
--- a/stock/stock_ledger.py
+++ b/stock/stock_ledger.py
@@ -89,6 +89,14 @@
_raise_exceptions(args, verbose)
# update bin
+ if not webnotes.conn.exists({"doctype": "Bin", "item_code": args["item_code"],
+ "warehouse": args["warehouse"]}):
+ webnotes.model_wrapper([{
+ "doctype": "Bin",
+ "item_code": args["item_code"],
+ "warehouse": args["warehouse"],
+ }]).insert()
+
webnotes.conn.sql("""update `tabBin` set valuation_rate=%s, actual_qty=%s,
stock_value=%s,
projected_qty = (actual_qty + indented_qty + ordered_qty + planned_qty - reserved_qty)
@@ -209,7 +217,7 @@
def get_fifo_values(qty_after_transaction, sle, stock_queue):
incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty)
-
+
if not stock_queue:
stock_queue.append([0, 0])