Merge branch 'stock_reco' of github.com:webnotes/erpnext into stock_reco

This commit is contained in:
Anand Doshi 2013-01-14 11:17:54 +05:30
commit 279e08e523
12 changed files with 245 additions and 269 deletions

View File

@ -4,6 +4,7 @@ def execute():
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 @@ def rename_fields():
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 @@ def store_stock_reco_json():
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))

View File

@ -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");
}
});

View File

@ -31,18 +31,34 @@ class DocType:
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 @@ class DocType:
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 @@ class DocType:
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 @@ class DocType:
""", (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 @@ class DocType:
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

View File

@ -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 @@ erpnext.stock.StockEntry = erpnext.utils.Controller.extend({
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.toggle_related_fields = function(doc) {
}
}
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);
}

View File

@ -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
},
{

View File

@ -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",

View File

@ -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 @@ erpnext.stock.StockReconciliation = erpnext.utils.Controller.extend({
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 @@ erpnext.stock.StockReconciliation = erpnext.utils.Controller.extend({
$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 @@ erpnext.stock.StockReconciliation = erpnext.utils.Controller.extend({
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});

View File

@ -23,6 +23,9 @@ from webnotes.model.controller import DocListController
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 @@ class DocType(DocListController):
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 @@ class DocType(DocListController):
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 @@ class DocType(DocListController):
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 @@ class DocType(DocListController):
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 @@ class DocType(DocListController):
"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 @@ class DocType(DocListController):
"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

View File

@ -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"
},
{
@ -77,22 +84,6 @@
"fieldname": "col1",
"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,
@ -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"
}
]

View File

@ -36,18 +36,19 @@ class TestStockReconciliation(unittest.TestCase):
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 @@ class TestStockReconciliation(unittest.TestCase):
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 @@ class TestStockReconciliation(unittest.TestCase):
[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 @@ class TestStockReconciliation(unittest.TestCase):
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 @@ class TestStockReconciliation(unittest.TestCase):
},
]
# 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))
webnotes.get_obj("Stock Ledger").update_stock(existing_ledgers)

View File

@ -35,15 +35,13 @@ class DocType:
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

View File

@ -89,6 +89,14 @@ def update_entries_after(args, verbose=1):
_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_moving_average_values(qty_after_transaction, sle, valuation_rate):
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])