[Enhancement] Purchase return for rejected qty
This commit is contained in:
parent
b2b238323b
commit
560ba391f9
@ -138,20 +138,15 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
qty: function(doc, cdt, cdn) {
|
qty: function(doc, cdt, cdn) {
|
||||||
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) {
|
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) {
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
|
||||||
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
|
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
|
||||||
if(!(item.received_qty || item.rejected_qty) && item.qty) {
|
if(!(item.received_qty || item.rejected_qty) && item.qty) {
|
||||||
item.received_qty = item.qty;
|
item.received_qty = item.qty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(item.qty > item.received_qty) {
|
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
|
||||||
msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "qty", item.name)),
|
item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
|
||||||
__(frappe.meta.get_label(item.doctype, "received_qty", item.name))]))
|
|
||||||
item.qty = item.rejected_qty = 0.0;
|
|
||||||
} else {
|
|
||||||
item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._super(doc, cdt, cdn);
|
this._super(doc, cdt, cdn);
|
||||||
@ -160,26 +155,18 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
received_qty: function(doc, cdt, cdn) {
|
received_qty: function(doc, cdt, cdn) {
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
this.calculate_accepted_qty(doc, cdt, cdn)
|
||||||
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
|
|
||||||
|
|
||||||
item.qty = (item.qty < item.received_qty) ? item.qty : item.received_qty;
|
|
||||||
this.qty(doc, cdt, cdn);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
rejected_qty: function(doc, cdt, cdn) {
|
rejected_qty: function(doc, cdt, cdn) {
|
||||||
|
this.calculate_accepted_qty(doc, cdt, cdn)
|
||||||
|
},
|
||||||
|
|
||||||
|
calculate_accepted_qty: function(doc, cdt, cdn){
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]);
|
frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]);
|
||||||
|
|
||||||
if(item.rejected_qty > item.received_qty) {
|
item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item));
|
||||||
msgprint(__("Error: {0} > {1}", [__(frappe.meta.get_label(item.doctype, "rejected_qty", item.name)),
|
|
||||||
__(frappe.meta.get_label(item.doctype, "received_qty", item.name))]));
|
|
||||||
item.qty = item.rejected_qty = 0.0;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.qty(doc, cdt, cdn);
|
this.qty(doc, cdt, cdn);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class BuyingController(StockController):
|
|||||||
self.validate_purchase_receipt_if_update_stock()
|
self.validate_purchase_receipt_if_update_stock()
|
||||||
|
|
||||||
if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and self.update_stock):
|
if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and self.update_stock):
|
||||||
self.validate_purchase_return()
|
# self.validate_purchase_return()
|
||||||
self.validate_rejected_warehouse()
|
self.validate_rejected_warehouse()
|
||||||
self.validate_accepted_rejected_qty()
|
self.validate_accepted_rejected_qty()
|
||||||
|
|
||||||
@ -346,7 +346,7 @@ class BuyingController(StockController):
|
|||||||
})
|
})
|
||||||
sl_entries.append(sle)
|
sl_entries.append(sle)
|
||||||
|
|
||||||
if flt(d.rejected_qty) > 0:
|
if flt(d.rejected_qty) != 0:
|
||||||
sl_entries.append(self.get_sl_entries(d, {
|
sl_entries.append(self.get_sl_entries(d, {
|
||||||
"warehouse": d.rejected_warehouse,
|
"warehouse": d.rejected_warehouse,
|
||||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||||
|
@ -53,13 +53,15 @@ def validate_returned_items(doc):
|
|||||||
|
|
||||||
valid_items = frappe._dict()
|
valid_items = frappe._dict()
|
||||||
|
|
||||||
select_fields = "item_code, qty" if doc.doctype=="Purchase Invoice" \
|
select_fields = "item_code, qty, parenttype" if doc.doctype=="Purchase Invoice" \
|
||||||
else "item_code, qty, serial_no, batch_no"
|
else "item_code, qty, serial_no, batch_no, parenttype"
|
||||||
|
|
||||||
|
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||||
|
select_fields += ",rejected_qty, received_qty"
|
||||||
|
|
||||||
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s"""
|
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s"""
|
||||||
.format(select_fields, doc.doctype), doc.return_against, as_dict=1):
|
.format(select_fields, doc.doctype), doc.return_against, as_dict=1):
|
||||||
valid_items = get_ref_item_dict(valid_items, d)
|
valid_items = get_ref_item_dict(valid_items, d)
|
||||||
|
|
||||||
|
|
||||||
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
||||||
for d in frappe.db.sql("""select item_code, qty, serial_no, batch_no from `tabPacked Item`
|
for d in frappe.db.sql("""select item_code, qty, serial_no, batch_no from `tabPacked Item`
|
||||||
@ -73,21 +75,15 @@ def validate_returned_items(doc):
|
|||||||
|
|
||||||
items_returned = False
|
items_returned = False
|
||||||
for d in doc.get("items"):
|
for d in doc.get("items"):
|
||||||
if flt(d.qty) < 0:
|
if flt(d.qty) < 0 or d.get('received_qty') < 0:
|
||||||
if d.item_code not in valid_items:
|
if d.item_code not in valid_items:
|
||||||
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
|
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
|
||||||
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
|
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
|
||||||
else:
|
else:
|
||||||
ref = valid_items.get(d.item_code, frappe._dict())
|
ref = valid_items.get(d.item_code, frappe._dict())
|
||||||
already_returned_qty = flt(already_returned_items.get(d.item_code))
|
validate_quantity(doc, d, ref, valid_items, already_returned_items)
|
||||||
max_return_qty = flt(ref.qty) - already_returned_qty
|
|
||||||
|
|
||||||
if already_returned_qty >= ref.qty:
|
if ref.batch_no and d.batch_no not in ref.batch_no:
|
||||||
frappe.throw(_("Item {0} has already been returned").format(d.item_code), StockOverReturnError)
|
|
||||||
elif abs(d.qty) > max_return_qty:
|
|
||||||
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
|
|
||||||
.format(d.idx, ref.qty, d.item_code), StockOverReturnError)
|
|
||||||
elif ref.batch_no and d.batch_no not in ref.batch_no:
|
|
||||||
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
|
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
|
||||||
.format(d.idx, doc.doctype, doc.return_against))
|
.format(d.idx, doc.doctype, doc.return_against))
|
||||||
elif ref.serial_no:
|
elif ref.serial_no:
|
||||||
@ -107,18 +103,45 @@ def validate_returned_items(doc):
|
|||||||
|
|
||||||
if not items_returned:
|
if not items_returned:
|
||||||
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):
|
||||||
|
fields = ['qty']
|
||||||
|
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||||
|
fields.extend(['received_qty', 'rejected_qty'])
|
||||||
|
|
||||||
|
already_returned_data = already_returned_items.get(args.item_code) or {}
|
||||||
|
|
||||||
|
for column in fields:
|
||||||
|
return_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
|
||||||
|
referenced_qty = ref.get(column)
|
||||||
|
max_return_qty = flt(referenced_qty) - return_qty
|
||||||
|
label = column.replace('_', ' ').title()
|
||||||
|
|
||||||
|
if flt(args.get(column)) > 0:
|
||||||
|
frappe.throw(_("{0} must be negative in return document").format(label))
|
||||||
|
elif return_qty >= referenced_qty and flt(args.get(column)) != 0:
|
||||||
|
frappe.throw(_("Item {0} has already been returned").format(args.item_code), StockOverReturnError)
|
||||||
|
elif abs(args.get(column)) > max_return_qty:
|
||||||
|
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
|
||||||
|
.format(args.idx, referenced_qty, args.item_code), StockOverReturnError)
|
||||||
|
|
||||||
def get_ref_item_dict(valid_items, ref_item_row):
|
def get_ref_item_dict(valid_items, ref_item_row):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
valid_items.setdefault(ref_item_row.item_code, frappe._dict({
|
valid_items.setdefault(ref_item_row.item_code, frappe._dict({
|
||||||
"qty": 0,
|
"qty": 0,
|
||||||
|
"rejected_qty": 0,
|
||||||
|
"received_qty": 0,
|
||||||
"serial_no": [],
|
"serial_no": [],
|
||||||
"batch_no": []
|
"batch_no": []
|
||||||
}))
|
}))
|
||||||
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
|
||||||
|
|
||||||
|
if ref_item_row.parenttype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||||
|
item_dict["received_qty"] += ref_item_row.received_qty
|
||||||
|
item_dict["rejected_qty"] += ref_item_row.rejected_qty
|
||||||
|
|
||||||
if ref_item_row.get("serial_no"):
|
if ref_item_row.get("serial_no"):
|
||||||
item_dict["serial_no"] += get_serial_nos(ref_item_row.serial_no)
|
item_dict["serial_no"] += get_serial_nos(ref_item_row.serial_no)
|
||||||
|
|
||||||
@ -128,16 +151,30 @@ 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):
|
||||||
return frappe._dict(frappe.db.sql("""
|
column = 'child.item_code, sum(abs(child.qty)) as qty'
|
||||||
select
|
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||||
child.item_code, sum(abs(child.qty)) as qty
|
column += ', sum(abs(child.rejected_qty)) as rejected_qty, sum(abs(child.received_qty)) as received_qty'
|
||||||
|
|
||||||
|
data = frappe.db.sql("""
|
||||||
|
select {0}
|
||||||
from
|
from
|
||||||
`tab{0} Item` child, `tab{1}` par
|
`tab{1} Item` child, `tab{2}` par
|
||||||
where
|
where
|
||||||
child.parent = par.name and par.docstatus = 1
|
child.parent = par.name and par.docstatus = 1
|
||||||
and par.is_return = 1 and par.return_against = %s and child.qty < 0
|
and par.is_return = 1 and par.return_against = %s
|
||||||
group by item_code
|
group by item_code
|
||||||
""".format(doc.doctype, doc.doctype), doc.return_against))
|
""".format(column, doc.doctype, doc.doctype), doc.return_against, as_dict=1)
|
||||||
|
|
||||||
|
items = {}
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
items.setdefault(d.item_code, frappe._dict({
|
||||||
|
"qty": d.get("qty"),
|
||||||
|
"received_qty": d.get("received_qty"),
|
||||||
|
"rejected_qty": d.get("rejected_qty")
|
||||||
|
}))
|
||||||
|
|
||||||
|
return items
|
||||||
|
|
||||||
def make_return_doc(doctype, source_name, target_doc=None):
|
def make_return_doc(doctype, source_name, target_doc=None):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
@ -166,12 +203,18 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
def update_item(source_doc, target_doc, source_parent):
|
def update_item(source_doc, target_doc, source_parent):
|
||||||
target_doc.qty = -1* source_doc.qty
|
target_doc.qty = -1* source_doc.qty
|
||||||
if doctype == "Purchase Receipt":
|
if doctype == "Purchase Receipt":
|
||||||
target_doc.received_qty = -1* source_doc.qty
|
target_doc.received_qty = -1* source_doc.received_qty
|
||||||
|
target_doc.rejected_qty = -1* source_doc.rejected_qty
|
||||||
|
target_doc.qty = -1* source_doc.qty
|
||||||
target_doc.purchase_order = source_doc.purchase_order
|
target_doc.purchase_order = source_doc.purchase_order
|
||||||
|
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||||
elif doctype == "Purchase Invoice":
|
elif doctype == "Purchase Invoice":
|
||||||
target_doc.received_qty = -1* source_doc.qty
|
target_doc.received_qty = -1* source_doc.received_qty
|
||||||
|
target_doc.rejected_qty = -1* source_doc.rejected_qty
|
||||||
|
target_doc.qty = -1* source_doc.qty
|
||||||
target_doc.purchase_order = source_doc.purchase_order
|
target_doc.purchase_order = source_doc.purchase_order
|
||||||
target_doc.purchase_receipt = source_doc.purchase_receipt
|
target_doc.purchase_receipt = source_doc.purchase_receipt
|
||||||
|
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||||
target_doc.po_detail = source_doc.po_detail
|
target_doc.po_detail = source_doc.po_detail
|
||||||
target_doc.pr_detail = source_doc.pr_detail
|
target_doc.pr_detail = source_doc.pr_detail
|
||||||
elif doctype == "Delivery Note":
|
elif doctype == "Delivery Note":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user