Warehouse merging feature and update_bin cleanup

This commit is contained in:
Nabin Hait
2012-10-26 14:42:48 +05:30
parent b38e34d203
commit 981d48339a
17 changed files with 742 additions and 719 deletions

View File

@@ -16,4 +16,16 @@
cur_frm.cscript.refresh = function(doc) {
cur_frm.toggle_display('warehouse_name', doc.__islocal);
}
cur_frm.cscript.merge = function(doc, cdt, cdn) {
if (!doc.merge_with) {
msgprint("Please enter the warehouse to which you want to merge?");
return;
}
var check = confirm("Are you sure you want to merge this warehouse into "
+ doc.merge_with + "?");
if (check) {
$c_obj(make_doclist(cdt, cdn), 'merge_warehouses', '', '');
}
}

View File

@@ -18,106 +18,186 @@
from __future__ import unicode_literals
import webnotes
from webnotes.utils import add_days, add_months, add_years, cint, cstr, date_diff, default_fields, flt, fmt_money, formatdate, getTraceback, get_defaults, get_first_day, get_last_day, getdate, has_common, month_name, now, nowdate, replace_newlines, sendmail, set_default, str_esc_quote, user_format, validate_email_add
from webnotes.model import db_exists
from webnotes.model.doc import Document, addchild, getchildren, make_autoname
from webnotes.model.doclist import getlist, copy_doclist
from webnotes.model.code import get_obj, get_server_obj, run_server_obj, updatedb, check_syntax
from webnotes import session, form, msgprint, errprint
from webnotes.utils import add_days, add_months, add_years, cint, cstr, flt, get_defaults, getdate, has_common, month_name, now, nowdate, set_default, validate_email_add
from webnotes.model.doc import Document, addchild, getchildren
from webnotes.model.doclist import getlist
from webnotes.model.code import get_obj
from webnotes import msgprint, errprint
set = webnotes.conn.set
sql = webnotes.conn.sql
get_value = webnotes.conn.get_value
in_transaction = webnotes.conn.in_transaction
convert_to_lists = webnotes.conn.convert_to_lists
# -----------------------------------------------------------------------------------------
class DocType:
def __init__(self, doc, doclist=[]):
self.doc = doc
self.doclist = doclist
def get_bin(self, item_code):
def get_bin(self, item_code, warehouse=None):
warehouse = warehouse or self.doc.name
bin = sql("select name from tabBin where item_code = %s and \
warehouse = %s", (item_code, self.doc.name))
warehouse = %s", (item_code, warehouse))
bin = bin and bin[0][0] or ''
if not bin:
if not self.doc.warehouse_type :
msgprint("[Warehouse Type is Mandatory] Please Enter warehouse type in Warehouse " + self.doc.name)
raise Exception
bin = Document('Bin')
bin.item_code = item_code
bin.stock_uom = get_value('Item', item_code, 'stock_uom')
bin.warehouse = self.doc.name
bin.warehouse_type = self.doc.warehouse_type
bin.stock_uom = webnotes.conn.get_value('Item', item_code, 'stock_uom')
bin.warehouse = warehouse
bin.warehouse_type = webnotes.conn.get_value("Warehouse", warehouse, "warehouse_type")
bin_obj = get_obj(doc=bin)
bin_obj.validate()
bin.save(1)
bin = bin.name
else:
bin_obj = get_obj('Bin',bin)
bin_obj = get_obj('Bin', bin)
return bin_obj
def validate_asset(self, item_code):
if sql("select is_asset_item from tabItem where name=%s", item_code)[0][0] == 'Yes' and self.doc.warehouse_type != 'Fixed Asset':
msgprint("Fixed Asset Item %s can only be transacted in a Fixed Asset type Warehouse" % item_code)
raise Exception
if webnotes.conn.get_value("Item", item_code, "is_asset_item") == 'Yes' \
and self.doc.warehouse_type != 'Fixed Asset':
msgprint("""Fixed Asset Item %s can only be transacted in a
Fixed Asset type Warehouse""" % item_code, raise_exception=1)
# update bin
# ----------
def update_bin(self, actual_qty, reserved_qty, ordered_qty, indented_qty, planned_qty, item_code, dt, sle_id = '',posting_time = '', serial_no = '', is_cancelled = 'No',doc_type='',doc_name='',is_amended='No'):
self.validate_asset(item_code)
it_det = get_value('Item', item_code, 'is_stock_item')
if it_det and it_det == 'Yes':
bin = self.get_bin(item_code)
bin.update_stock(actual_qty, reserved_qty, ordered_qty, indented_qty, planned_qty, dt, sle_id, posting_time, serial_no, is_cancelled,doc_type,doc_name,is_amended)
def update_bin(self, args):
self.validate_asset(args.get("item_code"))
is_stock_item = webnotes.conn.get_value('Item', args.get("item_code"), 'is_stock_item')
if is_stock_item == 'Yes':
bin = self.get_bin(args.get("item_code"))
bin.update_stock(args)
return bin
else:
msgprint("[Stock Update] Ignored %s since it is not a stock item" % item_code)
# repost stock
# ------------
def repost_stock(self):
bl = sql("select name from tabBin where warehouse=%s", self.doc.name)
for b in bl:
bobj = get_obj('Bin',b[0])
bobj.update_entries_after(posting_date = '0000-00-00', posting_time = '00:00')
sql("COMMIT")
sql("START TRANSACTION")
msgprint("[Stock Update] Ignored %s since it is not a stock item"
% args.get("item_code"))
def check_state(self):
return "\n" + "\n".join([i[0] for i in sql("select state_name from `tabState` where `tabState`.country='%s' " % self.doc.country)])
return "\n" + "\n".join([i[0] for i in sql("""
select state_name from `tabState` where country=%s""", self.doc.country)])
def validate(self):
if self.doc.email_id:
if not validate_email_add(self.doc.email_id):
msgprint("Please enter valid Email Id.")
raise Exception
if self.doc.email_id and not validate_email_add(self.doc.email_id):
msgprint("Please enter valid Email Id", raise_exception=1)
if not self.doc.warehouse_type:
msgprint("[Warehouse Type is Mandatory] Please Enter Please Entry warehouse type in Warehouse " + self.doc.name)
raise Exception
wt = sql("select warehouse_type from `tabWarehouse` where name ='%s'" % self.doc.name)
if cstr(self.doc.warehouse_type) != cstr(wt and wt[0][0] or ''):
sql("update `tabStock Ledger Entry` set warehouse_type = '%s' where warehouse = '%s'" % (self.doc.warehouse_type, self.doc.name))
msgprint("All Stock Ledger Entries Updated.")
msgprint("Warehouse Type is Mandatory", raise_exception=1)
wt = sql("select warehouse_type from `tabWarehouse` where name ='%s'" % self.doc.name)
if wt and cstr(self.doc.warehouse_type) != cstr(wt[0][0]):
sql("""update `tabStock Ledger Entry` set warehouse_type = %s
where warehouse = %s""", (self.doc.warehouse_type, self.doc.name))
def merge_warehouses(self):
webnotes.conn.auto_commit_on_many_writes = 1
# get items which dealt with current warehouse
items = webnotes.conn.sql("select item_code from tabBin where warehouse=%s" , self.doc.name)
# delete old bins
webnotes.conn.sql("delete from tabBin where warehouse=%s", self.doc.name)
# replace link fields
from webnotes.model import rename_doc
link_fields = rename_doc.get_link_fields('Warehouse')
rename_doc.update_link_field_values(link_fields, self.doc.name, self.doc.merge_with)
for item_code in items:
self.repost(item_code[0], self.doc.merge_with)
webnotes.conn.auto_commit_on_many_writes = 0
def repost(self, item_code, warehouse=None):
bin = self.get_bin(item_code, warehouse)
self.repost_actual_qty(bin)
self.repost_reserved_qty(bin)
self.repost_indented_qty(bin)
self.repost_ordered_qty(bin)
self.repost_planned_qty(bin)
bin.doc.projected_qty = flt(bin.doc.actual_qty) + flt(bin.doc.planned_qty) \
+ flt(bin.doc.indented_qty) + flt(bin.doc.ordered_qty) - flt(bin.doc.reserved_qty)
bin.doc.save()
def repost_actual_qty(self, bin):
bin.update_entries_after(posting_date = '0000-00-00', posting_time = '00:00')
def repost_reserved_qty(self, bin):
reserved_qty = webnotes.conn.sql("""
select
sum((dnpi_qty / so_item_qty) * (so_item_qty - so_item_delivered_qty))
from
(
select
qty as dnpi_qty,
(
select qty from `tabSales Order Item`
where name = dnpi.parent_detail_docname
) as so_item_qty,
(
select ifnull(delivered_qty, 0) from `tabSales Order Item`
where name = dnpi.parent_detail_docname
) as so_item_delivered_qty
from
(
select qty, parent_detail_docname
from `tabDelivery Note Packing Item` dnpi_in
where item_code = %s and warehouse = %s
and parenttype="Sales Order"
and exists (select * from `tabSales Order` so
where name = dnpi_in.parent and docstatus = 1 and status != 'Stopped')
) dnpi
) tab
where
so_item_qty >= so_item_delivered_qty
""", (bin.doc.item_code, bin.doc.warehouse))
if flt(bin.doc.reserved_qty) != flt(reserved_qty[0][0]):
webnotes.conn.set_value("Bin", bin.doc.name, "reserved_qty", flt(reserved_qty[0][0]))
def repost_indented_qty(self, bin):
indented_qty = webnotes.conn.sql("""select sum(pr_item.qty - pr_item.ordered_qty)
from `tabPurchase Request Item` pr_item, `tabPurchase Request` pr
where pr_item.item_code=%s and pr_item.warehouse=%s
and pr_item.qty > pr_item.ordered_qty and pr_item.parent=pr.name
and pr.status!='Stopped' and pr.docstatus=1"""
, (bin.doc.item_code, bin.doc.warehouse))
if flt(bin.doc.indented_qty) != flt(indented_qty[0][0]):
webnotes.conn.set_value("Bin", bin.doc.name, "indented_qty", flt(indented_qty[0][0]))
def repost_ordered_qty(self, bin):
ordered_qty = webnotes.conn.sql("""
select sum((po_item.qty - po_item.received_qty)*po_item.conversion_factor)
from `tabPurchase Order Item` po_item, `tabPurchase Order` po
where po_item.item_code=%s and po_item.warehouse=%s
and po_item.qty > po_item.received_qty and po_item.parent=po.name
and po.status!='Stopped' and po.docstatus=1"""
, (bin.doc.item_code, bin.doc.warehouse))
if flt(bin.doc.ordered_qty) != flt(ordered_qty[0][0]):
webnotes.conn.set_value("Bin", bin.doc.name, "ordered_qty", flt(ordered_qty[0][0]))
def repost_planned_qty(self, bin):
planned_qty = webnotes.conn.sql("""
select sum(qty - produced_qty) from `tabProduction Order`
where production_item = %s and fg_warehouse = %s and status != "Stopped"
and docstatus=1""", (bin.doc.item_code, bin.doc.warehouse))
if flt(bin.doc.planned_qty) != flt(planned_qty[0][0]):
webnotes.conn.set_value("Bin", bin.doc.name, "planned_qty", flt(planned_qty[0][0]))
def on_trash(self):
# delete bin
bins = sql("select * from `tabBin` where warehouse = %s", self.doc.name, as_dict=1)
for d in bins:
if d['actual_qty'] or d['reserved_qty'] or d['ordered_qty'] or d['indented_qty'] or d['projected_qty'] or d['planned_qty']:
msgprint("Warehouse: %s can not be deleted as qty exists for item: %s" % (self.doc.name, d['item_code']), raise_exception=1)
if d['actual_qty'] or d['reserved_qty'] or d['ordered_qty'] or \
d['indented_qty'] or d['projected_qty'] or d['planned_qty']:
msgprint("""Warehouse: %s can not be deleted as qty exists for item: %s"""
% (self.doc.name, d['item_code']), raise_exception=1)
else:
sql("delete from `tabBin` where name = %s", d['name'])
# delete cancelled sle
if sql("select name from `tabStock Ledger Entry` where warehouse = %s and ifnull('is_cancelled', '') = 'No'", self.doc.name):
mdgprint("Warehosue can not be deleted as stock ledger entry exists for this warehosue.", raise_exception=1)
if sql("""select name from `tabStock Ledger Entry`
where warehouse = %s and ifnull('is_cancelled', '') = 'No'""", self.doc.name):
mdgprint("""Warehosue can not be deleted as stock ledger entry
exists for this warehosue.""", raise_exception=1)
else:
sql("delete from `tabStock Ledger Entry` where warehouse = %s", self.doc.name)
sql("delete from `tabStock Ledger Entry` where warehouse = %s", self.doc.name)

