Merge branch 'develop' into pr-dn-return
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 91c4dfb..014f05c 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1032,7 +1032,9 @@
updated_pr += update_billed_amount_based_on_po(d.po_detail, update_modified)
for pr in set(updated_pr):
- frappe.get_doc("Purchase Receipt", pr).update_billing_percentage(update_modified=update_modified)
+ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
+ pr_doc = frappe.get_doc("Purchase Receipt", pr)
+ update_billing_percentage(pr_doc, update_modified=update_modified)
def on_recurring(self, reference_doc, auto_repeat_doc):
self.due_date = None
diff --git a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
index 3ffb3ac..2aea3f6 100644
--- a/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
+++ b/erpnext/accounts/report/delivered_items_to_be_billed/delivered_items_to_be_billed.py
@@ -14,11 +14,19 @@
def get_column():
return [
- _("Delivery Note") + ":Link/Delivery Note:120", _("Status") + "::120", _("Date") + ":Date:100",
- _("Suplier") + ":Link/Customer:120", _("Customer Name") + "::120",
- _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
- _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Pending Amount") + ":Currency:100",
- _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
+ _("Delivery Note") + ":Link/Delivery Note:160",
+ _("Date") + ":Date:100",
+ _("Customer") + ":Link/Customer:120",
+ _("Customer Name") + "::120",
+ _("Item Code") + ":Link/Item:120",
+ _("Amount") + ":Currency:100",
+ _("Billed Amount") + ":Currency:100",
+ _("Returned Amount") + ":Currency:120",
+ _("Pending Amount") + ":Currency:100",
+ _("Item Name") + "::120",
+ _("Description") + "::120",
+ _("Project") + ":Link/Project:120",
+ _("Company") + ":Link/Company:120",
]
def get_args():
diff --git a/erpnext/accounts/report/non_billed_report.py b/erpnext/accounts/report/non_billed_report.py
index a9e25bc..2e18ce1 100644
--- a/erpnext/accounts/report/non_billed_report.py
+++ b/erpnext/accounts/report/non_billed_report.py
@@ -17,18 +17,26 @@
return frappe.db.sql("""
Select
- `{parent_tab}`.name, `{parent_tab}`.status, `{parent_tab}`.{date_field}, `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
- {project_field}, `{child_tab}`.item_code, `{child_tab}`.base_amount,
+ `{parent_tab}`.name, `{parent_tab}`.{date_field},
+ `{parent_tab}`.{party}, `{parent_tab}`.{party}_name,
+ `{child_tab}`.item_code,
+ `{child_tab}`.base_amount,
(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)),
- (`{child_tab}`.base_amount - (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1))),
- `{child_tab}`.item_name, `{child_tab}`.description, `{parent_tab}`.company
+ (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0)),
+ (`{child_tab}`.base_amount -
+ (`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1)) -
+ (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))),
+ `{child_tab}`.item_name, `{child_tab}`.description,
+ {project_field}, `{parent_tab}`.company
from
`{parent_tab}`, `{child_tab}`
where
`{parent_tab}`.name = `{child_tab}`.parent and `{parent_tab}`.docstatus = 1
and `{parent_tab}`.status not in ('Closed', 'Completed')
- and `{child_tab}`.amount > 0 and round(`{child_tab}`.billed_amt *
- ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) < `{child_tab}`.base_amount
+ and `{child_tab}`.amount > 0
+ and (`{child_tab}`.base_amount -
+ round(`{child_tab}`.billed_amt * ifnull(`{parent_tab}`.conversion_rate, 1), {precision}) -
+ (`{child_tab}`.base_rate * ifnull(`{child_tab}`.returned_qty, 0))) > 0
order by
`{parent_tab}`.{order} {order_by}
""".format(parent_tab = 'tab' + doctype, child_tab = 'tab' + child_tab, precision= precision, party = party,
diff --git a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
index 5e8d773..c7d4384 100644
--- a/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
+++ b/erpnext/accounts/report/received_items_to_be_billed/received_items_to_be_billed.py
@@ -14,11 +14,19 @@
def get_column():
return [
- _("Purchase Receipt") + ":Link/Purchase Receipt:120", _("Status") + "::120", _("Date") + ":Date:100",
- _("Supplier") + ":Link/Supplier:120", _("Supplier Name") + "::120",
- _("Project") + ":Link/Project:120", _("Item Code") + ":Link/Item:120",
- _("Amount") + ":Currency:100", _("Billed Amount") + ":Currency:100", _("Amount to Bill") + ":Currency:100",
- _("Item Name") + "::120", _("Description") + "::120", _("Company") + ":Link/Company:120",
+ _("Purchase Receipt") + ":Link/Purchase Receipt:160",
+ _("Date") + ":Date:100",
+ _("Supplier") + ":Link/Supplier:120",
+ _("Supplier Name") + "::120",
+ _("Item Code") + ":Link/Item:120",
+ _("Amount") + ":Currency:100",
+ _("Billed Amount") + ":Currency:100",
+ _("Returned Amount") + ":Currency:120",
+ _("Pending Amount") + ":Currency:120",
+ _("Item Name") + "::120",
+ _("Description") + "::120",
+ _("Project") + ":Link/Project:120",
+ _("Company") + ":Link/Company:120",
]
def get_args():
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 9ee83e3..5fabf70 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -497,6 +497,10 @@
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
+ if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
+ # Set Received Qty in Stock UOM
+ d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
+
def validate_purchase_return(self):
for d in self.get("items"):
if self.is_return and flt(d.rejected_qty) != 0:
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index afc5f81..e11289d 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -203,10 +203,46 @@
return items
+def get_returned_qty_map_for_row(row_name, doctype):
+ child_doctype = doctype + " Item"
+ reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail"
+ reference_field = "child." + reference_field
+ columns = ""
+
+ if doctype == "Purchase Receipt":
+ columns += ", sum(abs(child.rejected_qty)) as rejected_qty, \
+ sum(abs(child.received_qty)) as received_qty, \
+ sum(abs(child.received_stock_qty)) as received_stock_qty"
+
+ data = frappe.db.sql("""
+ select
+ sum(abs(child.qty)) as qty,
+ sum(abs(child.stock_qty)) as stock_qty,
+ %(columns)s
+ from
+ `tab{0}` child, `tab{1}` parent
+ where
+ child.parent = parent.name
+ and parent.docstatus = 1
+ and parent.is_return = 1
+ and {2} = %(row_name)s
+ """.format(child_doctype, doctype, reference_field),
+ {
+ "row_name": row_name,
+ "columns": columns,
+ "child_doctype": child_doctype,
+ "doctype": doctype,
+ "reference_field": reference_field
+ },
+ as_dict=1)
+
+ return data[0]
+
def make_return_doc(doctype, source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
company = frappe.db.get_value("Delivery Note", source_name, "company")
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
+
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.is_return = 1
@@ -261,20 +297,25 @@
doc.run_method("calculate_taxes_and_totals")
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":
- 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.stock_qty = -1 * source_doc.stock_qty
+ returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+ target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
+ target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+ target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
+
target_doc.purchase_order = source_doc.purchase_order
target_doc.purchase_order_item = source_doc.purchase_order_item
target_doc.rejected_warehouse = source_doc.rejected_warehouse
target_doc.purchase_receipt_item = source_doc.name
elif doctype == "Purchase Invoice":
- target_doc.received_qty = -1* source_doc.received_qty
- target_doc.rejected_qty = -1* source_doc.rejected_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.stock_qty = -1 * source_doc.stock_qty
target_doc.purchase_order = source_doc.purchase_order
@@ -285,6 +326,10 @@
target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note":
+ returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+ target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+ target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+
target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice
target_doc.so_detail = source_doc.so_detail
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 9feac78..2555edf 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -58,6 +58,7 @@
"Delivery Note": [
["Draft", None],
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
+ ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
@@ -65,6 +66,7 @@
"Purchase Receipt": [
["Draft", None],
["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"],
+ ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
@@ -232,7 +234,7 @@
self._update_children(args, update_modified)
- if "percent_join_field" in args:
+ if "percent_join_field" in args or "percent_join_field_parent" in args:
self._update_percent_field_in_targets(args, update_modified)
def _update_children(self, args, update_modified):
@@ -272,13 +274,19 @@
def _update_percent_field_in_targets(self, args, update_modified=True):
"""Update percent field in parent transaction"""
- distinct_transactions = set([d.get(args['percent_join_field'])
- for d in self.get_all_children(args['source_dt'])])
+ if args.get('percent_join_field_parent'):
+ # if reference to target doc where % is to be updated, is
+ # in source doc's parent form, consider percent_join_field_parent
+ args['name'] = self.get(args['percent_join_field_parent'])
+ self._update_percent_field(args, update_modified)
+ else:
+ distinct_transactions = set([d.get(args['percent_join_field'])
+ for d in self.get_all_children(args['source_dt'])])
- for name in distinct_transactions:
- if name:
- args['name'] = name
- self._update_percent_field(args, update_modified)
+ for name in distinct_transactions:
+ if name:
+ args['name'] = name
+ self._update_percent_field(args, update_modified)
def _update_percent_field(self, args, update_modified=True):
"""Update percent field in parent transaction"""
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index f743d70..4436ab0 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -229,8 +229,8 @@
def check_expense_account(self, item):
if not item.get("expense_account"):
- frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
- Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
+ frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense Account in the Items table")
+ .format(item.idx, frappe.bold(item.item_code)),
title=_("Expense Account Missing"))
else:
@@ -338,11 +338,15 @@
validate_warehouse_company(w, self.company)
def update_billing_percentage(self, update_modified=True):
+ target_ref_field = "amount"
+ if self.doctype == "Delivery Note":
+ target_ref_field = "amount - (returned_qty * rate)"
+
self._update_percent_field({
"target_dt": self.doctype + " Item",
"target_parent_dt": self.doctype,
"target_parent_field": "per_billed",
- "target_ref_field": "amount",
+ "target_ref_field": target_ref_field,
"target_field": "billed_amt",
"name": self.name,
}, update_modified)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 5ef5371..d50d5df 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -733,3 +733,4 @@
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
+erpnext.patches.v13_0.update_returned_qty_in_pr_dn
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
new file mode 100644
index 0000000..7f42cd9
--- /dev/null
+++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
@@ -0,0 +1,27 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
+ frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
+ frappe.reload_doc('stock', 'doctype', 'delivery_note')
+ frappe.reload_doc('stock', 'doctype', 'delivery_note_item')
+
+ def update_from_return_docs(doctype):
+ for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}):
+ # Update original receipt/delivery document from return
+ return_doc = frappe.get_cached_doc(doctype, return_doc.name)
+ return_doc.update_prevdoc_status()
+ return_against = frappe.get_doc(doctype, return_doc.return_against)
+ return_against.update_billing_status()
+
+ # Set received qty in stock uom in PR, as returned qty is checked against it
+ frappe.db.sql(""" update `tabPurchase Receipt Item`
+ set received_stock_qty = received_qty * conversion_factor
+ where docstatus = 1 """)
+
+ for doctype in ('Purchase Receipt', 'Delivery Note'):
+ update_from_return_docs(doctype)
\ No newline at end of file
diff --git a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
index 6e92ffb..910814f 100644
--- a/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
+++ b/erpnext/patches/v7_0/po_status_issue_for_pr_return.py
@@ -7,19 +7,23 @@
def execute():
parent_list = []
count = 0
- for data in frappe.db.sql("""
- select
+
+ frappe.reload_doc('stock', 'doctype', 'purchase_receipt')
+ frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
+
+ for data in frappe.db.sql("""
+ select
`tabPurchase Receipt Item`.purchase_order, `tabPurchase Receipt Item`.name,
`tabPurchase Receipt Item`.item_code, `tabPurchase Receipt Item`.idx,
`tabPurchase Receipt Item`.parent
- from
+ from
`tabPurchase Receipt Item`, `tabPurchase Receipt`
where
`tabPurchase Receipt Item`.parent = `tabPurchase Receipt`.name and
`tabPurchase Receipt Item`.purchase_order_item is null and
`tabPurchase Receipt Item`.purchase_order is not null and
`tabPurchase Receipt`.is_return = 1""", as_dict=1):
- name = frappe.db.get_value('Purchase Order Item',
+ name = frappe.db.get_value('Purchase Order Item',
{'item_code': data.item_code, 'parent': data.purchase_order, 'idx': data.idx}, 'name')
if name:
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index cb76c87..11f70f7 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -189,6 +189,7 @@
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
+ item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty);
}
this._super(doc, cdt, cdn);
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index ea385c8..32fe760 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -132,6 +132,7 @@
"per_installed",
"installation_status",
"column_break_89",
+ "per_returned",
"excise_page",
"instructions",
"subscription_section",
@@ -1097,7 +1098,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
+ "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
@@ -1249,13 +1250,23 @@
"fieldtype": "Link",
"label": "Inter Company Reference",
"options": "Purchase Receipt"
+ },
+ {
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "per_returned",
+ "fieldtype": "Percent",
+ "in_list_view": 1,
+ "label": "% Returned",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
- "modified": "2020-08-03 23:18:47.739997",
+ "modified": "2020-09-08 11:22:09.056684",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index d04cf78..895295b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -55,7 +55,7 @@
'no_allowance': 1
}]
if cint(self.is_return):
- self.status_updater.append({
+ self.status_updater.extend([{
'source_dt': 'Delivery Note Item',
'target_dt': 'Sales Order Item',
'join_field': 'so_detail',
@@ -69,7 +69,19 @@
where name=`tabDelivery Note Item`.parent and is_return=1)""",
'second_source_extra_cond': """ and exists (select name from `tabSales Invoice`
where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)"""
- })
+ },
+ {
+ 'source_dt': 'Delivery Note Item',
+ 'target_dt': 'Delivery Note Item',
+ 'join_field': 'dn_detail',
+ 'target_field': 'returned_qty',
+ 'target_parent_dt': 'Delivery Note',
+ 'target_parent_field': 'per_returned',
+ 'target_ref_field': 'stock_qty',
+ 'source_field': '-1 * stock_qty',
+ 'percent_join_field_parent': 'return_against'
+ }
+ ])
def before_print(self):
def toggle_print_hide(meta, fieldname):
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
index 0ae7c37..4a6500c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
@@ -6,9 +6,11 @@
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
+ } else if (flt(doc.per_returned, 2) === 100) {
+ return [__("Return Issued"), "grey", "per_returned,=,100"];
} else if (flt(doc.per_billed, 2) < 100) {
return [__("To Bill"), "orange", "per_billed,<,100"];
- } else if (flt(doc.per_billed, 2) == 100) {
+ } else if (flt(doc.per_billed, 2) === 100) {
return [__("Completed"), "green", "per_billed,=,100"];
}
},
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 0168613..fa07a25 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -206,7 +206,7 @@
for field, value in field_values.items():
self.assertEqual(cstr(serial_no.get(field)), value)
- def test_sales_return_for_non_bundled_items(self):
+ def test_sales_return_for_non_bundled_items_partial(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
@@ -225,7 +225,10 @@
# return entry
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500,
- company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1", do_not_submit=1)
+ dn1.items[0].dn_detail = dn.items[0].name
+ dn1.submit()
actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1")
@@ -243,6 +246,70 @@
self.assertEqual(gle_warehouse_amount, stock_value_difference)
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Delivery Note", dn1.name)
+ returned.update_prevdoc_status()
+ dn.load_from_db()
+
+ # Check if Original DN updated
+ self.assertEqual(dn.items[0].returned_qty, 2)
+ self.assertEqual(dn.per_returned, 40)
+
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+ return_dn_2 = make_return_doc("Delivery Note", dn.name)
+
+ # Check if unreturned amount is mapped in 2nd return
+ self.assertEqual(return_dn_2.items[0].qty, -3)
+
+ si = make_sales_invoice(dn.name)
+ si.submit()
+
+ self.assertEqual(si.items[0].qty, 3)
+
+ dn.load_from_db()
+ # DN should be completed on billing all unreturned amount
+ self.assertEqual(dn.items[0].billed_amt, 1500)
+ self.assertEqual(dn.per_billed, 100)
+ self.assertEqual(dn.status, 'Completed')
+
+ si.load_from_db()
+ si.cancel()
+
+ dn.load_from_db()
+ self.assertEqual(dn.per_billed, 0)
+
+ dn1.cancel()
+ dn.cancel()
+
+ def test_sales_return_for_non_bundled_items_full(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+
+ make_item("Box", {'is_stock_item': 1})
+
+ make_stock_entry(item_code="Box", target="Stores - TCP1", qty=10, basic_rate=100)
+
+ dn = create_delivery_note(item_code="Box", qty=5, rate=500, warehouse="Stores - TCP1", company=company,
+ expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+
+ #return entry
+ dn1 = create_delivery_note(item_code="Box", is_return=1, return_against=dn.name, qty=-5, rate=500,
+ company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1", do_not_submit=1)
+ dn1.items[0].dn_detail = dn.items[0].name
+ dn1.submit()
+
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Delivery Note", dn1.name)
+ returned.update_prevdoc_status()
+ dn.load_from_db()
+
+ # Check if Original DN updated
+ self.assertEqual(dn.items[0].returned_qty, 5)
+ self.assertEqual(dn.per_returned, 100)
+ self.assertEqual(dn.status, 'Return Issued')
+
def test_return_single_item_from_bundled_items(self):
company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 3d57f47..7b47187 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"autoname": "hash",
"creation": "2013-04-22 13:15:44",
"doctype": "DocType",
@@ -24,7 +25,10 @@
"col_break2",
"uom",
"conversion_factor",
+ "stock_qty_sec_break",
"stock_qty",
+ "stock_qty_col_break",
+ "returned_qty",
"section_break_17",
"price_list_rate",
"base_price_list_rate",
@@ -211,7 +215,7 @@
{
"fieldname": "stock_qty",
"fieldtype": "Float",
- "label": "Qty as per Stock UOM",
+ "label": "Qty in Stock UOM",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
@@ -715,12 +719,29 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "stock_qty_sec_break",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "stock_qty_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "returned_qty",
+ "fieldname": "returned_qty",
+ "fieldtype": "Float",
+ "label": "Returned Qty in Stock UOM",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-07-20 12:25:06.177894",
+ "modified": "2020-07-31 20:12:43.054342",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index ce54fc8..bbfaeab 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -110,6 +110,7 @@
"range",
"column_break4",
"per_billed",
+ "per_returned",
"is_internal_supplier",
"inter_company_reference",
"subscription_detail",
@@ -894,7 +895,7 @@
"no_copy": 1,
"oldfieldname": "status",
"oldfieldtype": "Select",
- "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed",
+ "options": "\nDraft\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
"print_hide": 1,
"print_width": "150px",
"read_only": 1,
@@ -1103,13 +1104,23 @@
"fieldtype": "Small Text",
"label": "Billing Address",
"read_only": 1
+ },
+ {
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "per_returned",
+ "fieldtype": "Percent",
+ "in_list_view": 1,
+ "label": "% Returned",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 261,
"is_submittable": 1,
"links": [],
- "modified": "2020-08-03 23:20:26.381024",
+ "modified": "2020-09-08 11:21:25.465966",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index faa9dd9..1852985 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -55,20 +55,33 @@
'percent_join_field': 'material_request'
}]
if cint(self.is_return):
- self.status_updater.append({
- 'source_dt': 'Purchase Receipt Item',
- 'target_dt': 'Purchase Order Item',
- 'join_field': 'purchase_order_item',
- 'target_field': 'returned_qty',
- 'source_field': '-1 * qty',
- 'second_source_dt': 'Purchase Invoice Item',
- 'second_source_field': '-1 * qty',
- 'second_join_field': 'po_detail',
- 'extra_cond': """ and exists (select name from `tabPurchase Receipt`
- where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
- 'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
- where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
- })
+ self.status_updater.extend([
+ {
+ 'source_dt': 'Purchase Receipt Item',
+ 'target_dt': 'Purchase Order Item',
+ 'join_field': 'purchase_order_item',
+ 'target_field': 'returned_qty',
+ 'source_field': '-1 * qty',
+ 'second_source_dt': 'Purchase Invoice Item',
+ 'second_source_field': '-1 * qty',
+ 'second_join_field': 'po_detail',
+ 'extra_cond': """ and exists (select name from `tabPurchase Receipt`
+ where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
+ 'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
+ where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
+ },
+ {
+ 'source_dt': 'Purchase Receipt Item',
+ 'target_dt': 'Purchase Receipt Item',
+ 'join_field': 'purchase_receipt_item',
+ 'target_field': 'returned_qty',
+ 'target_parent_dt': 'Purchase Receipt',
+ 'target_parent_field': 'per_returned',
+ 'target_ref_field': 'received_stock_qty',
+ 'source_field': '-1 * received_stock_qty',
+ 'percent_join_field_parent': 'return_against'
+ }
+ ])
def validate(self):
self.validate_posting_time()
@@ -478,7 +491,7 @@
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate))
def update_status(self, status):
- self.set_status(update=True, status = status)
+ self.set_status(update=True, status=status)
self.notify_update()
clear_doctype_notifications(self)
@@ -490,7 +503,7 @@
for pr in set(updated_pr):
pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
- pr_doc.update_billing_percentage(update_modified=update_modified)
+ update_billing_percentage(pr_doc, update_modified=update_modified)
self.load_from_db()
@@ -500,7 +513,7 @@
where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""", po_detail)
billed_against_po = billed_against_po and billed_against_po[0][0] or 0
- # Get all Delivery Note Item rows against the Sales Order Item row
+ # Get all Purchase Receipt Item rows against the Purchase Order Item row
pr_details = frappe.db.sql("""select pr_item.name, pr_item.amount, pr_item.parent
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name=pr_item.parent and pr_item.purchase_order_item=%s
@@ -530,6 +543,41 @@
return updated_pr
+def update_billing_percentage(pr_doc, update_modified=True):
+ # Reload as billed amount was set in db directly
+ pr_doc.load_from_db()
+
+ # Update Billing % based on pending accepted qty
+ total_amount, total_billed_amount = 0, 0
+ for item in pr_doc.items:
+ returned_qty = frappe.db.sql("""
+ select sum(abs(child.qty)) as qty
+ from
+ `tabPurchase Receipt Item` child,
+ `tabPurchase Receipt` parent
+ where
+ child.parent = parent.name
+ and parent.docstatus = 1
+ and parent.is_return = 1
+ and child.purchase_receipt_item = %(row_name)s
+ """, {"row_name": item.name})
+ returned_qty = returned_qty[0][0] if returned_qty else 0
+
+ returned_amount = flt(returned_qty) * flt(item.rate)
+ pending_amount = flt(item.amount) - returned_amount
+ total_billable_amount = pending_amount if item.billed_amt <= pending_amount else item.billed_amt
+
+ total_amount += total_billable_amount
+ total_billed_amount += flt(item.billed_amt)
+
+ percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
+ pr_doc.db_set("per_billed", percent_billed)
+ pr_doc.load_from_db()
+
+ if update_modified:
+ pr_doc.set_status(update=True)
+ pr_doc.notify_update()
+
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
@@ -549,6 +597,7 @@
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
+ target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
returned_qty_map[source_doc.name] = returned_qty
def get_pending_qty(item_row):
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
index e81f323..c9501a4 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
@@ -6,9 +6,11 @@
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if (doc.status === "Closed") {
return [__("Closed"), "green", "status,=,Closed"];
+ } else if (flt(doc.per_returned, 2) === 100) {
+ return [__("Return Issued"), "grey", "per_returned,=,100"];
} else if (flt(doc.grand_total) !== 0 && flt(doc.per_billed, 2) < 100) {
return [__("To Bill"), "orange", "per_billed,<,100"];
- } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) == 100) {
+ } else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
return [__("Completed"), "green", "per_billed,=,100"];
}
}
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index a1e01bf..722b2c9 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -100,7 +100,10 @@
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
def test_purchase_receipt_gl_entry(self):
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True, get_taxes_and_charges = True)
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+ warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
+ get_multiple_items = True, get_taxes_and_charges = True)
+
self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
@@ -244,11 +247,15 @@
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"),
pr.get("items")[0].rejected_warehouse)
- def test_purchase_return(self):
+ def test_purchase_return_partial(self):
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+ warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
-
- return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-2)
+ return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
+ warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
+ is_return=1, return_against=pr.name, qty=-2, do_not_submit=1)
+ return_pr.items[0].purchase_receipt_item = pr.items[0].name
+ return_pr.submit()
# check sle
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
@@ -272,6 +279,60 @@
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Purchase Receipt", return_pr.name)
+ returned.update_prevdoc_status()
+ pr.load_from_db()
+
+ # Check if Original PR updated
+ self.assertEqual(pr.items[0].returned_qty, 2)
+ self.assertEqual(pr.per_returned, 40)
+
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+ return_pr_2 = make_return_doc("Purchase Receipt", pr.name)
+
+ # Check if unreturned amount is mapped in 2nd return
+ self.assertEqual(return_pr_2.items[0].qty, -3)
+
+ # Make PI against unreturned amount
+ pi = make_purchase_invoice(pr.name)
+ pi.submit()
+
+ self.assertEqual(pi.items[0].qty, 3)
+
+ pr.load_from_db()
+ # PR should be completed on billing all unreturned amount
+ self.assertEqual(pr.items[0].billed_amt, 150)
+ self.assertEqual(pr.per_billed, 100)
+ self.assertEqual(pr.status, 'Completed')
+
+ pi.load_from_db()
+ pi.cancel()
+
+ pr.load_from_db()
+ self.assertEqual(pr.per_billed, 0)
+
+ return_pr.cancel()
+ pr.cancel()
+
+ def test_purchase_return_full(self):
+ pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
+ supplier_warehouse = "Work in Progress - TCP1")
+
+ return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
+ supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1)
+ return_pr.items[0].purchase_receipt_item = pr.items[0].name
+ return_pr.submit()
+
+ # hack because new_doc isn't considering is_return portion of status_updater
+ returned = frappe.get_doc("Purchase Receipt", return_pr.name)
+ returned.update_prevdoc_status()
+ pr.load_from_db()
+
+ # Check if Original PR updated
+ self.assertEqual(pr.items[0].returned_qty, 5)
+ self.assertEqual(pr.per_returned, 100)
+ self.assertEqual(pr.status, 'Return Issued')
def test_purchase_return_for_rejected_qty(self):
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
@@ -379,6 +440,7 @@
self.assertEqual(pr1.per_billed, 100)
self.assertEqual(pr1.status, "Completed")
+ pr2.load_from_db()
self.assertEqual(pr2.get("items")[0].billed_amt, 2000)
self.assertEqual(pr2.per_billed, 80)
self.assertEqual(pr2.status, "To Bill")
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index c1e1f90..84c64aa 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -28,9 +28,13 @@
"uom",
"stock_uom",
"conversion_factor",
- "stock_qty",
"retain_sample",
"sample_quantity",
+ "tracking_section",
+ "received_stock_qty",
+ "stock_qty",
+ "col_break_tracking_section",
+ "returned_qty",
"rate_and_amount",
"price_list_rate",
"discount_percentage",
@@ -526,7 +530,7 @@
{
"fieldname": "stock_qty",
"fieldtype": "Float",
- "label": "Accepted Qty as per Stock UOM",
+ "label": "Accepted Qty in Stock UOM",
"oldfieldname": "stock_qty",
"oldfieldtype": "Currency",
"print_hide": 1,
@@ -834,12 +838,35 @@
"collapsible": 1,
"fieldname": "image_column",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "tracking_section",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "col_break_tracking_section",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "returned_qty",
+ "fieldname": "returned_qty",
+ "fieldtype": "Float",
+ "label": "Returned Qty in Stock UOM",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "received_stock_qty",
+ "fieldtype": "Float",
+ "label": "Received Qty in Stock UOM",
+ "print_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2020-04-28 19:01:21.154963",
+ "modified": "2020-11-02 10:00:38.204294",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",