Skip to content

Commit

Permalink
Merge pull request #1137 from resilient-tech/version-14-hotfix
Browse files Browse the repository at this point in the history
  • Loading branch information
vorasmit authored Oct 12, 2023
2 parents 3ba6834 + 6d190fe commit f717067
Show file tree
Hide file tree
Showing 100 changed files with 10,896 additions and 1,051 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def get_columns(self):
def get_data(self):
pass

def append_rows(self):
def append_rows(self, new_count, modified_count, doctype):
pass

def get_conditions(self):
Expand Down
23 changes: 13 additions & 10 deletions india_compliance/audit_trail/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ def setup_fixtures():

def create_property_setters_for_versioning():
for doctype in get_audit_trail_doctypes():
property_setter_data = {
"doctype_or_field": "DocType",
"doc_type": doctype,
"property": "track_changes",
"value": "1",
"property_type": "Check",
"is_system_generated": 1,
}

if frappe.db.exists("Property Setter", property_setter_data):
continue

property_setter = frappe.new_doc("Property Setter")
property_setter.update(
{
"doctype_or_field": "DocType",
"doc_type": doctype,
"property": "track_changes",
"value": "1",
"property_type": "Check",
"is_system_generated": 1,
}
)
property_setter.update(property_setter_data)
property_setter.flags.ignore_permissions = True
property_setter.insert()

Expand Down
91 changes: 64 additions & 27 deletions india_compliance/gst_india/api_classes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class BaseAPI:
API_NAME = "GST"
BASE_PATH = ""
SENSITIVE_HEADERS = ("x-api-key",)
SENSITIVE_INFO = ("x-api-key",)

def __init__(self, *args, **kwargs):
self.settings = frappe.get_cached_doc("GST Settings")
Expand Down Expand Up @@ -58,6 +58,9 @@ def fetch_credentials(self, gstin, service, require_password=True):

self.username = row.username
self.company = row.company
self._fetch_credentials(row, require_password=require_password)

def _fetch_credentials(self, row, require_password=True):
self.password = row.get_password(raise_exception=require_password)

def get_url(self, *parts):
Expand Down Expand Up @@ -94,19 +97,13 @@ def _make_request(
params=params,
headers={
# auto-generated hash, required by some endpoints
"requestid": self.generate_request_id(),
**self.default_headers,
**(headers or {}),
},
)

log_headers = request_args.headers.copy()

# Mask sensitive headers
for header in self.SENSITIVE_HEADERS:
if header in log_headers:
log_headers[header] = "*****"

