Merge branch 'develop' of https://github.com/frappe/erpnext into pr-item-gl-fix
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 2735b1c..703e93c 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -257,9 +257,10 @@
   },
   {
    "default": "1",
+   "description": "If enabled, ledger entries will be posted for change amount in POS transactions",
    "fieldname": "post_change_gl_entries",
    "fieldtype": "Check",
-   "label": "Post Ledger Entries for Given Change"
+   "label": "Create Ledger Entries for Change Amount"
   }
  ],
  "icon": "icon-cog",
@@ -267,7 +268,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-05-25 12:34:05.858669",
+ "modified": "2021-06-17 20:26:03.721202",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index a714ac7..00ef7d5 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -175,9 +175,7 @@
    "hidden": 1,
    "label": "Title",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "naming_series",
@@ -189,9 +187,7 @@
    "options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
    "print_hide": 1,
    "reqd": 1,
-   "set_only_once": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "set_only_once": 1
   },
   {
    "fieldname": "supplier",
@@ -203,9 +199,7 @@
    "options": "Supplier",
    "print_hide": 1,
    "reqd": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "bold": 1,
@@ -217,9 +211,7 @@
    "label": "Supplier Name",
    "oldfieldname": "supplier_name",
    "oldfieldtype": "Data",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fetch_from": "supplier.tax_id",
@@ -227,27 +219,21 @@
    "fieldtype": "Read Only",
    "label": "Tax Id",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "due_date",
    "fieldtype": "Date",
    "label": "Due Date",
    "oldfieldname": "due_date",
-   "oldfieldtype": "Date",
-   "show_days": 1,
-   "show_seconds": 1
+   "oldfieldtype": "Date"
   },
   {
    "default": "0",
    "fieldname": "is_paid",
    "fieldtype": "Check",
    "label": "Is Paid",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
@@ -255,25 +241,19 @@
    "fieldtype": "Check",
    "label": "Is Return (Debit Note)",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
    "fieldname": "apply_tds",
    "fieldtype": "Check",
    "label": "Apply Tax Withholding Amount",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break1",
    "fieldtype": "Column Break",
    "oldfieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -283,17 +263,13 @@
    "label": "Company",
    "options": "Company",
    "print_hide": 1,
-   "remember_last_selected_value": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "remember_last_selected_value": 1
   },
   {
    "fieldname": "cost_center",
    "fieldtype": "Link",
    "label": "Cost Center",
-   "options": "Cost Center",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Cost Center"
   },
   {
    "default": "Today",
@@ -305,9 +281,7 @@
    "oldfieldtype": "Date",
    "print_hide": 1,
    "reqd": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "fieldname": "posting_time",
@@ -316,8 +290,6 @@
    "no_copy": 1,
    "print_hide": 1,
    "print_width": "100px",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "100px"
   },
   {
@@ -326,9 +298,7 @@
    "fieldname": "set_posting_time",
    "fieldtype": "Check",
    "label": "Edit Posting Date and Time",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "amended_from",
@@ -340,58 +310,44 @@
    "oldfieldtype": "Link",
    "options": "Purchase Invoice",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "eval:doc.on_hold",
    "fieldname": "sb_14",
    "fieldtype": "Section Break",
-   "label": "Hold Invoice",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Hold Invoice"
   },
   {
    "default": "0",
    "fieldname": "on_hold",
    "fieldtype": "Check",
-   "label": "Hold Invoice",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Hold Invoice"
   },
   {
    "depends_on": "eval:doc.on_hold",
    "description": "Once set, this invoice will be on hold till the set date",
    "fieldname": "release_date",
    "fieldtype": "Date",
-   "label": "Release Date",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Release Date"
   },
   {
    "fieldname": "cb_17",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "depends_on": "eval:doc.on_hold",
    "fieldname": "hold_comment",
    "fieldtype": "Small Text",
-   "label": "Reason For Putting On Hold",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Reason For Putting On Hold"
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "bill_no",
    "fieldname": "supplier_invoice_details",
    "fieldtype": "Section Break",
-   "label": "Supplier Invoice Details",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Supplier Invoice Details"
   },
   {
    "fieldname": "bill_no",
@@ -399,15 +355,11 @@
    "label": "Supplier Invoice No",
    "oldfieldname": "bill_no",
    "oldfieldtype": "Data",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_15",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "bill_date",
@@ -416,17 +368,13 @@
    "no_copy": 1,
    "oldfieldname": "bill_date",
    "oldfieldtype": "Date",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "depends_on": "return_against",
    "fieldname": "returns",
    "fieldtype": "Section Break",
-   "label": "Returns",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Returns"
   },
   {
    "depends_on": "return_against",
@@ -436,34 +384,26 @@
    "no_copy": 1,
    "options": "Purchase Invoice",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "fieldname": "section_addresses",
    "fieldtype": "Section Break",
-   "label": "Address and Contact",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Address and Contact"
   },
   {
    "fieldname": "supplier_address",
    "fieldtype": "Link",
    "label": "Select Supplier Address",
    "options": "Address",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "address_display",
    "fieldtype": "Small Text",
    "label": "Address",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "contact_person",
@@ -471,67 +411,51 @@
    "in_global_search": 1,
    "label": "Contact Person",
    "options": "Contact",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "contact_display",
    "fieldtype": "Small Text",
    "label": "Contact",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "contact_mobile",
    "fieldtype": "Small Text",
    "label": "Mobile No",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "contact_email",
    "fieldtype": "Small Text",
    "label": "Contact Email",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "col_break_address",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "shipping_address",
    "fieldtype": "Link",
    "label": "Select Shipping Address",
    "options": "Address",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "shipping_address_display",
    "fieldtype": "Small Text",
    "label": "Shipping Address",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "fieldname": "currency_and_price_list",
    "fieldtype": "Section Break",
    "label": "Currency and Price List",
-   "options": "fa fa-tag",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-tag"
   },
   {
    "fieldname": "currency",
@@ -540,9 +464,7 @@
    "oldfieldname": "currency",
    "oldfieldtype": "Select",
    "options": "Currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "conversion_rate",
@@ -551,24 +473,18 @@
    "oldfieldname": "conversion_rate",
    "oldfieldtype": "Currency",
    "precision": "9",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break2",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "buying_price_list",
    "fieldtype": "Link",
    "label": "Price List",
    "options": "Price List",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "price_list_currency",
@@ -576,18 +492,14 @@
    "label": "Price List Currency",
    "options": "Currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "plc_conversion_rate",
    "fieldtype": "Float",
    "label": "Price List Exchange Rate",
    "precision": "9",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
@@ -596,15 +508,11 @@
    "label": "Ignore Pricing Rule",
    "no_copy": 1,
    "permlevel": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "sec_warehouse",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "depends_on": "update_stock",
@@ -613,9 +521,7 @@
    "fieldtype": "Link",
    "label": "Set Accepted Warehouse",
    "options": "Warehouse",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "depends_on": "update_stock",
@@ -625,15 +531,11 @@
    "label": "Rejected Warehouse",
    "no_copy": 1,
    "options": "Warehouse",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "col_break_warehouse",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "No",
@@ -641,33 +543,25 @@
    "fieldtype": "Select",
    "label": "Raw Materials Supplied",
    "options": "No\nYes",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "items_section",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
-   "options": "fa fa-shopping-cart",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-shopping-cart"
   },
   {
    "default": "0",
    "fieldname": "update_stock",
    "fieldtype": "Check",
    "label": "Update Stock",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "scan_barcode",
    "fieldtype": "Data",
-   "label": "Scan Barcode",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Scan Barcode"
   },
   {
    "allow_bulk_edit": 1,
@@ -677,56 +571,43 @@
    "oldfieldname": "entries",
    "oldfieldtype": "Table",
    "options": "Purchase Invoice Item",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "pricing_rule_details",
    "fieldtype": "Section Break",
-   "label": "Pricing Rules",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Pricing Rules"
   },
   {
    "fieldname": "pricing_rules",
    "fieldtype": "Table",
    "label": "Pricing Rule Detail",
    "options": "Pricing Rule Detail",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible_depends_on": "supplied_items",
    "fieldname": "raw_materials_supplied",
    "fieldtype": "Section Break",
-   "label": "Raw Materials Supplied",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Raw Materials Supplied"
   },
   {
+   "depends_on": "update_stock",
    "fieldname": "supplied_items",
    "fieldtype": "Table",
    "label": "Supplied Items",
-   "options": "Purchase Receipt Item Supplied",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "no_copy": 1,
+   "options": "Purchase Receipt Item Supplied"
   },
   {
    "fieldname": "section_break_26",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "fieldname": "total_qty",
    "fieldtype": "Float",
    "label": "Total Quantity",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_total",
@@ -734,9 +615,7 @@
    "label": "Total (Company Currency)",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_net_total",
@@ -746,24 +625,18 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_28",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "total",
    "fieldtype": "Currency",
    "label": "Total",
    "options": "currency",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "net_total",
@@ -773,56 +646,42 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "total_net_weight",
    "fieldtype": "Float",
    "label": "Total Net Weight",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "taxes_section",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
-   "options": "fa fa-money",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-money"
   },
   {
    "fieldname": "tax_category",
    "fieldtype": "Link",
    "label": "Tax Category",
    "options": "Tax Category",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_49",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "shipping_rule",
    "fieldtype": "Link",
    "label": "Shipping Rule",
    "options": "Shipping Rule",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "section_break_51",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "fieldname": "taxes_and_charges",
@@ -831,9 +690,7 @@
    "oldfieldname": "purchase_other_charges",
    "oldfieldtype": "Link",
    "options": "Purchase Taxes and Charges Template",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "taxes",
@@ -841,17 +698,13 @@
    "label": "Purchase Taxes and Charges",
    "oldfieldname": "purchase_tax_details",
    "oldfieldtype": "Table",
-   "options": "Purchase Taxes and Charges",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Purchase Taxes and Charges"
   },
   {
    "collapsible": 1,
    "fieldname": "sec_tax_breakup",
    "fieldtype": "Section Break",
-   "label": "Tax Breakup",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Tax Breakup"
   },
   {
    "fieldname": "other_charges_calculation",
@@ -860,17 +713,13 @@
    "no_copy": 1,
    "oldfieldtype": "HTML",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "totals",
    "fieldtype": "Section Break",
    "oldfieldtype": "Section Break",
-   "options": "fa fa-money",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-money"
   },
   {
    "fieldname": "base_taxes_and_charges_added",
@@ -880,9 +729,7 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_taxes_and_charges_deducted",
@@ -892,9 +739,7 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_total_taxes_and_charges",
@@ -904,15 +749,11 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_40",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "taxes_and_charges_added",
@@ -922,9 +763,7 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "taxes_and_charges_deducted",
@@ -934,9 +773,7 @@
    "oldfieldtype": "Currency",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "total_taxes_and_charges",
@@ -944,18 +781,14 @@
    "label": "Total Taxes and Charges",
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "discount_amount",
    "fieldname": "section_break_44",
    "fieldtype": "Section Break",
-   "label": "Additional Discount",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Additional Discount"
   },
   {
    "default": "Grand Total",
@@ -963,9 +796,7 @@
    "fieldtype": "Select",
    "label": "Apply Additional Discount On",
    "options": "\nGrand Total\nNet Total",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "base_discount_amount",
@@ -973,38 +804,28 @@
    "label": "Additional Discount Amount (Company Currency)",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_46",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "additional_discount_percentage",
    "fieldtype": "Float",
    "label": "Additional Discount Percentage",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "discount_amount",
    "fieldtype": "Currency",
    "label": "Additional Discount Amount",
    "options": "currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "section_break_49",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "fieldname": "base_grand_total",
@@ -1014,9 +835,7 @@
    "oldfieldtype": "Currency",
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1026,9 +845,7 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1038,9 +855,7 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "base_in_words",
@@ -1050,17 +865,13 @@
    "oldfieldname": "in_words",
    "oldfieldtype": "Data",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break8",
    "fieldtype": "Column Break",
    "oldfieldtype": "Column Break",
    "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -1071,9 +882,7 @@
    "oldfieldname": "grand_total_import",
    "oldfieldtype": "Currency",
    "options": "currency",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1083,9 +892,7 @@
    "no_copy": 1,
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:!doc.disable_rounded_total",
@@ -1095,9 +902,7 @@
    "no_copy": 1,
    "options": "currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "in_words",
@@ -1107,9 +912,7 @@
    "oldfieldname": "in_words_import",
    "oldfieldtype": "Data",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "total_advance",
@@ -1120,9 +923,7 @@
    "oldfieldtype": "Currency",
    "options": "party_account_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "outstanding_amount",
@@ -1133,18 +934,14 @@
    "oldfieldtype": "Currency",
    "options": "party_account_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "default": "0",
    "depends_on": "grand_total",
    "fieldname": "disable_rounded_total",
    "fieldtype": "Check",
-   "label": "Disable Rounded Total",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Disable Rounded Total"
   },
   {
    "collapsible": 1,
@@ -1152,26 +949,20 @@
    "depends_on": "eval:doc.is_paid===1||(doc.advances && doc.advances.length>0)",
    "fieldname": "payments_section",
    "fieldtype": "Section Break",
-   "label": "Payments",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Payments"
   },
   {
    "fieldname": "mode_of_payment",
    "fieldtype": "Link",
    "label": "Mode of Payment",
    "options": "Mode of Payment",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "cash_bank_account",
    "fieldtype": "Link",
    "label": "Cash/Bank Account",
-   "options": "Account",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Account"
   },
   {
    "fieldname": "clearance_date",
@@ -1179,15 +970,11 @@
    "label": "Clearance Date",
    "no_copy": 1,
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "col_br_payments",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "depends_on": "is_paid",
@@ -1196,9 +983,7 @@
    "label": "Paid Amount",
    "no_copy": 1,
    "options": "currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "base_paid_amount",
@@ -1207,9 +992,7 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
@@ -1217,9 +1000,7 @@
    "depends_on": "grand_total",
    "fieldname": "write_off",
    "fieldtype": "Section Break",
-   "label": "Write Off",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Write Off"
   },
   {
    "fieldname": "write_off_amount",
@@ -1227,9 +1008,7 @@
    "label": "Write Off Amount",
    "no_copy": 1,
    "options": "currency",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "base_write_off_amount",
@@ -1238,15 +1017,11 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "column_break_61",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1254,9 +1029,7 @@
    "fieldtype": "Link",
    "label": "Write Off Account",
    "options": "Account",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "depends_on": "eval:flt(doc.write_off_amount)!=0",
@@ -1264,9 +1037,7 @@
    "fieldtype": "Link",
    "label": "Write Off Cost Center",
    "options": "Cost Center",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
@@ -1276,17 +1047,13 @@
    "label": "Advance Payments",
    "oldfieldtype": "Section Break",
    "options": "fa fa-money",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "default": "0",
    "fieldname": "allocate_advances_automatically",
    "fieldtype": "Check",
-   "label": "Set Advances and Allocate (FIFO)",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Set Advances and Allocate (FIFO)"
   },
   {
    "depends_on": "eval:!doc.allocate_advances_automatically",
@@ -1294,9 +1061,7 @@
    "fieldtype": "Button",
    "label": "Get Advances Paid",
    "oldfieldtype": "Button",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "advances",
@@ -1306,26 +1071,20 @@
    "oldfieldname": "advance_allocation_details",
    "oldfieldtype": "Table",
    "options": "Purchase Invoice Advance",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
    "collapsible_depends_on": "eval:(!doc.is_return)",
    "fieldname": "payment_schedule_section",
    "fieldtype": "Section Break",
-   "label": "Payment Terms",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Payment Terms"
   },
   {
    "fieldname": "payment_terms_template",
    "fieldtype": "Link",
    "label": "Payment Terms Template",
-   "options": "Payment Terms Template",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Payment Terms Template"
   },
   {
    "fieldname": "payment_schedule",
@@ -1333,9 +1092,7 @@
    "label": "Payment Schedule",
    "no_copy": 1,
    "options": "Payment Schedule",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
@@ -1343,33 +1100,25 @@
    "fieldname": "terms_section_break",
    "fieldtype": "Section Break",
    "label": "Terms and Conditions",
-   "options": "fa fa-legal",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "fa fa-legal"
   },
   {
    "fieldname": "tc_name",
    "fieldtype": "Link",
    "label": "Terms",
    "options": "Terms and Conditions",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "terms",
    "fieldtype": "Text Editor",
-   "label": "Terms and Conditions1",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Terms and Conditions1"
   },
   {
    "collapsible": 1,
    "fieldname": "printing_settings",
    "fieldtype": "Section Break",
-   "label": "Printing Settings",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Printing Settings"
   },
   {
    "allow_on_submit": 1,
@@ -1377,9 +1126,7 @@
    "fieldtype": "Link",
    "label": "Letter Head",
    "options": "Letter Head",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "allow_on_submit": 1,
@@ -1387,15 +1134,11 @@
    "fieldname": "group_same_items",
    "fieldtype": "Check",
    "label": "Group same items",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_112",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "allow_on_submit": 1,
@@ -1407,18 +1150,14 @@
    "oldfieldtype": "Link",
    "options": "Print Heading",
    "print_hide": 1,
-   "report_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "report_hide": 1
   },
   {
    "fieldname": "language",
    "fieldtype": "Data",
    "label": "Print Language",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "collapsible": 1,
@@ -1427,9 +1166,7 @@
    "label": "More Information",
    "oldfieldtype": "Section Break",
    "options": "fa fa-file-text",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "credit_to",
@@ -1440,9 +1177,7 @@
    "options": "Account",
    "print_hide": 1,
    "reqd": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "fieldname": "party_account_currency",
@@ -1452,9 +1187,7 @@
    "no_copy": 1,
    "options": "Currency",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "default": "No",
@@ -1464,9 +1197,7 @@
    "oldfieldname": "is_opening",
    "oldfieldtype": "Select",
    "options": "No\nYes",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "against_expense_account",
@@ -1476,15 +1207,11 @@
    "no_copy": 1,
    "oldfieldname": "against_expense_account",
    "oldfieldtype": "Small Text",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_63",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "Draft",
@@ -1493,9 +1220,7 @@
    "in_standard_filter": 1,
    "label": "Status",
    "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "inter_company_invoice_reference",
@@ -1504,9 +1229,7 @@
    "no_copy": 1,
    "options": "Sales Invoice",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "remarks",
@@ -1515,18 +1238,14 @@
    "no_copy": 1,
    "oldfieldname": "remarks",
    "oldfieldtype": "Text",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "collapsible": 1,
    "fieldname": "subscription_section",
    "fieldtype": "Section Break",
    "label": "Subscription Section",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "allow_on_submit": 1,
@@ -1535,9 +1254,7 @@
    "fieldtype": "Date",
    "label": "From Date",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "allow_on_submit": 1,
@@ -1546,15 +1263,11 @@
    "fieldtype": "Date",
    "label": "To Date",
    "no_copy": 1,
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "column_break_114",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "auto_repeat",
@@ -1563,32 +1276,24 @@
    "no_copy": 1,
    "options": "Auto Repeat",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "allow_on_submit": 1,
    "depends_on": "eval: doc.auto_repeat",
    "fieldname": "update_auto_repeat_reference",
    "fieldtype": "Button",
-   "label": "Update Auto Repeat Reference",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Update Auto Repeat Reference"
   },
   {
    "collapsible": 1,
    "fieldname": "accounting_dimensions_section",
    "fieldtype": "Section Break",
-   "label": "Accounting Dimensions ",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Accounting Dimensions "
   },
   {
    "fieldname": "dimension_col_break",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "default": "0",
@@ -1596,9 +1301,7 @@
    "fieldname": "is_internal_supplier",
    "fieldtype": "Check",
    "label": "Is Internal Supplier",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "tax_withholding_category",
@@ -1606,25 +1309,19 @@
    "hidden": 1,
    "label": "Tax Withholding Category",
    "options": "Tax Withholding Category",
-   "print_hide": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "print_hide": 1
   },
   {
    "fieldname": "billing_address",
    "fieldtype": "Link",
    "label": "Select Billing Address",
-   "options": "Address",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Address"
   },
   {
    "fieldname": "billing_address_display",
    "fieldtype": "Small Text",
    "label": "Billing Address",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "project",
@@ -1638,9 +1335,7 @@
    "fieldname": "unrealized_profit_loss_account",
    "fieldtype": "Link",
    "label": "Unrealized Profit / Loss Account",
-   "options": "Account",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Account"
   },
   {
    "depends_on": "eval:doc.is_internal_supplier",
@@ -1649,9 +1344,7 @@
    "fieldname": "represents_company",
    "fieldtype": "Link",
    "label": "Represents Company",
-   "options": "Company",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Company"
   },
   {
    "depends_on": "eval:doc.update_stock && doc.is_internal_supplier",
@@ -1663,8 +1356,6 @@
    "options": "Warehouse",
    "print_hide": 1,
    "print_width": "50px",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50px"
   },
   {
@@ -1692,7 +1383,7 @@
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-09 12:30:25.632109",
+ "modified": "2021-06-15 18:20:56.806195",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 0ee0bc7..45d89ad 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -400,6 +400,7 @@
 		# because updating ordered qty in bin depends upon updated ordered qty in PO
 		if self.update_stock == 1:
 			self.update_stock_ledger()
+			self.set_consumed_qty_in_po()
 			from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
 			update_serial_nos_after_submit(self, "items")
 
@@ -998,6 +999,7 @@
 		if self.update_stock == 1:
 			self.update_stock_ledger()
 			self.delete_auto_created_batches()
+			self.set_consumed_qty_in_po()
 
 		self.make_gl_entries_on_cancel()
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 503dda7..ff433b9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -621,8 +621,10 @@
 		self.assertEqual(actual_qty_0, get_qty_after_transaction())
 
 	def test_subcontracting_via_purchase_invoice(self):
+		from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
+		update_backflush_based_on('BOM')
 		make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
 		make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
 			qty=100, basic_rate=100)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e14f305..55a5b99 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -989,7 +989,7 @@
 
 			for payment_mode in self.payments:
 				if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
