Multi-UOM for sales/purchase return (#13132)
* Multi-UOM for sales/purchase return * Update sales_and_purchase_return.py
This commit is contained in:
parent
6578bc11b6
commit
0df95fa781
@ -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")
|
||||
}))
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user