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

This commit is contained in:
Nabin Hait
2013-12-18 10:42:04 +05:30
82 changed files with 1057 additions and 1644 deletions

View File

@@ -73,6 +73,9 @@ class DocType(SellingController):
self.update_current_stock()
self.validate_with_previous_doc()
from stock.doctype.packed_item.packed_item import make_packing_list
self.doclist = make_packing_list(self, 'delivery_note_details')
self.doc.status = 'Draft'
if not self.doc.installation_status: self.doc.installation_status = 'Not Installed'
@@ -142,10 +145,6 @@ class DocType(SellingController):
bin = webnotes.conn.sql("select actual_qty, projected_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1)
d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0
def on_update(self):
from stock.doctype.packed_item.packed_item import make_packing_list
self.doclist = make_packing_list(self, 'delivery_note_details')
def on_submit(self):
self.validate_packed_qty()

View File

@@ -2,7 +2,7 @@
{
"creation": "2013-05-24 19:29:09",
"docstatus": 0,
"modified": "2013-11-03 14:20:19",
"modified": "2013-12-14 17:26:12",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -229,7 +229,7 @@
"doctype": "DocField",
"fieldname": "po_date",
"fieldtype": "Date",
"hidden": 1,
"hidden": 0,
"label": "Customer's Purchase Order Date",
"no_copy": 0,
"oldfieldname": "po_date",
@@ -1058,7 +1058,7 @@
},
{
"doctype": "DocPerm",
"match": "customer_name",
"match": "customer",
"role": "Customer"
}
]

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import webnotes
from webnotes.utils import cstr, flt, cint
from webnotes.utils import cstr, flt
from webnotes.model.doc import addchild
from webnotes.model.bean import getlist
from webnotes import msgprint, _
@@ -116,40 +116,29 @@ class DocType(DocListController, WebsiteGenerator):
self.doc.is_pro_applicable = "No"
if self.doc.is_pro_applicable == 'Yes' and self.doc.is_stock_item == 'No':
msgprint("As Production Order can be made for this Item, then Is Stock Item Should be 'Yes' as we maintain it's stock. Refer Manufacturing and Inventory section.", raise_exception=1)
webnotes.throw(_("As Production Order can be made for this item, \
it must be a stock item."))
if self.doc.has_serial_no == 'Yes' and self.doc.is_stock_item == 'No':
msgprint("'Has Serial No' can not be 'Yes' for non-stock item", raise_exception=1)
def check_for_active_boms(self):
def _check_for_active_boms(field_label):
if field_label in ['Is Active', 'Is Purchase Item']:
bom_mat = webnotes.conn.sql("""select distinct t1.parent
from `tabBOM Item` t1, `tabBOM` t2 where t2.name = t1.parent
and t1.item_code =%s and ifnull(t1.bom_no, '') = '' and t2.is_active = 1
and t2.docstatus = 1 and t1.docstatus =1 """, self.doc.name)
if bom_mat and bom_mat[0][0]:
msgprint(_(field_label) + _(" should be 'Yes'. As Item: ") + self.doc.name +
_(" is present in one or many Active BOMs"), raise_exception=1)
if ((field_label == 'Allow Production Order'
and self.doc.is_sub_contracted_item != 'Yes')
or (field_label == 'Is Sub Contracted Item'
and self.doc.is_manufactured_item != 'Yes')):
bom = webnotes.conn.sql("""select name from `tabBOM` where item = %s
and is_active = 1""", (self.doc.name,))
if bom and bom[0][0]:
msgprint(_(field_label) + _(" should be 'Yes'. As Item: ") + self.doc.name +
_(" is present in one or many Active BOMs"), raise_exception=1)
if not cint(self.doc.fields.get("__islocal")):
fl = {'is_manufactured_item' :'Allow Bill of Materials',
'is_sub_contracted_item':'Is Sub Contracted Item',
'is_purchase_item' :'Is Purchase Item',
'is_pro_applicable' :'Allow Production Order'}
for d in fl:
if cstr(self.doc.fields.get(d)) != 'Yes':
_check_for_active_boms(fl[d])
if self.doc.is_active != "Yes" or self.doc.is_purchase_item != "Yes":
bom_mat = webnotes.conn.sql("""select distinct t1.parent
from `tabBOM Item` t1, `tabBOM` t2 where t2.name = t1.parent
and t1.item_code =%s and ifnull(t1.bom_no, '') = '' and t2.is_active = 1
and t2.docstatus = 1 and t1.docstatus =1 """, self.doc.name)
if bom_mat and bom_mat[0][0]:
webnotes.throw(_("Item must be active and purchase item, \
as it is present in one or many Active BOMs"))
if self.doc.is_manufactured_item != "Yes":
bom = webnotes.conn.sql("""select name from `tabBOM` where item = %s
and is_active = 1""", (self.doc.name,))
if bom and bom[0][0]:
webnotes.throw(_("""Allow Bill of Materials should be 'Yes'. Because one or many \
active BOMs present for this item"""))
def fill_customer_code(self):
""" Append all the customer codes and insert into "customer_code" field of item table """

View File

