# Part of Odoo. See LICENSE file for full copyright and licensing details. import logging import contextlib from datetime import timedelta from markupsafe import Markup from odoo import fields from odoo.exceptions import AccessError from odoo.addons.l10n_in_edi_ewaybill.models.error_codes import ERROR_CODES from odoo.tools import _, LazyTranslate _lt = LazyTranslate(__name__) _logger = logging.getLogger(__name__) class EWayBillError(Exception): def __init__(self, response): self.error_json = self._set_missing_error_message(response) self.error_json.setdefault('odoo_warning', []) self.error_codes = self.get_error_codes() super().__init__(response) def _set_missing_error_message(self, response): for error in response.get('error', []): if error.get('code') and not error.get('message'): error['message'] = self._find_missing_error_message(error.get('code')) return response @staticmethod def _find_missing_error_message(code): error_message = ERROR_CODES.get(code) return error_message or _("We don't know the error message for this error code. Please contact support.") def get_all_error_message(self): return Markup("
").join( ["[%s] %s" % (e.get("code"), e.get("message")) for e in self.error_json.get('error')] ) def get_error_codes(self): return [e.get("code") for e in self.error_json['error']] class EWayBillApi: DEFAULT_HELP_MESSAGE = _lt( "Somehow this E-waybill has been %s in the government portal before. " "You can verify by checking the details into the government " "(https://ewaybillgst.gov.in/Others/EBPrintnew.asp)" ) def __init__(self, company): company.ensure_one() self.company = company self.env = self.company.env def _ewaybill_jsonrpc_to_server(self, url_path, params): params.update({ "username": self.company.sudo().l10n_in_edi_ewaybill_username, "gstin": self.company.vat, }) try: response = self.env['iap.account']._l10n_in_connect_to_server( is_production=self.company.sudo().l10n_in_edi_production_env, params=params, url_path=url_path, config_parameter="l10n_in_edi_ewaybill.endpoint", timeout=10 ) if response.get('error'): raise EWayBillError(response) except AccessError as e: _logger.warning("Connection error: %s", e.args[0]) raise EWayBillError({ "error": [{ "code": "access_error", "message": _("Unable to connect to the E-WayBill service." "The web service may be temporary down. Please try again in a moment.") }] }) return response def _ewaybill_check_authentication(self): sudo_company = self.company.sudo() if sudo_company.l10n_in_edi_ewaybill_username and sudo_company._l10n_in_edi_ewaybill_token_is_valid(): return True elif sudo_company.l10n_in_edi_ewaybill_username and sudo_company.l10n_in_edi_ewaybill_password: try: self._ewaybill_authenticate() return True except EWayBillError: return False return False def _ewaybill_authenticate(self): params = {"password": self.company.sudo().l10n_in_edi_ewaybill_password} response = self._ewaybill_jsonrpc_to_server(url_path="/iap/l10n_in_edi_ewaybill/1/authenticate", params=params) if response and response.get("status_cd") == "1": self.company.sudo().l10n_in_edi_ewaybill_auth_validity = fields.Datetime.now() + timedelta( hours=6, minutes=00, seconds=00) def _ewaybill_make_transaction(self, operation_type, json_payload): """ :params operation_type: operation_type must be strictly `generate` or `cancel` :params json_payload: to be sent as params This method handles the common errors in generating and canceling the ewaybill """ try: if not self._ewaybill_check_authentication(): self._raise_ewaybill_no_config_error() params = {"json_payload": json_payload} url_path = f"/iap/l10n_in_edi_ewaybill/1/{operation_type}" response = self._ewaybill_jsonrpc_to_server( url_path=url_path, params=params ) return response except EWayBillError as e: if "no-credit" in e.error_codes: e.error_json['odoo_warning'].append({ 'message': self.env['account.edi.format']._l10n_in_edi_get_iap_buy_credits_message(self.company) }) raise if '238' in e.error_codes: # Invalid token eror then create new token and send generate request again. # This happens when authenticate called from another odoo instance with same credentials # (like. Demo/Test) with contextlib.suppress(EWayBillError): self._ewaybill_authenticate() return self._ewaybill_jsonrpc_to_server( url_path=url_path, params=params, ) if operation_type == "cancel" and "312" in e.error_codes: # E-waybill is already canceled # this happens when timeout from the Government portal but IRN is generated e.error_json['odoo_warning'].append({ 'message': Markup("%s
%s:
%s") % ( self.DEFAULT_HELP_MESSAGE % 'cancelled', _("Error"), e.get_all_error_message() ), 'message_post': True }) raise if operation_type == "generate" and "604" in e.error_codes: # Get E-waybill by details in case of E-waybill is already generated # this happens when timeout from the Government portal but E-waybill is generated response = self._ewaybill_get_by_consigner( document_type=json_payload.get("docType"), document_number=json_payload.get("docNo") ) return response raise def _ewaybill_generate(self, json_payload): return self._ewaybill_make_transaction("generate", json_payload) def _ewaybill_cancel(self, json_payload): return self._ewaybill_make_transaction("cancel", json_payload) def _ewaybill_get_by_consigner(self, document_type, document_number): if not self._ewaybill_check_authentication(): self._raise_ewaybill_no_config_error() params = {"document_type": document_type, "document_number": document_number} response = self._ewaybill_jsonrpc_to_server( url_path="/iap/l10n_in_edi_ewaybill/1/getewaybillgeneratedbyconsigner", params=params ) # Add warning that ewaybill was already generated response.update({ 'odoo_warning': [{ 'message': self.DEFAULT_HELP_MESSAGE % 'generated', 'message_post': True }] }) return response @staticmethod def _raise_ewaybill_no_config_error(): raise EWayBillError({ "error": [{ "code": "0", "message": _( "Unable to send E-waybill." "Create an API user in NIC portal, and set it using the top menu: Configuration > Settings." ) }] })