Merge branch 'master' into purchase_price_list

This commit is contained in:
Anand Doshi
2013-01-17 10:58:41 +05:30
55 changed files with 1185 additions and 616 deletions

View File

@@ -2,21 +2,21 @@
{
"owner": "Administrator",
"docstatus": 0,
"creation": "2012-12-17 14:56:32",
"creation": "2012-12-28 11:01:35",
"modified_by": "Administrator",
"modified": "2012-12-27 10:36:56"
"modified": "2013-01-16 11:14:57"
},
{
"allow_attach": 1,
"search_fields": "item_name,description,item_group,customer_code",
"module": "Stock",
"doctype": "DocType",
"autoname": "field:item_code",
"document_type": "Master",
"description": "A Product or a Service that is bought, sold or kept in stock.",
"autoname": "field:item_code",
"name": "__common__",
"default_print_format": "Standard",
"allow_rename": 1,
"doctype": "DocType",
"max_attachments": 1
},
{
@@ -30,7 +30,9 @@
"name": "__common__",
"parent": "Item",
"read": 1,
"submit": 0,
"doctype": "DocPerm",
"report": 1,
"parenttype": "DocType",
"parentfield": "permissions"
},
@@ -860,46 +862,6 @@
"fieldtype": "Text Editor",
"permlevel": 0
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"submit": 0,
"write": 0,
"cancel": 0,
"role": "Material Manager",
"permlevel": 1
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"submit": 0,
"write": 0,
"cancel": 0,
"role": "Material Manager",
"permlevel": 0
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"submit": 0,
"write": 0,
"cancel": 0,
"role": "Material User",
"permlevel": 1
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"submit": 0,
"write": 0,
"cancel": 0,
"role": "Material User",
"permlevel": 0
},
{
"create": 1,
"doctype": "DocPerm",
@@ -909,23 +871,48 @@
"permlevel": 0
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"write": 0,
"role": "Material Manager",
"cancel": 0,
"permlevel": 0
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"write": 0,
"role": "Material User",
"cancel": 0,
"permlevel": 0
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"write": 0,
"role": "Material Master Manager",
"cancel": 0,
"permlevel": 1
},
{
"create": 1,
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"write": 1,
"role": "System Manager",
"cancel": 1,
"permlevel": 0
"write": 0,
"role": "Material Manager",
"cancel": 0,
"permlevel": 1
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"role": "System Manager",
"write": 0,
"role": "Material User",
"cancel": 0,
"permlevel": 1
}
]

View File

@@ -226,7 +226,8 @@ cur_frm.cscript.s_warehouse = function(doc, cdt, cdn) {
'warehouse' : cstr(d.s_warehouse) || cstr(d.t_warehouse),
'transfer_qty' : d.transfer_qty,
'serial_no' : d.serial_no,
'bom_no' : d.bom_no
'bom_no' : d.bom_no,
'qty' : d.s_warehouse ? -1* d.qty : d.qty
}
get_server_fields('get_warehouse_details', JSON.stringify(args),
'mtn_details', doc, cdt, cdn, 1);

View File