@@ -37,20 +37,25 @@ class DocType(BuyingController):
for so_no in so_items.keys():
for item in so_items[so_no].keys():
already_indented = webnotes.conn.sql("select sum(qty) from `tabMaterial Request Item` where item_code = '%s' and sales_order_no = '%s' and docstatus = 1 and parent != '%s'" % (item, so_no, self.doc.name))
already_indented = webnotes.conn.sql("""select sum(qty) from `tabMaterial Request Item`
where item_code = %s and sales_order_no = %s and
docstatus = 1 and parent != %s""", (item, so_no, self.doc.name))
already_indented = already_indented and flt(already_indented[0][0]) or 0
actual_so_qty = webnotes.conn.sql("select sum(qty) from `tabSales Order Item` where parent = '%s' and item_code = '%s' and docstatus = 1 group by parent" % (so_no, item))
actual_so_qty = webnotes.conn.sql("""select sum(qty) from `tabSales Order Item`
where parent = %s and item_code = %s and docstatus = 1
group by parent""", (so_no, item))
actual_so_qty = actual_so_qty and flt(actual_so_qty[0][0]) or 0
if flt(so_items[so_no][item]) + already_indented > actual_so_qty:
msgprint("You can raise indent of maximum qty: %s for item: %s against sales order: %s\n Anyway, you can add more qty in new row for the same item." % (actual_so_qty - already_indented, item, so_no), raise_exception=1)
if actual_so_qty and (flt(so_items[so_no][item]) + already_indented > actual_so_qty):
webnotes.throw("You can raise indent of maximum qty: %s for item: %s against sales order: %s\
\n Anyway, you can add more qty in new row for the same item."
% (actual_so_qty - already_indented, item, so_no))
def validate_schedule_date(self):
for d in getlist(self.doclist, 'indent_details'):
if d.schedule_date < self.doc.transaction_date:
msgprint("Expected Date cannot be before Material Request Date")
raise Exception
webnotes.throw(_("Expected Date cannot be before Material Request Date"))
# Validate
# ---------------------
@@ -80,8 +85,8 @@ class DocType(BuyingController):
for d in getlist(self.doclist, 'indent_details'):
if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes":
if not d.warehouse:
msgprint("Please Enter Warehouse for Item %s as it is stock item"
% cstr(d.item_code), raise_exception=1)
webnotes.throw("Please Enter Warehouse for Item %s as it is stock item"
% cstr(d.item_code))
qty =flt(d.qty)
if is_stopped:
@@ -96,16 +101,17 @@ class DocType(BuyingController):
update_bin(args)
def on_submit(self):
webnotes.conn.set(self.doc,'status','Submitted')
webnotes.conn.set(self.doc, 'status', 'Submitted')
self.update_bin(is_submit = 1, is_stopped = 0)
def check_modified_date(self):
mod_db = webnotes.conn.sql("select modified from `tabMaterial Request` where name = '%s'" % self.doc.name)
date_diff = webnotes.conn.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.doc.modified)))
mod_db = webnotes.conn.sql("""select modified from `tabMaterial Request` where name = %s""",
self.doc.name)
date_diff = webnotes.conn.sql("""select TIMEDIFF('%s', '%s')"""
% (mod_db[0][0], cstr(self.doc.modified)))
if date_diff and date_diff[0][0]:
msgprint(cstr(self.doc.doctype) +" => "+ cstr(self.doc.name) +" has been modified. Please Refresh. ")
raise Exception
webnotes.throw(cstr(self.doc.doctype) + " => " + cstr(self.doc.name) + " has been modified. Please Refresh.")
def update_status(self, status):
self.check_modified_date()
@@ -113,10 +119,10 @@ class DocType(BuyingController):
self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1)
# Step 2:=> Set status
webnotes.conn.set(self.doc,'status',cstr(status))
webnotes.conn.set(self.doc, 'status', cstr(status))
# Step 3:=> Acknowledge User
msgprint(self.doc.doctype + ": " + self.doc.name + " has been %s." % ((status == 'Submitted') and 'Unstopped' or cstr(status)) )
msgprint(self.doc.doctype + ": " + self.doc.name + " has been %s." % ((status == 'Submitted') and 'Unstopped' or cstr(status)))
def on_cancel(self):
@@ -177,9 +183,9 @@ def update_completed_qty(controller, caller_method):
mr_doctype = webnotes.get_doctype("Material Request")
if mr_obj.doc.status in ["Stopped", "Cancelled"]:
msgprint(_("Material Request") + ": %s, " % mr_obj.doc.name
webnotes.throw(_("Material Request") + ": %s, " % mr_obj.doc.name
+ _(mr_doctype.get_label("status")) + " = %s. " % _(mr_obj.doc.status)
+ _("Cannot continue."), raise_exception=webnotes.InvalidStatusError)
+ _("Cannot continue."), exc=webnotes.InvalidStatusError)
_update_requested_qty(controller, mr_obj, mr_items)

View File

@@ -56,9 +56,6 @@ def update_packing_list_item(obj, packing_item_code, qty, warehouse, line, packi
pi.batch_no = cstr(line.batch_no)
pi.idx = packing_list_idx
# saved, since this function is called on_update of delivery note
pi.save()
packing_list_idx += 1
@@ -87,19 +84,13 @@ def cleanup_packing_list(obj, parent_items):
for d in obj.doclist.get({"parentfield": "packing_details"}):
if [d.parent_item, d.parent_detail_docname] not in parent_items:
# mark for deletion from doclist
delete_list.append(d.name)
delete_list.append([d.parent_item, d.parent_detail_docname])
if not delete_list:
return obj.doclist
# delete from doclist
obj.doclist = webnotes.doclist(filter(lambda d: d.name not in delete_list, obj.doclist))
# delete from db
webnotes.conn.sql("""\
delete from `tabPacked Item`
where name in (%s)"""
% (", ".join(["%s"] * len(delete_list))),
tuple(delete_list))
obj.doclist = webnotes.doclist(filter(lambda d: [d.parent_item, d.parent_detail_docname]
not in delete_list, obj.doclist))
return obj.doclist

View File

@@ -150,7 +150,7 @@ class DocType(StockController):
where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No'
order by posting_date desc, posting_time desc, name desc""",
("%%%s%%" % self.doc.name, self.doc.item_code), as_dict=1):
if self.doc.name in get_serial_nos(sle.serial_no):
if self.doc.name.upper() in get_serial_nos(sle.serial_no):
if sle.actual_qty > 0:
sle_dict.setdefault("incoming", []).append(sle)
else:
@@ -268,7 +268,8 @@ def get_item_details(item_code):
from tabItem where name=%s""", item_code, as_dict=True)[0]
def get_serial_nos(serial_no):
return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()]
return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
if s.strip()]
def make_serial_no(serial_no, sle):
sr = webnotes.new_bean("Serial No")

View File