-					payment_mode.base_amount -= self.change_amount
+					payment_mode.base_amount -= flt(self.change_amount)
 
 				if payment_mode.amount:
 					# POS, make payment entries
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index b5ebc56..521432d 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -53,6 +53,39 @@
 		} else {
 			frm.set_value("tax_withholding_category", frm.supplier_tds);
 		}
+	},
+
+	refresh: function(frm) {
+		frm.trigger('get_materials_from_supplier');
+	},
+
+	get_materials_from_supplier: function(frm) {
+		let po_details = [];
+
+		if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
+			frm.doc.supplied_items.forEach(d => {
+				if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
+					po_details.push(d.name)
+				}
+			});
+		}
+
+		if (po_details && po_details.length) {
+			frm.add_custom_button(__('Return of Components'), () => {
+				frm.call({
+					method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier',
+					freeze: true,
+					freeze_message: __('Creating Stock Entry'),
+					args: { purchase_order: frm.doc.name, po_details: po_details },
+					callback: function(r) {
+						if (r && r.message) {
+							const doc = frappe.model.sync(r.message);
+							frappe.set_route("Form", doc[0].doctype, doc[0].name);
+						}
+					}
+				});
+			}, __('Create'));
+		}
 	}
 });
 
@@ -217,7 +250,7 @@
 	}
 
 	has_unsupplied_items() {
-		return this.frm.doc['supplied_items'].some(item => item.required_qty != item.supplied_qty)
+		return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
 	}
 
 	make_stock_entry() {
@@ -513,12 +546,14 @@
 			],
 			primary_action: function() {
 				var data = d.get_values();
+				let reason_for_hold = 'Reason for hold: ' + data.reason_for_hold;
+
 				frappe.call({
 					method: "frappe.desk.form.utils.add_comment",
 					args: {
 						reference_doctype: me.frm.doctype,
 						reference_name: me.frm.docname,
-						content: __('Reason for hold:') + " " +data.reason_for_hold,
+						content: __(reason_for_hold),
 						comment_email: frappe.session.user,
 						comment_by: frappe.session.user_fullname
 					},
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 41668c6..bb0ad60 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -609,6 +609,7 @@
    "fieldname": "supplied_items",
    "fieldtype": "Table",
    "label": "Supplied Items",
+   "no_copy": 1,
    "oldfieldname": "po_raw_material_details",
    "oldfieldtype": "Table",
    "options": "Purchase Order Item Supplied",
@@ -1377,7 +1378,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 00:55:30.781375",
+ "modified": "2021-05-30 15:17:53.663648",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 2629ba7..eaa502f 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -14,12 +14,11 @@
 from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status
 from erpnext.stock.utils import get_bin
 from erpnext.accounts.party import get_party_account_currency
-from six import string_types
 from erpnext.stock.doctype.item.item import get_item_defaults
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
 from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
-from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
-	unlink_inter_company_doc
+from erpnext.accounts.doctype.sales_invoice.sales_invoice import (validate_inter_company_party,
+	update_linked_doc, unlink_inter_company_doc)
 
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
@@ -503,9 +502,11 @@
 
 @frappe.whitelist()
 def make_rm_stock_entry(purchase_order, rm_items):
-	if isinstance(rm_items, string_types):
+	rm_items_list = rm_items
+
+	if isinstance(rm_items, str):
 		rm_items_list = json.loads(rm_items)
-	else:
+	elif not rm_items:
 		frappe.throw(_("No Items available for transfer"))
 
 	if rm_items_list:
@@ -543,6 +544,8 @@
 							'qty': rm_item_data["qty"],
 							'from_warehouse': rm_item_data["warehouse"],
 							'stock_uom': rm_item_data["stock_uom"],
+							'serial_no': rm_item_data.get('serial_no'),
+							'batch_no': rm_item_data.get('batch_no'),
 							'main_item_code': rm_item_data["item_code"],
 							'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
 						}
@@ -582,3 +585,58 @@
 def make_inter_company_sales_order(source_name, target_doc=None):
 	from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
 	return make_inter_company_transaction("Purchase Order", source_name, target_doc)
+
+@frappe.whitelist()
+def get_materials_from_supplier(purchase_order, po_details):
+	if isinstance(po_details, str):
+		po_details = json.loads(po_details)
+
+	doc = frappe.get_cached_doc('Purchase Order', purchase_order)
+	doc.initialized_fields()
+	doc.purchase_orders = [doc.name]
+	doc.get_available_materials()
+
+	if not doc.available_materials:
+		frappe.throw(_('Materials are already received against the purchase order {0}')
+			.format(purchase_order))
+
+	return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details)
+
+def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details):
+	ste_doc = frappe.new_doc('Stock Entry')
+	ste_doc.purpose = 'Material Transfer'
+	ste_doc.purchase_order = po_doc.name
+	ste_doc.company = po_doc.company
+	ste_doc.is_return = 1
+
+	for key, value in available_materials.items():
+		if not value.qty:
+			continue
+
+		if value.batch_no:
+			for batch_no, qty in value.batch_no.items():
+				if qty > 0:
+					add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no)
+		else:
+			add_items_in_ste(ste_doc, value, value.qty, po_details)
+
+	ste_doc.set_stock_entry_type()
+	ste_doc.calculate_rate_and_amount()
+
+	return ste_doc
+
+def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
+	item = ste_doc.append('items', row.item_details)
+
+	po_detail = list(set(row.po_details).intersection(po_details))
+	item.update({
+		'qty': qty,
+		'batch_no': batch_no,
+		'basic_rate': row.item_details['rate'],
+		'po_detail': po_detail[0] if po_detail else '',
+		's_warehouse': row.item_details['t_warehouse'],
+		't_warehouse': row.item_details['s_warehouse'],
+		'item_code': row.item_details['rm_item_code'],
+		'subcontracted_item': row.item_details['main_item_code'],
+		'serial_no': '\n'.join(row.serial_no) if row.serial_no else ''
+	})
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 3b9f8e9..8563b97 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -20,7 +20,6 @@
 from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
 
 from erpnext.stock.doctype.batch.test_batch import make_new_batch
-from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials
 
 class TestPurchaseOrder(unittest.TestCase):
 	def test_make_purchase_receipt(self):
@@ -771,7 +770,7 @@
 		self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
 
 	def test_exploded_items_in_subcontracted(self):
