feat(india): generate qrcode button for e-invoice (#30939)

diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index f2a696d..a0c0ecc 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2648,6 +2648,7 @@
 		# reset
 		einvoice_settings = frappe.get_doc("E Invoice Settings")
 		einvoice_settings.enable = 0
+		einvoice_settings.save()
 		frappe.flags.country = country
 
 	def test_einvoice_json(self):
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
index c4b27a5..ea56d07 100644
--- a/erpnext/regional/india/e_invoice/einvoice.js
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -204,6 +204,29 @@
 				};
 				add_custom_button(__("Cancel E-Way Bill"), action);
 			}
+
+			if (irn && !irn_cancelled) {
+				const action = () => {
+					const dialog = frappe.msgprint({
+						title: __("Generate QRCode"),
+						message: __("Generate and attach QR Code using IRN?"),
+						primary_action: {
+							action: function() {
+								frappe.call({
+									method: 'erpnext.regional.india.e_invoice.utils.generate_qrcode',
+									args: { doctype, docname: name },
+									freeze: true,
+									callback: () => frm.reload_doc() || dialog.hide(),
+									error: () => dialog.hide()
+								});
+							}
+						},
+						primary_action_label: __('Yes')
+					});
+					dialog.show();
+				};
+				add_custom_button(__("Generate QRCode"), action);
+			}
 		}
 	});
 };
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 53d3211..417f883 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -794,6 +794,7 @@
 		self.gstin_details_url = self.base_url + "/enriched/ei/api/master/gstin"
 		self.cancel_ewaybill_url = self.base_url + "/enriched/ei/api/ewayapi"
 		self.generate_ewaybill_url = self.base_url + "/enriched/ei/api/ewaybill"
+		self.get_qrcode_url = self.base_url + "/enriched/ei/others/qr/image"
 
 	def set_invoice(self):
 		self.invoice = None
@@ -857,8 +858,8 @@
 		return res
 
 	def auto_refresh_token(self):
-		self.fetch_auth_token()
 		self.token_auto_refreshed = True
+		self.fetch_auth_token()
 
 	def log_request(self, url, headers, data, res):
 		headers.update({"password": self.credentials.password})
@@ -998,6 +999,37 @@
 
 		return failed
 
+	def fetch_and_attach_qrcode_from_irn(self):
+		qrcode = self.get_qrcode_from_irn(self.invoice.irn)
+		if qrcode:
+			qrcode_file = self.create_qr_code_file(qrcode)
+			frappe.db.set_value("Sales Invoice", self.invoice.name, "qrcode_image", qrcode_file.file_url)
+			frappe.msgprint(_("QR Code attached to the invoice"), alert=True)
+		else:
+			frappe.msgprint(_("QR Code not found for the IRN"), alert=True)
+
+	def get_qrcode_from_irn(self, irn):
+		import requests
+
+		headers = self.get_headers()
+		headers.update({"width": "215", "height": "215", "imgtype": "jpg", "irn": irn})
+
+		try:
+			# using requests.get instead of make_request to avoid parsing the response
+			res = requests.get(self.get_qrcode_url, headers=headers)
+			self.log_request(self.get_qrcode_url, headers, None, None)
+			if res.status_code == 200:
+				return res.content
+			else:
+				raise RequestFailed(str(res.content, "utf-8"))
+
+		except RequestFailed as e:
+			self.raise_error(errors=str(e))
+
+		except Exception:
+			log_error()
+			self.raise_error()
+
 	def get_irn_details(self, irn):
 		headers = self.get_headers()
 
@@ -1198,8 +1230,6 @@
 		return errors
 
 	def raise_error(self, raise_exception=False, errors=None):
-		if errors is None:
-			errors = []
 		title = _("E Invoice Request Failed")
 		if errors:
 			frappe.throw(errors, title=title, as_list=1)
@@ -1240,13 +1270,18 @@
 
 	def attach_qrcode_image(self):
 		qrcode = self.invoice.signed_qr_code
-		doctype = self.invoice.doctype
-		docname = self.invoice.name
-		filename = "QRCode_{}.png".format(docname).replace(os.path.sep, "__")
 
 		qr_image = io.BytesIO()
 		url = qrcreate(qrcode, error="L")
 		url.png(qr_image, scale=2, quiet_zone=1)
+		qrcode_file = self.create_qr_code_file(qr_image.getvalue())
+		self.invoice.qrcode_image = qrcode_file.file_url
+
+	def create_qr_code_file(self, qr_image):
+		doctype = self.invoice.doctype
+		docname = self.invoice.name
+		filename = "QRCode_{}.png".format(docname).replace(os.path.sep, "__")
+
 		_file = frappe.get_doc(
 			{
 				"doctype": "File",
@@ -1255,12 +1290,12 @@
 				"attached_to_name": docname,
 				"attached_to_field": "qrcode_image",
 				"is_private": 0,
-				"content": qr_image.getvalue(),
+				"content": qr_image,
 			}
 		)
 		_file.save()
 		frappe.db.commit()
-		self.invoice.qrcode_image = _file.file_url
+		return _file
 
 	def update_invoice(self):
 		self.invoice.flags.ignore_validate_update_after_submit = True
@@ -1306,6 +1341,12 @@
 
 
 @frappe.whitelist()
+def generate_qrcode(doctype, docname):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.fetch_and_attach_qrcode_from_irn()
+
+
+@frappe.whitelist()
 def generate_eway_bill(doctype, docname, **kwargs):
 	gsp_connector = GSPConnector(doctype, docname)
 	gsp_connector.generate_eway_bill(**kwargs)