@@ -106,7 +106,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
},
callback: function(r) {
if (!r.exc) {
for(d in getchildren('Stock Entry Detail',doc.name,'mtn_details')) {
for(d in getchildren('Stock Entry Detail', me.frm.doc.name, 'mtn_details')) {
if(!d.expense_account) d.expense_account = r.message;
}
}
@@ -244,7 +244,7 @@ cur_frm.script_manager.make(erpnext.stock.StockEntry);
cur_frm.cscript.toggle_related_fields = function(doc) {
disable_from_warehouse = inList(["Material Receipt", "Sales Return"], doc.purpose);
disable_to_warehouse = inList(["Material Issue", "Purchase Return"], doc.purpose)
disable_to_warehouse = inList(["Material Issue", "Purchase Return"], doc.purpose);
cur_frm.toggle_enable("from_warehouse", !disable_from_warehouse);
cur_frm.toggle_enable("to_warehouse", !disable_to_warehouse);
@@ -302,7 +302,7 @@ cur_frm.fields_dict['production_order'].get_query = function(doc) {
}
cur_frm.cscript.purpose = function(doc, cdt, cdn) {
cur_frm.cscript.toggle_related_fields(doc, cdt, cdn);
cur_frm.cscript.toggle_related_fields(doc);
}
// Overloaded query for link batch_no

View File

@@ -2,7 +2,7 @@
{
"creation": "2013-04-09 11:43:55",
"docstatus": 0,
"modified": "2013-11-03 14:11:42",
"modified": "2013-12-09 16:24:10",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -108,7 +108,7 @@
"doctype": "DocField",
"fieldname": "delivery_note_no",
"fieldtype": "Link",
"hidden": 1,
"hidden": 0,
"in_filter": 0,
"label": "Delivery Note No",
"no_copy": 1,
@@ -126,7 +126,7 @@
"doctype": "DocField",
"fieldname": "sales_invoice_no",
"fieldtype": "Link",
"hidden": 1,
"hidden": 0,
"label": "Sales Invoice No",
"no_copy": 1,
"options": "Sales Invoice",
@@ -139,7 +139,7 @@
"doctype": "DocField",
"fieldname": "purchase_receipt_no",
"fieldtype": "Link",
"hidden": 1,
"hidden": 0,
"in_filter": 0,
"label": "Purchase Receipt No",
"no_copy": 1,
@@ -299,7 +299,7 @@
"doctype": "DocField",
"fieldname": "production_order",
"fieldtype": "Link",
"hidden": 1,
"hidden": 0,
"in_filter": 1,
"label": "Production Order",
"no_copy": 0,

View File

@@ -1 +0,0 @@
Average "age" of an Item in a particular Warehouse based on First-in-first-out (FIFO).

View File

@@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@@ -1,183 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.pages['stock-ageing'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: wn._('Stock Ageing'),
single_column: true
});
new erpnext.StockAgeing(wrapper);
wrapper.appframe.add_module_icon("Stock")
}
wn.require("app/js/stock_grid_report.js");
erpnext.StockAgeing = erpnext.StockGridReport.extend({
init: function(wrapper) {
this._super({
title: wn._("Stock Ageing"),
page: wrapper,
parent: $(wrapper).find('.layout-main'),
appframe: wrapper.appframe,
doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Item Group", "Brand", "Serial No"],
})
},
setup_columns: function() {
this.columns = [
{id: "name", name: wn._("Item"), field: "name", width: 300,
link_formatter: {
open_btn: true,
doctype: '"Item"'
}},
{id: "item_name", name: wn._("Item Name"), field: "item_name",
width: 100, formatter: this.text_formatter},
{id: "description", name: wn._("Description"), field: "description",
width: 200, formatter: this.text_formatter},
{id: "brand", name: wn._("Brand"), field: "brand", width: 100},
{id: "average_age", name: wn._("Average Age"), field: "average_age",
formatter: this.currency_formatter},
{id: "earliest", name: wn._("Earliest"), field: "earliest",
formatter: this.currency_formatter},
{id: "latest", name: wn._("Latest"), field: "latest",
formatter: this.currency_formatter},
{id: "stock_uom", name: "UOM", field: "stock_uom", width: 100},
];
},
filters: [
{fieldtype:"Select", label: wn._("Warehouse"), link:"Warehouse",
default_value: "Select Warehouse..."},
{fieldtype:"Select", label: wn._("Brand"), link:"Brand",
default_value: "Select Brand...", filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val;
}, link_formatter: {filter_input: "brand"}},
{fieldtype:"Select", label: wn._("Plot By"),
options: ["Average Age", "Earliest", "Latest"]},
{fieldtype:"Date", label: wn._("To Date")},
{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: wn._("Reset Filters")}
],
setup_filters: function() {
var me = this;
this._super();
this.trigger_refresh_on_change(["warehouse", "plot_by", "brand"]);
this.show_zero_check();
},
init_filter_values: function() {
this._super();
this.filter_inputs.to_date.val(dateutil.obj_to_user(new Date()));
},
prepare_data: function() {
var me = this;
if(!this.data) {
me._data = wn.report_dump.data["Item"];
me.item_by_name = me.make_name_map(me._data);
}
this.data = [].concat(this._data);
this.serialized_buying_rates = this.get_serialized_buying_rates();
$.each(this.data, function(i, d) {
me.reset_item_values(d);
});
this.prepare_balances();
// filter out brand
this.data = $.map(this.data, function(d) {
return me.apply_filter(d, "brand") ? d : null;
});
// filter out rows with zero values
this.data = $.map(this.data, function(d) {
return me.apply_zero_filter(null, d, null, me) ? d : null;
});
},
prepare_balances: function() {
var me = this;
var to_date = dateutil.str_to_obj(this.to_date);
var data = wn.report_dump.data["Stock Ledger Entry"];
this.item_warehouse = {};
for(var i=0, j=data.length; i<j; i++) {
var sl = data[i];
var posting_date = dateutil.str_to_obj(sl.posting_date);
if(me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) {
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
// call diff to build fifo stack in item_warehouse
var diff = me.get_value_diff(wh, sl, true);
if(posting_date > to_date)
break;
}
}
$.each(me.data, function(i, item) {
var full_fifo_stack = [];
if(me.is_default("warehouse")) {
$.each(me.item_warehouse[item.name] || {}, function(i, wh) {
full_fifo_stack = full_fifo_stack.concat(wh.fifo_stack || [])
});
} else {
full_fifo_stack = me.get_item_warehouse(me.warehouse, item.name).fifo_stack || [];
}
var age_qty = total_qty = 0.0;
var min_age = max_age = null;
$.each(full_fifo_stack, function(i, batch) {
var batch_age = dateutil.get_diff(me.to_date, batch[2]);
age_qty += batch_age * batch[0];
total_qty += batch[0];
max_age = Math.max(max_age, batch_age);
if(min_age===null) min_age=batch_age;
else min_age = Math.min(min_age, batch_age);
});
item.average_age = total_qty.toFixed(2)==0.0 ? 0
: (age_qty / total_qty).toFixed(2);
item.earliest = max_age || 0.0;
item.latest = min_age || 0.0;
});
this.data = this.data.sort(function(a, b) {
var sort_by = me.plot_by.replace(" ", "_").toLowerCase();
return b[sort_by] - a[sort_by];
});
},
get_plot_data: function() {
var data = [];
var me = this;
data.push({
label: me.plot_by,
data: $.map(me.data, function(item, idx) {
return [[idx+1, item[me.plot_by.replace(" ", "_").toLowerCase() ]]]
}),
bars: {show: true},
});
return data.length ? data : false;
},
get_plot_options: function() {
var me = this;
return {
grid: { hoverable: true, clickable: true },
xaxis: {
ticks: $.map(me.data, function(item, idx) { return [[idx+1, item.name]] }),
max: 15
},
series: { downsample: { threshold: 1000 } }
}
}
});

View File

@@ -1,37 +0,0 @@
[
{
"creation": "2012-09-21 20:15:14",
"docstatus": 0,
"modified": "2013-07-11 14:44:08",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-table",
"module": "Stock",
"name": "__common__",
"page_name": "stock-ageing",
"standard": "Yes",
"title": "Stock Ageing"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "stock-ageing",
"parentfield": "roles",
"parenttype": "Page"
},
{
"doctype": "Page",
"name": "stock-ageing"
},
{
"doctype": "Page Role",
"role": "Analytics"
},
{
"doctype": "Page Role",
"role": "Material Manager"
}
]

View File

@@ -138,7 +138,7 @@ wn.module_page["Stock"] = [
items: [
{
"label":wn._("Stock Ledger"),
doctype: "Delivery Note",
doctype: "Item",
route: "query-report/Stock Ledger"
},
{
@@ -146,12 +146,14 @@ wn.module_page["Stock"] = [
page: "stock-balance"
},
{
"page":"stock-level",
"label": wn._("Stock Level")
"label":wn._("Stock Projected Qty"),
doctype: "Item",
route: "query-report/Stock Projected Qty"
},
{
"page":"stock-ageing",
"label": wn._("Stock Ageing")
"label":wn._("Stock Ageing"),
doctype: "Item",
route: "query-report/Stock Ageing"
},
]
},

View File

@@ -1 +0,0 @@
Stock movement report based on Stock Ledger Entry.

View File

@@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@@ -1,247 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.pages['stock-ledger'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: wn._('Stock Ledger'),
single_column: true
});
new erpnext.StockLedger(wrapper);
wrapper.appframe.add_module_icon("Stock")
}
wn.require("app/js/stock_grid_report.js");
erpnext.StockLedger = erpnext.StockGridReport.extend({
init: function(wrapper) {
this._super({
title: wn._("Stock Ledger"),
page: wrapper,
parent: $(wrapper).find('.layout-main'),
appframe: wrapper.appframe,
doctypes: ["Item", "Item Group", "Warehouse", "Stock Ledger Entry", "Brand", "Serial No"],
})
},
setup_columns: function() {
this.hide_balance = (this.is_default("item_code") || this.voucher_no) ? true : false;
this.columns = [
{id: "posting_datetime", name: wn._("Posting Date"), field: "posting_datetime", width: 120,
formatter: this.date_formatter},
{id: "item_code", name: wn._("Item Code"), field: "item_code", width: 160,
link_formatter: {
filter_input: "item_code",
open_btn: true,
doctype: '"Item"',
}},
{id: "description", name: wn._("Description"), field: "description", width: 200,
formatter: this.text_formatter},
{id: "warehouse", name: wn._("Warehouse"), field: "warehouse", width: 100,
link_formatter: {filter_input: "warehouse"}},
{id: "brand", name: wn._("Brand"), field: "brand", width: 100},
{id: "stock_uom", name: wn._("UOM"), field: "stock_uom", width: 100},
{id: "qty", name: wn._("Qty"), field: "qty", width: 100,
formatter: this.currency_formatter},
{id: "balance", name: wn._("Balance Qty"), field: "balance", width: 100,
formatter: this.currency_formatter,
hidden: this.hide_balance},
{id: "balance_value", name: wn._("Balance Value"), field: "balance_value", width: 100,
formatter: this.currency_formatter, hidden: this.hide_balance},
{id: "voucher_type", name: wn._("Voucher Type"), field: "voucher_type", width: 120},
{id: "voucher_no", name: wn._("Voucher No"), field: "voucher_no", width: 160,
link_formatter: {
filter_input: "voucher_no",
open_btn: true,
doctype: "dataContext.voucher_type"
}},
];
},
filters: [
{fieldtype:"Select", label: wn._("Warehouse"), link:"Warehouse",
default_value: "Select Warehouse...", filter: function(val, item, opts) {
return item.warehouse == val || val == opts.default_value;
}},
{fieldtype:"Link", label: wn._("Item Code"), link:"Item", default_value: "Select Item...",
filter: function(val, item, opts) {
return item.item_code == val || !val;
}},
{fieldtype:"Select", label: "Brand", link:"Brand",
default_value: "Select Brand...", filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val || item._show;
}, link_formatter: {filter_input: "brand"}},
{fieldtype:"Data", label: wn._("Voucher No"),
filter: function(val, item, opts) {
if(!val) return true;
return (item.voucher_no && item.voucher_no.indexOf(val)!=-1);
}},
{fieldtype:"Date", label: wn._("From Date"), filter: function(val, item) {
return dateutil.str_to_obj(val) <= dateutil.str_to_obj(item.posting_date);
}},
{fieldtype:"Label", label: wn._("To")},
{fieldtype:"Date", label: wn._("To Date"), filter: function(val, item) {
return dateutil.str_to_obj(val) >= dateutil.str_to_obj(item.posting_date);
}},
{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: wn._("Reset Filters")}
],
setup_filters: function() {
var me = this;
this._super();
this.wrapper.bind("apply_filters_from_route", function() { me.toggle_enable_brand(); });
this.filter_inputs.item_code.change(function() { me.toggle_enable_brand(); });
this.trigger_refresh_on_change(["item_code", "warehouse", "brand"]);
},
toggle_enable_brand: function() {
if(!this.filter_inputs.item_code.val()) {
this.filter_inputs.brand.prop("disabled", false);
} else {
this.filter_inputs.brand
.val(this.filter_inputs.brand.get(0).opts.default_value)
.prop("disabled", true);
}
},
init_filter_values: function() {
this._super();
this.filter_inputs.warehouse.get(0).selectedIndex = 0;
},
prepare_data: function() {
var me = this;
if(!this.item_by_name)
this.item_by_name = this.make_name_map(wn.report_dump.data["Item"]);
var data = wn.report_dump.data["Stock Ledger Entry"];
var out = [];
var opening = {
item_code: "On " + dateutil.str_to_user(this.from_date), qty: 0.0, balance: 0.0,
id:"_opening", _show: true, _style: "font-weight: bold", balance_value: 0.0
}
var total_in = {
item_code: "Total In", qty: 0.0, balance: 0.0, balance_value: 0.0,
id:"_total_in", _show: true, _style: "font-weight: bold"
}
var total_out = {
item_code: "Total Out", qty: 0.0, balance: 0.0, balance_value: 0.0,
id:"_total_out", _show: true, _style: "font-weight: bold"
}
// clear balance
$.each(wn.report_dump.data["Item"], function(i, item) {
item.balance = item.balance_value = 0.0;
});
// initialize warehouse-item map
this.item_warehouse = {};
this.serialized_buying_rates = this.get_serialized_buying_rates();
var from_datetime = dateutil.str_to_obj(me.from_date + " 00:00:00");
var to_datetime = dateutil.str_to_obj(me.to_date + " 23:59:59");
//
for(var i=0, j=data.length; i<j; i++) {
var sl = data[i];
var item = me.item_by_name[sl.item_code]
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
sl.description = item.description;
sl.posting_datetime = sl.posting_date + " " + (sl.posting_time || "00:00:00");
sl.brand = item.brand;
var posting_datetime = dateutil.str_to_obj(sl.posting_datetime);
var is_fifo = item.valuation_method ? item.valuation_method=="FIFO"
: sys_defaults.valuation_method=="FIFO";
var value_diff = me.get_value_diff(wh, sl, is_fifo);
// opening, transactions, closing, total in, total out
var before_end = posting_datetime <= to_datetime;
if((!me.is_default("item_code") ? me.apply_filter(sl, "item_code") : true)
&& me.apply_filter(sl, "warehouse") && me.apply_filter(sl, "voucher_no")
&& me.apply_filter(sl, "brand")) {
if(posting_datetime < from_datetime) {
opening.balance += sl.qty;
opening.balance_value += value_diff;
} else if(before_end) {
if(sl.qty > 0) {
total_in.qty += sl.qty;
total_in.balance_value += value_diff;
} else {
total_out.qty += (-1 * sl.qty);
total_out.balance_value += value_diff;
}
}
}
if(!before_end) break;
// apply filters
if(me.apply_filters(sl)) {
out.push(sl);
}
// update balance
if((!me.is_default("warehouse") ? me.apply_filter(sl, "warehouse") : true)) {
sl.balance = me.item_by_name[sl.item_code].balance + sl.qty;
me.item_by_name[sl.item_code].balance = sl.balance;
sl.balance_value = me.item_by_name[sl.item_code].balance_value + value_diff;
me.item_by_name[sl.item_code].balance_value = sl.balance_value;
}
}
if(me.item_code && !me.voucher_no) {
var closing = {
item_code: "On " + dateutil.str_to_user(this.to_date),
balance: (out.length ? out[out.length-1].balance : 0), qty: 0,
balance_value: (out.length ? out[out.length-1].balance_value : 0),
id:"_closing", _show: true, _style: "font-weight: bold"
};
total_out.balance_value = -total_out.balance_value;
var out = [opening].concat(out).concat([total_in, total_out, closing]);
}
this.data = out;
},
get_plot_data: function() {
var data = [];
var me = this;
if(me.hide_balance) return false;
data.push({
label: me.item_code,
data: [[dateutil.str_to_obj(me.from_date).getTime(), me.data[0].balance]]
.concat($.map(me.data, function(col, idx) {
if (col.posting_datetime) {
return [[dateutil.str_to_obj(col.posting_datetime).getTime(), col.balance - col.qty],
[dateutil.str_to_obj(col.posting_datetime).getTime(), col.balance]]
}
return null;
})).concat([
// closing
[dateutil.str_to_obj(me.to_date).getTime(), me.data[me.data.length - 1].balance]
]),
points: {show: true},
lines: {show: true, fill: true},
});
return data;
},
get_plot_options: function() {
return {
grid: { hoverable: true, clickable: true },
xaxis: { mode: "time",
min: dateutil.str_to_obj(this.from_date).getTime(),
max: dateutil.str_to_obj(this.to_date).getTime(),
},
series: { downsample: { threshold: 1000 } }
}
},
get_tooltip_text: function(label, x, y) {
var d = new Date(x);
var date = dateutil.obj_to_user(d) + " " + d.getHours() + ":" + d.getMinutes();
var value = format_number(y);
return value.bold() + " on " + date;
}
});

