Merge pull request #26240 from rohitwaghchaure/production-plan-sub-assembly-enhancement

feat: provision to make subcontracted purchase order from the production plan
diff --git a/erpnext/hr/doctype/attendance/attendance.js b/erpnext/hr/doctype/attendance/attendance.js
index c3c3cb8..7964078 100644
--- a/erpnext/hr/doctype/attendance/attendance.js
+++ b/erpnext/hr/doctype/attendance/attendance.js
@@ -11,5 +11,5 @@
 cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
 	return{
 		query: "erpnext.controllers.queries.employee_query"
-	}	
+	}
 }
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index f3b8a79..3412675 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -15,6 +15,7 @@
 		validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
 		self.validate_attendance_date()
 		self.validate_duplicate_record()
+		self.validate_employee_status()
 		self.check_leave_record()
 
 	def validate_attendance_date(self):
@@ -38,6 +39,10 @@
 			frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format(
 				frappe.bold(self.employee), frappe.bold(self.attendance_date)))
 
+	def validate_employee_status(self):
+		if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
+			frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))
+
 	def check_leave_record(self):
 		leave_record = frappe.db.sql("""
 			select leave_type, half_day, half_day_date
diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js
index 0c7eafe..9a3bac0 100644
--- a/erpnext/hr/doctype/attendance/attendance_list.js
+++ b/erpnext/hr/doctype/attendance/attendance_list.js
@@ -21,6 +21,9 @@
 						label: __('For Employee'),
 						fieldtype: 'Link',
 						options: 'Employee',
+						get_query: () => {
+							return {query: "erpnext.controllers.queries.employee_query"}
+						},
 						reqd: 1,
 						onchange: function() {
 							dialog.set_df_property("unmarked_days", "hidden", 1);
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
index 18bd4ae..68bac8e 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json
@@ -35,7 +35,9 @@
    "no_copy": 1,
    "options": "Loan Security Pledge",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fetch_from": "loan_application.applicant",
@@ -45,47 +47,63 @@
    "in_standard_filter": 1,
    "label": "Applicant",
    "options": "applicant_type",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "loan_security_details_section",
    "fieldtype": "Section Break",
-   "label": "Loan Security Details"
+   "label": "Loan Security Details",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "column_break_3",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "loan",
    "fieldtype": "Link",
    "label": "Loan",
-   "options": "Loan"
+   "options": "Loan",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "loan_application",
    "fieldtype": "Link",
    "label": "Loan Application",
-   "options": "Loan Application"
+   "options": "Loan Application",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "total_security_value",
    "fieldtype": "Currency",
    "label": "Total Security Value",
    "options": "Company:company:default_currency",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "maximum_loan_value",
    "fieldtype": "Currency",
    "label": "Maximum Loan Value",
    "options": "Company:company:default_currency",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "loan_details_section",
    "fieldtype": "Section Break",
-   "label": "Loan  Details"
+   "label": "Loan  Details",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "default": "Requested",
@@ -94,37 +112,49 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Status",
-   "options": "Requested\nUnpledged\nPledged\nPartially Pledged",
-   "read_only": 1
+   "options": "Requested\nUnpledged\nPledged\nPartially Pledged\nCancelled",
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "pledge_time",
    "fieldtype": "Datetime",
    "label": "Pledge Time",
-   "read_only": 1
+   "read_only": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "securities",
    "fieldtype": "Table",
    "label": "Securities",
    "options": "Pledge",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "column_break_11",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "section_break_10",
    "fieldtype": "Section Break",
-   "label": "Totals"
+   "label": "Totals",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "company",
    "fieldtype": "Link",
    "label": "Company",
    "options": "Company",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fetch_from": "loan.applicant_type",
@@ -132,35 +162,45 @@
    "fieldtype": "Select",
    "label": "Applicant Type",
    "options": "Employee\nMember\nCustomer",
-   "reqd": 1
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "collapsible": 1,
    "fieldname": "more_information_section",
    "fieldtype": "Section Break",
-   "label": "More Information"
+   "label": "More Information",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "allow_on_submit": 1,
    "fieldname": "reference_no",
    "fieldtype": "Data",
-   "label": "Reference No"
+   "label": "Reference No",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "fieldname": "column_break_18",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
    "allow_on_submit": 1,
    "fieldname": "description",
    "fieldtype": "Text",
-   "label": "Description"
+   "label": "Description",
+   "show_days": 1,
+   "show_seconds": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 18:23:16.953305",
+ "modified": "2021-06-29 17:15:16.082256",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Security Pledge",
diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
index cbc8376..c390b6c 100644
--- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
+++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py
@@ -23,6 +23,12 @@
 			update_shortfall_status(self.loan, self.total_security_value)
 			update_loan(self.loan, self.maximum_loan_value)
 
+	def on_cancel(self):
+		if self.loan:
+			self.db_set("status", "Cancelled")
+			self.db_set("pledge_time", None)
+			update_loan(self.loan, self.maximum_loan_value, cancel=1)
+
 	def validate_duplicate_securities(self):
 		security_list = []
 		for security in self.securities:
@@ -36,7 +42,7 @@
 		existing_pledge = ''
 
 		if self.loan:
-			existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan}, ['name'])
+			existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan, 'docstatus': 1}, ['name'])
 
 		if existing_pledge:
 			loan_security_type = frappe.db.get_value('Pledge', {'parent': existing_pledge}, ['loan_security_type'])
@@ -77,8 +83,12 @@
 		self.total_security_value = total_security_value
 		self.maximum_loan_value = maximum_loan_value
 
-def update_loan(loan, maximum_value_against_pledge):
+def update_loan(loan, maximum_value_against_pledge, cancel=0):
 	maximum_loan_value = frappe.db.get_value('Loan', {'name': loan}, ['maximum_loan_amount'])
 
-	frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1
-		WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan))
+	if cancel:
+		frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s
+			WHERE name=%s""", (maximum_loan_value - maximum_value_against_pledge, loan))
+	else:
+		frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1
+			WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan))
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
index 4c31bd0..f19a1b0 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
@@ -13,7 +13,7 @@
 
 	refresh: function(frm) {
 		erpnext.hide_company();
-		if (frm.doc.customer && frm.doc.docstatus === 1) {
+		if (frm.doc.customer && frm.doc.docstatus === 1 && frm.doc.to_date > frappe.datetime.get_today()) {
 			frm.add_custom_button(__("Sales Order"), function() {
 				frappe.model.open_mapped_doc({
 					method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_order",
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json
index 0330e5c..a63fc4d 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "naming_series:",
  "creation": "2018-05-24 07:18:08.256060",
  "doctype": "DocType",
@@ -79,6 +80,7 @@
    "reqd": 1
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "to_date",
    "fieldtype": "Date",
    "label": "To Date",
@@ -129,8 +131,10 @@
    "label": "Terms and Conditions Details"
   }
  ],
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
- "modified": "2019-11-18 19:37:37.151686",
+ "links": [],
+ "modified": "2021-06-29 00:30:30.621636",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Blanket Order",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index e71d81f..5c7c0a3 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -459,6 +459,7 @@
 			where
 				t1.name = t2.employee
 				and t2.docstatus = 1
+				and t1.status != 'Inactive'
 		%s order by t2.from_date desc
 		""" % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True)
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index e646600..dd94e7c 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -404,17 +404,18 @@
 
 			key = (d.item_code, d.warehouse)
 			if key not in merge_similar_entries:
+				d.total_amount = (d.actual_qty * d.valuation_rate)
 				merge_similar_entries[key] = d
 			elif d.serial_no:
 				data = merge_similar_entries[key]
 				data.actual_qty += d.actual_qty
 				data.qty_after_transaction += d.qty_after_transaction
 
-				data.valuation_rate = (data.valuation_rate + d.valuation_rate) / data.actual_qty
+				data.total_amount += (d.actual_qty * d.valuation_rate)
+				data.valuation_rate = (data.total_amount) / data.actual_qty
 				data.serial_no += '\n' + d.serial_no
 
-				if data.incoming_rate:
-					data.incoming_rate = (data.incoming_rate + d.incoming_rate) / data.actual_qty
+				data.incoming_rate = (data.total_amount) / data.actual_qty
 
 		for key, value in merge_similar_entries.items():
 			new_sl_entries.append(value)
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 36380b8..ce4cbd2 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -6,7 +6,7 @@
 
 from __future__ import unicode_literals
 import frappe, unittest
-from frappe.utils import flt, nowdate, nowtime
+from frappe.utils import flt, nowdate, nowtime, random_string
 from erpnext.accounts.utils import get_stock_and_account_balance
 from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
 from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
@@ -150,6 +150,42 @@
 			stock_doc = frappe.get_doc("Stock Reconciliation", d)
 			stock_doc.cancel()
 
+
+	def test_stock_reco_for_merge_serialized_item(self):
+		to_delete_records = []
+
+		# Add new serial nos
+		serial_item_code = "Stock-Reco-Serial-Item-2"
+		serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC"
+
+		sr = create_stock_reconciliation(item_code=serial_item_code, serial_no=random_string(6),
+			warehouse = serial_warehouse, qty=1, rate=100, do_not_submit=True, purpose='Opening Stock')
+
+		for i in range(3):
+			sr.append('items', {
+				'item_code': serial_item_code,
+				'warehouse': serial_warehouse,
+				'qty': 1,
+				'valuation_rate': 100,
+				'serial_no': random_string(6)
+			})
+
+		sr.save()
+		sr.submit()
+
+		sle_entries = frappe.get_all('Stock Ledger Entry', filters= {'voucher_no': sr.name},
+			fields = ['name', 'incoming_rate'])
+
+		self.assertEqual(len(sle_entries), 1)
+		self.assertEqual(sle_entries[0].incoming_rate, 100)
+
+		to_delete_records.append(sr.name)
+		to_delete_records.reverse()
+
+		for d in to_delete_records:
+			stock_doc = frappe.get_doc("Stock Reconciliation", d)
+			stock_doc.cancel()
+
 	def test_stock_reco_for_batch_item(self):
 		to_delete_records = []
 		to_delete_serial_nos = []
@@ -231,6 +267,12 @@
 		serial_item_doc.serial_no_series = "SRSI.####"
 		serial_item_doc.save(ignore_permissions=True)
 
+	serial_item_doc = create_item("Stock-Reco-Serial-Item-2", is_stock_item=1)
+	if not serial_item_doc.has_serial_no:
+		serial_item_doc.has_serial_no = 1
+		serial_item_doc.serial_no_series = "SRSII.####"
+		serial_item_doc.save(ignore_permissions=True)
+
 	batch_item_doc = create_item("Stock-Reco-batch-Item-1", is_stock_item=1)
 	if not batch_item_doc.has_batch_no:
 		batch_item_doc.has_batch_no = 1