Merge pull request #16745 from nabinhait/manage-returned-qty
fix: Consider returned qty while making invoice from DN / PR
This commit is contained in:
commit
e08cb3a49b
@ -201,7 +201,8 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
get_query: function() {
|
||||
var filters = {
|
||||
docstatus: 1,
|
||||
company: me.frm.doc.company
|
||||
company: me.frm.doc.company,
|
||||
is_return: 0
|
||||
};
|
||||
if(me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
|
||||
return {
|
||||
|
@ -391,7 +391,7 @@ def get_invoiced_qty_map(delivery_note):
|
||||
|
||||
return invoiced_qty_map
|
||||
|
||||
def get_returned_qty_map(sales_orders):
|
||||
def get_returned_qty_map_against_so(sales_orders):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = {}
|
||||
|
||||
@ -403,12 +403,25 @@ def get_returned_qty_map(sales_orders):
|
||||
|
||||
return returned_qty_map
|
||||
|
||||
def get_returned_qty_map_against_dn(delivery_note):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
|
||||
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
|
||||
where dn.name = dn_item.parent
|
||||
and dn.docstatus = 1
|
||||
and dn.is_return = 1
|
||||
and dn.return_against = %s
|
||||
group by dn_item.item_code
|
||||
""", delivery_note))
|
||||
|
||||
return returned_qty_map
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_invoice(source_name, target_doc=None):
|
||||
doc = frappe.get_doc('Delivery Note', source_name)
|
||||
sales_orders = [d.against_sales_order for d in doc.items]
|
||||
returned_qty_map = get_returned_qty_map(sales_orders)
|
||||
|
||||
returned_qty_map_against_so = get_returned_qty_map_against_so(sales_orders)
|
||||
returned_qty_map_against_dn = get_returned_qty_map_against_dn(source_name)
|
||||
invoiced_qty_map = get_invoiced_qty_map(source_name)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
@ -428,13 +441,26 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = (source_doc.qty -
|
||||
invoiced_qty_map.get(source_doc.name, 0) - returned_qty_map.get(source_doc.so_detail, 0))
|
||||
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
||||
if not source_doc.so_detail:
|
||||
returned_qty_map_against_dn[source_doc.item_code] = returned_qty
|
||||
|
||||
if source_doc.serial_no and source_parent.per_billed > 0:
|
||||
target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
|
||||
target_doc.qty, source_parent.name)
|
||||
|
||||
def get_pending_qty(item_row):
|
||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) - returned_qty_map_against_so.get(item_row.so_detail, 0)
|
||||
returned_qty = flt(returned_qty_map_against_dn.get(item_row.item_code, 0))
|
||||
if not item_row.so_detail:
|
||||
if returned_qty >= pending_qty:
|
||||
pending_qty = 0
|
||||
returned_qty -= pending_qty
|
||||
else:
|
||||
pending_qty -= returned_qty
|
||||
returned_qty = 0
|
||||
return pending_qty, returned_qty
|
||||
|
||||
doc = get_mapped_doc("Delivery Note", source_name, {
|
||||
"Delivery Note": {
|
||||
"doctype": "Sales Invoice",
|
||||
@ -453,7 +479,7 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
"cost_center": "cost_center"
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"filter": lambda d: abs(d.qty) - abs(invoiced_qty_map.get(d.name, 0))<=0
|
||||
"filter": lambda d: get_pending_qty(d)[0]<=0
|
||||
},
|
||||
"Sales Taxes and Charges": {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
|
@ -636,7 +636,7 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
||||
|
||||
set_perpetual_inventory(0, company)
|
||||
|
||||
|
||||
def test_make_sales_invoice_from_dn_for_returned_qty(self):
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
@ -655,6 +655,33 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
si = make_sales_invoice(dn.name)
|
||||
self.assertEquals(si.items[0].qty, 1)
|
||||
|
||||
def test_make_sales_invoice_from_dn_with_returned_qty_against_dn(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
|
||||
dn = create_delivery_note(qty=8, do_not_submit=True)
|
||||
dn.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"conversion_factor": 1.0,
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
})
|
||||
dn.submit()
|
||||
|
||||
si1 = make_sales_invoice(dn.name)
|
||||
si1.items[0].qty = 4
|
||||
si1.items.pop(1)
|
||||
si1.save()
|
||||
si1.submit()
|
||||
|
||||
create_delivery_note(is_return=1, return_against=dn.name, qty=-2)
|
||||
|
||||
si2 = make_sales_invoice(dn.name)
|
||||
self.assertEquals(si2.items[0].qty, 2)
|
||||
self.assertEquals(si2.items[1].qty, 1)
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
args = frappe._dict(args)
|
||||
|
@ -125,7 +125,7 @@ class PurchaseReceipt(BuyingController):
|
||||
else:
|
||||
self.status = "Completed"
|
||||
|
||||
|
||||
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating ordered qty, reserved_qty_for_subcontract in bin
|
||||
# depends upon updated ordered qty in PO
|
||||
@ -311,7 +311,7 @@ class PurchaseReceipt(BuyingController):
|
||||
"\n".join(warehouse_with_no_account))
|
||||
|
||||
return process_gl_map(gl_entries)
|
||||
|
||||
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
for d in self.get("items"):
|
||||
if d.is_fixed_asset:
|
||||
@ -405,6 +405,11 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
|
||||
@frappe.whitelist()
|
||||
def make_purchase_invoice(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
doc = frappe.get_doc('Purchase Receipt', source_name)
|
||||
purchase_orders = [d.purchase_order for d in doc.items]
|
||||
returned_qty_map_against_po = get_returned_qty_map_against_po(purchase_orders)
|
||||
returned_qty_map_against_pr = get_returned_qty_map_against_pr(source_name)
|
||||
|
||||
invoiced_qty_map = get_invoiced_qty_map(source_name)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
@ -417,7 +422,23 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0)
|
||||
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
||||
if not source_doc.purchase_order_item:
|
||||
returned_qty_map_against_pr[source_doc.item_code] = returned_qty
|
||||
|
||||
def get_pending_qty(item_row):
|
||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) \
|
||||
- returned_qty_map_against_po.get(item_row.purchase_order_item, 0)
|
||||
returned_qty = flt(returned_qty_map_against_pr.get(item_row.item_code, 0))
|
||||
if not item_row.purchase_order_item:
|
||||
if returned_qty >= pending_qty:
|
||||
pending_qty = 0
|
||||
returned_qty -= pending_qty
|
||||
else:
|
||||
pending_qty -= returned_qty
|
||||
returned_qty = 0
|
||||
return pending_qty, returned_qty
|
||||
|
||||
|
||||
doclist = get_mapped_doc("Purchase Receipt", source_name, {
|
||||
"Purchase Receipt": {
|
||||
@ -440,7 +461,7 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
"asset": "asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"filter": lambda d: abs(d.qty) - abs(invoiced_qty_map.get(d.name, 0))<=0
|
||||
"filter": lambda d: get_pending_qty(d)[0]<=0
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
@ -462,6 +483,31 @@ def get_invoiced_qty_map(purchase_receipt):
|
||||
|
||||
return invoiced_qty_map
|
||||
|
||||
def get_returned_qty_map_against_po(purchase_orders):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = {}
|
||||
|
||||
for name, returned_qty in frappe.get_all('Purchase Order Item', fields = ["name", "returned_qty"],
|
||||
filters = {'parent': ('in', purchase_orders), 'docstatus': 1}, as_list=1):
|
||||
if not returned_qty_map.get(name):
|
||||
returned_qty_map[name] = 0
|
||||
returned_qty_map[name] += returned_qty
|
||||
|
||||
return returned_qty_map
|
||||
|
||||
def get_returned_qty_map_against_pr(purchase_receipt):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
|
||||
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
|
||||
where pr.name = pr_item.parent
|
||||
and pr.docstatus = 1
|
||||
and pr.is_return = 1
|
||||
and pr.return_against = %s
|
||||
group by pr_item.item_code
|
||||
""", purchase_receipt))
|
||||
|
||||
return returned_qty_map
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
@ -404,6 +404,44 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
set_perpetual_inventory(0, pr.company)
|
||||
|
||||
def test_make_purchase_invoice_from_pr_for_returned_qty(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, create_pr_against_po
|
||||
|
||||
po = create_purchase_order()
|
||||
pr = create_pr_against_po(po.name)
|
||||
|
||||
pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
|
||||
pr1.items[0].purchase_order = po.name
|
||||
pr1.items[0].purchase_order_item = po.items[0].name
|
||||
pr1.submit()
|
||||
|
||||
pi = make_purchase_invoice(pr.name)
|
||||
self.assertEquals(pi.items[0].qty, 3)
|
||||
|
||||
def test_make_purchase_invoice_from_dn_with_returned_qty_against_dn(self):
|
||||
pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
|
||||
pr1.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 1,
|
||||
"received_qty": 1,
|
||||
"rate": 100,
|
||||
"conversion_factor": 1.0,
|
||||
})
|
||||
pr1.submit()
|
||||
|
||||
pi1 = make_purchase_invoice(pr1.name)
|
||||
pi1.items[0].qty = 4
|
||||
pi1.items.pop(1)
|
||||
pi1.save()
|
||||
pi1.submit()
|
||||
|
||||
make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2)
|
||||
|
||||
pi2 = make_purchase_invoice(pr1.name)
|
||||
self.assertEquals(pi2.items[0].qty, 2)
|
||||
self.assertEquals(pi2.items[1].qty, 1)
|
||||
|
||||
def get_gl_entries(voucher_type, voucher_no):
|
||||
return frappe.db.sql("""select account, debit, credit, cost_center
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
|
Loading…
x
Reference in New Issue
Block a user