View File

@@ -1,41 +0,0 @@
[
{
"creation": "2012-09-21 20:15:14",
"docstatus": 0,
"modified": "2013-07-11 14:44:19",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-table",
"module": "Stock",
"name": "__common__",
"page_name": "stock-ledger",
"standard": "Yes",
"title": "Stock Ledger"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "stock-ledger",
"parentfield": "roles",
"parenttype": "Page"
},
{
"doctype": "Page",
"name": "stock-ledger"
},
{
"doctype": "Page Role",
"role": "Analytics"
},
{
"doctype": "Page Role",
"role": "Material Manager"
},
{
"doctype": "Page Role",
"role": "Material User"
}
]

View File

@@ -1 +0,0 @@
Stock levels (actual, planned, reserved, ordered) for Items on a particular date.

View File

@@ -1,228 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.pages['stock-level'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: wn._('Stock Level'),
single_column: true
});
new erpnext.StockLevel(wrapper);
wrapper.appframe.add_module_icon("Stock")
;
}
wn.require("app/js/stock_grid_report.js");
erpnext.StockLevel = erpnext.StockGridReport.extend({
init: function(wrapper) {
var me = this;
this._super({
title: wn._("Stock Level"),
page: wrapper,
parent: $(wrapper).find('.layout-main'),
appframe: wrapper.appframe,
doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Production Order",
"Material Request Item", "Purchase Order Item", "Sales Order Item", "Brand", "Serial No"],
});
this.wrapper.bind("make", function() {
wn.utils.set_footnote(me, me.wrapper.get(0),
"<ul> \
<li style='font-weight: bold;'> \
Projected Qty = Actual Qty + Planned Qty + Requested Qty \
+ Ordered Qty - Reserved Qty </li> \
<ul> \
<li>"+wn._("Actual Qty: Quantity available in the warehouse.") +"</li>"+
"<li>"+wn._("Planned Qty: Quantity, for which, Production Order has been raised,")+
wn._("but is pending to be manufactured.")+ "</li> " +
"<li>"+wn._("Requested Qty: Quantity requested for purchase, but not ordered.") + "</li>" +
"<li>" + wn._("Ordered Qty: Quantity ordered for purchase, but not received.")+ "</li>" +
"<li>" + wn._("Reserved Qty: Quantity ordered for sale, but not delivered.") + "</li>" +
"</ul> \
</ul>");
});
},
setup_columns: function() {
this.columns = [
{id: "item_code", name: wn._("Item Code"), field: "item_code", width: 160,
link_formatter: {
filter_input: "item_code",
open_btn: true,
doctype: '"Item"',
}},
{id: "item_name", name: wn._("Item Name"), field: "item_name", width: 100,
formatter: this.text_formatter},
{id: "description", name: wn._("Description"), field: "description", width: 200,
formatter: this.text_formatter},
{id: "brand", name: wn._("Brand"), field: "brand", width: 100,
link_formatter: {filter_input: "brand"}},
{id: "warehouse", name: wn._("Warehouse"), field: "warehouse", width: 100,
link_formatter: {filter_input: "warehouse"}},
{id: "uom", name: wn._("UOM"), field: "uom", width: 60},
{id: "actual_qty", name: wn._("Actual Qty"),
field: "actual_qty", width: 80, formatter: this.currency_formatter},
{id: "planned_qty", name: wn._("Planned Qty"),
field: "planned_qty", width: 80, formatter: this.currency_formatter},
{id: "requested_qty", name: wn._("Requested Qty"),
field: "requested_qty", width: 80, formatter: this.currency_formatter},
{id: "ordered_qty", name: wn._("Ordered Qty"),
field: "ordered_qty", width: 80, formatter: this.currency_formatter},
{id: "reserved_qty", name: wn._("Reserved Qty"),
field: "reserved_qty", width: 80, formatter: this.currency_formatter},
{id: "projected_qty", name: wn._("Projected Qty"),
field: "projected_qty", width: 80, formatter: this.currency_formatter},
{id: "re_order_level", name: wn._("Re-Order Level"),
field: "re_order_level", width: 80, formatter: this.currency_formatter},
{id: "re_order_qty", name: wn._("Re-Order Qty"),
field: "re_order_qty", width: 80, formatter: this.currency_formatter},
];
},
filters: [
{fieldtype:"Link", label: wn._("Item Code"), link:"Item", default_value: "Select Item...",
filter: function(val, item, opts) {
return item.item_code == val || !val;
}},
{fieldtype:"Select", label: wn._("Warehouse"), link:"Warehouse",
default_value: "Select Warehouse...", filter: function(val, item, opts) {
return item.warehouse == val || val == opts.default_value;
}},
{fieldtype:"Select", label: wn._("Brand"), link:"Brand",
default_value: "Select Brand...", filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val;
}},
{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: wn._("Reset Filters")}
],
setup_filters: function() {
var me = this;
this._super();
this.wrapper.bind("apply_filters_from_route", function() { me.toggle_enable_brand(); });
this.filter_inputs.item_code.change(function() { me.toggle_enable_brand(); });
this.trigger_refresh_on_change(["item_code", "warehouse", "brand"]);
},
toggle_enable_brand: function() {
if(!this.filter_inputs.item_code.val()) {
this.filter_inputs.brand.prop("disabled", false);
} else {
this.filter_inputs.brand
.val(this.filter_inputs.brand.get(0).opts.default_value)
.prop("disabled", true);
}
},
init_filter_values: function() {
this._super();
this.filter_inputs.warehouse.get(0).selectedIndex = 0;
},
prepare_data: function() {
var me = this;
if(!this._data) {
this._data = [];
this.item_warehouse_map = {};
this.item_by_name = this.make_name_map(wn.report_dump.data["Item"]);
this.calculate_quantities();
}
this.data = [].concat(this._data);
this.data = $.map(this.data, function(d) {
return me.apply_filters(d) ? d : null;
});
this.calculate_total();
},
calculate_quantities: function() {
var me = this;
$.each([
["Stock Ledger Entry", "actual_qty"],
["Production Order", "planned_qty"],
["Material Request Item", "requested_qty"],
["Purchase Order Item", "ordered_qty"],
["Sales Order Item", "reserved_qty"]],
function(i, v) {
$.each(wn.report_dump.data[v[0]], function(i, item) {
var row = me.get_row(item.item_code, item.warehouse);
row[v[1]] += flt(item.qty);
});
}
);
// sort by item, warehouse
this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) {
return me.item_warehouse_map[key];
});
// calculate projected qty
$.each(this._data, function(i, row) {
row.projected_qty = row.actual_qty + row.planned_qty + row.requested_qty
+ row.ordered_qty - row.reserved_qty;
});
// filter out rows with zero values
this._data = $.map(this._data, function(d) {
return me.apply_zero_filter(null, d, null, me) ? d : null;
});
},
get_row: function(item_code, warehouse) {
var key = item_code + ":" + warehouse;
if(!this.item_warehouse_map[key]) {
var item = this.item_by_name[item_code];
var row = {
item_code: item_code,
warehouse: warehouse,
description: item.description,
brand: item.brand,
item_name: item.item_name || item.name,
uom: item.stock_uom,
id: key,
}
this.reset_item_values(row);
row["re_order_level"] = item.re_order_level
row["re_order_qty"] = item.re_order_qty
this.item_warehouse_map[key] = row;
}
return this.item_warehouse_map[key];
},
calculate_total: function() {
var me = this;
// show total if a specific item is selected and warehouse is not filtered
if(this.is_default("warehouse") && !this.is_default("item_code")) {
var total = {
id: "_total",
item_code: "Total",
_style: "font-weight: bold",
_show: true
};
this.reset_item_values(total);
$.each(this.data, function(i, row) {
$.each(me.columns, function(i, col) {
if (col.formatter==me.currency_formatter) {
total[col.id] += row[col.id];
}
});
});
this.data = this.data.concat([total]);
}
}
})