-		item_code = "_Test Subcontracted FG Item 1"
+		item_code = "_Test Subcontracted FG Item 11"
 		make_subcontracted_item(item_code=item_code)
 
 		po = create_purchase_order(item_code=item_code, qty=1,
@@ -848,79 +847,6 @@
 		for item in rm_items:
 			transferred_rm_map[item.get('rm_item_code')] = item
 
-		for item in pr.get('supplied_items'):
-			self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
-
-		update_backflush_based_on("BOM")
-
-	def test_backflushed_based_on_for_multiple_batches(self):
-		item_code = "_Test Subcontracted FG Item 2"
-		make_item('Sub Contracted Raw Material 2', {
-			'is_stock_item': 1,
-			'is_sub_contracted_item': 1
-		})
-
-		make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1,
-			raw_materials=["Sub Contracted Raw Material 2"])
-
-		update_backflush_based_on("Material Transferred for Subcontract")
-
-		order_qty = 500
-		po = create_purchase_order(item_code=item_code, qty=order_qty,
-			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
-
-		make_stock_entry(target="_Test Warehouse - _TC",
-			item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100)
-
-		rm_items = [
-			{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item",
-				"qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}]
-
-		rm_item_string = json.dumps(rm_items)
-		se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
-		se.submit()
-
-		for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]:
-			make_new_batch(batch_id=batch, item_code=item_code)
-
-		pr = make_purchase_receipt(po.name)
-
-		# partial receipt
-		pr.get('items')[0].qty = 30
-		pr.get('items')[0].batch_no = "ABCD1"
-
-		purchase_order = po.name
-		purchase_order_item = po.items[0].name
-
-		for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items():
-			pr.append("items", {
-				"item_code": pr.get('items')[0].item_code,
-				"item_name": pr.get('items')[0].item_name,
-				"uom": pr.get('items')[0].uom,
-				"stock_uom": pr.get('items')[0].stock_uom,
-				"warehouse": pr.get('items')[0].warehouse,
-				"conversion_factor": pr.get('items')[0].conversion_factor,
-				"cost_center": pr.get('items')[0].cost_center,
-				"rate": pr.get('items')[0].rate,
-				"qty": qty,
-				"batch_no": batch_no,
-				"purchase_order": purchase_order,
-				"purchase_order_item": purchase_order_item
-			})
-
-		pr.submit()
-
-		pr1 = make_purchase_receipt(po.name)
-		pr1.get('items')[0].qty = 300
-		pr1.get('items')[0].batch_no = "ABCD1"
-		pr1.save()
-
-		pr_key = ("Sub Contracted Raw Material 2", po.name)
-		consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key)
-
-		self.assertTrue(pr1.supplied_items[0].consumed_qty > 0)
-		self.assertTrue(pr1.supplied_items[0].consumed_qty,  flt(552.0) - flt(consumed_qty))
-
 		update_backflush_based_on("BOM")
 
 	def test_supplied_qty_against_subcontracted_po(self):
@@ -1117,22 +1043,29 @@
 	po.conversion_factor = args.conversion_factor or 1
 	po.supplier_warehouse = args.supplier_warehouse or None
 
-	po.append("items", {
-		"item_code": args.item or args.item_code or "_Test Item",
-		"warehouse": args.warehouse or "_Test Warehouse - _TC",
-		"qty": args.qty or 10,
-		"rate": args.rate or 500,
-		"schedule_date": add_days(nowdate(), 1),
-		"include_exploded_items": args.get('include_exploded_items', 1),
-		"against_blanket_order": args.against_blanket_order
-	})
+	if args.rm_items:
+		for row in args.rm_items:
+			po.append("items", row)
+	else:
+		po.append("items", {
+			"item_code": args.item or args.item_code or "_Test Item",
+			"warehouse": args.warehouse or "_Test Warehouse - _TC",
+			"qty": args.qty or 10,
+			"rate": args.rate or 500,
+			"schedule_date": add_days(nowdate(), 1),
+			"include_exploded_items": args.get('include_exploded_items', 1),
+			"against_blanket_order": args.against_blanket_order
+		})
+
+	po.set_missing_values()
 	if not args.do_not_save:
 		po.insert()
 		if not args.do_not_submit:
 			if po.is_subcontracted == "Yes":
 				supp_items = po.get("supplied_items")
 				for d in supp_items:
-					d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
+					if not d.reserve_warehouse:
+						d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
 			po.submit()
 
 	return po
diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
index d7ea9c1..60247bd 100644
--- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
+++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
@@ -6,21 +6,25 @@
  "engine": "InnoDB",
  "field_order": [
   "main_item_code",
-  "bom_detail_no",
+  "rm_item_code",
+  "column_break_3",
   "stock_uom",
+  "reserve_warehouse",
   "conversion_factor",
   "column_break_6",
-  "rm_item_code",
+  "bom_detail_no",
   "reference_name",
-  "reserve_warehouse",
   "section_break2",
   "rate",
   "col_break2",
   "amount",
   "section_break1",
   "required_qty",
+  "supplied_qty",
   "col_break1",
-  "supplied_qty"
+  "consumed_qty",
+  "returned_qty",
+  "total_supplied_qty"
  ],
  "fields": [
   {
@@ -125,6 +129,8 @@
    "fieldtype": "Float",
    "in_list_view": 1,
    "label": "Supplied Qty",
+   "no_copy": 1,
+   "print_hide": 1,
    "read_only": 1
   },
   {
@@ -142,13 +148,42 @@
   {
    "fieldname": "col_break2",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "consumed_qty",
+   "fieldtype": "Float",
+   "label": "Consumed Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "returned_qty",
+   "fieldtype": "Float",
+   "label": "Returned Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "total_supplied_qty",
+   "fieldtype": "Float",
+   "hidden": 1,
+   "label": "Total Supplied Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
   }
  ],
  "hide_toolbar": 1,
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-06-09 15:17:58.128242",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item Supplied",
diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
index dc00bca..f9cd720 100644
--- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
+++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
@@ -6,10 +6,11 @@
  "engine": "InnoDB",
  "field_order": [
   "main_item_code",
-  "description",
+  "rm_item_code",
+  "item_name",
   "bom_detail_no",
   "col_break1",
-  "rm_item_code",
+  "description",
   "stock_uom",
   "conversion_factor",
   "reference_name",
@@ -25,7 +26,8 @@
   "secbreak_3",
   "batch_no",
   "col_break4",
-  "serial_no"
+  "serial_no",
+  "purchase_order"
  ],
  "fields": [
   {
@@ -52,7 +54,6 @@
    "fieldname": "description",
    "fieldtype": "Text Editor",
    "in_global_search": 1,
-   "in_list_view": 1,
    "label": "Description",
    "oldfieldname": "description",
    "oldfieldtype": "Data",
@@ -81,18 +82,20 @@
    "fieldname": "required_qty",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Required Qty",
+   "label": "Available Qty For Consumption",
    "oldfieldname": "required_qty",
    "oldfieldtype": "Currency",
+   "print_hide": 1,
    "read_only": 1
   },
   {
+   "columns": 2,
    "fieldname": "consumed_qty",
    "fieldtype": "Float",
-   "label": "Consumed Qty",
+   "in_list_view": 1,
+   "label": "Qty to Be Consumed",
    "oldfieldname": "consumed_qty",
    "oldfieldtype": "Currency",
-   "read_only": 1,
    "reqd": 1
   },
   {
@@ -183,12 +186,28 @@
   {
    "fieldname": "col_break4",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "purchase_order",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Purchase Order",
+   "no_copy": 1,
+   "options": "Purchase Order",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-18 17:26:09.703215",
+ "modified": "2021-06-19 19:33:04.431213",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Receipt Item Supplied",
diff --git a/erpnext/buying/report/subcontract_order_summary/__init__.py b/erpnext/buying/report/subcontract_order_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/__init__.py
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
new file mode 100644
index 0000000..5ba52f1
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
@@ -0,0 +1,45 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Subcontract Order Summary"] = {
+	"filters": [
+		{
+			label: __("Company"),
+			fieldname: "company",
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			label: __("From Date"),
+			fieldname:"from_date",
+			fieldtype: "Date",
+			default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+			reqd: 1
+		},
+		{
+			label: __("To Date"),
+			fieldname:"to_date",
+			fieldtype: "Date",
+			default: frappe.datetime.get_today(),
+			reqd: 1
+		},
+		{
+			label: __("Purchase Order"),
+			fieldname: "name",
+			fieldtype: "Link",
+			options: "Purchase Order",
+			get_query: function() {
+				return {
+					filters: {
+						docstatus: 1,
+						is_subcontracted: 'Yes',
+						company: frappe.query_report.get_filter_value('company')
+					}
+				}
+			}
+		}
+	]
+};
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json
new file mode 100644
index 0000000..526a8d8
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-05-31 14:43:32.417694",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-05-31 14:43:32.417694",
+ "modified_by": "Administrator",
+ "module": "Buying",
+ "name": "Subcontract Order Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Purchase Order",
+ "report_name": "Subcontract Order Summary",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Stock User"
+  },
+  {
+   "role": "Purchase Manager"
+  },
+  {
+   "role": "Purchase User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
new file mode 100644
index 0000000..0c0d4f0
--- /dev/null
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.py
@@ -0,0 +1,152 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+	columns, data = [], []
+	columns = get_columns()
+	data = get_data(filters)
+
+	return columns, data
+
+def get_data(report_filters):
+	data = []
+	orders = get_subcontracted_orders(report_filters)
+
+	if orders:
+		supplied_items = get_supplied_items(orders, report_filters)
+		po_details = prepare_subcontracted_data(orders, supplied_items)
+		get_subcontracted_data(po_details, data)
+
+	return data
+
+def get_subcontracted_orders(report_filters):
+	fields = ['`tabPurchase Order Item`.`parent` as po_id', '`tabPurchase Order Item`.`item_code`',
+		'`tabPurchase Order Item`.`item_name`', '`tabPurchase Order Item`.`qty`', '`tabPurchase Order Item`.`name`',
+		'`tabPurchase Order Item`.`received_qty`', '`tabPurchase Order`.`status`']
+
+	filters = get_filters(report_filters)
+
+	return frappe.get_all('Purchase Order', fields = fields, filters=filters) or []
+
+def get_filters(report_filters):
+	filters = [['Purchase Order', 'docstatus', '=', 1], ['Purchase Order', 'is_subcontracted', '=', 'Yes'],
+		['Purchase Order', 'transaction_date', 'between', (report_filters.from_date, report_filters.to_date)]]
+
+	for field in ['name', 'company']:
+		if report_filters.get(field):
+			filters.append(['Purchase Order', field, '=', report_filters.get(field)])
+
+	return filters
+
+def get_supplied_items(orders, report_filters):
+	if not orders:
+		return []
+
+	fields = ['parent', 'main_item_code', 'rm_item_code', 'required_qty',
+		'supplied_qty', 'returned_qty', 'total_supplied_qty', 'consumed_qty', 'reference_name']
+
+	filters = {'parent': ('in', [d.po_id for d in orders]), 'docstatus': 1}
+
+	supplied_items = {}
+	for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters):
+		new_key = (row.parent, row.reference_name, row.main_item_code)
+
+		supplied_items.setdefault(new_key, []).append(row)
+
+	return supplied_items
+
+def prepare_subcontracted_data(orders, supplied_items):
+	po_details = {}
+	for row in orders:
+		key = (row.po_id, row.name, row.item_code)
+		if key not in po_details:
+			po_details.setdefault(key, frappe._dict({'po_item': row, 'supplied_items': []}))
+
+		details = po_details[key]
+
+		if supplied_items.get(key):
+			for supplied_item in supplied_items[key]:
+				details['supplied_items'].append(supplied_item)
+
+	return po_details
+
+def get_subcontracted_data(po_details, data):
+	for key, details in po_details.items():
+		res = details.po_item
+		for index, row in enumerate(details.supplied_items):
+			if index != 0:
+				res = {}
+
+			res.update(row)
+			data.append(res)
+
+def get_columns():
+	return [
+		{
+			"label": _("Purchase Order"),
+			"fieldname": "po_id",
+			"fieldtype": "Link",
+			"options": "Purchase Order",
+			"width": 100
+		},
+		{
+			"label": _("Status"),
+			"fieldname": "status",
+			"fieldtype": "Data",
+			"width": 80
+		},
+		{
+			"label": _("Subcontracted Item"),
+			"fieldname": "item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 160
+		},
+		{
+			"label": _("Order Qty"),
+			"fieldname": "qty",
+			"fieldtype": "Float",
+			"width": 90
+		},
+		{
+			"label": _("Received Qty"),
+			"fieldname": "received_qty",
+			"fieldtype": "Float",
+			"width": 110
+		},
+		{
+			"label": _("Supplied Item"),
+			"fieldname": "rm_item_code",
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 160
+		},
+		{
+			"label": _("Required Qty"),
+			"fieldname": "required_qty",
+			"fieldtype": "Float",
+			"width": 110
+		},
+		{
+			"label": _("Supplied Qty"),
+			"fieldname": "supplied_qty",
+			"fieldtype": "Float",
+			"width": 110
+		},
+		{
+			"label": _("Consumed Qty"),
+			"fieldname": "consumed_qty",
+			"fieldtype": "Float",
+			"width": 120
+		},
+		{
+			"label": _("Returned Qty"),
+			"fieldname": "returned_qty",
+			"fieldtype": "Float",
+			"width": 110
+		}
+	]
\ No newline at end of file
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index da81911..6a550e0 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -11,16 +11,17 @@
 from erpnext.stock.get_item_details import get_conversion_factor
 from erpnext.buying.utils import validate_for_items, update_last_purchase_rate
 from erpnext.stock.stock_ledger import get_valuation_rate
-from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items
 from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos, get_serial_nos
 from frappe.contacts.doctype.address.address import get_address_display
 
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
-from erpnext.controllers.stock_controller import StockController
 from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
 from erpnext.stock.utils import get_incoming_rate
 
-class BuyingController(StockController):
+from erpnext.controllers.stock_controller import StockController
+from erpnext.controllers.subcontracting import Subcontracting
+
+class BuyingController(StockController, Subcontracting):
 
 	def get_feed(self):
 		if self.get("supplier_name"):
@@ -57,6 +58,11 @@
 		if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
 			self.update_valuation_rate()
 
+	def onload(self):
+		super(BuyingController, self).onload()
+		self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings',
+			'backflush_raw_materials_of_subcontract_based_on'))
+
 	def set_missing_values(self, for_validate=False):
 		super(BuyingController, self).set_missing_values(for_validate)
 
@@ -171,12 +177,13 @@
 
 			TODO: rename item_tax_amount to valuation_tax_amount
 		"""
+		stock_and_asset_items = []
 		stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
 
 		stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
 		last_item_idx = 1
 		for d in self.get("items"):
-			if d.item_code and d.item_code in stock_and_asset_items:
+			if (d.item_code and d.item_code in stock_and_asset_items):
 				stock_and_asset_items_qty += flt(d.qty)
 				stock_and_asset_items_amount += flt(d.base_net_amount)
 				last_item_idx = d.idx
@@ -255,7 +262,7 @@
 		supplied_items_cost = 0.0
 		for d in self.get("supplied_items"):
 			if d.reference_name == item_row_id:
-				if reset_outgoing_rate and frappe.db.get_value('Item', d.rm_item_code, 'is_stock_item'):
+				if reset_outgoing_rate and frappe.get_cached_value('Item', d.rm_item_code, 'is_stock_item'):
 					rate = get_incoming_rate({
 						"item_code": d.rm_item_code,
 						"warehouse": self.supplier_warehouse,
@@ -285,11 +292,13 @@
 				if item in self.sub_contracted_items and not item.bom:
 					frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
 
-			if self.doctype == "Purchase Order":
-				for supplied_item in self.get("supplied_items"):
-					if not supplied_item.reserve_warehouse:
-						frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code)))
+			if self.doctype != "Purchase Order":
+				return
 
+			for row in self.get("supplied_items"):
+				if not row.reserve_warehouse:
+					msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied"
+					frappe.throw(_(msg))
 		else:
 			for item in self.get("items"):
 				if item.bom:
@@ -297,23 +306,7 @@
 
 	def create_raw_materials_supplied(self, raw_material_table):
 		if self.is_subcontracted=="Yes":
-			parent_items = []
-			backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings",
-				"backflush_raw_materials_of_subcontract_based_on")
-			if (self.doctype == 'Purchase Receipt' and
-				backflush_raw_materials_based_on != 'BOM'):
-				self.update_raw_materials_supplied_based_on_stock_entries()
-			else:
-				for item in self.get("items"):
-					if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
-						item.rm_supp_cost = 0.0
-					if item.bom and item.item_code in self.sub_contracted_items:
-						self.update_raw_materials_supplied_based_on_bom(item, raw_material_table)
-
-						if [item.item_code, item.name] not in parent_items:
-							parent_items.append([item.item_code, item.name])
-
-				self.cleanup_raw_materials_supplied(parent_items, raw_material_table)
+			self.set_materials_for_subcontracted_items(raw_material_table)
 
 		elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
 			for item in self.get("items"):
@@ -322,176 +315,6 @@
 		if self.is_subcontracted == "No" and self.get("supplied_items"):
 			self.set('supplied_items', [])
 
-	def update_raw_materials_supplied_based_on_stock_entries(self):
-		self.set('supplied_items', [])
-
-		purchase_orders = set(d.purchase_order for d in self.items)
-
-		# qty of raw materials backflushed (for each item per purchase order)
-		backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
-
-		# qty of "finished good" item yet to be received
-		qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
-
-		for item in self.get('items'):
-			if not item.purchase_order:
-				continue
-
-			# reset raw_material cost
-			item.rm_supp_cost = 0
-
-			# qty of raw materials transferred to the supplier
-			transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
-
-			non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
-
-			item_key = '{}{}'.format(item.item_code, item.purchase_order)
-
-			fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
-
-			if not fg_yet_to_be_received:
-				frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}")
-					.format(item.idx, frappe.bold(item.item_code),
-						frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)),
-					title=_("Limit Crossed"))
-
-			transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
-			# backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
-
-			for raw_material in transferred_raw_materials + non_stock_items:
-				rm_item_key = (raw_material.rm_item_code, item.item_code, item.purchase_order)
-				raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
-
-				consumed_qty = raw_material_data.get('qty', 0)
-				consumed_serial_nos = raw_material_data.get('serial_no', '')
-				consumed_batch_nos = raw_material_data.get('batch_nos', '')
-
-				transferred_qty = raw_material.qty
-
-				rm_qty_to_be_consumed = transferred_qty - consumed_qty
-
-				# backflush all remaining transferred qty in the last Purchase Receipt
-				if fg_yet_to_be_received == item.qty:
-					qty = rm_qty_to_be_consumed
-				else:
-					qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
-
-					if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
-						qty = frappe.utils.ceil(qty)
-
-				if qty > rm_qty_to_be_consumed:
-					qty = rm_qty_to_be_consumed
-
-				if not qty: continue
-
-				if raw_material.serial_nos:
-					set_serial_nos(raw_material, consumed_serial_nos, qty)
-
-				if raw_material.batch_nos:
-					backflushed_batch_qty_map = raw_material_data.get('consumed_batch', {})
-
-					batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
-						qty, transferred_batch_qty_map, backflushed_batch_qty_map, item.purchase_order)
-
-					for batch_data in batches_qty:
-						qty = batch_data['qty']
-						raw_material.batch_no = batch_data['batch']
-						if qty > 0:
-							self.append_raw_material_to_be_backflushed(item, raw_material, qty)
-				else:
-					self.append_raw_material_to_be_backflushed(item, raw_material, qty)
-
-	def append_raw_material_to_be_backflushed(self, fg_item_row, raw_material_data, qty):
-		rm = self.append('supplied_items', {})
-		rm.update(raw_material_data)
-
-		if not rm.main_item_code:
-			rm.main_item_code = fg_item_row.item_code
-
-		rm.reference_name = fg_item_row.name
-		rm.required_qty = qty
-		rm.consumed_qty = qty
-
-	def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
-		exploded_item = 1
-		if hasattr(item, 'include_exploded_items'):
-			exploded_item = item.get('include_exploded_items')
-
-		bom_items = get_items_from_bom(item.item_code, item.bom, exploded_item)
-
-		used_alternative_items = []
-		if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order:
-			used_alternative_items = get_used_alternative_items(purchase_order = item.purchase_order)
-
-		raw_materials_cost = 0
-		items = list(set([d.item_code for d in bom_items]))
-		item_wh = frappe._dict(frappe.db.sql("""select i.item_code, id.default_warehouse
-			from `tabItem` i, `tabItem Default` id
-			where id.parent=i.name and id.company=%s and i.name in ({0})"""
-			.format(", ".join(["%s"] * len(items))), [self.company] + items))
-
-		for bom_item in bom_items:
-			if self.doctype == "Purchase Order":
-				reserve_warehouse = bom_item.source_warehouse or item_wh.get(bom_item.item_code)
-				if frappe.db.get_value("Warehouse", reserve_warehouse, "company") != self.company:
-					reserve_warehouse = None
-
-			conversion_factor = item.conversion_factor
-			if (self.doctype in ["Purchase Receipt", "Purchase Invoice"] and item.purchase_order and
-				bom_item.item_code in used_alternative_items):
-				alternative_item_data = used_alternative_items.get(bom_item.item_code)
-				bom_item.item_code = alternative_item_data.item_code
-				bom_item.item_name = alternative_item_data.item_name
-				bom_item.stock_uom = alternative_item_data.stock_uom
-				conversion_factor = alternative_item_data.conversion_factor
-				bom_item.description = alternative_item_data.description
-
-			# check if exists
-			exists = 0
-			for d in self.get(raw_material_table):
-				if d.main_item_code == item.item_code and d.rm_item_code == bom_item.item_code \
-					and d.reference_name == item.name:
-						rm, exists = d, 1
-						break
-
-			if not exists:
-				rm = self.append(raw_material_table, {})
-
-			required_qty = flt(flt(bom_item.qty_consumed_per_unit) * (flt(item.qty) + getattr(item, 'rejected_qty', 0)) *
-				flt(conversion_factor), rm.precision("required_qty"))
-			rm.reference_name = item.name
-			rm.bom_detail_no = bom_item.name
-			rm.main_item_code = item.item_code
-			rm.rm_item_code = bom_item.item_code
-			rm.stock_uom = bom_item.stock_uom
-			rm.required_qty = required_qty
-			rm.rate = bom_item.rate
-			rm.conversion_factor = conversion_factor
-
-			if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
-				rm.consumed_qty = required_qty
-				rm.description = bom_item.description
-				if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no:
-					rm.batch_no = item.batch_no
-			elif not rm.reserve_warehouse:
-				rm.reserve_warehouse = reserve_warehouse
-
-	def cleanup_raw_materials_supplied(self, parent_items, raw_material_table):
-		"""Remove all those child items which are no longer present in main item table"""
-		delete_list = []
-		for d in self.get(raw_material_table):
-			if [d.main_item_code, d.reference_name] not in parent_items:
-				# mark for deletion from doclist
-				delete_list.append(d)
-
-		# delete from doclist
-		if delete_list:
-			rm_supplied_details = self.get(raw_material_table)
-			self.set(raw_material_table, [])
-			for d in rm_supplied_details:
-				if d not in delete_list:
-					self.append(raw_material_table, d)
-
 	@property
 	def sub_contracted_items(self):
 		if not hasattr(self, "_sub_contracted_items"):
@@ -683,7 +506,8 @@
 			self.process_fixed_asset()
 			self.update_fixed_asset(field)
 
-		update_last_purchase_rate(self, is_submit = 1)
+		if self.doctype in ['Purchase Order', 'Purchase Receipt']:
+			update_last_purchase_rate(self, is_submit = 1)
 
 	def on_cancel(self):
 		super(BuyingController, self).on_cancel()
@@ -691,7 +515,9 @@
 		if self.get('is_return'):
 			return
 
-		update_last_purchase_rate(self, is_submit = 0)
+		if self.doctype in ['Purchase Order', 'Purchase Receipt']:
+			update_last_purchase_rate(self, is_submit = 0)
+
 		if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
 			field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
 
@@ -863,104 +689,6 @@
 		else:
 			validate_item_type(self, "is_purchase_item", "purchase")
 
-
-def get_items_from_bom(item_code, bom, exploded_item=1):
-	doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
-
-	bom_items = frappe.db.sql("""select t2.item_code, t2.name,
-			t2.rate, t2.stock_uom, t2.source_warehouse, t2.description,
-			t2.stock_qty / ifnull(t1.quantity, 1) as qty_consumed_per_unit
-		from
-			`tabBOM` t1, `tab{0}` t2, tabItem t3
-		where
-			t2.parent = t1.name and t1.item = %s
-			and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
-			and t2.sourced_by_supplier = 0
-			and t2.item_code = t3.name""".format(doctype),
-			(item_code, bom), as_dict=1)
-
-	if not bom_items:
-		msgprint(_("Specified BOM {0} does not exist for Item {1}").format(bom, item_code), raise_exception=1)
-
-	return bom_items
-
-def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
-	common_query = """
-		SELECT
-			sed.item_code AS rm_item_code,
-			SUM(sed.qty) AS qty,
-			sed.description,
-			sed.stock_uom,
-			sed.subcontracted_item AS main_item_code,
-			{serial_no_concat_syntax} AS serial_nos,
-			{batch_no_concat_syntax} AS batch_nos
-		FROM `tabStock Entry` se,`tabStock Entry Detail` sed
-		WHERE
-			se.name = sed.parent
-			AND se.docstatus=1
-			AND se.purpose='Send to Subcontractor'
-			AND se.purchase_order = %s
-			AND IFNULL(sed.t_warehouse, '') != ''
-			AND IFNULL(sed.subcontracted_item, '') in ('', %s)
-		GROUP BY sed.item_code, sed.subcontracted_item
-	"""
-	raw_materials = frappe.db.multisql({
-		'mariadb': common_query.format(
-			serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
-			batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
-		),
-		'postgres': common_query.format(
-			serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
-			batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
-		)
-	}, (purchase_order, fg_item), as_dict=1)
-
-	return raw_materials
-
-def get_backflushed_subcontracted_raw_materials(purchase_orders):
-	purchase_receipts = frappe.get_all("Purchase Receipt Item",
-		fields = ["purchase_order", "item_code", "name", "parent"],
-		filters={"docstatus": 1, "purchase_order": ("in", list(purchase_orders))})
-
-	distinct_purchase_receipts = {}
-	for pr in purchase_receipts:
-		key = (pr.purchase_order, pr.item_code, pr.parent)
-		distinct_purchase_receipts.setdefault(key, []).append(pr.name)
-
-	backflushed_raw_materials_map = frappe._dict()
-	for args, references in iteritems(distinct_purchase_receipts):
-		purchase_receipt_supplied_items = get_supplied_items(args[1], args[2], references)
-
-		for data in purchase_receipt_supplied_items:
-			pr_key = (data.rm_item_code, data.main_item_code, args[0])
-			if pr_key not in backflushed_raw_materials_map:
-				backflushed_raw_materials_map.setdefault(pr_key, frappe._dict({
-					"qty": 0.0,
-					"serial_no": [],
-					"batch_no": [],
-					"consumed_batch": {}
-				}))
-
-			row = backflushed_raw_materials_map.get(pr_key)
-			row.qty += data.consumed_qty
-
-			for field in ["serial_no", "batch_no"]:
-				if data.get(field):
-					row[field].append(data.get(field))
-
-			if data.get("batch_no"):
-				if data.get("batch_no") in row.consumed_batch:
-					row.consumed_batch[data.get("batch_no")] += data.consumed_qty
-				else:
-					row.consumed_batch[data.get("batch_no")] = data.consumed_qty
-
-	return backflushed_raw_materials_map
-
-def get_supplied_items(item_code, purchase_receipt, references):
-	return frappe.get_all("Purchase Receipt Item Supplied",
-		fields=["rm_item_code", "main_item_code", "consumed_qty", "serial_no", "batch_no"],
-		filters={"main_item_code": item_code, "parent": purchase_receipt, "reference_name": ("in", references)})
-
 def get_asset_item_details(asset_items):
 	asset_items_data = {}
 	for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
@@ -992,135 +720,3 @@
 			error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
 
 		frappe.throw(error_message)
-
-def get_qty_to_be_received(purchase_orders):
-	return frappe._dict(frappe.db.sql("""
-		SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
-		SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
-		FROM `tabPurchase Order Item` poi
-		WHERE
-			poi.`parent` in %s
-		GROUP BY poi.`item_code`, poi.`parent`
-		HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
-	""", (purchase_orders)))
-
-def get_non_stock_items(purchase_order, fg_item_code):
-	return frappe.db.sql("""
-		SELECT
-			pois.main_item_code,
-			pois.rm_item_code,
-			item.description,
-			pois.required_qty AS qty,
-			pois.rate,
-			1 as non_stock_item,
-			pois.stock_uom
-		FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
-		WHERE
-			pois.`rm_item_code` = item.`name`
-			AND item.is_stock_item = 0
-			AND pois.`parent` = %s
-			AND pois.`main_item_code` = %s
-	""", (purchase_order, fg_item_code), as_dict=1)
-
-
-def set_serial_nos(raw_material, consumed_serial_nos, qty):
-	serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
-		set(get_serial_nos(consumed_serial_nos))
-	if serial_nos and qty <= len(serial_nos):
-		raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
-
-def get_transferred_batch_qty_map(purchase_order, fg_item):
-	# returns
-	# {
-	# 	(item_code, fg_code): {
-	# 		batch1: 10, # qty
-	# 		batch2: 16
-	# 	},
-	# }
-	transferred_batch_qty_map = {}
-	transferred_batches = frappe.db.sql("""
-		SELECT
-			sed.batch_no,
-			SUM(sed.qty) AS qty,
-			sed.item_code,
-			sed.subcontracted_item
-		FROM `tabStock Entry` se,`tabStock Entry Detail` sed
-		WHERE
-			se.name = sed.parent
-			AND se.docstatus=1
-			AND se.purpose='Send to Subcontractor'
-			AND se.purchase_order = %s
-			AND ifnull(sed.subcontracted_item, '') in ('', %s)
-			AND sed.batch_no IS NOT NULL
-		GROUP BY
-			sed.batch_no,
-			sed.item_code
-	""", (purchase_order, fg_item), as_dict=1)
-
-	for batch_data in transferred_batches:
-		key = ((batch_data.item_code, fg_item)
-			if batch_data.subcontracted_item else (batch_data.item_code, purchase_order))
-		transferred_batch_qty_map.setdefault(key, OrderedDict())
-		transferred_batch_qty_map[key][batch_data.batch_no] = batch_data.qty
-
-	return transferred_batch_qty_map
-
-def get_backflushed_batch_qty_map(purchase_order, fg_item):
-	# returns
-	# {
-	# 	(item_code, fg_code): {
-	# 		batch1: 10, # qty
-	# 		batch2: 16
-	# 	},
-	# }
-	backflushed_batch_qty_map = {}
-	backflushed_batches = frappe.db.sql("""
-		SELECT
-			pris.batch_no,
-			SUM(pris.consumed_qty) AS qty,
-			pris.rm_item_code AS item_code
-		FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
-		WHERE
-			pr.name = pri.parent
-			AND pri.parent = pris.parent
-			AND pri.purchase_order = %s
-			AND pri.item_code = pris.main_item_code
-			AND pr.docstatus = 1
-			AND pris.main_item_code = %s
-			AND pris.batch_no IS NOT NULL
-		GROUP BY
-			pris.rm_item_code, pris.batch_no
-	""", (purchase_order, fg_item), as_dict=1)
-
-	for batch_data in backflushed_batches:
-		backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
-		backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
-
-	return backflushed_batch_qty_map
-
-def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batches, po):
-	# Returns available batches to be backflushed based on requirements
-	transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
-	if not transferred_batches:
-		transferred_batches = transferred_batch_qty_map.get((item_code, po), {})
-
-	available_batches = []
-
-	for (batch, transferred_qty) in transferred_batches.items():
-		backflushed_qty = backflushed_batches.get(batch, 0)
-		available_qty = transferred_qty - backflushed_qty
-
-		if available_qty >= required_qty:
-			available_batches.append({'batch': batch, 'qty': required_qty})
-			break
-		elif available_qty != 0:
-			available_batches.append({'batch': batch, 'qty': available_qty})
-			required_qty -= available_qty
-
-	for row in available_batches:
-		if backflushed_batches.get(row.get('batch'), 0) > 0:
-			backflushed_batches[row.get('batch')] += row.get('qty')
-		else:
-			backflushed_batches[row.get('batch')] = row.get('qty')
-
-	return available_batches
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 9c29b00..35097b9 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -501,7 +501,6 @@
 			check_if_stock_and_account_balance_synced(self.posting_date,
 				self.company, self.doctype, self.name)
 
-
 @frappe.whitelist()
 def make_quality_inspections(doctype, docname, items):
 	if isinstance(items, str):
@@ -533,21 +532,75 @@
 
 	return inspections
 
-
 def is_reposting_pending():
 	return frappe.db.exists("Repost Item Valuation",
 		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
 
+def future_sle_exists(args, sl_entries=None):
+	key = (args.voucher_type, args.voucher_no)
 
-def future_sle_exists(args):
-	sl_entries = frappe.get_all("Stock Ledger Entry",
+	if validate_future_sle_not_exists(args, key, sl_entries):
+		return False
+	elif get_cached_data(args, key):
+		return True
+
+	if not sl_entries:
+		sl_entries = get_sle_entries_against_voucher(args)
+		if not sl_entries:
+			return
+
+	or_conditions = get_conditions_to_validate_future_sle(sl_entries)
+
+	data = frappe.db.sql("""
+		select item_code, warehouse, count(name) as total_row
+		from `tabStock Ledger Entry`
+		where
+			({})
+			and timestamp(posting_date, posting_time)
+				>= timestamp(%(posting_date)s, %(posting_time)s)
+			and voucher_no != %(voucher_no)s
+			and is_cancelled = 0
+		GROUP BY
+			item_code, warehouse
+		""".format(" or ".join(or_conditions)), args, as_dict=1)
+
+	for d in data:
+		frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
+
+	return len(data)
+
+def validate_future_sle_not_exists(args, key, sl_entries=None):
+	item_key = ''
+	if args.get('item_code'):
+		item_key = (args.get('item_code'), args.get('warehouse'))
+
+	if not sl_entries and hasattr(frappe.local, 'future_sle'):
+		if (not frappe.local.future_sle.get(key) or
+			(item_key and item_key not in frappe.local.future_sle.get(key))):
+			return True
+
+def get_cached_data(args, key):
+	if not hasattr(frappe.local, 'future_sle'):
+		frappe.local.future_sle = {}
+
+	if key not in frappe.local.future_sle:
+		frappe.local.future_sle[key] = frappe._dict({})
+
+	if args.get('item_code'):
+		item_key = (args.get('item_code'), args.get('warehouse'))
+		count = frappe.local.future_sle[key].get(item_key)
+
+		return True if (count or count == 0) else False
+	else:
+		return frappe.local.future_sle[key]
+
+def get_sle_entries_against_voucher(args):
+	return frappe.get_all("Stock Ledger Entry",
 		filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
 		fields=["item_code", "warehouse"],
 		order_by="creation asc")
 
-	if not sl_entries:
-		return
-
+def get_conditions_to_validate_future_sle(sl_entries):
 	warehouse_items_map = {}
 	for entry in sl_entries:
 		if entry.warehouse not in warehouse_items_map:
@@ -558,23 +611,10 @@
 	or_conditions = []
 	for warehouse, items in warehouse_items_map.items():
 		or_conditions.append(
-			"warehouse = '{}' and item_code in ({})".format(
-				warehouse,
-				", ".join(frappe.db.escape(item) for item in items)
-			)
-		)
+			f"""warehouse = {frappe.db.escape(warehouse)}
+				and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""")
 
-	return frappe.db.sql("""
-		select name
-		from `tabStock Ledger Entry`
-		where
-			({})
-			and timestamp(posting_date, posting_time)
-				>= timestamp(%(posting_date)s, %(posting_time)s)
-			and voucher_no != %(voucher_no)s
-			and is_cancelled = 0
-		limit 1
-		""".format(" or ".join(or_conditions)), args)
+	return or_conditions
 
 def create_repost_item_valuation_entry(args):
 	args = frappe._dict(args)
diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py
new file mode 100644
index 0000000..36ae110
--- /dev/null
+++ b/erpnext/controllers/subcontracting.py
@@ -0,0 +1,393 @@
+import frappe
+import copy
+from frappe import _
+from frappe.utils import flt, cint, get_link_to_form
+from collections import defaultdict
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+class Subcontracting():
+	def set_materials_for_subcontracted_items(self, raw_material_table):
+		if self.doctype == 'Purchase Invoice' and not self.update_stock:
+			return
+
+		self.raw_material_table = raw_material_table
+		self.__identify_change_in_item_table()
+		self.__prepare_supplied_items()
+		self.__validate_supplied_items()
+
+	def __prepare_supplied_items(self):
+		self.initialized_fields()
+		self.__get_purchase_orders()
+		self.__get_pending_qty_to_receive()
+		self.get_available_materials()
+		self.__remove_changed_rows()
+		self.__set_supplied_items()
+
+	def initialized_fields(self):
+		self.available_materials = frappe._dict()
+		self.__transferred_items = frappe._dict()
+		self.alternative_item_details = frappe._dict()
+		self.__get_backflush_based_on()
+
+	def __get_backflush_based_on(self):
+		self.backflush_based_on = frappe.db.get_single_value("Buying Settings",
+			"backflush_raw_materials_of_subcontract_based_on")
+
+	def __get_purchase_orders(self):
+		self.purchase_orders = []
+
+		if self.doctype == 'Purchase Order':
+			return
+
+		self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order]
+
+	def __identify_change_in_item_table(self):
+		self.__changed_name = []
+		self.__reference_name = []
+
+		if self.doctype == 'Purchase Order' or self.is_new():
+			self.set(self.raw_material_table, [])
+			return
+
+		item_dict = self.__get_data_before_save()
+		if not item_dict:
+			return True
+
+		for n_row in self.items:
+			self.__reference_name.append(n_row.name)
+			if (n_row.name not in item_dict) or (n_row.item_code, n_row.qty) != item_dict[n_row.name]:
+				self.__changed_name.append(n_row.name)
+
+			if item_dict.get(n_row.name):
+				del item_dict[n_row.name]
+
+		self.__changed_name.extend(item_dict.keys())
+
+	def __get_data_before_save(self):
+		item_dict = {}
+		if self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self._doc_before_save:
+			for row in self._doc_before_save.get('items'):
+				item_dict[row.name] = (row.item_code, row.qty)
+
+		return item_dict
+
+	def get_available_materials(self):
+		''' Get the available raw materials which has been transferred to the supplier.
+			available_materials = {
+				(item_code, subcontracted_item, purchase_order): {
+					'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
+				}
+			}
+		'''
+		if not self.purchase_orders:
+			return
+
+		for row in self.__get_transferred_items():
+			key = (row.rm_item_code, row.main_item_code, row.purchase_order)
+
+			if key not in self.available_materials:
+				self.available_materials.setdefault(key, frappe._dict({'qty': 0, 'serial_no': [],
+					'batch_no': defaultdict(float), 'item_details': row, 'po_details': []})
+				)
+
+			details = self.available_materials[key]
+			details.qty += row.qty
+			details.po_details.append(row.po_detail)
+
+			if row.serial_no:
+				details.serial_no.extend(get_serial_nos(row.serial_no))
+
+			if row.batch_no:
+				details.batch_no[row.batch_no] += row.qty
+
+			self.__set_alternative_item_details(row)
+
+		self.__transferred_items = copy.deepcopy(self.available_materials)
+		for doctype in ['Purchase Receipt', 'Purchase Invoice']:
+			self.__update_consumed_materials(doctype)
+
+	def __update_consumed_materials(self, doctype, return_consumed_items=False):
+		'''Deduct the consumed materials from the available materials.'''
+
+		pr_items = self.__get_received_items(doctype)
+		if not pr_items:
+			return ([], {}) if return_consumed_items else None
+
+		pr_items = {d.name: d.get(self.get('po_field') or 'purchase_order') for d in pr_items}
+		consumed_materials = self.__get_consumed_items(doctype, pr_items.keys())
+
+		if return_consumed_items:
+			return (consumed_materials, pr_items)
+
+		for row in consumed_materials:
+			key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
+			if not self.available_materials.get(key):
+				continue
+
+			self.available_materials[key]['qty'] -= row.consumed_qty
+			if row.serial_no:
+				self.available_materials[key]['serial_no'] = list(
+					set(self.available_materials[key]['serial_no']) - set(get_serial_nos(row.serial_no))
+				)
+
+			if row.batch_no:
+				self.available_materials[key]['batch_no'][row.batch_no] -= row.consumed_qty
+
+	def __get_transferred_items(self):
+		fields = ['`tabStock Entry`.`purchase_order`']
+		alias_dict = {'item_code': 'rm_item_code', 'subcontracted_item': 'main_item_code', 'basic_rate': 'rate'}
+
+		child_table_fields = ['item_code', 'item_name', 'description', 'qty', 'basic_rate', 'amount',
+			'serial_no', 'uom', 'subcontracted_item', 'stock_uom', 'batch_no', 'conversion_factor',
+			's_warehouse', 't_warehouse', 'item_group', 'po_detail']
+
+		if self.backflush_based_on == 'BOM':
+			child_table_fields.append('original_item')
+
+		for field in child_table_fields:
+			fields.append(f'`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}')
+
+		filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purpose', '=', 'Send to Subcontractor'],
+			['Stock Entry', 'purchase_order', 'in', self.purchase_orders]]
+
+		return frappe.get_all('Stock Entry', fields = fields, filters=filters)
+
+	def __get_received_items(self, doctype):
+		fields = []
+		self.po_field = 'purchase_order'
+
+		for field in ['name', self.po_field, 'parent']:
+			fields.append(f'`tab{doctype} Item`.`{field}`')
+
+		filters = [[doctype, 'docstatus', '=', 1], [f'{doctype} Item', self.po_field, 'in', self.purchase_orders]]
+		if doctype == 'Purchase Invoice':
+			filters.append(['Purchase Invoice', 'update_stock', "=", 1])
+
+		return frappe.get_all(f'{doctype}', fields = fields, filters = filters)
+
+	def __get_consumed_items(self, doctype, pr_items):
+		return frappe.get_all('Purchase Receipt Item Supplied',
+			fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'],
+			filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items)), 'parenttype': doctype})
+
+	def __set_alternative_item_details(self, row):
+		if row.get('original_item'):
+			self.alternative_item_details[row.get('original_item')] = row
+
+	def __get_pending_qty_to_receive(self):
+		'''Get qty to be received against the purchase order.'''
+
+		self.qty_to_be_received = defaultdict(float)
+
+		if self.doctype != 'Purchase Order' and self.backflush_based_on != 'BOM' and self.purchase_orders:
+			for row in frappe.get_all('Purchase Order Item',
+				fields = ['item_code', '(qty - received_qty) as qty', 'parent', 'name'],
+				filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}):
+
+				self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
+
+	def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
+		doctype = 'BOM Item' if not exploded_item else 'BOM Explosion Item'
+		fields = [f'`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit']
+
+		alias_dict = {'item_code': 'rm_item_code', 'name': 'bom_detail_no', 'source_warehouse': 'reserve_warehouse'}
+		for field in ['item_code', 'name', 'rate', 'stock_uom',
+			'source_warehouse', 'description', 'item_name', 'stock_uom']:
+			fields.append(f'`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}')
+
+		filters = [[doctype, 'parent', '=', bom_no], [doctype, 'docstatus', '=', 1],
+			['BOM', 'item', '=', item_code], [doctype, 'sourced_by_supplier', '=', 0]]
+
+		return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or []
+
+	def __remove_changed_rows(self):
+		if not self.__changed_name:
+			return
+
+		i=1
+		self.set(self.raw_material_table, [])
+		for d in self._doc_before_save.supplied_items:
+			if d.reference_name in self.__changed_name:
+				continue
+
+			if (d.reference_name not in self.__reference_name):
+				continue
+
+			d.idx = i
+			self.append('supplied_items', d)
+
+			i += 1
+
+	def __set_supplied_items(self):
+		self.bom_items = {}
+
+		has_supplied_items = True if self.get(self.raw_material_table) else False
+		for row in self.items:
+			if (self.doctype != 'Purchase Order' and ((self.__changed_name and row.name not in self.__changed_name)
+				or (has_supplied_items and not self.__changed_name))):
+				continue
+
+			if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM':
+				for bom_item in self.__get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')):
+					qty = (flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor)
+					bom_item.main_item_code = row.item_code
+					self.__update_reserve_warehouse(bom_item, row)
+					self.__set_alternative_item(bom_item)
+					self.__add_supplied_item(row, bom_item, qty)
+
+			elif self.backflush_based_on != 'BOM':
+				for key, transfer_item in self.available_materials.items():
+					if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0:
+						qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
+						transfer_item.qty -= qty
+						self.__add_supplied_item(row, transfer_item.get('item_details'), qty)
+
+				if self.qty_to_be_received:
+					self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty
+
+	def __update_reserve_warehouse(self, row, item):
+		if self.doctype == 'Purchase Order':
+			row.reserve_warehouse = (self.set_reserve_warehouse or item.warehouse)
+
+	def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
+		key = (item_row.item_code, item_row.purchase_order)
+
+		if self.qty_to_be_received == item_row.qty:
+			return transfer_item.qty
+
+		if self.qty_to_be_received:
+			qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
+			transfer_item.item_details.required_qty = transfer_item.qty
+
+			if (transfer_item.serial_no or frappe.get_cached_value('UOM',
+				transfer_item.item_details.stock_uom, 'must_be_whole_number')):
+				return frappe.utils.ceil(qty)
+
+			return qty
+
+	def __set_alternative_item(self, bom_item):
+		if self.alternative_item_details.get(bom_item.rm_item_code):
+			bom_item.update(self.alternative_item_details[bom_item.rm_item_code])
+
+	def __add_supplied_item(self, item_row, bom_item, qty):
+		bom_item.conversion_factor = item_row.conversion_factor
+		rm_obj = self.append(self.raw_material_table, bom_item)
+		rm_obj.reference_name = item_row.name
+
+		if self.doctype == 'Purchase Order':
+			rm_obj.required_qty = qty
+		else:
+			rm_obj.consumed_qty = 0
+			rm_obj.purchase_order = item_row.purchase_order
+			self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
+
+	def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
+		key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
+
+		if (self.available_materials.get(key) and self.available_materials[key]['batch_no']):
+			new_rm_obj = None
+			for batch_no, batch_qty in self.available_materials[key]['batch_no'].items():
+				if batch_qty >= qty:
+					self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
+					self.available_materials[key]['batch_no'][batch_no] -= qty
+					return
+
+				elif qty > 0 and batch_qty > 0:
+					qty -= batch_qty
+					new_rm_obj = self.append(self.raw_material_table, bom_item)
+					new_rm_obj.reference_name = item_row.name
+					self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
+					self.available_materials[key]['batch_no'][batch_no] = 0
+
+			if abs(qty) > 0 and not new_rm_obj:
+				self.__set_consumed_qty(rm_obj, qty)
+		else:
+			self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
+			self.__set_serial_nos(item_row, rm_obj)
+
+	def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
+		rm_obj.required_qty = required_qty
+		rm_obj.consumed_qty = consumed_qty
+
+	def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
+		rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no,
+			'required_qty': qty, 'purchase_order': item_row.purchase_order})
+
+		self.__set_serial_nos(item_row, rm_obj)
+
+	def __set_serial_nos(self, item_row, rm_obj):
+		key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
+		if (self.available_materials.get(key) and self.available_materials[key]['serial_no']):
+			used_serial_nos = self.available_materials[key]['serial_no'][0: cint(rm_obj.consumed_qty)]
+			rm_obj.serial_no = '\n'.join(used_serial_nos)
+
+			# Removed the used serial nos from the list
+			for sn in used_serial_nos:
+				self.available_materials[key]['serial_no'].remove(sn)
+
+	def set_consumed_qty_in_po(self):
+		# Update consumed qty back in the purchase order
+		if self.is_subcontracted != 'Yes':
+			return
+
+		self.__get_purchase_orders()
+		itemwise_consumed_qty = defaultdict(float)
+		for doctype in ['Purchase Receipt', 'Purchase Invoice']:
+			consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True)
+
+			for row in consumed_items:
+				key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
+				itemwise_consumed_qty[key] += row.consumed_qty
+
+		self.__update_consumed_qty_in_po(itemwise_consumed_qty)
+
+	def __update_consumed_qty_in_po(self, itemwise_consumed_qty):
+		fields = ['main_item_code', 'rm_item_code', 'parent', 'supplied_qty', 'name']
+		filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}
+
+		for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters, order_by='idx'):
+			key = (row.rm_item_code, row.main_item_code, row.parent)
+			consumed_qty = itemwise_consumed_qty.get(key, 0)
+
+			if row.supplied_qty < consumed_qty:
+				consumed_qty = row.supplied_qty
+
+			itemwise_consumed_qty[key] -= consumed_qty
+			frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty)
+
+	def __validate_supplied_items(self):
+		if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
+			return
+
+		for row in self.get(self.raw_material_table):
+			self.__validate_consumed_qty(row)
+
+			key = (row.rm_item_code, row.main_item_code, row.purchase_order)
+			if not self.__transferred_items or not self.__transferred_items.get(key):
+				return
+
+			self.__validate_batch_no(row, key)
+			self.__validate_serial_no(row, key)
+
+	def __validate_consumed_qty(self, row):
+		if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0:
+			msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
+
+			frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
+
+	def __validate_batch_no(self, row, key):
+		if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
+			link = get_link_to_form('Purchase Order', row.purchase_order)
+			msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
+			frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
+
+	def __validate_serial_no(self, row, key):
+		if row.get('serial_no'):
+			serial_nos = get_serial_nos(row.get('serial_no'))
+			incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no'))
+
+			if incorrect_sn:
+				incorrect_sn = "\n".join(incorrect_sn)
+				link = get_link_to_form('Purchase Order', row.purchase_order)
+				msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
+				frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index 69d11a8..ff7fbbd 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -60,8 +60,9 @@
 			self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
 
 	def check_sanctioned_amount_limit(self):
-		total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 		sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
+		if sanctioned_amount_limit:
+			total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 
 		if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
 			frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
@@ -155,9 +156,29 @@
 	frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
 
 def get_total_loan_amount(applicant_type, applicant, company):
-	return frappe.db.get_value('Loan',
-		{'applicant_type': applicant_type, 'company': company, 'applicant': applicant, 'docstatus': 1},
-		'sum(loan_amount)')
+	pending_amount = 0
+	loan_details = frappe.db.get_all("Loan",
+		filters={"applicant_type": applicant_type, "company": company, "applicant": applicant, "docstatus": 1,
+			"status": ("!=", "Closed")},
+		fields=["status", "total_payment", "disbursed_amount", "total_interest_payable", "total_principal_paid",
+			"written_off_amount"])
+
+	interest_amount = flt(frappe.db.get_value("Loan Interest Accrual", {"applicant_type": applicant_type,
+		"company": company, "applicant": applicant, "docstatus": 1}, "sum(interest_amount - paid_interest_amount)"))
+
+	for loan in loan_details:
+		if loan.status in ("Disbursed", "Loan Closure Requested"):
+			pending_amount += flt(loan.total_payment) - flt(loan.total_interest_payable) \
+				- flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+		elif loan.status == "Partially Disbursed":
+			pending_amount += flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
+				- flt(loan.total_principal_paid) - flt(loan.written_off_amount)
+		elif loan.status == "Sanctioned":
+			pending_amount += flt(loan.total_payment)
+
+	pending_amount += interest_amount
+
+	return pending_amount
 
 def get_sanctioned_amount_limit(applicant_type, applicant, company):
 	return frappe.db.get_value('Sanctioned Loan Amount',
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index fa4707c..314f58d 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -49,7 +49,11 @@
 		if not frappe.db.exists("Customer", "_Test Loan Customer"):
 			frappe.get_doc(get_customer_dict('_Test Loan Customer')).insert(ignore_permissions=True)
 
-		self.applicant2 = frappe.db.get_value("Customer", {'name': '_Test Loan Customer'}, 'name')
+		if not frappe.db.exists("Customer", "_Test Loan Customer 1"):
+			frappe.get_doc(get_customer_dict("_Test Loan Customer 1")).insert(ignore_permissions=True)
+
+		self.applicant2 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name")
+		self.applicant3 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer 1"}, "name")
 
 		create_loan(self.applicant1, "Personal Loan", 280000, "Repay Over Number of Periods", 20)
 
@@ -125,6 +129,38 @@
 		self.assertTrue(gl_entries1)
 		self.assertTrue(gl_entries2)
 
+	def test_sanctioned_amount_limit(self):
+		# Clear loan docs before checking
+		frappe.db.sql("DELETE FROM `tabLoan` where applicant = '_Test Loan Customer 1'")
+		frappe.db.sql("DELETE FROM `tabLoan Application` where applicant = '_Test Loan Customer 1'")
+		frappe.db.sql("DELETE FROM `tabLoan Security Pledge` where applicant = '_Test Loan Customer 1'")
+
+		if not frappe.db.get_value("Sanctioned Loan Amount", filters={"applicant_type": "Customer",
+			"applicant": "_Test Loan Customer 1", "company": "_Test Company"}):
+			frappe.get_doc({
+				"doctype": "Sanctioned Loan Amount",
+				"applicant_type": "Customer",
+				"applicant": "_Test Loan Customer 1",
+				"sanctioned_amount_limit": 1500000,
+				"company": "_Test Company"
+			}).insert(ignore_permissions=True)
+
+		# Make First Loan
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+		loan = create_demand_loan(self.applicant3, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		# Make second loan greater than the sanctioned amount
+		loan_application = create_loan_application('_Test Company', self.applicant3, 'Demand Loan', pledge,
+			do_not_save=True)
+		self.assertRaises(frappe.ValidationError, loan_application.save)
+
 	def test_regular_loan_repayment(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
@@ -367,7 +403,7 @@
 		unpledge_request.load_from_db()
 		self.assertEqual(unpledge_request.docstatus, 1)
 
-	def test_santined_loan_security_unpledge(self):
+	def test_sanctioned_loan_security_unpledge(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
 			"qty": 4000.00
@@ -858,7 +894,7 @@
 	return lr
 
 def create_loan_application(company, applicant, loan_type, proposed_pledges, repayment_method=None,
-	repayment_periods=None, posting_date=None):
+	repayment_periods=None, posting_date=None, do_not_save=False):
 	loan_application = frappe.new_doc('Loan Application')
 	loan_application.applicant_type = 'Customer'
 	loan_application.company = company
@@ -874,6 +910,9 @@
 	for pledge in proposed_pledges:
 		loan_application.append('proposed_pledges', pledge)
 
+	if do_not_save:
+		return loan_application
+
 	loan_application.save()
 	loan_application.submit()
 
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py
index 9c0147e..d8f3577 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.py
@@ -46,9 +46,11 @@
 			frappe.throw(_("Loan Amount exceeds maximum loan amount of {0} as per proposed securities").format(self.maximum_loan_amount))
 
 	def check_sanctioned_amount_limit(self):
-		total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
 		sanctioned_amount_limit = get_sanctioned_amount_limit(self.applicant_type, self.applicant, self.company)
 
+		if sanctioned_amount_limit:
+			total_loan_amount = get_total_loan_amount(self.applicant_type, self.applicant, self.company)
+
 		if sanctioned_amount_limit and flt(self.loan_amount) + flt(total_loan_amount) > flt(sanctioned_amount_limit):
 			frappe.throw(_("Sanctioned Amount limit crossed for {0} {1}").format(self.applicant_type, frappe.bold(self.applicant)))
 
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 3d99b1f..b8b1a40 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -235,70 +235,71 @@
 		else:
 			remarks = _("Repayment against Loan: ") + self.against_loan
 
-		if self.total_penalty_paid:
+		if not loan_details.repay_from_salary:
+			if self.total_penalty_paid:
+				gle_map.append(
+					self.get_gl_dict({
+						"account": loan_details.loan_account,
+						"against": loan_details.payment_account,
+						"debit": self.total_penalty_paid,
+						"debit_in_account_currency": self.total_penalty_paid,
+						"against_voucher_type": "Loan",
+						"against_voucher": self.against_loan,
+						"remarks": _("Penalty against loan:") + self.against_loan,
+						"cost_center": self.cost_center,
+						"party_type": self.applicant_type,
+						"party": self.applicant,
+						"posting_date": getdate(self.posting_date)
+					})
+				)
+
+				gle_map.append(
+					self.get_gl_dict({
+						"account": loan_details.penalty_income_account,
+						"against": loan_details.payment_account,
+						"credit": self.total_penalty_paid,
+						"credit_in_account_currency": self.total_penalty_paid,
+						"against_voucher_type": "Loan",
+						"against_voucher": self.against_loan,
+						"remarks": _("Penalty against loan:") + self.against_loan,
+						"cost_center": self.cost_center,
+						"posting_date": getdate(self.posting_date)
+					})
+				)
+
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.loan_account,
-					"against": loan_details.payment_account,
-					"debit": self.total_penalty_paid,
-					"debit_in_account_currency": self.total_penalty_paid,
+					"account": loan_details.payment_account,
+					"against": loan_details.loan_account + ", " + loan_details.interest_income_account
+							+ ", " + loan_details.penalty_income_account,
+					"debit": self.amount_paid,
+					"debit_in_account_currency": self.amount_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
-					"remarks": _("Penalty against loan:") + self.against_loan,
+					"remarks": remarks,
 					"cost_center": self.cost_center,
-					"party_type": self.applicant_type,
-					"party": self.applicant,
 					"posting_date": getdate(self.posting_date)
 				})
 			)
 
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.penalty_income_account,
+					"account": loan_details.loan_account,
+					"party_type": loan_details.applicant_type,
+					"party": loan_details.applicant,
 					"against": loan_details.payment_account,
-					"credit": self.total_penalty_paid,
-					"credit_in_account_currency": self.total_penalty_paid,
+					"credit": self.amount_paid,
+					"credit_in_account_currency": self.amount_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
-					"remarks": _("Penalty against loan:") + self.against_loan,
+					"remarks": remarks,
 					"cost_center": self.cost_center,
 					"posting_date": getdate(self.posting_date)
 				})
 			)
 
-		gle_map.append(
-			self.get_gl_dict({
-				"account": loan_details.payment_account,
-				"against": loan_details.loan_account + ", " + loan_details.interest_income_account
-						+ ", " + loan_details.penalty_income_account,
-				"debit": self.amount_paid,
-				"debit_in_account_currency": self.amount_paid,
-				"against_voucher_type": "Loan",
-				"against_voucher": self.against_loan,
-				"remarks": remarks,
-				"cost_center": self.cost_center,
-				"posting_date": getdate(self.posting_date)
-			})
-		)
-
-		gle_map.append(
-			self.get_gl_dict({
-				"account": loan_details.loan_account,
-				"party_type": loan_details.applicant_type,
-				"party": loan_details.applicant,
-				"against": loan_details.payment_account,
-				"credit": self.amount_paid,
-				"credit_in_account_currency": self.amount_paid,
-				"against_voucher_type": "Loan",
-				"against_voucher": self.against_loan,
-				"remarks": remarks,
-				"cost_center": self.cost_center,
-				"posting_date": getdate(self.posting_date)
-			})
-		)
-
-		if gle_map:
-			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
+			if gle_map:
+				make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj, merge_entries=False)
 
 def create_repayment_entry(loan, applicant, company, posting_date, loan_type,
 	payment_type, interest_payable, payable_principal_amount, amount_paid, penalty_amount=None):
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index e1cca9e..42b23f2 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -12,6 +12,7 @@
 from six import string_types
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+from erpnext.tests.test_subcontracting import set_backflush_based_on
 
 test_records = frappe.get_test_records('BOM')
 
@@ -160,6 +161,7 @@
 
 	def test_subcontractor_sourced_item(self):
 		item_code = "_Test Subcontracted FG Item 1"
+		set_backflush_based_on('Material Transferred for Subcontract')
 
 		if not frappe.db.exists('Item', item_code):
 			make_item(item_code, {
diff --git a/erpnext/projects/report/project_profitability/test_project_profitability.py b/erpnext/projects/report/project_profitability/test_project_profitability.py
index ea6bdb5..180926f 100644
--- a/erpnext/projects/report/project_profitability/test_project_profitability.py
+++ b/erpnext/projects/report/project_profitability/test_project_profitability.py
@@ -1,7 +1,7 @@
 from __future__ import unicode_literals
 import unittest
 import frappe
-from frappe.utils import getdate, nowdate
+from frappe.utils import getdate, nowdate, add_days
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.projects.doctype.timesheet.test_timesheet import make_salary_structure_for_timesheet, make_timesheet
 from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
@@ -16,17 +16,22 @@
 		make_salary_structure_for_timesheet(emp, company='_Test Company')
 		self.timesheet = make_timesheet(emp, simulate = True, is_billable=1)
 		self.salary_slip = make_salary_slip(self.timesheet.name)
+		holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate())
+		if holidays:
+			frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1)
+
 		self.salary_slip.submit()
 		self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
 		self.sales_invoice.due_date = nowdate()
 		self.sales_invoice.submit()
 
 		frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
+		frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 0)
 
 	def test_project_profitability(self):
 		filters = {
 			'company': '_Test Company',
-			'start_date': getdate(),
+			'start_date': add_days(getdate(), -3),
 			'end_date': getdate()
 		}
 
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 680bf04..86dadd3 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -122,9 +122,20 @@
 			this.set_from_product_bundle();
 		}
 
+		this.toggle_subcontracting_fields();
 		super.refresh();
 	}
 
+	toggle_subcontracting_fields() {
+		if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) {
+			this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
+				'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');
+
+			this.frm.set_df_property('supplied_items', 'cannot_add_rows', 1);
+			this.frm.set_df_property('supplied_items', 'cannot_delete_rows', 1);
+		}
+	}
+
 	supplier() {
 		var me = this;
 		erpnext.utils.get_party_details(this.frm, null, null, function(){
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 7e1ffa9..210237f 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -387,7 +387,7 @@
 
 		if(this.frm.doc.scan_barcode) {
 			frappe.call({
-				method: "erpnext.selling.page.point_of_sale.point_of_sale.search_serial_or_batch_or_barcode_number",
+				method: "erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number",
 				args: { search_value: this.frm.doc.scan_barcode }
 			}).then(r => {
 				const data = r && r.message;
@@ -743,6 +743,10 @@
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
 
+		if (item && item.doctype === 'Purchase Receipt Item Supplied') {
+			return;
+		}
+
 		if (item && item.serial_no) {
 			if (!item.item_code) {
 				this.frm.trigger("item_code", cdt, cdn);
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 0514bd2..4364201 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -54,7 +54,7 @@
 		self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty"))
 		self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))
 		self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty"))
-		
+
 		self.set_projected_qty()
 		self.db_update()
 
@@ -115,7 +115,7 @@
 		#Get Transferred Entries
 		materials_transferred = frappe.db.sql("""
 			select
-				ifnull(sum(transfer_qty),0)
+				ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0)
 			from
 				`tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po
 			where
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
index d5700fe..8f76844 100644
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
@@ -18,6 +18,9 @@
 		make_items()
 
 	def test_alternative_item_for_subcontract_rm(self):
+		frappe.db.set_value('Buying Settings', None,
+			'backflush_raw_materials_of_subcontract_based_on', 'BOM')
+
 		create_stock_reconciliation(item_code='Alternate Item For A RW 1', warehouse='_Test Warehouse - _TC',
 			qty=5, rate=2000)
 		create_stock_reconciliation(item_code='Test FG A RW 2', warehouse='_Test Warehouse - _TC',
@@ -65,6 +68,8 @@
 				status = True
 
 		self.assertEqual(status, True)
+		frappe.db.set_value('Buying Settings', None,
+			'backflush_raw_materials_of_subcontract_based_on', 'Material Transferred for Subcontract')
 
 	def test_alternative_item_for_production_rm(self):
 		create_stock_reconciliation(item_code='Alternate Item For A RW 1',
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index ad350d3..44fb736 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -514,8 +514,7 @@
    "oldfieldname": "pr_raw_material_details",
    "oldfieldtype": "Table",
    "options": "Purchase Receipt Item Supplied",
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "fieldname": "section_break0",
@@ -1149,7 +1148,7 @@
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 01:01:00.754119",
+ "modified": "2021-05-25 00:15:12.239017",
  "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 83ba324..b8580f9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -202,6 +202,7 @@
 
 		self.make_gl_entries()
 		self.repost_future_sle_and_gle()
+		self.set_consumed_qty_in_po()
 
 	def check_next_docstatus(self):
 		submit_rv = frappe.db.sql("""select t1.name
@@ -233,6 +234,7 @@
 		self.repost_future_sle_and_gle()
 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
 		self.delete_auto_created_batches()
+		self.set_consumed_qty_in_po()
 
 	@frappe.whitelist()
 	def get_current_stock(self):
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 8d9b675..95096d7 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -335,6 +335,10 @@
 		se2.cancel()
 		se3.cancel()
 		po.reload()
+		pr2.load_from_db()
+		pr2.cancel()
+
+		po.load_from_db()
 		po.cancel()
 
 	def test_serial_no_supplier(self):
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index b6ae564..908020d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -1079,6 +1079,10 @@
 }
 
 function attach_bom_items(bom_no) {
+	if (!bom_no) {
+		return
+	}
+
 	if (check_should_not_attach_bom_items(bom_no)) return
 	frappe.db.get_doc("BOM",bom_no).then(bom => {
 		const {name, items} = bom
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index a0b5457..523d332 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -74,7 +74,8 @@
   "total_amount",
   "job_card",
   "amended_from",
-  "credit_note"
+  "credit_note",
+  "is_return"
  ],
  "fields": [
   {
@@ -611,6 +612,16 @@
    "fieldname": "apply_putaway_rule",
    "fieldtype": "Check",
    "label": "Apply Putaway Rule"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_return",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Is Return",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "icon": "fa fa-file-text",
@@ -618,7 +629,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-05-24 11:32:23.904307",
+ "modified": "2021-05-26 17:07:58.015737",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 560ceaa..66f8b63 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -97,8 +97,7 @@
 		update_serial_nos_after_submit(self, "items")
 		self.update_work_order()
 		self.validate_purchase_order()
-		if self.purchase_order and self.purpose == "Send to Subcontractor":
-			self.update_purchase_order_supplied_items()
+		self.update_purchase_order_supplied_items()
 
 		self.make_gl_entries()
 
@@ -117,9 +116,7 @@
 			self.set_material_request_transfer_status('Completed')
 
 	def on_cancel(self):
-
-		if self.purchase_order and self.purpose == "Send to Subcontractor":
-			self.update_purchase_order_supplied_items()
+		self.update_purchase_order_supplied_items()
 
 		if self.work_order and self.purpose == "Material Consumption for Manufacture":
 			self.validate_work_order_status()
@@ -1008,10 +1005,12 @@
 					if self.purchase_order and self.purpose == "Send to Subcontractor":
 						#Get PO Supplied Items Details
 						item_wh = frappe._dict(frappe.db.sql("""
-							select rm_item_code, reserve_warehouse
-							from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
-							where po.name = poitemsup.parent
-								and po.name = %s""",self.purchase_order))
+							SELECT
+								rm_item_code, reserve_warehouse
+							FROM
+								`tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
+							WHERE
+								po.name = poitemsup.parent and po.name = %s """,self.purchase_order))
 
 					for item in itervalues(item_dict):
 						if self.pro_doc and cint(self.pro_doc.from_wip_warehouse):
@@ -1294,7 +1293,8 @@
 				item_dict[item]["qty"] = 0
 
 		# delete items with 0 qty
-		for item in item_dict.keys():
+		list_of_items = item_dict.keys()
+		for item in list_of_items:
 			if not item_dict[item]["qty"]:
 				del item_dict[item]
 
@@ -1347,7 +1347,7 @@
 			se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0)
 
 			for field in ["idx", "po_detail", "original_item",
-				"expense_account", "description", "item_name"]:
+				"expense_account", "description", "item_name", "serial_no", "batch_no"]:
 				if item_dict[d].get(field):
 					se_child.set(field, item_dict[d].get(field))
 
@@ -1400,33 +1400,26 @@
 							.format(item.batch_no, item.item_code))
 
 	def update_purchase_order_supplied_items(self):
-		#Get PO Supplied Items Details
-		item_wh = frappe._dict(frappe.db.sql("""
-			select rm_item_code, reserve_warehouse
-			from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
-			where po.name = poitemsup.parent
-			and po.name = %s""", self.purchase_order))
+		if (self.purchase_order and
+			(self.purpose in ['Send to Subcontractor', 'Material Transfer'] or self.is_return)):
 
-		#Update Supplied Qty in PO Supplied Items
+			#Get PO Supplied Items Details
+			item_wh = frappe._dict(frappe.db.sql("""
+				select rm_item_code, reserve_warehouse
+				from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
+				where po.name = poitemsup.parent
+				and po.name = %s""", self.purchase_order))
 
-		frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
-			SET
-				pos.supplied_qty = IFNULL((SELECT ifnull(sum(transfer_qty), 0)
-					FROM
-						`tabStock Entry Detail` sed, `tabStock Entry` se
-					WHERE
-						pos.name = sed.po_detail AND pos.rm_item_code = sed.item_code
-						AND pos.parent = se.purchase_order AND sed.docstatus = 1
-						AND se.name = sed.parent and se.purchase_order = %(po)s
-				), 0)
-			WHERE pos.docstatus = 1 and pos.parent = %(po)s""", {"po": self.purchase_order})
+			supplied_items = get_supplied_items(self.purchase_order)
+			for name, item in supplied_items.items():
+				frappe.db.set_value('Purchase Order Item Supplied', name, item)
 
