Merge branch 'stock_reco' of github.com:webnotes/erpnext into stock_reco
This commit is contained in:
commit
279e08e523
@ -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))
|
||||
|
32
public/js/stock_controller.js
Normal file
32
public/js/stock_controller.js
Normal 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");
|
||||
}
|
||||
});
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
{
|
||||
|
@ -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",
|
||||
|
@ -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});
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
@ -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)
|
@ -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
|
||||
|
@ -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])
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user