View File

@@ -1,37 +0,0 @@
[
{
"creation": "2012-12-31 10:52:14",
"docstatus": 0,
"modified": "2013-07-11 14:44:21",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-table",
"module": "Stock",
"name": "__common__",
"page_name": "stock-level",
"standard": "Yes",
"title": "Stock Level"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "stock-level",
"parentfield": "roles",
"parenttype": "Page"
},
{
"doctype": "Page",
"name": "stock-level"
},
{
"doctype": "Page Role",
"role": "Material Manager"
},
{
"doctype": "Page Role",
"role": "Analytics"
}
]

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.query_reports["Stock Ageing"] = {
"filters": [
{
"fieldname":"company",
"label": wn._("Company"),
"fieldtype": "Link",
"options": "Company",
"default": wn.defaults.get_user_default("company"),
"reqd": 1
},
{
"fieldname":"to_date",
"label": wn._("To Date"),
"fieldtype": "Date",
"default": wn.datetime.get_today(),
"reqd": 1
},
{
"fieldname":"warehouse",
"label": wn._("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse"
},
{
"fieldname":"item_code",
"label": wn._("Item"),
"fieldtype": "Link",
"options": "Item"
},
{
"fieldname":"brand",
"label": wn._("Brand"),
"fieldtype": "Link",
"options": "Brand"
}
]
}

View File

@@ -0,0 +1,94 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes
from webnotes.utils import date_diff
def execute(filters=None):
columns = get_columns()
item_details = get_fifo_queue(filters)
to_date = filters["to_date"]
data = []
for item, item_dict in item_details.items():
fifo_queue = item_dict["fifo_queue"]
details = item_dict["details"]
if not fifo_queue: continue
average_age = get_average_age(fifo_queue, to_date)
earliest_age = date_diff(to_date, fifo_queue[0][1])
latest_age = date_diff(to_date, fifo_queue[-1][1])
data.append([item, details.item_name, details.description, details.item_group,
details.brand, average_age, earliest_age, latest_age, details.stock_uom])
return columns, data
def get_average_age(fifo_queue, to_date):
batch_age = age_qty = total_qty = 0.0
for batch in fifo_queue:
batch_age = date_diff(to_date, batch[1])
age_qty += batch_age * batch[0]
total_qty += batch[0]
return (age_qty / total_qty) if total_qty else 0.0
def get_columns():
return ["Item Code:Link/Item:100", "Item Name::100", "Description::200",
"Item Group:Link/Item Group:100", "Brand:Link/Brand:100", "Average Age:Float:100",
"Earliest:Int:80", "Latest:Int:80", "UOM:Link/UOM:100"]
def get_fifo_queue(filters):
item_details = {}
for d in get_stock_ledger_entries(filters):
item_details.setdefault(d.name, {"details": d, "fifo_queue": []})
fifo_queue = item_details[d.name]["fifo_queue"]
if d.actual_qty > 0:
fifo_queue.append([d.actual_qty, d.posting_date])
else:
qty_to_pop = abs(d.actual_qty)
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
qty_to_pop -= batch[0]
fifo_queue.pop(0)
else:
# all from current batch
batch[0] -= qty_to_pop
qty_to_pop = 0
return item_details
def get_stock_ledger_entries(filters):
return webnotes.conn.sql("""select
item.name, item.item_name, item_group, brand, description, item.stock_uom,
actual_qty, posting_date
from `tabStock Ledger Entry` sle,
(select name, item_name, description, stock_uom, brand
from `tabItem` {item_conditions}) item
where item_code = item.name and
company = %(company)s and
posting_date <= %(to_date)s
{sle_conditions}
order by posting_date, posting_time, sle.name"""\
.format(item_conditions=get_item_conditions(filters),
sle_conditions=get_sle_conditions(filters)), filters, as_dict=True)
def get_item_conditions(filters):
conditions = []
if filters.get("item_code"):
conditions.append("item_code=%(item_code)s")
if filters.get("brand"):
conditions.append("brand=%(brand)s")
return "where {}".format(" and ".join(conditions)) if conditions else ""
def get_sle_conditions(filters):
conditions = []
if filters.get("warehouse"):
conditions.append("warehouse=%(warehouse)s")
return "and {}".format(" and ".join(conditions)) if conditions else ""

View File

@@ -0,0 +1,21 @@
[
{
"creation": "2013-12-02 17:09:31",
"docstatus": 0,
"modified": "2013-12-02 17:09:31",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"ref_doctype": "Item",
"report_name": "Stock Ageing",
"report_type": "Script Report"
},
{
"doctype": "Report",
"name": "Stock Ageing"
}
]

View File

@@ -15,14 +15,14 @@ wn.query_reports["Stock Ledger"] = {
"fieldname":"from_date",
"label": wn._("From Date"),
"fieldtype": "Date",
"default": wn.defaults.get_user_default("year_start_date"),
"default": wn.datetime.add_months(wn.datetime.get_today(), -1),
"reqd": 1
},
{
"fieldname":"to_date",
"label": wn._("To Date"),
"fieldtype": "Date",
"default": wn.defaults.get_user_default("year_end_date"),
"default": wn.datetime.get_today(),
"reqd": 1
},
{

View File

@@ -3,37 +3,62 @@
from __future__ import unicode_literals
import webnotes
from webnotes import _
def execute(filters=None):
columns = ["Date:Datetime:95", "Item:Link/Item:100", "Item Name::100",
columns = get_columns()
sl_entries = get_stock_ledger_entries(filters)
item_details = get_item_details(filters)
data = []
for sle in sl_entries:
item_detail = item_details[sle.item_code]
data.append([sle.date, sle.item_code, item_detail.item_name, item_detail.item_group,
item_detail.brand, item_detail.description, sle.warehouse, item_detail.stock_uom,
sle.actual_qty, sle.qty_after_transaction, sle.stock_value, sle.voucher_type,
sle.voucher_no, sle.batch_no, sle.serial_no, sle.company])
return columns, data
def get_columns():
return ["Date:Datetime:95", "Item:Link/Item:100", "Item Name::100",
"Item Group:Link/Item Group:100", "Brand:Link/Brand:100",
"Description::200", "Warehouse:Link/Warehouse:100",
"Stock UOM:Link/UOM:100", "Qty:Float:50", "Balance Qty:Float:80",
"Balance Value:Currency:100", "Voucher Type::100", "Voucher #::100",
"Batch:Link/Batch:100", "Serial #:Link/Serial No:100", "Company:Link/Company:100"]
data = webnotes.conn.sql("""select concat_ws(" ", posting_date, posting_time),
item.name, item.item_name, item.item_group, brand, description, warehouse, sle.stock_uom,
actual_qty, qty_after_transaction, stock_value, voucher_type, voucher_no,
batch_no, serial_no, company
from `tabStock Ledger Entry` sle,
(select name, item_name, description, stock_uom, brand, item_group
from `tabItem` {item_conditions}) item
where item_code = item.name and
company = %(company)s and
def get_stock_ledger_entries(filters):
if not filters.get("company"):
webnotes.throw(_("Company is mandatory"))
if not filters.get("from_date"):
webnotes.throw(_("From Date is mandatory"))
if not filters.get("to_date"):
webnotes.throw(_("To Date is mandatory"))
return webnotes.conn.sql("""select concat_ws(" ", posting_date, posting_time) as date,
item_code, warehouse, actual_qty, qty_after_transaction,
stock_value, voucher_type, voucher_no, batch_no, serial_no, company
from `tabStock Ledger Entry`
where company = %(company)s and
posting_date between %(from_date)s and %(to_date)s
{sle_conditions}
order by posting_date desc, posting_time desc, sle.name desc"""\
.format(item_conditions=get_item_conditions(filters),
sle_conditions=get_sle_conditions(filters)),
filters)
order by posting_date desc, posting_time desc, name desc"""\
.format(sle_conditions=get_sle_conditions(filters)), filters, as_dict=1)
return columns, data
def get_item_details(filters):
item_details = {}
for item in webnotes.conn.sql("""select name, item_name, description, item_group,
brand, stock_uom from `tabItem` {item_conditions}"""\
.format(item_conditions=get_item_conditions(filters)), filters, as_dict=1):
item_details.setdefault(item.name, item)
return item_details
def get_item_conditions(filters):
conditions = []
if filters.get("item_code"):
conditions.append("item_code=%(item_code)s")
conditions.append("name=%(item_code)s")
if filters.get("brand"):
conditions.append("brand=%(brand)s")

View File

@@ -0,0 +1,33 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.query_reports["Stock Projected Qty"] = {
"filters": [
{
"fieldname":"company",
"label": wn._("Company"),
"fieldtype": "Link",
"options": "Company",
"default": wn.defaults.get_user_default("company"),
"reqd": 1
},
{
"fieldname":"warehouse",
"label": wn._("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse"
},
{
"fieldname":"item_code",
"label": wn._("Item"),
"fieldtype": "Link",
"options": "Item"
},
{
"fieldname":"brand",
"label": wn._("Brand"),
"fieldtype": "Link",
"options": "Brand"
}
]
}

View File

@@ -0,0 +1,44 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes
def execute(filters=None):
columns = get_columns()
data = webnotes.conn.sql("""select
item.name, item.item_name, description, item_group, brand, warehouse, item.stock_uom,
actual_qty, planned_qty, indented_qty, ordered_qty, reserved_qty,
projected_qty, item.re_order_level, item.re_order_qty
from `tabBin` bin,
(select name, company from tabWarehouse
where company=%(company)s {warehouse_conditions}) wh,
(select name, item_name, description, stock_uom, item_group,
brand, re_order_level, re_order_qty
from `tabItem` {item_conditions}) item
where item_code = item.name and warehouse = wh.name
order by item.name, wh.name"""\
.format(item_conditions=get_item_conditions(filters),
warehouse_conditions=get_warehouse_conditions(filters)), filters, debug=1)
return columns, data
def get_columns():
return ["Item Code:Link/Item:140", "Item Name::100", "Description::200",
"Brand:Link/Brand:100", "Warehouse:Link/Warehouse:120", "UOM:Link/UOM:100",
"Actual Qty:Float:100", "Planned Qty:Float:100", "Requested Qty:Float:110",
"Ordered Qty:Float:100", "Reserved Qty:Float:100", "Projected Qty:Float:100",
"Reorder Level:Float:100", "Reorder Qty:Float:100"]
def get_item_conditions(filters):
conditions = []
if filters.get("item_code"):
conditions.append("name=%(item_code)s")
if filters.get("brand"):
conditions.append("brand=%(brand)s")
return "where {}".format(" and ".join(conditions)) if conditions else ""
def get_warehouse_conditions(filters):
return " and name=%(warehouse)s" if filters.get("warehouse") else ""

View File

@@ -0,0 +1,22 @@
[
{
"creation": "2013-12-04 18:21:56",
"docstatus": 0,
"modified": "2013-12-04 18:21:56",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"add_total_row": 1,
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"ref_doctype": "Item",
"report_name": "Stock Projected Qty",
"report_type": "Script Report"
},
{
"doctype": "Report",
"name": "Stock Projected Qty"
}
]