View File

@@ -3,11 +3,11 @@
# These values are common in all dictionaries
{
'creation': '2012-07-03 13:30:45',
'docstatus': 0,
'modified': '2012-07-11 10:18:07',
'modified_by': u'Administrator',
'owner': u'Administrator'
u'creation': '2012-10-10 12:07:10',
u'docstatus': 0,
u'modified': '2012-10-25 15:03:49',
u'modified_by': u'Administrator',
u'owner': u'Administrator'
},
# These values are common for all DocType
@@ -18,10 +18,10 @@
'colour': u'White:FFF',
'default_print_format': u'Standard',
'description': u'A logical Warehouse against which stock entries are made.\n\nThere are two main Warehouse Types that are significant in ERPNext.\n\n1. **Stores:** These are where your incoming **Items** are kept before they are consumed or sold. You can have as many \u201cStores\u201d type **Warehouses** as you wish. Stores type warehouses are significant because if you set an Item for automatic re-order, ERPNext will check its quantities in all \u201cStores\u201d type **Warehouses** when deciding whether to re-order or not.\n\n2. **Asset**: **Items** marked as type \u201cFixed Asset\u201d are maintained in Asset Type **Warehouses**. This helps you separate them for the **Items** that are consumed as a part of your regular operations or \u201cCost of Goods Sold\u201d.\n',
'doctype': 'DocType',
u'doctype': u'DocType',
'document_type': u'Master',
'module': u'Stock',
'name': '__common__',
u'name': u'__common__',
'search_fields': u'warehouse_type',
'section_style': u'Tabbed',
'server_code_error': u' ',
@@ -31,8 +31,8 @@
# These values are common for all DocField
{
'doctype': u'DocField',
'name': '__common__',
u'doctype': u'DocField',
u'name': u'__common__',
'parent': u'Warehouse',
'parentfield': u'fields',
'parenttype': u'DocType'
@@ -40,8 +40,8 @@
# These values are common for all DocPerm
{
'doctype': u'DocPerm',
'name': '__common__',
u'doctype': u'DocPerm',
u'name': u'__common__',
'parent': u'Warehouse',
'parentfield': u'permissions',
'parenttype': u'DocType',
@@ -50,130 +50,13 @@
# DocType, Warehouse
{
'doctype': 'DocType',
'name': u'Warehouse'
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
'doctype': u'DocPerm',
'permlevel': 2,
'role': u'Material User',
'submit': 0,
'write': 0
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
'doctype': u'DocPerm',
'permlevel': 0,
'role': u'Material User',
'submit': 0,
'write': 0
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
'doctype': u'DocPerm',
'permlevel': 1,
'role': u'Material User',
'submit': 0,
'write': 0
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
'doctype': u'DocPerm',
'permlevel': 2,
'role': u'Material Manager',
'submit': 0,
'write': 0
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
'doctype': u'DocPerm',
'permlevel': 0,
'role': u'Material Manager',
'submit': 0,
'write': 0
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
'doctype': u'DocPerm',
'permlevel': 1,
'role': u'Material Manager',
'submit': 0,
'write': 0
},
# DocPerm
{
'doctype': u'DocPerm',
'permlevel': 1,
'role': u'All'
},
# DocPerm
{
'amend': 0,
'cancel': 1,
'create': 1,
'doctype': u'DocPerm',
'permlevel': 0,
'role': u'Material Master Manager',
'submit': 0,
'write': 1
},
# DocPerm
{
'doctype': u'DocPerm',
'permlevel': 1,
'role': u'Material Master Manager'
},
# DocPerm
{
'cancel': 1,
'create': 1,
'doctype': u'DocPerm',
'permlevel': 0,
'role': u'System Manager',
'write': 1
},
# DocPerm
{
'create': 0,
'doctype': u'DocPerm',
'permlevel': 2,
'role': u'System Manager',
'write': 1
u'doctype': u'DocType',
u'name': u'Warehouse'
},
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'warehouse_detail',
'fieldtype': u'Section Break',
'label': u'Warehouse Detail',
@@ -183,7 +66,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'warehouse_name',
'fieldtype': u'Data',
'label': u'Warehouse Name',
@@ -196,7 +79,7 @@
# DocField
{
'colour': u'White:FFF',
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'warehouse_type',
'fieldtype': u'Link',
'label': u'Warehouse Type',
@@ -210,7 +93,7 @@
# DocField
{
'colour': u'White:FFF',
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'company',
'fieldtype': u'Link',
'in_filter': 1,
@@ -226,7 +109,7 @@
{
'colour': u'White:FFF',
'description': u'For Reference Only.',
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'warehouse_contact_info',
'fieldtype': u'Section Break',
'label': u'Warehouse Contact Info',
@@ -235,7 +118,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'email_id',
'fieldtype': u'Data',
'hidden': 1,
@@ -248,7 +131,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'phone_no',
'fieldtype': u'Data',
'label': u'Phone No',
@@ -260,7 +143,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'mobile_no',
'fieldtype': u'Data',
'label': u'Mobile No',
@@ -272,7 +155,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'column_break0',
'fieldtype': u'Column Break',
'oldfieldtype': u'Column Break',
@@ -281,7 +164,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'address_line_1',
'fieldtype': u'Data',
'label': u'Address Line 1',
@@ -292,7 +175,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'address_line_2',
'fieldtype': u'Data',
'label': u'Address Line 2',
@@ -303,7 +186,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'city',
'fieldtype': u'Data',
'label': u'City',
@@ -316,7 +199,7 @@
# DocField
{
'colour': u'White:FFF',
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'state',
'fieldtype': u'Data',
'label': u'State',
@@ -328,7 +211,7 @@
# DocField
{
'doctype': u'DocField',
u'doctype': u'DocField',
'fieldname': u'pin',
'fieldtype': u'Int',
'label': u'PIN',
@@ -340,24 +223,92 @@
# DocField
{
'colour': u'White:FFF',
'description': u'For Admin use only. Please report errors to support@erpnext.com',
'doctype': u'DocField',
'fieldname': u'repost_stock',
'description': u'This feature is for merging duplicate warehouses. It will replace all the links of this warehouse by "Merge With" warehouse. After merging you can delete this warehouse, as stock level for this warehouse will be zero.',
u'doctype': u'DocField',
'fieldname': u'merge_warehouses_section',
'fieldtype': u'Section Break',
'label': u'Repost Stock',
'oldfieldtype': u'Section Break',
'label': u'Merge Warehouses',
'permlevel': 2
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'repost_stock_ledger',
'fieldtype': u'Button',
'hidden': 0,
'label': u'Repost Stock Ledger',
'oldfieldtype': u'Button',
'options': u'repost_stock',
u'doctype': u'DocField',
'fieldname': u'merge_with',
'fieldtype': u'Link',
'label': u'Merge With',
'options': u'Warehouse',
'permlevel': 2
},
# DocField
{
u'doctype': u'DocField',
'fieldname': u'merge',
'fieldtype': u'Button',
'label': u'Merge',
'permlevel': 2
},
# DocPerm
{
'amend': 0,
'cancel': 1,
'create': 1,
u'doctype': u'DocPerm',
'permlevel': 0,
'role': u'Material Master Manager',
'submit': 0,
'write': 1
},
# DocPerm
{
'cancel': 1,
'create': 1,
u'doctype': u'DocPerm',
'permlevel': 0,
'role': u'System Manager',
'write': 1
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
u'doctype': u'DocPerm',
'permlevel': 0,
'role': u'Material Manager',
'submit': 0,
'write': 0
},
# DocPerm
{
'amend': 0,
'cancel': 0,
'create': 0,
u'doctype': u'DocPerm',
'permlevel': 0,
'role': u'Material User',
'submit': 0,
'write': 0
},
# DocPerm
{
u'doctype': u'DocPerm',
'permlevel': 1,
'role': u'All'
},
# DocPerm
{
'create': 0,
u'doctype': u'DocPerm',
'permlevel': 2,
'role': u'System Manager',
'write': 1
}
]