-		#Update reserved sub contracted quantity in bin based on Supplied Item Details and
-		for d in self.get("items"):
-			item_code = d.get('original_item') or d.get('item_code')
-			reserve_warehouse = item_wh.get(item_code)
-			stock_bin = get_bin(item_code, reserve_warehouse)
-			stock_bin.update_reserved_qty_for_sub_contracting()
+			#Update reserved sub contracted quantity in bin based on Supplied Item Details and
+			for d in self.get("items"):
+				item_code = d.get('original_item') or d.get('item_code')
+				reserve_warehouse = item_wh.get(item_code)
+				stock_bin = get_bin(item_code, reserve_warehouse)
+				stock_bin.update_reserved_qty_for_sub_contracting()
 
 	def update_so_in_serial_number(self):
 		so_name, item_code = frappe.db.get_value("Work Order", self.work_order, ["sales_order", "production_item"])
@@ -1480,7 +1473,7 @@
 				cond += """ WHEN (parent = %s and name = %s) THEN %s
 					""" %(frappe.db.escape(data[0]), frappe.db.escape(data[1]), transferred_qty)
 
-			if cond and stock_entries_child_list:
+			if stock_entries_child_list:
 				frappe.db.sql(""" UPDATE `tabStock Entry Detail`
 					SET
 						transferred_qty = CASE {cond} END
@@ -1751,3 +1744,30 @@
 			format(max_retain_qty, batch_no, item_code), alert=True)
 		sample_quantity = qty_diff
 	return sample_quantity
+
+def get_supplied_items(purchase_order):
+	fields = ['`tabStock Entry Detail`.`transfer_qty`', '`tabStock Entry`.`is_return`',
+			'`tabStock Entry Detail`.`po_detail`', '`tabStock Entry Detail`.`item_code`']
+
+	filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purchase_order', '=', purchase_order]]
+
+	supplied_item_details = {}
+	for row in frappe.get_all('Stock Entry', fields = fields, filters = filters):
+		if not row.po_detail:
+			continue
+
+		key = row.po_detail
+		if key not in supplied_item_details:
+			supplied_item_details.setdefault(key,
+				frappe._dict({'supplied_qty': 0, 'returned_qty':0, 'total_supplied_qty':0}))
+
+		supplied_item = supplied_item_details[key]
+
+		if row.is_return:
+			supplied_item.returned_qty += row.transfer_qty
+		else:
+			supplied_item.supplied_qty += row.transfer_qty
+
+		supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty)
+
+	return supplied_item_details
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index b0e7440..0febcb6 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, date_diff
+from frappe.utils import flt, getdate, add_days, formatdate, get_datetime, cint
 from frappe.model.document import Document
 from datetime import date
 from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
@@ -108,17 +108,18 @@
 		self.stock_uom = item_det.stock_uom
 
 	def check_stock_frozen_date(self):
-		stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
-		if stock_frozen_upto:
-			stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
-			if getdate(self.posting_date) <= getdate(stock_frozen_upto) and not stock_auth_role in frappe.get_roles():
-				frappe.throw(_("Stock transactions before {0} are frozen").format(formatdate(stock_frozen_upto)), StockFreezeError)
+		stock_settings = frappe.get_doc('Stock Settings', 'Stock Settings')
 
-		stock_frozen_upto_days = int(frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto_days') or 0)
+		if stock_settings.stock_frozen_upto:
+			if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto)
+				and stock_settings.stock_auth_role not in frappe.get_roles()):
+				frappe.throw(_("Stock transactions before {0} are frozen")
+					.format(formatdate(stock_settings.stock_frozen_upto)), StockFreezeError)
+
+		stock_frozen_upto_days = cint(stock_settings.stock_frozen_upto_days)
 		if stock_frozen_upto_days:
-			stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
 			older_than_x_days_ago = (add_days(getdate(self.posting_date), stock_frozen_upto_days) <= date.today())
-			if older_than_x_days_ago and not stock_auth_role in frappe.get_roles():
+			if older_than_x_days_ago and stock_settings.stock_auth_role not in frappe.get_roles():
 				frappe.throw(_("Not allowed to update stock transactions older than {0}").format(stock_frozen_upto_days), StockFreezeError)
 
 	def scrub_posting_time(self):
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index bcb40d8..93ab40a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -473,6 +473,13 @@
 		else:
 			self._submit()
 
+	def cancel(self):
+		if len(self.items) > 100:
+			msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"))
+			self.queue_action('cancel', timeout=2000)
+		else:
+			self._cancel()
+
 @frappe.whitelist()
 def get_items(warehouse, posting_date, posting_time, company):
 	lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index 95478f6..e3981c9 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -11,6 +11,7 @@
 import erpnext
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
+from erpnext.stock.doctype.item.test_item import create_item
 
 test_records = frappe.get_test_records('Warehouse')
 
@@ -92,6 +93,39 @@
 		self.assertTrue(frappe.db.get_value("Warehouse",
 			filters={"account": "Test Warehouse for Merging 2 - TCP1"}))
 
+	def test_unlinking_warehouse_from_item_defaults(self):
+		company = "_Test Company"
+
+		warehouse_names = [f'_Test Warehouse {i} for Unlinking' for i in range(2)]
+		warehouse_ids = []
+		for warehouse in warehouse_names:
+			warehouse_id = create_warehouse(warehouse, company=company)
+			warehouse_ids.append(warehouse_id)
+
+		item_names = [f'_Test Item {i} for Unlinking' for i in range(2)]
+		for item, warehouse in zip(item_names, warehouse_ids):
+			create_item(item, warehouse=warehouse, company=company)
+
+		# Delete warehouses
+		for warehouse in warehouse_ids:
+			frappe.delete_doc("Warehouse", warehouse)
+
+		# Check Item existance
+		for item in item_names:
+			self.assertTrue(
+				bool(frappe.db.exists("Item", item)),
+				f"{item} doesn't exist"
+			)
+
+			item_doc = frappe.get_doc("Item", item)
+			for item_default in item_doc.item_defaults:
+				self.assertNotIn(
+					item_default.default_warehouse,
+					warehouse_ids,
+					f"{item} linked to {item_default.default_warehouse} in {warehouse_ids}."
+				)
+
+
 def create_warehouse(warehouse_name, properties=None, company=None):
 	if not company:
 		company = "_Test Company"
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 2062bdd..3abc139 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -54,6 +54,7 @@
 			throw(_("Child warehouse exists for this warehouse. You can not delete this warehouse."))
 
 		self.update_nsm_model()
+		self.unlink_from_items()
 
 	def check_if_sle_exists(self):
 		return frappe.db.sql("""select name from `tabStock Ledger Entry`
@@ -138,6 +139,12 @@
 			self.save()
 			return 1
 
+	def unlink_from_items(self):
+		frappe.db.sql("""
+				update `tabItem Default`
+				set default_warehouse=NULL
+				where default_warehouse=%s""", self.name)
+
 @frappe.whitelist()
 def get_children(doctype, parent=None, company=None, is_root=False):
 	if is_root:
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index fc82c78..9fe89c3 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -22,6 +22,7 @@
 # _exceptions = []
 
 def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
+	from erpnext.controllers.stock_controller import future_sle_exists
 	if sl_entries:
 		from erpnext.stock.utils import update_bin
 
@@ -30,6 +31,9 @@
 			validate_cancellation(sl_entries)
 			set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
 
+		args = get_args_for_future_sle(sl_entries[0])
+		future_sle_exists(args, sl_entries)
+
 		for sle in sl_entries:
 			if sle.serial_no:
 				validate_serial_no(sle)
@@ -53,6 +57,14 @@
 			args = sle_doc.as_dict()
 			update_bin(args, allow_negative_stock, via_landed_cost_voucher)
 
+def get_args_for_future_sle(row):
+	return frappe._dict({
+		'voucher_type': row.get('voucher_type'),
+		'voucher_no': row.get('voucher_no'),
+		'posting_date': row.get('posting_date'),
+		'posting_time': row.get('posting_time')
+	})
+
 def validate_serial_no(sle):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 	for sn in get_serial_nos(sle.serial_no):
@@ -472,7 +484,7 @@
 			frappe.db.set_value("Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate)
 
 		# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
-		if frappe.db.get_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"):
+		if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes':
 			doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
 			doc.update_valuation_rate(reset_outgoing_rate=False)
 			for d in (doc.items + doc.supplied_items):
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 034d3eb..8a6a3a3 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -177,7 +177,7 @@
 	return bin_obj
 
 def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
-	is_stock_item = frappe.db.get_value('Item', args.get("item_code"), 'is_stock_item')
+	is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
 	if is_stock_item:
 		bin = get_bin(args.get("item_code"), args.get("warehouse"))
 		bin.update_stock(args, allow_negative_stock, via_landed_cost_voucher)
diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py
new file mode 100644
index 0000000..8b0ce09
--- /dev/null
+++ b/erpnext/tests/test_subcontracting.py
@@ -0,0 +1,877 @@
+from __future__ import unicode_literals
+import frappe
+import unittest
+import copy
+from frappe.utils import cint
+from collections import defaultdict
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+from erpnext.buying.doctype.purchase_order.purchase_order import (make_rm_stock_entry,
+	make_purchase_receipt, make_purchase_invoice, get_materials_from_supplier)
+
+class TestSubcontracting(unittest.TestCase):
+	def setUp(self):
+		make_subcontract_items()
+		make_raw_materials()
+		make_bom_for_subcontracted_items()
+
+	def test_po_with_bom(self):
+		'''
+			- Set backflush based on BOM
+			- Create subcontracted PO for the item Subcontracted Item SA1 and add same item two times.
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Create purchase receipt against the PO and check serial nos and batch no.
+		'''
+
+		set_backflush_based_on('BOM')
+		item_code = 'Subcontracted Item SA1'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 5, 'rate': 100},
+			{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 6, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 5},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 5},
+			{'item_code': 'Subcontracted SRM Item 1', 'qty': 6},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 6},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 6}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+
+			for field in ['qty', 'serial_no', 'batch_no']:
+				if value.get(field):
+					transfer, consumed = (transferred_detais.get(field), value.get(field))
+					if field == 'serial_no':
+						transfer, consumed = (sorted(transfer), sorted(consumed))
+
+					self.assertEqual(transfer, consumed)
+
+	def test_po_with_material_transfer(self):
+		'''
+			- Set backflush based on Material Transfer
+			- Create subcontracted PO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
+			- Create partial purchase receipt against the PO and check serial nos and batch no.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA1', 'qty': 5, 'rate': 100},
+			{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA5', 'qty': 6, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
+			{'item_code': 'Subcontracted SRM Item 5', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'},
+			{'item_code': 'Subcontracted SRM Item 4', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+
+		make_stock_transfer_entry(po_no = po.name,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.remove(pr1.items[1])
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+
+			for field in ['qty', 'serial_no', 'batch_no']:
+				if value.get(field):
+					self.assertEqual(value.get(field), transferred_detais.get(field))
+
+		pr2 = make_purchase_receipt(po.name)
+		pr2.submit()
+
+		for key, value in get_supplied_items(pr2).items():
+			transferred_detais = itemwise_details.get(key)
+
+			for field in ['qty', 'serial_no', 'batch_no']:
+				if value.get(field):
+					self.assertEqual(value.get(field), transferred_detais.get(field))
+
+	def test_subcontract_with_same_components_different_fg(self):
+		'''
+			- Set backflush based on Material Transfer
+			- Create subcontracted PO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
+			- Transfer the components from Stores to Supplier warehouse with serial nos.
+			- Transfer extra qty of components for the item Subcontracted Item SA2.
+			- Create partial purchase receipt against the PO and check serial nos and batch no.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100},
+			{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA3', 'qty': 6, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA3'}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+
+		make_stock_transfer_entry(po_no = po.name,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 3
+		pr1.remove(pr1.items[1])
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+			self.assertEqual(value.qty, 4)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:4]))
+
+		pr2 = make_purchase_receipt(po.name)
+		pr2.items[0].qty = 2
+		pr2.remove(pr2.items[1])
+		pr2.submit()
+
+		for key, value in get_supplied_items(pr2).items():
+			transferred_detais = itemwise_details.get(key)
+
+			self.assertEqual(value.qty, 2)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[4:6]))
+
+		pr3 = make_purchase_receipt(po.name)
+		pr3.submit()
+		for key, value in get_supplied_items(pr3).items():
+			transferred_detais = itemwise_details.get(key)
+
+			self.assertEqual(value.qty, 6)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[6:12]))
+
+	def test_return_non_consumed_materials(self):
+		'''
+			- Set backflush based on Material Transfer
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the components from Stores to Supplier warehouse with serial nos.
+			- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
+			- Create purchase receipt for full qty against the PO and change the qty of raw material.
+			- After that return the non consumed material back to the store from supplier's warehouse.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100}]
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.save()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].serial_no = '\n'.join(sorted(
+			itemwise_details.get('Subcontracted SRM Item 2').get('serial_no')[0:5]
+		))
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			transferred_detais = itemwise_details.get(key)
+			self.assertEqual(value.qty, 5)
+			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:5]))
+
+		po.load_from_db()
+		self.assertEqual(po.supplied_items[0].consumed_qty, 5)
+		doc = get_materials_from_supplier(po.name, [d.name for d in po.supplied_items])
+		self.assertEqual(doc.items[0].qty, 1)
+		self.assertEqual(doc.items[0].s_warehouse, '_Test Warehouse 1 - _TC')
+		self.assertEqual(doc.items[0].t_warehouse, '_Test Warehouse - _TC')
+		self.assertEqual(get_serial_nos(doc.items[0].serial_no),
+			itemwise_details.get(doc.items[0].item_code)['serial_no'][5:6])
+
+	def test_item_with_batch_based_on_bom(self):
+		'''
+			- Set backflush based on BOM
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+		'''
+
+		set_backflush_based_on('BOM')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+	def test_item_with_batch_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches with extra 2 qty for the batched item.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+			- In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			qty = 4 if key != 'Subcontracted SRM Item 3' else 6
+			self.assertEqual(value.qty, qty)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 2
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+	def test_partial_transfer_serial_no_components_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the partial components from Stores to Supplier warehouse with serial nos.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with serial nos.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA2'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 5
+		pr1.save()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+	def test_incorrect_serial_no_components_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the serialized componenets to the supplier.
+			- Create purchase receipt and change the serial no which is not transferred.
+			- System should throw the error and not allowed to save the purchase receipt.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA2'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 10}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.save()
+		pr1.supplied_items[0].serial_no = 'ABCD'
+		self.assertRaises(frappe.ValidationError, pr1.save)
+		pr1.delete()
+
+	def test_partial_transfer_batch_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA6.
+			- Transfer the partial components from Stores to Supplier warehouse with batch.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with batch.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA6'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.items[0].qty = 5
+		pr1.save()
+
+		transferred_batch_no = ''
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			transferred_batch_no = details.batch_no
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+
+	def test_item_with_batch_based_on_material_transfer_for_purchase_invoice(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches with extra 2 qty for the batched item.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+			- In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			qty = 4 if key != 'Subcontracted SRM Item 3' else 6
+			self.assertEqual(value.qty, qty)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.items[0].qty = 2
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+	def test_partial_transfer_serial_no_components_based_on_material_transfer_for_purchase_invoice(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the partial components from Stores to Supplier warehouse with serial nos.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with serial nos.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA2'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 5
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+
+	def test_partial_transfer_batch_based_on_material_transfer_for_purchase_invoice(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA6.
+			- Transfer the partial components from Stores to Supplier warehouse with batch.
+			- Create partial purchase receipt against the PO and change the qty manually.
+			- Transfer the remaining components from Stores to Supplier warehouse with batch.
+			- Create purchase receipt for remaining qty against the PO and change the qty manually.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA6'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 5
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+
+		transferred_batch_no = ''
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, 3)
+			transferred_batch_no = details.batch_no
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		pr1.load_from_db()
+		pr1.supplied_items[0].consumed_qty = 5
+		pr1.supplied_items[0].batch_no = list(transferred_batch_no.keys())[0]
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			details = itemwise_details.get(key)
+			self.assertEqual(value.qty, details.qty)
+			self.assertEqual(value.batch_no, details.batch_no)
+
+	def test_item_with_batch_based_on_bom_for_purchase_invoice(self):
+		'''
+			- Set backflush based on BOM
+			- Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+			- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+			- Transfer the components in multiple batches.
+			- Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+			- Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+		'''
+
+		set_backflush_based_on('BOM')
+		item_code = 'Subcontracted Item SA4'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
+			{'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
+		]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		add_second_row_in_pr(pr1)
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 4)
+
+		pr1 = make_purchase_invoice(po.name)
+		pr1.update_stock = 1
+		pr1.items[0].qty = 2
+		pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+		pr1.save()
+		pr1.submit()
+
+		for key, value in get_supplied_items(pr1).items():
+			self.assertEqual(value.qty, 2)
+
+def add_second_row_in_pr(pr):
+	item_dict = {}
+	for column in ['item_code', 'item_name', 'qty', 'uom', 'warehouse', 'stock_uom',
+		'purchase_order', 'purchase_order_item', 'conversion_factor', 'rate', 'expense_account', 'po_detail']:
+		item_dict[column] = pr.items[0].get(column)
+
+	pr.append('items', item_dict)
+	pr.set_missing_values()
+
+def get_supplied_items(pr_doc):
+	supplied_items = {}
+	for row in pr_doc.get('supplied_items'):
+		if row.rm_item_code not in supplied_items:
+			supplied_items.setdefault(row.rm_item_code,
+				frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
+
+		details = supplied_items[row.rm_item_code]
+		update_item_details(row, details)
+
+	return supplied_items
+
+def make_stock_in_entry(**args):
+	args = frappe._dict(args)
+
+	items = {}
+	for row in args.rm_items:
+		row = frappe._dict(row)
+
+		doc = make_stock_entry(target=row.warehouse or '_Test Warehouse - _TC',
+			item_code=row.item_code, qty=row.qty or 1, basic_rate=row.rate or 100)
+
+		if row.item_code not in items:
+			items.setdefault(row.item_code, frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
+
+		child_row = doc.items[0]
+		details = items[child_row.item_code]
+		update_item_details(child_row, details)
+
+	return items
+
+def update_item_details(child_row, details):
+	details.qty += (child_row.get('qty') if child_row.doctype == 'Stock Entry Detail'
+		else child_row.get('consumed_qty'))
+
+	if child_row.serial_no:
+		details.serial_no.extend(get_serial_nos(child_row.serial_no))
+
+	if child_row.batch_no:
+		details.batch_no[child_row.batch_no] += (child_row.get('qty') or child_row.get('consumed_qty'))
+
+def make_stock_transfer_entry(**args):
+	args = frappe._dict(args)
+
+	items = []
+	for row in args.rm_items:
+		row = frappe._dict(row)
+
+		item = {'item_code': row.main_item_code or args.main_item_code, 'rm_item_code': row.item_code,
+			'qty': row.qty or 1, 'item_name': row.item_code, 'rate': row.rate or 100,
+			'stock_uom': row.stock_uom or 'Nos', 'warehouse': row.warehuose or '_Test Warehouse - _TC'}
+
+		item_details = args.itemwise_details.get(row.item_code)
+
+		if item_details and item_details.serial_no:
+			serial_nos = item_details.serial_no[0:cint(row.qty)]
+			item['serial_no'] = '\n'.join(serial_nos)
+			item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos))
+
+		if item_details and item_details.batch_no:
+			for batch_no, batch_qty in item_details.batch_no.items():
+				if batch_qty >= row.qty:
+					item['batch_no'] = batch_no
+					item_details.batch_no[batch_no] -= row.qty
+					break
+
+		items.append(item)
+
+	ste_dict = make_rm_stock_entry(args.po_no, items)
+	doc = frappe.get_doc(ste_dict)
+	doc.insert()
+	doc.submit()
+
+	return doc
+
+def make_subcontract_items():
+	sub_contracted_items = {'Subcontracted Item SA1': {}, 'Subcontracted Item SA2': {}, 'Subcontracted Item SA3': {},
+		'Subcontracted Item SA4': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'SBAT.####'},
+		'Subcontracted Item SA5': {}, 'Subcontracted Item SA6': {}}
+
+	for item, properties in sub_contracted_items.items():
+		if not frappe.db.exists('Item', item):
+			properties.update({'is_stock_item': 1, 'is_sub_contracted_item': 1})
+			make_item(item, properties)
+
+def make_raw_materials():
+	raw_materials = {'Subcontracted SRM Item 1': {},
+		'Subcontracted SRM Item 2': {'has_serial_no': 1, 'serial_no_series': 'SRI.####'},
+		'Subcontracted SRM Item 3': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'BAT.####'},
+		'Subcontracted SRM Item 4': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'},
+		'Subcontracted SRM Item 5': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'}}
+
+	for item, properties in raw_materials.items():
+		if not frappe.db.exists('Item', item):
+			properties.update({'is_stock_item': 1})
+			make_item(item, properties)
+
+def make_bom_for_subcontracted_items():
+	boms = {
+		'Subcontracted Item SA1': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
+		'Subcontracted Item SA2': ['Subcontracted SRM Item 2'],
+		'Subcontracted Item SA3': ['Subcontracted SRM Item 2'],
+		'Subcontracted Item SA4': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
+		'Subcontracted Item SA5': ['Subcontracted SRM Item 5'],
+		'Subcontracted Item SA6': ['Subcontracted SRM Item 3']
+	}
+
+	for item_code, raw_materials in boms.items():
+		if not frappe.db.exists('BOM', {'item': item_code}):
+			make_bom(item=item_code, raw_materials=raw_materials, rate=100)
+
+def set_backflush_based_on(based_on):
+	frappe.db.set_value('Buying Settings', None,
+		'backflush_raw_materials_of_subcontract_based_on', based_on)
\ No newline at end of file