@@ -25,6 +25,8 @@ from webnotes.model.code import get_obj
from webnotes import msgprint, _
from stock.utils import get_incoming_rate
from stock.stock_ledger import get_previous_sle
import json
sql = webnotes.conn.sql
@@ -157,23 +159,46 @@ class DocType(TransactionBase):
def get_stock_and_rate(self):
"""get stock and incoming rate on posting date"""
for d in getlist(self.doclist, 'mtn_details'):
args = {
args = webnotes._dict({
"item_code": d.item_code,
"warehouse": d.s_warehouse or d.t_warehouse,
"posting_date": self.doc.posting_date,
"posting_time": self.doc.posting_time,
"qty": d.transfer_qty,
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
"serial_no": d.serial_no,
"bom_no": d.bom_no
}
"bom_no": d.bom_no,
})
# get actual stock at source warehouse
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
# get incoming rate
if not flt(d.incoming_rate):
d.incoming_rate = get_incoming_rate(args)
if not flt(d.incoming_rate) or self.doc.purpose == "Sales Return":
d.incoming_rate = self.get_incoming_rate(args)
d.amount = flt(d.qty) * flt(d.incoming_rate)
def get_incoming_rate(self, args):
if self.doc.purpose == "Sales Return" and \
(self.doc.delivery_note_no or self.doc.sales_invoice_no):
sle = webnotes.conn.sql("""select name, posting_date, posting_time,
actual_qty, stock_value from `tabStock Ledger Entry`
where voucher_type = %s and voucher_no = %s and
item_code = %s and ifnull(is_cancelled, 'No') = 'No' limit 1""",
((self.doc.delivery_note_no and "Delivery Note" or "Sales Invoice"),
self.doc.delivery_note_no or self.doc.sales_invoice_no, args.item_code), as_dict=1)
if sle:
args.update({
"posting_date": sle[0].posting_date,
"posting_time": sle[0].posting_time,
"sle": sle[0].name
})
previous_sle = get_previous_sle(args)
incoming_rate = (flt(sle[0].stock_value) - flt(previous_sle.get("stock_value"))) / \
flt(sle[0].actual_qty)
else:
incoming_rate = get_incoming_rate(args)
return incoming_rate
def validate_incoming_rate(self):
for d in getlist(self.doclist, 'mtn_details'):
@@ -264,8 +289,7 @@ class DocType(TransactionBase):
pro_obj.doc.save()
def get_item_details(self, arg):
import json
arg, actual_qty, in_rate = json.loads(arg), 0, 0
arg = json.loads(arg)
item = sql("""select stock_uom, description, item_name from `tabItem`
where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'
@@ -305,16 +329,16 @@ class DocType(TransactionBase):
return ret
def get_warehouse_details(self, args):
import json
args, actual_qty, in_rate = json.loads(args), 0, 0
args = json.loads(args)
args.update({
"posting_date": self.doc.posting_date,
"posting_time": self.doc.posting_time
"posting_time": self.doc.posting_time,
})
args = webnotes._dict(args)
ret = {
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
"incoming_rate" : get_incoming_rate(args)
"incoming_rate" : self.get_incoming_rate(args)
}
return ret

View File

@@ -55,7 +55,14 @@ class DocType(DocListController):
self.validation_messages = []
item_warehouse_combinations = []
for row_num, row in enumerate(data[data.index(self.head_row)+1:]):
# validate no of rows
rows = data[data.index(self.head_row)+1:]
if len(rows) > 100:
msgprint(_("""Sorry! We can only allow upto 100 rows for Stock Reconciliation."""),
raise_exception=True)
for row_num, row in enumerate(rows):
# find duplicates
if [row[0], row[1]] in item_warehouse_combinations:
self.validation_messages.append(_get_msg(row_num, "Duplicate entry"))
@@ -249,8 +256,6 @@ class DocType(DocListController):
""" Delete Stock Ledger Entries related to this Stock Reconciliation
and repost future Stock Ledger Entries"""
from stock.stock_ledger import update_entries_after
existing_entries = webnotes.conn.sql("""select item_code, warehouse
from `tabStock Ledger Entry` where voucher_type='Stock Reconciliation'
and voucher_no=%s""", self.doc.name, as_dict=1)

View File

@@ -2,9 +2,9 @@
{
"owner": "Administrator",
"docstatus": 0,
"creation": "2013-01-14 15:14:40",
"creation": "2013-01-15 12:28:57",
"modified_by": "Administrator",
"modified": "2013-01-15 12:25:13"
"modified": "2013-01-16 13:59:28"
},
{
"allow_attach": 0,
@@ -13,6 +13,7 @@
"search_fields": "posting_date",
"module": "Stock",
"doctype": "DocType",
"read_only_onload": 0,
"autoname": "SR/.######",
"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.",
"allow_email": 1,
@@ -33,7 +34,7 @@
"read": 1,
"cancel": 1,
"name": "__common__",
"amend": 0,
"amend": 1,
"create": 1,
"doctype": "DocPerm",
"submit": 1,

View File

@@ -54,7 +54,11 @@ erpnext.StockAgeing = erpnext.StockGridReport.extend({
{id: "earliest", name: "Earliest", field: "earliest",
formatter: this.currency_formatter},
{id: "latest", name: "Latest", field: "latest",
formatter: this.currency_formatter}
formatter: this.currency_formatter},
{id: "item_name", name: "Item Name", field: "item_name",
width: 100, formatter: this.text_formatter},
{id: "description", name: "Description", field: "description",
width: 200, formatter: this.text_formatter},
];
},
filters: [

View File

@@ -83,6 +83,10 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
field: "reserved_qty", width: 80, formatter: this.currency_formatter},
{id: "projected_qty", name: "Projected Qty",
field: "projected_qty", width: 80, formatter: this.currency_formatter},
{id: "re_order_level", name: "Re-Order Level",
field: "re_order_level", width: 80, formatter: this.currency_formatter},
{id: "re_order_qty", name: "Re-Order Qty",
field: "re_order_qty", width: 80, formatter: this.currency_formatter},
{id: "uom", name: "UOM", field: "uom", width: 60},
{id: "brand", name: "Brand", field: "brand", width: 100,
link_formatter: {filter_input: "brand"}},
@@ -171,7 +175,7 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
});
}
);
// sort by item, warehouse
this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) {
return me.item_warehouse_map[key];
@@ -202,6 +206,10 @@ erpnext.StockLevel = erpnext.StockGridReport.extend({
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];

View File

@@ -78,12 +78,11 @@ def get_incoming_rate(args):
valuation_method = get_valuation_method(args.get("item_code"))
previous_sle = get_previous_sle(args)
if valuation_method == 'FIFO':
# get rate based on the last item value?
if args.get("qty"):
if not previous_sle:
return 0.0
stock_queue = json.loads(previous_sle.get('stock_queue', '[]'))
in_rate = stock_queue and get_fifo_rate(stock_queue) or 0
if not previous_sle:
return 0.0
previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]'))
in_rate = previous_stock_queue and \
get_fifo_rate(previous_stock_queue, args.get("qty") or 0) or 0
elif valuation_method == 'Moving Average':
in_rate = previous_sle.get('valuation_rate') or 0
return in_rate
@@ -104,13 +103,29 @@ def get_valuation_method(item_code):
val_method = get_defaults().get('valuation_method', 'FIFO')
return val_method
def get_fifo_rate(stock_queue):
"""get FIFO (average) Rate from Stack"""
if not stock_queue:
return 0.0
total = sum(f[0] for f in stock_queue)
return total and sum(f[0] * f[1] for f in stock_queue) / flt(total) or 0.0
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
if qty >= 0:
total = sum(f[0] for f in previous_stock_queue)
return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0
else:
outgoing_cost = 0
qty_to_pop = abs(qty)
while qty_to_pop:
batch = previous_stock_queue[0]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
outgoing_cost += flt(batch[0]) * flt(batch[1])
qty_to_pop -= batch[0]
previous_stock_queue.pop(0)
else:
# all from current batch
outgoing_cost += flt(qty_to_pop) * flt(batch[1])
batch[0] -= qty_to_pop
qty_to_pop = 0
return outgoing_cost / abs(qty)
def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
"""split serial nos, validate and return list of valid serial nos"""