Ordered qty calculation logic

This commit is contained in:
Nabin Hait 2014-11-04 15:33:12 +05:30
parent 4acd431b92
commit 941a965af4
4 changed files with 75 additions and 98 deletions

View File

@ -3,15 +3,13 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cstr, flt
from frappe.utils import flt
from frappe import _
from erpnext.stock.doctype.item.item import get_last_purchase_details
from erpnext.controllers.buying_controller import BuyingController
class PurchaseCommon(BuyingController):
def update_last_purchase_rate(self, obj, is_submit):
"""updates last_purchase_rate in item table for each item"""
@ -123,27 +121,6 @@ class PurchaseCommon(BuyingController):
else:
chk_dupl_itm.append(f)
def get_qty(self, curr_doctype, ref_tab_fname, ref_tab_dn, ref_doc_tname, transaction, curr_parent_name):
# Get total Quantities of current doctype (eg. PR) except for qty of this transaction
#------------------------------
# please check as UOM changes from Material Request - Purchase Order ,so doing following else uom should be same .
# i.e. in PO uom is NOS then in PR uom should be NOS
# but if in Material Request uom KG it can change in PO
get_qty = (transaction == 'Material Request - Purchase Order') and 'qty * conversion_factor' or 'qty'
qty = frappe.db.sql("""select sum(%s) from `tab%s` where %s = %s and
docstatus = 1 and parent != %s""" % (get_qty, curr_doctype, ref_tab_fname, '%s', '%s'),
(ref_tab_dn, curr_parent_name))
qty = qty and flt(qty[0][0]) or 0
# get total qty of ref doctype
#--------------------
max_qty = frappe.db.sql("""select qty from `tab%s` where name = %s
and docstatus = 1""" % (ref_doc_tname, '%s'), ref_tab_dn)
max_qty = max_qty and flt(max_qty[0][0]) or 0
return cstr(qty)+'~~~'+cstr(max_qty)
def check_for_stopped_status(self, doctype, docname):
stopped = frappe.db.sql("""select name from `tab%s` where name = %s and
status = 'Stopped'""" % (doctype, '%s'), docname)

View File

@ -96,50 +96,45 @@ class PurchaseOrder(BuyingController):
check_list.append(d.prevdoc_docname)
pc_obj.check_for_stopped_status( d.prevdoc_doctype, d.prevdoc_docname)
def update_requested_qty(self):
material_request_map = {}
for d in self.get("po_details"):
if d.prevdoc_doctype and d.prevdoc_doctype == "Material Request" and d.prevdoc_detail_docname:
material_request_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname)
def update_bin(self, is_submit, is_stopped = 0):
from erpnext.stock.utils import update_bin
pc_obj = frappe.get_doc('Purchase Common')
for d in self.get('po_details'):
#1. Check if is_stock_item == 'Yes'
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes":
# this happens when item is changed from non-stock to stock item
if not d.warehouse:
continue
for mr, mr_item_rows in material_request_map.items():
if mr and mr_item_rows:
mr_obj = frappe.get_doc("Material Request", mr)
ind_qty, po_qty = 0, flt(d.qty) * flt(d.conversion_factor)
if is_stopped:
po_qty = flt(d.qty) > flt(d.received_qty) and \
flt( flt(flt(d.qty) - flt(d.received_qty))*flt(d.conversion_factor)) or 0
if mr_obj.status in ["Stopped", "Cancelled"]:
frappe.throw(_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError)
# No updates in Material Request on Stop / Unstop
if cstr(d.prevdoc_doctype) == 'Material Request' and not is_stopped:
# get qty and pending_qty of prevdoc
curr_ref_qty = pc_obj.get_qty(d.doctype, 'prevdoc_detail_docname',
d.prevdoc_detail_docname, 'Material Request Item',
'Material Request - Purchase Order', self.name)
max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \
flt(curr_ref_qty.split('~~~')[0]), 0
mr_obj.update_requested_qty(mr_item_rows)
if flt(qty) + flt(po_qty) > flt(max_qty):
curr_qty = flt(max_qty) - flt(qty)
# special case as there is no restriction
# for Material Request - Purchase Order
curr_qty = curr_qty > 0 and curr_qty or 0
else:
curr_qty = flt(po_qty)
def update_ordered_qty(self, po_item_rows=None):
"""update requested qty (before ordered_qty is updated)"""
from erpnext.stock.utils import get_bin
ind_qty = -flt(curr_qty)
def _update_ordered_qty(item_code, warehouse):
ordered_qty = frappe.db.sql("""
select sum((po_item.qty - ifnull(po_item.received_qty, 0))*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 > ifnull(po_item.received_qty, 0) and po_item.parent=po.name
and po.status!='Stopped' and po.docstatus=1""", (item_code, warehouse))
# Update ordered_qty and indented_qty in bin
args = {
"item_code": d.item_code,
"warehouse": d.warehouse,
"ordered_qty": (is_submit and 1 or -1) * flt(po_qty),
"indented_qty": (is_submit and 1 or -1) * flt(ind_qty),
"posting_date": self.transaction_date
}
update_bin(args)
bin_doc = get_bin(item_code, warehouse)
bin_doc.ordered_qty = flt(ordered_qty[0][0]) if ordered_qty else 0
bin_doc.save()
item_wh_list = []
for d in self.get("po_details"):
if (not po_item_rows or d.name in po_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
and frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse:
item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
_update_ordered_qty(item_code, warehouse)
def check_modified_date(self):
mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",
@ -152,13 +147,11 @@ class PurchaseOrder(BuyingController):
def update_status(self, status):
self.check_modified_date()
# step 1:=> Set Status
frappe.db.set(self,'status',cstr(status))
# step 2:=> Update Bin
self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1)
self.update_requested_qty()
self.update_ordered_qty()
# step 3:=> Acknowledge user
msgprint(_("Status of {0} {1} is now {2}").format(self.doctype, self.name, status))
def on_submit(self):
@ -167,7 +160,8 @@ class PurchaseOrder(BuyingController):
purchase_controller = frappe.get_doc("Purchase Common")
self.update_prevdoc_status()
self.update_bin(is_submit = 1, is_stopped = 0)
self.update_requested_qty()
self.update_ordered_qty()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.grand_total)
@ -192,8 +186,13 @@ class PurchaseOrder(BuyingController):
throw(_("Purchase Invoice {0} is already submitted").format(", ".join(submitted)))
frappe.db.set(self,'status','Cancelled')
self.update_prevdoc_status()
self.update_bin( is_submit = 0, is_stopped = 0)
# Must be called after updating ordered qty in Material Request
self.update_requested_qty()
self.update_ordered_qty()
pc_obj.update_last_purchase_rate(self, is_submit = 0)
def on_update(self):