log = frappe._dict(
**self.default_log_values,
url=request_args.url,
Expand All @@ -117,17 +114,20 @@ def _make_request(
if method == "POST" and json:
request_args.json = json

json_data = json.copy()
if not request_args.params:
log.data = json
log.data = json_data
else:
log.data = {
"params": request_args.params,
"body": json,
"body": json_data,
}

response_json = None

try:
self.before_request(request_args)

response = requests.request(method, **request_args)
if api_request_id := response.headers.get("x-amzn-RequestId"):
log.request_id = api_request_id
Expand All @@ -145,31 +145,25 @@ def _make_request(

# Expect all successful responses to be JSON
if not response_json:
frappe.throw(_("Error parsing response: {0}").format(response.content))
else:
self.response = response_json

# All error responses have a success key set to false
success_value = response_json.get("success", True)
if isinstance(success_value, str):
success_value = sbool(success_value)

if not success_value and not self.handle_failed_response(response_json):
frappe.throw(
response_json.get("message")
# Fallback to response body if message is not present
or frappe.as_json(response_json, indent=4),
title=_("API Request Failed"),
)
if "tar.gz" in request_args.url:
response_json = response.content

else:
frappe.throw(
_("Error parsing response: {0}").format(response.content)
)

response_json = self.process_response(response_json)
return response_json.get("result", response_json)

except Exception as e:
log.error = str(e)
raise e

finally:
log.output = response_json
log.output = response_json.copy()
self.mask_sensitive_info(log)

enqueue_integration_request(**log)

if self.sandbox_mode and not frappe.flags.ic_sandbox_message_shown:
Expand All @@ -179,7 +173,29 @@ def _make_request(
)
frappe.flags.ic_sandbox_message_shown = True

def handle_failed_response(self, response_json):
def before_request(self, request_args):
return

def process_response(self, response):
self.handle_error_response(response)
self.response = response
return response

def handle_error_response(self, response_json):
# All error responses have a success key set to false
success_value = response_json.get("success", True)
if isinstance(success_value, str):
success_value = sbool(success_value)

if not success_value and not self.is_ignored_error(response_json):
frappe.throw(
response_json.get("message")
# Fallback to response body if message is not present
or frappe.as_json(response_json, indent=4),
title=_("API Request Failed"),
)

def is_ignored_error(self, response_json):
# Override in subclass, return truthy value to stop frappe.throw
pass

Expand Down Expand Up @@ -219,3 +235,24 @@ def handle_http_code(self, status_code, response_json):

def generate_request_id(self, length=12):
return frappe.generate_hash(length=length)

def mask_sensitive_info(self, log):
for key in self.SENSITIVE_INFO:
if key in log.request_headers:
log.request_headers[key] = "*****"

if key in log.output:
log.output[key] = "*****"

if not log.data:
return

if key in log.get("data", {}):
log.data[key] = "*****"

if key in log.get("data", {}).get("body", {}):
log.data["body"][key] = "*****"


def get_public_ip():
return requests.get("https://api.ipify.org").text
11 changes: 9 additions & 2 deletions india_compliance/gst_india/api_classes/e_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class EInvoiceAPI(BaseAPI):
API_NAME = "e-Invoice"
BASE_PATH = "ei/api"
SENSITIVE_HEADERS = BaseAPI.SENSITIVE_HEADERS + ("password",)
SENSITIVE_INFO = BaseAPI.SENSITIVE_INFO + ("password",)
IGNORED_ERROR_CODES = {
# Generate IRN errors
"2150": "Duplicate IRN",
Expand All @@ -21,6 +21,9 @@ class EInvoiceAPI(BaseAPI):
# Cancel IRN errors
"9999": "Invoice is not active",
"4002": "EwayBill is already generated for this IRN",
# Invalid GSTIN error
"3028": "GSTIN is invalid",
"3029": "GSTIN is not active",
}

def setup(self, doc=None, *, company_gstin=None):
Expand Down Expand Up @@ -50,10 +53,11 @@ def setup(self, doc=None, *, company_gstin=None):
"gstin": company_gstin,
"user_name": self.username,
"password": self.password,
"requestid": self.generate_request_id(),
}
)

def handle_failed_response(self, response_json):
def is_ignored_error(self, response_json):
message = response_json.get("message", "").strip()

for error_code in self.IGNORED_ERROR_CODES:
Expand Down Expand Up @@ -103,3 +107,6 @@ def update_distance(self, result):

def get_gstin_info(self, gstin):
return self.get(endpoint="master/gstin", params={"gstin": gstin})

def sync_gstin_info(self, gstin):
return self.get(endpoint="master/syncgstin", params={"gstin": gstin})
5 changes: 3 additions & 2 deletions india_compliance/gst_india/api_classes/e_waybill.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class EWaybillAPI(BaseAPI):
API_NAME = "e-Waybill"
BASE_PATH = "ewb/ewayapi"
SENSITIVE_HEADERS = BaseAPI.SENSITIVE_HEADERS + ("password",)
SENSITIVE_INFO = BaseAPI.SENSITIVE_INFO + ("password",)
IGNORED_ERROR_CODES = {
# Cancel e-waybill errors
"312": "This eway bill is either not generated by you or cancelled",
Expand Down Expand Up @@ -45,6 +45,7 @@ def setup(self, doc=None, *, company_gstin=None):
"gstin": company_gstin,
"username": self.username,
"password": self.password,
"requestid": self.generate_request_id(),
}
)

Expand Down Expand Up @@ -82,7 +83,7 @@ def update_distance(self, result):
):
result.distance = int(distance_match.group())

def handle_failed_response(self, response_json):
def is_ignored_error(self, response_json):
message = response_json.get("message", "")

for error_code, error_message in self.IGNORED_ERROR_CODES.items():
Expand Down
1 change: 1 addition & 0 deletions india_compliance/gst_india/api_classes/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def setup(self):
"Autofill Party Information based on GSTIN is not supported in sandbox mode"
)
)
self.default_headers.update({"requestid": self.generate_request_id()})

def get_gstin_info(self, gstin):
response = self.get("search", params={"action": "TP", "gstin": gstin})
Expand Down
Loading

0 comments on commit f717067

Please sign in to comment.