Multi-UOM for sales/purchase return (#13132)

* Multi-UOM for sales/purchase return

* Update sales_and_purchase_return.py
This commit is contained in:
rohitwaghchaure 2018-03-01 11:31:33 +05:30 committed by Nabin Hait
parent 6578bc11b6
commit 0df95fa781
3 changed files with 38 additions and 12 deletions

View File

@ -53,8 +53,9 @@ def validate_returned_items(doc):
valid_items = frappe._dict()
select_fields = "item_code, qty, rate, parenttype" if doc.doctype=="Purchase Invoice" \
else "item_code, qty, rate, serial_no, batch_no, parenttype"
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
if doc.doctype != 'Purchase Invoice':
select_fields += ",serial_no, batch_no"
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
select_fields += ",rejected_qty, received_qty"
@ -111,7 +112,7 @@ def validate_returned_items(doc):
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
fields = ['qty']
fields = ['stock_qty']
if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']:
fields.extend(['received_qty', 'rejected_qty'])
@ -119,16 +120,19 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
for column in fields:
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
reference_qty = ref.get(column)
reference_qty = (ref.get(column) if column == 'stock_qty'
else ref.get(column) * ref.get("conversion_factor", 1.0))
max_returnable_qty = flt(reference_qty) - returned_qty
label = column.replace('_', ' ').title()
if reference_qty:
if flt(args.get(column)) > 0:
frappe.throw(_("{0} must be negative in return document").format(label))
elif returned_qty >= reference_qty and args.get(column):
frappe.throw(_("Item {0} has already been returned")
.format(args.item_code), StockOverReturnError)
elif abs(args.get(column)) > max_returnable_qty:
elif (abs(args.get(column)) * args.get("conversion_factor", 1.0)) > max_returnable_qty:
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
.format(args.idx, reference_qty, args.item_code), StockOverReturnError)
@ -138,6 +142,7 @@ def get_ref_item_dict(valid_items, ref_item_row):
valid_items.setdefault(ref_item_row.item_code, frappe._dict({
"qty": 0,
"rate": 0,
"stock_qty": 0,
"rejected_qty": 0,
"received_qty": 0,
"serial_no": [],
@ -145,6 +150,7 @@ def get_ref_item_dict(valid_items, ref_item_row):
}))
item_dict = valid_items[ref_item_row.item_code]
item_dict["qty"] += ref_item_row.qty
item_dict["stock_qty"] += ref_item_row.get('stock_qty', 0)
if ref_item_row.get("rate", 0) > item_dict["rate"]:
item_dict["rate"] = ref_item_row.get("rate", 0)
@ -161,9 +167,10 @@ def get_ref_item_dict(valid_items, ref_item_row):
return valid_items
def get_already_returned_items(doc):
column = 'child.item_code, sum(abs(child.qty)) as qty'
column = 'child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty'
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
column += ', sum(abs(child.rejected_qty)) as rejected_qty, sum(abs(child.received_qty)) as received_qty'
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
data = frappe.db.sql("""
select {0}
@ -180,6 +187,7 @@ def get_already_returned_items(doc):
for d in data:
items.setdefault(d.item_code, frappe._dict({
"qty": d.get("qty"),
"stock_qty": d.get("stock_qty"),
"received_qty": d.get("received_qty"),
"rejected_qty": d.get("rejected_qty")
}))

View File

@ -11,7 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas
from erpnext import set_perpetual_inventory
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import make_item
class TestPurchaseReceipt(unittest.TestCase):
def setUp(self):
@ -203,6 +203,22 @@ class TestPurchaseReceipt(unittest.TestCase):
"delivery_document_no": return_pr.name
})
def test_purchase_return_for_multi_uom(self):
item_code = "_Test Purchase Return For Multi-UOM"
if not frappe.db.exists('Item', item_code):
item = make_item(item_code, {'stock_uom': 'Box'})
row = item.append('uoms', {
'uom': 'Unit',
'conversion_factor': 0.1
})
row.db_update()
pr = make_purchase_receipt(item_code=item_code, qty=1, uom="Box", conversion_factor=1.0)
return_pr = make_purchase_receipt(item_code=item_code, qty=-10, uom="Unit",
stock_uom="Box", conversion_factor=0.1, is_return=1, return_against=pr.name)
self.assertEquals(abs(return_pr.items[0].stock_qty), 1.0)
def test_closed_purchase_receipt(self):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_purchase_receipt_status
@ -255,7 +271,6 @@ class TestPurchaseReceipt(unittest.TestCase):
def test_not_accept_duplicate_serial_no(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
item_code = frappe.db.get_value('Item', {'has_serial_no': 1})
@ -307,9 +322,10 @@ def make_purchase_receipt(**args):
"rejected_qty": rejected_qty,
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
"rate": args.rate or 50,
"conversion_factor": 1.0,
"conversion_factor": args.conversion_factor or 1.0,
"serial_no": args.serial_no,
"stock_uom": "_Test UOM"
"stock_uom": args.stock_uom or "_Test UOM",
"uom": args.uom or "_Test UOM"
})
if not args.do_not_save:

View File

@ -71,6 +71,8 @@ class TransactionBase(StatusUpdater):
validate_uom_is_integer(self, uom_field, qty_fields)
def validate_with_previous_doc(self, ref):
self.exclude_fields = ["conversion_factor", "uom"] if self.get('is_return') else []
for key, val in ref.items():
is_child = val.get("is_child_table")
ref_doc = {}
@ -101,7 +103,7 @@ class TransactionBase(StatusUpdater):
frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
for field, condition in fields:
if prevdoc_values[field] is not None:
if prevdoc_values[field] is not None and field not in self.exclude_fields:
self.validate_value(field, condition, prevdoc_values[field], doc)