View File

@ -29,8 +29,7 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.get_doc(pr).insert()
def test_ordered_qty(self):
frappe.db.sql("delete from tabBin")
existing_ordered_qty = self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC")
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
po = frappe.copy_doc(test_records[0]).insert()
@ -43,8 +42,7 @@ class TestPurchaseOrder(unittest.TestCase):
po.get("po_details")[0].item_code = "_Test Item"
po.submit()
self.assertEquals(frappe.db.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty"), 10)
self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 10)
pr = make_purchase_receipt(po.name)
@ -56,8 +54,9 @@ class TestPurchaseOrder(unittest.TestCase):
pr.insert()
pr.submit()
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 6.0)
po.load_from_db()
self.assertEquals(po.get("po_details")[0].received_qty, 4)
self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 6)
frappe.db.set_value('Item', '_Test Item', 'tolerance', 50)
@ -68,8 +67,16 @@ class TestPurchaseOrder(unittest.TestCase):
pr1.insert()
pr1.submit()
self.assertEquals(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "ordered_qty")), 0.0)
po.load_from_db()
self.assertEquals(po.get("po_details")[0].received_qty, 12)
self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty)
pr1.load_from_db()
pr1.cancel()
po.load_from_db()
self.assertEquals(po.get("po_details")[0].received_qty, 4)
self.assertEquals(self._get_ordered_qty("_Test Item", "_Test Warehouse - _TC"), existing_ordered_qty + 6)
def test_make_purchase_invoice(self):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
@ -111,6 +118,9 @@ class TestPurchaseOrder(unittest.TestCase):
from erpnext.controllers.tests.test_recurring_document import test_recurring_document
test_recurring_document(self, test_records)
def _get_ordered_qty(self, item_code, warehouse):
return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "ordered_qty"))
test_dependencies = ["BOM", "Item Price"]

View File

@ -8,7 +8,6 @@ from frappe.utils import cstr, flt, cint
from frappe import _
import frappe.defaults
from erpnext.stock.utils import update_bin
from erpnext.controllers.buying_controller import BuyingController
@ -157,29 +156,19 @@ class PurchaseReceipt(BuyingController):
self.make_sl_entries(sl_entries)
def update_ordered_qty(self):
stock_items = self.get_stock_items()
po_map = {}
for d in self.get("purchase_receipt_details"):
if d.item_code in stock_items and d.warehouse \
and cstr(d.prevdoc_doctype) == 'Purchase Order':
if d.prevdoc_doctype and d.prevdoc_doctype == "Purchase Order" and d.prevdoc_detail_docname:
po_map.setdefault(d.prevdoc_docname, []).append(d.prevdoc_detail_docname)
already_received_qty = self.get_already_received_qty(d.prevdoc_docname,
d.prevdoc_detail_docname)
po_qty, ordered_warehouse = self.get_po_qty_and_warehouse(d.prevdoc_detail_docname)
for po, po_item_rows in po_map.items():
if po and po_item_rows:
po_obj = frappe.get_doc("Purchase Order", po)
if not ordered_warehouse:
frappe.throw(_("Warehouse is missing in Purchase Order"))
if po_obj.status in ["Stopped", "Cancelled"]:
frappe.throw(_("Material Request {0} is cancelled or stopped").format(po), frappe.InvalidStatusError)
if already_received_qty + d.qty > po_qty:
ordered_qty = - (po_qty - already_received_qty) * flt(d.conversion_factor)
else:
ordered_qty = - flt(d.qty) * flt(d.conversion_factor)
update_bin({
"item_code": d.item_code,
"warehouse": ordered_warehouse,
"posting_date": self.posting_date,
"ordered_qty": flt(ordered_qty) if self.docstatus==1 else -flt(ordered_qty)
})
po_obj.update_ordered_qty(po_item_rows)
def get_already_received_qty(self, po, po_detail):
qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item`
@ -265,11 +254,13 @@ class PurchaseReceipt(BuyingController):
frappe.db.set(self,'status','Cancelled')
self.update_ordered_qty()
self.update_stock_ledger()
self.update_prevdoc_status()
# Must be called after updating received qty in PO
self.update_ordered_qty()
pc_obj.update_last_purchase_rate(self, 0)
self.make_gl_entries_on_cancel()