[stock reco] added items table

This commit is contained in:
Rushabh Mehta 2015-02-17 12:50:20 +05:30
parent 06072c1e51
commit 2712e36f08
7 changed files with 232 additions and 163 deletions

View File

@ -5,6 +5,26 @@ frappe.require("assets/erpnext/js/controllers/stock_controller.js");
frappe.require("assets/erpnext/js/utils.js");
frappe.provide("erpnext.stock");
frappe.ui.form.on("Stock Reconciliation", "get_items", function(frm) {
frappe.prompt({label:"Warehouse", fieldtype:"Link", options:"Warehouse", reqd: 1},
function(data) {
frappe.call({
method:"erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_items",
args: {warehouse: data.warehouse},
callback: function(r) {
var items = [];
frm.clear_table("items");
for(var i=0; i< r.message.length; i++) {
var d = frm.add_child("items");
$.extend(d, r.message[i]);
}
frm.refresh_field("items");
}
});
}
);
});
erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
onload: function() {
this.set_default_expense_account();
@ -31,6 +51,8 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
setup: function() {
var me = this;
this.frm.get_field("items").grid.allow_build_edit();
if (sys_defaults.auto_accounting_for_stock) {
this.frm.add_fetch("company", "stock_adjustment_account", "expense_account");
this.frm.add_fetch("company", "cost_center", "cost_center");
@ -55,108 +77,29 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
},
refresh: function() {
if(this.frm.doc.docstatus===0) {
this.show_download_template();
this.show_upload();
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 appropriate data and attach the modified file."));
}
} else if(this.frm.doc.docstatus == 1) {
this.frm.set_intro(__("Cancelling this Stock Reconciliation will nullify its effect."));
this.show_stock_ledger();
this.show_general_ledger();
} else {
this.frm.set_intro("");
}
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");
frappe.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");
},
// show_download_template: function() {
// var me = this;
// this.frm.add_custom_button(__("Download Template"), function() {
// this.title = __("Stock Reconcilation Template");
// frappe.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");
// },
show_upload: function() {
var me = this;
var $wrapper = $(cur_frm.fields_dict.upload_html.wrapper).empty();
// upload
frappe.upload.make({
parent: $wrapper,
args: {
method: 'erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.upload'
},
sample_url: "e.g. http://example.com/somefile.csv",
callback: function(attachment, r) {
me.frm.set_value("reconciliation_json", JSON.stringify(r.message));
me.show_reconciliation_data();
me.frm.save();
}
});
// rename button
$wrapper.find('form input[type="submit"]')
.attr('value', 'Upload')
},
show_download_reconciliation_data: function() {
var me = this;
if(this.frm.doc.reconciliation_json) {
this.frm.add_custom_button(__("Download Reconcilation Data"), function() {
this.title = __("Stock Reconcilation Data");
frappe.tools.downloadify(JSON.parse(me.frm.doc.reconciliation_json), null, this);
return false;
}, "icon-download", "btn-default");
}
},
show_reconciliation_data: function() {
var $wrapper = $(cur_frm.fields_dict.reconciliation_html.wrapper).empty();
if(this.frm.doc.reconciliation_json) {
var reconciliation_data = JSON.parse(this.frm.doc.reconciliation_json);
var _make = function(data, header) {
var result = "";
var _render = header
? function(col) { return "<th>" + col + "</th>"; }
: function(col) { return "<td>" + col + "</td>"; };
$.each(data, function(i, row) {
result += "<tr>"
+ $.map(row, _render).join("")
+ "</tr>";
});
return result;
};
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});
@ -167,4 +110,4 @@ cur_frm.cscript.company = function(doc, cdt, cdn) {
cur_frm.cscript.posting_date = function(doc, cdt, cdn){
erpnext.get_fiscal_year(doc.company, doc.posting_date);
}
}

View File

@ -1,5 +1,5 @@
{
"allow_copy": 1,
"allow_copy": 0,
"autoname": "SR/.######",
"creation": "2013-03-28 10:35:31",
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
@ -31,6 +31,14 @@
"read_only": 0,
"reqd": 1
},
{
"depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)",
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Difference Account",
"options": "Account",
"permlevel": 0
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
@ -42,6 +50,11 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "col1",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"fieldname": "company",
"fieldtype": "Link",
@ -59,14 +72,6 @@
"print_hide": 1,
"reqd": 1
},
{
"depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)",
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Difference Account",
"options": "Account",
"permlevel": 0
},
{
"depends_on": "eval:cint(sys_defaults.auto_accounting_for_stock)",
"fieldname": "cost_center",
@ -76,9 +81,31 @@
"permlevel": 0
},
{
"fieldname": "col1",
"fieldtype": "Column Break",
"permlevel": 0
"fieldname": "sb9",
"fieldtype": "Section Break",
"permlevel": 0,
"precision": ""
},
{
"fieldname": "items",
"fieldtype": "Table",
"label": "Items",
"options": "Stock Reconciliation Item",
"permlevel": 0,
"precision": ""
},
{
"fieldname": "get_items",
"fieldtype": "Button",
"label": "Get Items",
"permlevel": 0,
"precision": ""
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"permlevel": 0,
"precision": ""
},
{
"fieldname": "upload_html",
@ -119,7 +146,7 @@
"idx": 1,
"is_submittable": 1,
"max_attachments": 1,
"modified": "2015-02-05 05:11:47.153367",
"modified": "2015-02-17 02:09:17.483016",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",

View File

@ -4,7 +4,6 @@
from __future__ import unicode_literals
import frappe
import frappe.defaults
import json
from frappe import msgprint, _
from frappe.utils import cstr, flt, cint
from erpnext.stock.stock_ledger import update_entries_after
@ -28,61 +27,41 @@ class StockReconciliation(StockController):
self.make_gl_entries_on_cancel()
def validate_data(self):
if not self.reconciliation_json:
return
data = json.loads(self.reconciliation_json)
# strip out extra columns (if any)
data = [row[:4] for row in data]
if self.head_row not in data:
msgprint(_("""Wrong Template: Unable to find head row."""),
raise_exception=1)
# remove the help part and save the json
head_row_no = 0
if data.index(self.head_row) != 0:
head_row_no = data.index(self.head_row)
data = data[head_row_no:]
self.reconciliation_json = json.dumps(data)
def _get_msg(row_num, msg):
return _("Row # {0}: ").format(row_num+head_row_no+2) + msg
return _("Row # {0}: ").format(row_num+1) + msg
self.validation_messages = []
item_warehouse_combinations = []
# validate no of rows
rows = data[1:]
rows = self.items
if len(rows) > 100:
msgprint(_("""Sorry! We can only allow upto 100 rows for Stock Reconciliation."""),
raise_exception=True)
frappe.throw(_("""Max 100 rows for Stock Reconciliation."""))
for row_num, row in enumerate(rows):
# find duplicates
if [row[0], row[1]] in item_warehouse_combinations:
if [row.item_code, row.warehouse] in item_warehouse_combinations:
self.validation_messages.append(_get_msg(row_num, _("Duplicate entry")))
else:
item_warehouse_combinations.append([row[0], row[1]])
item_warehouse_combinations.append([row.item_code, row.warehouse])
self.validate_item(row[0], row_num+head_row_no+2)
self.validate_item(row.item_code, row_num+1)
# validate warehouse
if not frappe.db.get_value("Warehouse", row[1]):
if not frappe.db.get_value("Warehouse", row.warehouse):
self.validation_messages.append(_get_msg(row_num, _("Warehouse not found in the system")))
# if both not specified
if row[2] in ["", None] and row[3] in ["", None]:
if row.qty in ["", None] and row.valuation_rate in ["", None]:
self.validation_messages.append(_get_msg(row_num,
_("Please specify either Quantity or Valuation Rate or both")))
# do not allow negative quantity
if flt(row[2]) < 0:
if flt(row.qty) < 0:
self.validation_messages.append(_get_msg(row_num,
_("Negative Quantity is not allowed")))
# do not allow negative valuation
if flt(row[3]) < 0:
if flt(row.valuation_rate) < 0:
self.validation_messages.append(_get_msg(row_num,
_("Negative Valuation Rate is not allowed")))
@ -129,16 +108,7 @@ class StockReconciliation(StockController):
and create stock ledger entries based on the difference"""
from erpnext.stock.stock_ledger import get_previous_sle
row_template = ["item_code", "warehouse", "qty", "valuation_rate"]
if not self.reconciliation_json:
msgprint(_("""Stock Reconciliation file not uploaded"""), raise_exception=1)
data = json.loads(self.reconciliation_json)
for row_num, row in enumerate(data[data.index(self.head_row)+1:]):
row = frappe._dict(zip(row_template, row))
row["row_num"] = row_num
for row in self.items:
if row.qty in ("", None) or row.valuation_rate in ("", None):
previous_sle = get_previous_sle({
"item_code": row.item_code,
@ -216,7 +186,14 @@ class StockReconciliation(StockController):
frappe.throw(_("Difference Account must be a 'Liability' type account, since this Stock Reconciliation is an Opening Entry"))
@frappe.whitelist()
def upload():
from frappe.utils.csvutils import read_csv_content_from_uploaded_file
csv_content = read_csv_content_from_uploaded_file()
return filter(lambda x: x and any(x), csv_content)
def get_items(warehouse):
from erpnext.stock.utils import get_stock_balance
items = frappe.get_list("Item", fields=["name"], filters=
{"is_stock_item": "Yes", "has_serial_no": "No", "has_batch_no": "No"})
for item in items:
item.item_code = item.name
item.warehouse = warehouse
del item["name"]
item.qty, item.valuation_rate = get_stock_balance(item.name, warehouse, with_valuation_rate=True)
return items

View File

@ -0,0 +1,110 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"creation": "2015-02-17 01:06:05.072764",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Other",
"fields": [
{
"allow_on_submit": 0,
"fieldname": "item_code",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Item Code",
"no_copy": 0,
"options": "Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0
},
{
"allow_on_submit": 0,
"fieldname": "warehouse",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Warehouse",
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0
},
{
"allow_on_submit": 0,
"description": "Leave blank if no change",
"fieldname": "qty",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Quantity",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0
},
{
"allow_on_submit": 0,
"description": "Leave blank if no change",
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Valuation Rate",
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"modified": "2015-02-17 01:07:50.200649",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class StockReconciliationItem(Document):
pass

View File

@ -34,19 +34,22 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
return sum(sle_map.values())
def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None):
def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None, with_valuation_rate=False):
"""Returns stock balance quantity at given warehouse on given posting date or current date.
If `with_valuation_rate` is True, will return tuple (qty, rate)"""
if not posting_date: posting_date = nowdate()
if not posting_time: posting_time = nowtime()
last_entry = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry`
last_entry = frappe.db.sql("""select qty_after_transaction, valuation_rate from `tabStock Ledger Entry`
where item_code=%s and warehouse=%s
and timestamp(posting_date, posting_time) < timestamp(%s, %s)
order by timestamp(posting_date, posting_time) limit 1""",
(item_code, warehouse, posting_date, posting_time))
if last_entry:
return last_entry[0][0]
if with_valuation_rate:
return (last_entry[0][0], last_entry[0][1]) if last_entry else (0.0, 0.0)
else:
return 0.0
return last_entry[0][0] if last_entry else 0.0
def get_latest_stock_balance():
bin_map = {}