mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 08:54:45 +00:00
Multi-UOM for sales/purchase return (#13132)
* Multi-UOM for sales/purchase return * Update sales_and_purchase_return.py
This commit is contained in:
committed by
Nabin Hait
parent
6578bc11b6
commit
0df95fa781
@@ -53,8 +53,9 @@ def validate_returned_items(doc):
|
|||||||
|
|
||||||
valid_items = frappe._dict()
|
valid_items = frappe._dict()
|
||||||
|
|
||||||
select_fields = "item_code, qty, rate, parenttype" if doc.doctype=="Purchase Invoice" \
|
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
|
||||||
else "item_code, qty, rate, serial_no, batch_no, parenttype"
|
if doc.doctype != 'Purchase Invoice':
|
||||||
|
select_fields += ",serial_no, batch_no"
|
||||||
|
|
||||||
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
|
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||||
select_fields += ",rejected_qty, received_qty"
|
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"))
|
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):
|
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
|
||||||
fields = ['qty']
|
fields = ['stock_qty']
|
||||||
if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']:
|
if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']:
|
||||||
fields.extend(['received_qty', 'rejected_qty'])
|
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:
|
for column in fields:
|
||||||
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
|
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
|
max_returnable_qty = flt(reference_qty) - returned_qty
|
||||||
label = column.replace('_', ' ').title()
|
label = column.replace('_', ' ').title()
|
||||||
|
|
||||||
if reference_qty:
|
if reference_qty:
|
||||||
if flt(args.get(column)) > 0:
|
if flt(args.get(column)) > 0:
|
||||||
frappe.throw(_("{0} must be negative in return document").format(label))
|
frappe.throw(_("{0} must be negative in return document").format(label))
|
||||||
elif returned_qty >= reference_qty and args.get(column):
|
elif returned_qty >= reference_qty and args.get(column):
|
||||||
frappe.throw(_("Item {0} has already been returned")
|
frappe.throw(_("Item {0} has already been returned")
|
||||||
.format(args.item_code), StockOverReturnError)
|
.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}")
|
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
|
||||||
.format(args.idx, reference_qty, args.item_code), StockOverReturnError)
|
.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({
|
valid_items.setdefault(ref_item_row.item_code, frappe._dict({
|
||||||
"qty": 0,
|
"qty": 0,
|
||||||
"rate": 0,
|
"rate": 0,
|
||||||
|
"stock_qty": 0,
|
||||||
"rejected_qty": 0,
|
"rejected_qty": 0,
|
||||||
"received_qty": 0,
|
"received_qty": 0,
|
||||||
"serial_no": [],
|
"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 = valid_items[ref_item_row.item_code]
|
||||||
item_dict["qty"] += ref_item_row.qty
|
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"]:
|
if ref_item_row.get("rate", 0) > item_dict["rate"]:
|
||||||
item_dict["rate"] = ref_item_row.get("rate", 0)
|
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
|
return valid_items
|
||||||
|
|
||||||
def get_already_returned_items(doc):
|
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']:
|
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("""
|
data = frappe.db.sql("""
|
||||||
select {0}
|
select {0}
|
||||||
@@ -180,6 +187,7 @@ def get_already_returned_items(doc):
|
|||||||
for d in data:
|
for d in data:
|
||||||
items.setdefault(d.item_code, frappe._dict({
|
items.setdefault(d.item_code, frappe._dict({
|
||||||
"qty": d.get("qty"),
|
"qty": d.get("qty"),
|
||||||
|
"stock_qty": d.get("stock_qty"),
|
||||||
"received_qty": d.get("received_qty"),
|
"received_qty": d.get("received_qty"),
|
||||||
"rejected_qty": d.get("rejected_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 import set_perpetual_inventory
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
|
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
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):
|
class TestPurchaseReceipt(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -203,6 +203,22 @@ class TestPurchaseReceipt(unittest.TestCase):
|
|||||||
"delivery_document_no": return_pr.name
|
"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):
|
def test_closed_purchase_receipt(self):
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_purchase_receipt_status
|
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):
|
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.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
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
item_code = frappe.db.get_value('Item', {'has_serial_no': 1})
|
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_qty": rejected_qty,
|
||||||
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
|
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
|
||||||
"rate": args.rate or 50,
|
"rate": args.rate or 50,
|
||||||
"conversion_factor": 1.0,
|
"conversion_factor": args.conversion_factor or 1.0,
|
||||||
"serial_no": args.serial_no,
|
"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:
|
if not args.do_not_save:
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ class TransactionBase(StatusUpdater):
|
|||||||
validate_uom_is_integer(self, uom_field, qty_fields)
|
validate_uom_is_integer(self, uom_field, qty_fields)
|
||||||
|
|
||||||
def validate_with_previous_doc(self, ref):
|
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():
|
for key, val in ref.items():
|
||||||
is_child = val.get("is_child_table")
|
is_child = val.get("is_child_table")
|
||||||
ref_doc = {}
|
ref_doc = {}
|
||||||
@@ -101,7 +103,7 @@ class TransactionBase(StatusUpdater):
|
|||||||
frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
|
frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
|
||||||
|
|
||||||
for field, condition in fields:
|
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)
|
self.validate_value(field, condition, prevdoc_values[field], doc)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user