Merge pull request #2296 from nabinhait/stock_reco
Stock balance report and valuation fixes
This commit is contained in:
commit
a538f8a24a
@ -142,10 +142,10 @@ def get_data():
|
|||||||
"doctype": "Item",
|
"doctype": "Item",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "page",
|
"type": "report",
|
||||||
"name": "stock-balance",
|
"is_query_report": True,
|
||||||
"label": _("Stock Balance"),
|
"name": "Stock Balance",
|
||||||
"icon": "icon-table",
|
"doctype": "Warehouse"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "report",
|
"type": "report",
|
||||||
@ -170,13 +170,7 @@ def get_data():
|
|||||||
"name": "stock-analytics",
|
"name": "stock-analytics",
|
||||||
"label": _("Stock Analytics"),
|
"label": _("Stock Analytics"),
|
||||||
"icon": "icon-bar-chart"
|
"icon": "icon-bar-chart"
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"type": "report",
|
|
||||||
"is_query_report": True,
|
|
||||||
"name": "Warehouse-Wise Stock Balance",
|
|
||||||
"doctype": "Warehouse"
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -269,7 +269,7 @@ class BuyingController(StockController):
|
|||||||
# get raw materials rate
|
# get raw materials rate
|
||||||
if self.doctype == "Purchase Receipt":
|
if self.doctype == "Purchase Receipt":
|
||||||
from erpnext.stock.utils import get_incoming_rate
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
rm.rate = get_incoming_rate({
|
item_rate = get_incoming_rate({
|
||||||
"item_code": bom_item.item_code,
|
"item_code": bom_item.item_code,
|
||||||
"warehouse": self.supplier_warehouse,
|
"warehouse": self.supplier_warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
@ -277,6 +277,7 @@ class BuyingController(StockController):
|
|||||||
"qty": -1 * required_qty,
|
"qty": -1 * required_qty,
|
||||||
"serial_no": rm.serial_no
|
"serial_no": rm.serial_no
|
||||||
})
|
})
|
||||||
|
rm.rate = item_rate or bom_item.rate
|
||||||
else:
|
else:
|
||||||
rm.rate = bom_item.rate
|
rm.rate = bom_item.rate
|
||||||
|
|
||||||
|
@ -305,9 +305,15 @@ def get_valuation_rate(item_code, warehouse):
|
|||||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where item_code = %s and warehouse = %s
|
where item_code = %s and warehouse = %s
|
||||||
and ifnull(qty_after_transaction, 0) > 0
|
and ifnull(valuation_rate, 0) > 0
|
||||||
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse))
|
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse))
|
||||||
|
|
||||||
|
if not last_valuation_rate:
|
||||||
|
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||||
|
from `tabStock Ledger Entry`
|
||||||
|
where item_code = %s and ifnull(valuation_rate, 0) > 0
|
||||||
|
order by posting_date desc, posting_time desc, name desc limit 1""", item_code)
|
||||||
|
|
||||||
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
|
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
|
||||||
|
|
||||||
if not valuation_rate:
|
if not valuation_rate:
|
||||||
|
@ -81,4 +81,5 @@ erpnext.patches.v4_2.default_website_style
|
|||||||
erpnext.patches.v4_2.set_company_country
|
erpnext.patches.v4_2.set_company_country
|
||||||
erpnext.patches.v4_2.update_sales_order_invoice_field_name
|
erpnext.patches.v4_2.update_sales_order_invoice_field_name
|
||||||
erpnext.patches.v4_2.cost_of_production_cycle
|
erpnext.patches.v4_2.cost_of_production_cycle
|
||||||
erpnext.patches.v4_2.seprate_manufacture_and_repack
|
erpnext.patches.v4_2.seprate_manufacture_and_repack
|
||||||
|
execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance")
|
||||||
|
@ -95,7 +95,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
pr.insert()
|
pr.insert()
|
||||||
|
|
||||||
self.assertEquals(len(pr.get("pr_raw_material_details")), 2)
|
self.assertEquals(len(pr.get("pr_raw_material_details")), 2)
|
||||||
self.assertEquals(pr.get("purchase_receipt_details")[0].rm_supp_cost, 70000.0)
|
self.assertEquals(pr.get("purchase_receipt_details")[0].rm_supp_cost, 20750.0)
|
||||||
|
|
||||||
|
|
||||||
def test_serial_no_supplier(self):
|
def test_serial_no_supplier(self):
|
||||||
|
@ -9,14 +9,64 @@ from erpnext.stock.doctype.serial_no.serial_no import *
|
|||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||||
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
|
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
|
||||||
|
|
||||||
|
def get_sle(**args):
|
||||||
|
condition, values = "", []
|
||||||
|
for key, value in args.iteritems():
|
||||||
|
condition += " and " if condition else " where "
|
||||||
|
condition += "`{0}`=%s".format(key)
|
||||||
|
values.append(value)
|
||||||
|
|
||||||
|
return frappe.db.sql("""select * from `tabStock Ledger Entry` %s
|
||||||
|
order by timestamp(posting_date, posting_time) desc, name desc limit 1"""% condition,
|
||||||
|
values, as_dict=1)
|
||||||
|
|
||||||
|
def make_zero(item_code, warehouse):
|
||||||
|
sle = get_sle(item_code = item_code, warehouse = warehouse)
|
||||||
|
qty = sle[0].qty_after_transaction if sle else 0
|
||||||
|
if qty < 0:
|
||||||
|
make_stock_entry(item_code, None, warehouse, abs(qty), incoming_rate=10)
|
||||||
|
elif qty > 0:
|
||||||
|
make_stock_entry(item_code, warehouse, None, qty, incoming_rate=10)
|
||||||
|
|
||||||
class TestStockEntry(unittest.TestCase):
|
class TestStockEntry(unittest.TestCase):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
set_perpetual_inventory(0)
|
set_perpetual_inventory(0)
|
||||||
if hasattr(self, "old_default_company"):
|
if hasattr(self, "old_default_company"):
|
||||||
frappe.db.set_default("company", self.old_default_company)
|
frappe.db.set_default("company", self.old_default_company)
|
||||||
|
|
||||||
|
def test_fifo(self):
|
||||||
|
frappe.db.set_default("allow_negative_stock", 1)
|
||||||
|
item_code = "_Test Item 2"
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
make_zero(item_code, warehouse)
|
||||||
|
|
||||||
|
make_stock_entry(item_code, None, warehouse, 1, incoming_rate=10)
|
||||||
|
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
|
||||||
|
|
||||||
|
self.assertEqual([[1, 10]], eval(sle.stock_queue))
|
||||||
|
|
||||||
|
# negative qty
|
||||||
|
make_zero(item_code, warehouse)
|
||||||
|
make_stock_entry(item_code, warehouse, None, 1, incoming_rate=10)
|
||||||
|
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
|
||||||
|
|
||||||
|
self.assertEqual([[-1, 10]], eval(sle.stock_queue))
|
||||||
|
|
||||||
|
# further negative
|
||||||
|
make_stock_entry(item_code, warehouse, None, 1)
|
||||||
|
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
|
||||||
|
|
||||||
|
self.assertEqual([[-2, 10]], eval(sle.stock_queue))
|
||||||
|
|
||||||
|
# move stock to positive
|
||||||
|
make_stock_entry(item_code, None, warehouse, 3, incoming_rate=10)
|
||||||
|
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
|
||||||
|
|
||||||
|
self.assertEqual([[1, 10]], eval(sle.stock_queue))
|
||||||
|
|
||||||
|
frappe.db.set_default("allow_negative_stock", 0)
|
||||||
|
|
||||||
def test_auto_material_request(self):
|
def test_auto_material_request(self):
|
||||||
frappe.db.sql("""delete from `tabMaterial Request Item`""")
|
frappe.db.sql("""delete from `tabMaterial Request Item`""")
|
||||||
frappe.db.sql("""delete from `tabMaterial Request`""")
|
frappe.db.sql("""delete from `tabMaterial Request`""")
|
||||||
@ -821,19 +871,19 @@ class TestStockEntry(unittest.TestCase):
|
|||||||
se = frappe.copy_doc(test_records[0]).insert()
|
se = frappe.copy_doc(test_records[0]).insert()
|
||||||
self.assertRaises (StockFreezeError, se.submit)
|
self.assertRaises (StockFreezeError, se.submit)
|
||||||
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0)
|
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0)
|
||||||
|
|
||||||
def test_production_order(self):
|
def test_production_order(self):
|
||||||
bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
|
bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
|
||||||
"is_default": 1, "docstatus": 1})
|
"is_default": 1, "docstatus": 1})
|
||||||
|
|
||||||
production_order = frappe.new_doc("Production Order")
|
production_order = frappe.new_doc("Production Order")
|
||||||
production_order.update({
|
production_order.update({
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"fg_warehouse": "_Test Warehouse 1 - _TC",
|
"fg_warehouse": "_Test Warehouse 1 - _TC",
|
||||||
"production_item": "_Test FG Item 2",
|
"production_item": "_Test FG Item 2",
|
||||||
"bom_no": bom_no,
|
"bom_no": bom_no,
|
||||||
"qty": 1.0,
|
"qty": 1.0,
|
||||||
"stock_uom": "Nos",
|
"stock_uom": "Nos",
|
||||||
"wip_warehouse": "_Test Warehouse - _TC"
|
"wip_warehouse": "_Test Warehouse - _TC"
|
||||||
})
|
})
|
||||||
production_order.insert()
|
production_order.insert()
|
||||||
|
@ -28,7 +28,7 @@ class TestStockReconciliation(unittest.TestCase):
|
|||||||
[20, "", "2012-12-26", "12:05", 16000, 15, 18000],
|
[20, "", "2012-12-26", "12:05", 16000, 15, 18000],
|
||||||
[10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
|
[10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
|
||||||
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
|
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
|
||||||
[0, "", "2012-12-26", "12:10", 0, -5, 0]
|
[0, "", "2012-12-26", "12:10", 0, -5, -6000]
|
||||||
]
|
]
|
||||||
|
|
||||||
for d in input_data:
|
for d in input_data:
|
||||||
@ -63,16 +63,16 @@ class TestStockReconciliation(unittest.TestCase):
|
|||||||
input_data = [
|
input_data = [
|
||||||
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
|
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
|
||||||
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
|
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
|
||||||
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
|
[15, 1000, "2012-12-26", "12:00", 15000, 10, 11500],
|
||||||
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
|
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
|
||||||
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
|
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
|
||||||
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
|
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
|
||||||
[5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
|
[5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
|
||||||
["", 1000, "2012-12-26", "12:05", 15000, 10, 12000],
|
["", 1000, "2012-12-26", "12:05", 15000, 10, 11500],
|
||||||
[20, "", "2012-12-26", "12:05", 18000, 15, 18000],
|
[20, "", "2012-12-26", "12:05", 18000, 15, 18000],
|
||||||
[10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
|
[10, 2000, "2012-12-26", "12:10", 20000, 5, 7600],
|
||||||
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
|
[1, 1000, "2012-12-01", "00:00", 1000, 11, 12512.73],
|
||||||
[0, "", "2012-12-26", "12:10", 0, -5, 0]
|
[0, "", "2012-12-26", "12:10", 0, -5, -5142.86]
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
Stock balances on a particular day, per warehouse.
|
|
@ -1,191 +0,0 @@
|
|||||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
|
||||||
// License: GNU General Public License v3. See license.txt
|
|
||||||
|
|
||||||
frappe.require("assets/erpnext/js/stock_analytics.js");
|
|
||||||
|
|
||||||
frappe.pages['stock-balance'].onload = function(wrapper) {
|
|
||||||
frappe.ui.make_app_page({
|
|
||||||
parent: wrapper,
|
|
||||||
title: __('Stock Balance'),
|
|
||||||
single_column: true
|
|
||||||
});
|
|
||||||
|
|
||||||
new erpnext.StockBalance(wrapper);
|
|
||||||
|
|
||||||
wrapper.appframe.add_module_icon("Stock");
|
|
||||||
}
|
|
||||||
|
|
||||||
erpnext.StockBalance = erpnext.StockAnalytics.extend({
|
|
||||||
init: function(wrapper) {
|
|
||||||
this._super(wrapper, {
|
|
||||||
title: __("Stock Balance"),
|
|
||||||
doctypes: ["Item", "Item Group", "Warehouse", "Stock Ledger Entry", "Brand",
|
|
||||||
"Stock Entry", "Project", "Serial No"],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
setup_columns: function() {
|
|
||||||
this.columns = [
|
|
||||||
{id: "name", name: __("Item"), field: "name", width: 300,
|
|
||||||
formatter: this.tree_formatter},
|
|
||||||
{id: "item_name", name: __("Item Name"), field: "item_name", width: 100},
|
|
||||||
{id: "description", name: __("Description"), field: "description", width: 200,
|
|
||||||
formatter: this.text_formatter},
|
|
||||||
{id: "brand", name: __("Brand"), field: "brand", width: 100},
|
|
||||||
{id: "stock_uom", name: __("UOM"), field: "stock_uom", width: 100},
|
|
||||||
{id: "opening_qty", name: __("Opening Qty"), field: "opening_qty", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
{id: "inflow_qty", name: __("In Qty"), field: "inflow_qty", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
{id: "outflow_qty", name: __("Out Qty"), field: "outflow_qty", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
{id: "closing_qty", name: __("Closing Qty"), field: "closing_qty", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
|
|
||||||
{id: "opening_value", name: __("Opening Value"), field: "opening_value", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
{id: "inflow_value", name: __("In Value"), field: "inflow_value", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
{id: "outflow_value", name: __("Out Value"), field: "outflow_value", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
{id: "closing_value", name: __("Closing Value"), field: "closing_value", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
{id: "valuation_rate", name: __("Valuation Rate"), field: "valuation_rate", width: 100,
|
|
||||||
formatter: this.currency_formatter},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
filters: [
|
|
||||||
{fieldtype:"Select", label: __("Brand"), link:"Brand", fieldname: "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:"Select", label: __("Warehouse"), link:"Warehouse", fieldname: "warehouse",
|
|
||||||
default_value: __("Select Warehouse..."), filter: function(val, item, opts, me) {
|
|
||||||
return me.apply_zero_filter(val, item, opts, me);
|
|
||||||
}},
|
|
||||||
{fieldtype:"Select", label: __("Project"), link:"Project", fieldname: "project",
|
|
||||||
default_value: __("Select Project..."), filter: function(val, item, opts, me) {
|
|
||||||
return me.apply_zero_filter(val, item, opts, me);
|
|
||||||
}, link_formatter: {filter_input: "project"}},
|
|
||||||
{fieldtype:"Date", label: __("From Date"), fieldname: "from_date"},
|
|
||||||
{fieldtype:"Label", label: __("To")},
|
|
||||||
{fieldtype:"Date", label: __("To Date"), fieldname: "to_date"},
|
|
||||||
{fieldtype:"Button", label: __("Refresh"), icon:"icon-refresh icon-white"},
|
|
||||||
{fieldtype:"Button", label: __("Reset Filters"), icon: "icon-filter"}
|
|
||||||
],
|
|
||||||
|
|
||||||
setup_plot_check: function() {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
prepare_data: function() {
|
|
||||||
this.stock_entry_map = this.make_name_map(frappe.report_dump.data["Stock Entry"], "name");
|
|
||||||
this._super();
|
|
||||||
},
|
|
||||||
|
|
||||||
prepare_balances: function() {
|
|
||||||
var me = this;
|
|
||||||
var from_date = dateutil.str_to_obj(this.from_date);
|
|
||||||
var to_date = dateutil.str_to_obj(this.to_date);
|
|
||||||
var data = frappe.report_dump.data["Stock Ledger Entry"];
|
|
||||||
|
|
||||||
this.item_warehouse = {};
|
|
||||||
this.serialized_buying_rates = this.get_serialized_buying_rates();
|
|
||||||
|
|
||||||
for(var i=0, j=data.length; i<j; i++) {
|
|
||||||
var sl = data[i];
|
|
||||||
var sl_posting_date = dateutil.str_to_obj(sl.posting_date);
|
|
||||||
|
|
||||||
if((me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) &&
|
|
||||||
(me.is_default("project") ? true : me.project == sl.project)) {
|
|
||||||
var item = me.item_by_name[sl.item_code];
|
|
||||||
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
|
|
||||||
var valuation_method = item.valuation_method ?
|
|
||||||
item.valuation_method : sys_defaults.valuation_method;
|
|
||||||
var is_fifo = valuation_method == "FIFO";
|
|
||||||
|
|
||||||
if(sl.voucher_type=="Stock Reconciliation") {
|
|
||||||
var qty_diff = sl.qty_after_transaction - (item.temp_closing_qty || 0.0);
|
|
||||||
var value_diff = (sl.valuation_rate * sl.qty_after_transaction) - (item.temp_closing_value || 0.0);
|
|
||||||
wh.fifo_stack.push([sl.qty_after_transaction, sl.valuation_rate, sl.posting_date]);
|
|
||||||
wh.balance_qty = sl.qty_after_transaction;
|
|
||||||
wh.balance_value = sl.valuation_rate * sl.qty_after_transaction;
|
|
||||||
} else {
|
|
||||||
var qty_diff = sl.qty;
|
|
||||||
var value_diff = me.get_value_diff(wh, sl, is_fifo);
|
|
||||||
}
|
|
||||||
item.temp_closing_qty += qty_diff;
|
|
||||||
item.temp_closing_value += value_diff;
|
|
||||||
|
|
||||||
if(sl_posting_date < from_date) {
|
|
||||||
item.opening_qty += qty_diff;
|
|
||||||
item.opening_value += value_diff;
|
|
||||||
} else if(sl_posting_date <= to_date) {
|
|
||||||
var ignore_inflow_outflow = this.is_default("warehouse")
|
|
||||||
&& sl.voucher_type=="Stock Entry"
|
|
||||||
&& this.stock_entry_map[sl.voucher_no].purpose=="Material Transfer";
|
|
||||||
|
|
||||||
if(!ignore_inflow_outflow) {
|
|
||||||
if(qty_diff < 0) {
|
|
||||||
item.outflow_qty += Math.abs(qty_diff);
|
|
||||||
} else {
|
|
||||||
item.inflow_qty += qty_diff;
|
|
||||||
}
|
|
||||||
if(value_diff < 0) {
|
|
||||||
item.outflow_value += Math.abs(value_diff);
|
|
||||||
} else {
|
|
||||||
item.inflow_value += value_diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.closing_qty += qty_diff;
|
|
||||||
item.closing_value += value_diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// opening + diff = closing
|
|
||||||
// adding opening, since diff already added to closing
|
|
||||||
$.each(me.item_by_name, function(key, item) {
|
|
||||||
item.closing_qty += item.opening_qty;
|
|
||||||
item.closing_value += item.opening_value;
|
|
||||||
|
|
||||||
// valuation rate
|
|
||||||
if(!item.is_group && flt(item.closing_qty) > 0)
|
|
||||||
item.valuation_rate = flt(item.closing_value) / flt(item.closing_qty);
|
|
||||||
else item.valuation_rate = 0.0
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
update_groups: function() {
|
|
||||||
var me = this;
|
|
||||||
|
|
||||||
$.each(this.data, function(i, item) {
|
|
||||||
// update groups
|
|
||||||
if(!item.is_group && me.apply_filter(item, "brand")) {
|
|
||||||
var parent = me.parent_map[item.name];
|
|
||||||
while(parent) {
|
|
||||||
parent_group = me.item_by_name[parent];
|
|
||||||
$.each(me.columns, function(c, col) {
|
|
||||||
if (col.formatter == me.currency_formatter && col.field != "valuation_rate") {
|
|
||||||
parent_group[col.field] = flt(parent_group[col.field]) + flt(item[col.field]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// show parent if filtered by brand
|
|
||||||
if(item.brand == me.brand)
|
|
||||||
parent_group._show = true;
|
|
||||||
|
|
||||||
parent = me.parent_map[parent];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
get_plot_data: function() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"creation": "2012-12-27 18:57:47.000000",
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Page",
|
|
||||||
"icon": "icon-table",
|
|
||||||
"idx": 1,
|
|
||||||
"modified": "2013-07-11 14:44:15.000000",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Stock",
|
|
||||||
"name": "stock-balance",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"page_name": "stock-balance",
|
|
||||||
"roles": [
|
|
||||||
{
|
|
||||||
"role": "Material Manager"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"role": "Analytics"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"standard": "Yes",
|
|
||||||
"title": "Stock Balance"
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.query_reports["Warehouse-Wise Stock Balance"] = {
|
frappe.query_reports["Stock Balance"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
"fieldname":"from_date",
|
"fieldname":"from_date",
|
||||||
@ -18,4 +18,4 @@ frappe.query_reports["Warehouse-Wise Stock Balance"] = {
|
|||||||
"default": frappe.datetime.get_today()
|
"default": frappe.datetime.get_today()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
"apply_user_permissions": 1,
|
"apply_user_permissions": 1,
|
||||||
"creation": "2013-06-05 11:00:31",
|
"creation": "2014-10-10 17:58:11.577901",
|
||||||
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
"idx": 1,
|
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2014-06-03 07:18:17.384923",
|
"modified": "2014-10-10 17:58:11.577901",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Warehouse-Wise Stock Balance",
|
"name": "Stock Balance",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"ref_doctype": "Stock Ledger Entry",
|
"ref_doctype": "Stock Ledger Entry",
|
||||||
"report_name": "Warehouse-Wise Stock Balance",
|
"report_name": "Stock Balance",
|
||||||
"report_type": "Script Report"
|
"report_type": "Script Report"
|
||||||
}
|
}
|
@ -59,9 +59,9 @@ def get_conditions(filters):
|
|||||||
def get_stock_ledger_entries(filters):
|
def get_stock_ledger_entries(filters):
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters)
|
||||||
return frappe.db.sql("""select item_code, warehouse, posting_date,
|
return frappe.db.sql("""select item_code, warehouse, posting_date,
|
||||||
actual_qty, valuation_rate, stock_uom, company
|
actual_qty, valuation_rate, stock_uom, company, voucher_type, qty_after_transaction
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where docstatus < 2 %s order by item_code, warehouse""" %
|
where docstatus < 2 %s order by posting_date, posting_time, name""" %
|
||||||
conditions, as_dict=1)
|
conditions, as_dict=1)
|
||||||
|
|
||||||
def get_item_warehouse_map(filters):
|
def get_item_warehouse_map(filters):
|
||||||
@ -80,21 +80,27 @@ def get_item_warehouse_map(filters):
|
|||||||
qty_dict = iwb_map[d.company][d.item_code][d.warehouse]
|
qty_dict = iwb_map[d.company][d.item_code][d.warehouse]
|
||||||
qty_dict.uom = d.stock_uom
|
qty_dict.uom = d.stock_uom
|
||||||
|
|
||||||
|
if d.voucher_type == "Stock Reconciliation":
|
||||||
|
qty_diff = flt(d.qty_after_transaction) - qty_dict.bal_qty
|
||||||
|
value_diff = flt(d.stock_value) - qty_dict.bal_val
|
||||||
|
else:
|
||||||
|
qty_diff = flt(d.actual_qty)
|
||||||
|
value_diff = flt(d.actual_qty) * flt(d.valuation_rate)
|
||||||
|
|
||||||
if d.posting_date < filters["from_date"]:
|
if d.posting_date < filters["from_date"]:
|
||||||
qty_dict.opening_qty += flt(d.actual_qty)
|
qty_dict.opening_qty += qty_diff
|
||||||
qty_dict.opening_val += flt(d.actual_qty) * flt(d.valuation_rate)
|
qty_dict.opening_val += value_diff
|
||||||
elif d.posting_date >= filters["from_date"] and d.posting_date <= filters["to_date"]:
|
elif d.posting_date >= filters["from_date"] and d.posting_date <= filters["to_date"]:
|
||||||
qty_dict.val_rate = d.valuation_rate
|
qty_dict.val_rate = d.valuation_rate
|
||||||
|
if qty_diff > 0:
|
||||||
if flt(d.actual_qty) > 0:
|
qty_dict.in_qty += qty_diff
|
||||||
qty_dict.in_qty += flt(d.actual_qty)
|
qty_dict.in_val += value_diff
|
||||||
qty_dict.in_val += flt(d.actual_qty) * flt(d.valuation_rate)
|
|
||||||
else:
|
else:
|
||||||
qty_dict.out_qty += abs(flt(d.actual_qty))
|
qty_dict.out_qty += abs(qty_diff)
|
||||||
qty_dict.out_val += flt(abs(flt(d.actual_qty) * flt(d.valuation_rate)))
|
qty_dict.out_val += abs(value_diff)
|
||||||
|
|
||||||
qty_dict.bal_qty += flt(d.actual_qty)
|
qty_dict.bal_qty += qty_diff
|
||||||
qty_dict.bal_val += flt(d.actual_qty) * flt(d.valuation_rate)
|
qty_dict.bal_val += value_diff
|
||||||
|
|
||||||
return iwb_map
|
return iwb_map
|
||||||
|
|
@ -6,6 +6,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, cstr, now
|
from frappe.utils import cint, flt, cstr, now
|
||||||
from erpnext.stock.utils import get_valuation_method
|
from erpnext.stock.utils import get_valuation_method
|
||||||
|
from erpnext.controllers.stock_controller import get_valuation_rate
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# future reposting
|
# future reposting
|
||||||
@ -110,14 +111,14 @@ def update_entries_after(args, verbose=1):
|
|||||||
else:
|
else:
|
||||||
valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue)
|
valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue)
|
||||||
|
|
||||||
|
|
||||||
qty_after_transaction += flt(sle.actual_qty)
|
qty_after_transaction += flt(sle.actual_qty)
|
||||||
|
|
||||||
# get stock value
|
# get stock value
|
||||||
if sle.serial_no:
|
if sle.serial_no:
|
||||||
stock_value = qty_after_transaction * valuation_rate
|
stock_value = qty_after_transaction * valuation_rate
|
||||||
elif valuation_method == "Moving Average":
|
elif valuation_method == "Moving Average":
|
||||||
stock_value = (qty_after_transaction > 0) and \
|
stock_value = qty_after_transaction * valuation_rate
|
||||||
(qty_after_transaction * valuation_rate) or 0
|
|
||||||
else:
|
else:
|
||||||
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue))
|
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue))
|
||||||
|
|
||||||
@ -255,65 +256,72 @@ def get_moving_average_values(qty_after_transaction, sle, valuation_rate):
|
|||||||
incoming_rate = flt(sle.incoming_rate)
|
incoming_rate = flt(sle.incoming_rate)
|
||||||
actual_qty = flt(sle.actual_qty)
|
actual_qty = flt(sle.actual_qty)
|
||||||
|
|
||||||
if not incoming_rate:
|
if flt(sle.actual_qty) > 0:
|
||||||
# In case of delivery/stock issue in_rate = 0 or wrong incoming rate
|
if qty_after_transaction < 0 and not valuation_rate:
|
||||||
incoming_rate = valuation_rate
|
# if negative stock, take current valuation rate as incoming rate
|
||||||
|
valuation_rate = incoming_rate
|
||||||
|
|
||||||
elif qty_after_transaction < 0:
|
new_stock_qty = abs(qty_after_transaction) + actual_qty
|
||||||
# if negative stock, take current valuation rate as incoming rate
|
new_stock_value = (abs(qty_after_transaction) * valuation_rate) + (actual_qty * incoming_rate)
|
||||||
valuation_rate = incoming_rate
|
|
||||||
|
|
||||||
new_stock_qty = qty_after_transaction + actual_qty
|
if new_stock_qty:
|
||||||
new_stock_value = qty_after_transaction * valuation_rate + actual_qty * incoming_rate
|
valuation_rate = new_stock_value / flt(new_stock_qty)
|
||||||
|
elif not valuation_rate:
|
||||||
|
valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse)
|
||||||
|
|
||||||
if new_stock_qty > 0 and new_stock_value > 0:
|
return abs(valuation_rate)
|
||||||
valuation_rate = new_stock_value / flt(new_stock_qty)
|
|
||||||
elif new_stock_qty <= 0:
|
|
||||||
valuation_rate = 0.0
|
|
||||||
|
|
||||||
# NOTE: val_rate is same as previous entry if new stock value is negative
|
|
||||||
|
|
||||||
return valuation_rate
|
|
||||||
|
|
||||||
def get_fifo_values(qty_after_transaction, sle, stock_queue):
|
def get_fifo_values(qty_after_transaction, sle, stock_queue):
|
||||||
incoming_rate = flt(sle.incoming_rate)
|
incoming_rate = flt(sle.incoming_rate)
|
||||||
actual_qty = flt(sle.actual_qty)
|
actual_qty = flt(sle.actual_qty)
|
||||||
if not stock_queue:
|
|
||||||
stock_queue.append([0, 0])
|
intialize_stock_queue(stock_queue, sle.item_code, sle.warehouse)
|
||||||
|
|
||||||
if actual_qty > 0:
|
if actual_qty > 0:
|
||||||
if stock_queue[-1][0] > 0:
|
if stock_queue[-1][0] > 0:
|
||||||
stock_queue.append([actual_qty, incoming_rate])
|
stock_queue.append([actual_qty, incoming_rate])
|
||||||
else:
|
else:
|
||||||
qty = stock_queue[-1][0] + actual_qty
|
qty = stock_queue[-1][0] + actual_qty
|
||||||
stock_queue[-1] = [qty, qty > 0 and incoming_rate or 0]
|
if qty == 0:
|
||||||
|
stock_queue.pop(-1)
|
||||||
|
else:
|
||||||
|
stock_queue[-1] = [qty, incoming_rate]
|
||||||
else:
|
else:
|
||||||
incoming_cost = 0
|
|
||||||
qty_to_pop = abs(actual_qty)
|
qty_to_pop = abs(actual_qty)
|
||||||
while qty_to_pop:
|
while qty_to_pop:
|
||||||
if not stock_queue:
|
intialize_stock_queue(stock_queue, sle.item_code, sle.warehouse)
|
||||||
stock_queue.append([0, 0])
|
|
||||||
|
|
||||||
batch = stock_queue[0]
|
batch = stock_queue[0]
|
||||||
|
|
||||||
if 0 < batch[0] <= qty_to_pop:
|
# print qty_to_pop, batch
|
||||||
# if batch qty > 0
|
|
||||||
# not enough or exactly same qty in current batch, clear batch
|
if qty_to_pop >= batch[0]:
|
||||||
incoming_cost += flt(batch[0]) * flt(batch[1])
|
# consume current batch
|
||||||
qty_to_pop -= batch[0]
|
qty_to_pop = qty_to_pop - batch[0]
|
||||||
stock_queue.pop(0)
|
stock_queue.pop(0)
|
||||||
|
if not stock_queue and qty_to_pop:
|
||||||
|
# stock finished, qty still remains to be withdrawn
|
||||||
|
# negative stock, keep in as a negative batch
|
||||||
|
stock_queue.append([-qty_to_pop, batch[1]])
|
||||||
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# all from current batch
|
# qty found in current batch
|
||||||
incoming_cost += flt(qty_to_pop) * flt(batch[1])
|
# consume it and exit
|
||||||
batch[0] -= qty_to_pop
|
batch[0] = batch[0] - qty_to_pop
|
||||||
qty_to_pop = 0
|
qty_to_pop = 0
|
||||||
|
|
||||||
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue))
|
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue))
|
||||||
stock_qty = sum((flt(batch[0]) for batch in stock_queue))
|
stock_qty = sum((flt(batch[0]) for batch in stock_queue))
|
||||||
|
|
||||||
valuation_rate = stock_qty and (stock_value / flt(stock_qty)) or 0
|
valuation_rate = (stock_value / flt(stock_qty)) if stock_qty else 0
|
||||||
|
|
||||||
return valuation_rate
|
return abs(valuation_rate)
|
||||||
|
|
||||||
|
def intialize_stock_queue(stock_queue, item_code, warehouse):
|
||||||
|
if not stock_queue:
|
||||||
|
estimated_val_rate = get_valuation_rate(item_code, warehouse)
|
||||||
|
stock_queue.append([0, estimated_val_rate])
|
||||||
|
|
||||||
def _raise_exceptions(args, verbose=1):
|
def _raise_exceptions(args, verbose=1):
|
||||||
deficiency = min(e["diff"] for e in _exceptions)
|
deficiency = min(e["diff"] for e in _exceptions)
|
||||||
|
@ -5,7 +5,6 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
import json
|
import json
|
||||||
from frappe.utils import flt, cstr, nowdate, add_days, cint
|
from frappe.utils import flt, cstr, nowdate, add_days, cint
|
||||||
from frappe.defaults import get_global_default
|
|
||||||
from frappe.utils.email_lib import sendmail
|
from frappe.utils.email_lib import sendmail
|
||||||
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
|
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
|
||||||
|
|
||||||
@ -94,7 +93,7 @@ def get_valuation_method(item_code):
|
|||||||
"""get valuation method from item or default"""
|
"""get valuation method from item or default"""
|
||||||
val_method = frappe.db.get_value('Item', item_code, 'valuation_method')
|
val_method = frappe.db.get_value('Item', item_code, 'valuation_method')
|
||||||
if not val_method:
|
if not val_method:
|
||||||
val_method = get_global_default('valuation_method') or "FIFO"
|
val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO"
|
||||||
return val_method
|
return val_method
|
||||||
|
|
||||||
def get_fifo_rate(previous_stock_queue, qty):
|
def get_fifo_rate(previous_stock_queue, qty):
|
||||||
|
@ -211,12 +211,14 @@ def reset_serial_no_status_and_warehouse(serial_nos=None):
|
|||||||
|
|
||||||
def repost_all_stock_vouchers():
|
def repost_all_stock_vouchers():
|
||||||
vouchers = frappe.db.sql("""select distinct voucher_type, voucher_no
|
vouchers = frappe.db.sql("""select distinct voucher_type, voucher_no
|
||||||
from `tabStock Ledger Entry` order by posting_date, posting_time, name""")
|
from `tabStock Ledger Entry`
|
||||||
|
order by posting_date, posting_time, name""")
|
||||||
|
|
||||||
rejected = []
|
rejected = []
|
||||||
# vouchers = [["Purchase Receipt", "GRN00062"]]
|
i = 0
|
||||||
for voucher_type, voucher_no in vouchers:
|
for voucher_type, voucher_no in vouchers:
|
||||||
print voucher_type, voucher_no
|
i+=1
|
||||||
|
print i, "/", len(vouchers)
|
||||||
try:
|
try:
|
||||||
for dt in ["Stock Ledger Entry", "GL Entry"]:
|
for dt in ["Stock Ledger Entry", "GL Entry"]:
|
||||||
frappe.db.sql("""delete from `tab%s` where voucher_type=%s and voucher_no=%s"""%
|
frappe.db.sql("""delete from `tab%s` where voucher_type=%s and voucher_no=%s"""%
|
||||||
@ -225,8 +227,8 @@ def repost_all_stock_vouchers():
|
|||||||
doc = frappe.get_doc(voucher_type, voucher_no)
|
doc = frappe.get_doc(voucher_type, voucher_no)
|
||||||
if voucher_type=="Stock Entry" and doc.purpose in ["Manufacture", "Repack"]:
|
if voucher_type=="Stock Entry" and doc.purpose in ["Manufacture", "Repack"]:
|
||||||
doc.get_stock_and_rate(force=1)
|
doc.get_stock_and_rate(force=1)
|
||||||
# elif voucher_type=="Purchase Receipt":
|
elif voucher_type=="Purchase Receipt" and doc.is_subcontracted == "Yes":
|
||||||
# doc.create_raw_materials_supplied("pr_raw_material_details")
|
doc.validate()
|
||||||
|
|
||||||
doc.update_stock_ledger()
|
doc.update_stock_ledger()
|
||||||
doc.make_gl_entries(repost_future_gle=False, allow_negative_stock=True)
|
doc.make_gl_entries(repost_future_gle=False, allow_negative_stock=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user