Skip to content

Commit

Permalink
Merge pull request #2625 from resilient-tech/version-15-hotfix
Browse files Browse the repository at this point in the history
chore: release v15
  • Loading branch information
ljain112 authored Sep 21, 2024
2 parents 1d1c71f + 53e0fcb commit 2f7fc8d
Show file tree
Hide file tree
Showing 40 changed files with 795 additions and 527 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,8 @@ def get_columns(self):
},
{
"label": _("DocType"),
"fieldtype": "Link",
"fieldtype": "Data",
"fieldname": "doctype",
"options": "DocType",
"width": 120,
},
{
Expand All @@ -172,10 +171,9 @@ def get_columns(self):
},
{
"label": _("Party Type"),
"fieldtype": "Link",
"fieldtype": "Data",
"fieldname": "party_type",
"width": 100,
"options": "DocType",
},
{
"label": _("Party Name"),
Expand Down Expand Up @@ -302,9 +300,8 @@ def get_columns(self):
columns = [
{
"label": _("DocType"),
"fieldtype": "Link",
"fieldtype": "Data",
"fieldname": "doctype",
"options": "DocType",
"width": 150,
},
{
Expand Down
17 changes: 17 additions & 0 deletions india_compliance/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,20 @@ def __init__(self, message="GSP/GST server is down", *args, **kwargs):
class GatewayTimeoutError(GSPServerError):
def __init__(self, message="The server took too long to respond", *args, **kwargs):
super().__init__(message, *args, **kwargs)


class OTPRequestedError(Exception):
def __init__(self, message="OTP has been requested", *args, **kwargs):
self.response = kwargs.pop("response", None)
super().__init__(message, *args, **kwargs)


class InvalidOTPError(Exception):
def __init__(self, message="Invalid OTP", *args, **kwargs):
self.response = kwargs.pop("response", None)
super().__init__(message, *args, **kwargs)


class InvalidAuthTokenError(Exception):
def __init__(self, message="Invalid Auth Token", *args, **kwargs):
super().__init__(message, *args, **kwargs)
113 changes: 108 additions & 5 deletions india_compliance/gst_india/api_classes/taxpayer_base.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import json
from base64 import b64decode, b64encode
from functools import wraps

from cryptography import x509
from cryptography.hazmat.backends import default_backend

import frappe
import frappe.utils
from frappe import _
from frappe.utils import add_to_date, cint, now_datetime

from india_compliance.exceptions import (
InvalidAuthTokenError,
InvalidOTPError,
OTPRequestedError,
)
from india_compliance.gst_india.api_classes.base import BaseAPI, get_public_ip
from india_compliance.gst_india.utils import merge_dicts, tar_gz_bytes_to_data
from india_compliance.gst_india.utils.cryptography import (
Expand All @@ -19,6 +26,25 @@
)


def otp_handler(func):

@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)

except OTPRequestedError as e:
return e.response

except InvalidOTPError as e:
return e.response

except Exception as e:
raise e

return wrapper


class PublicCertificate(BaseAPI):
BASE_PATH = "static"

Expand Down Expand Up @@ -84,7 +110,7 @@ class TaxpayerAuthenticate(BaseAPI):
IGNORED_ERROR_CODES = {
"RETOTPREQUEST": "otp_requested",
"EVCREQUEST": "otp_requested",
"AUTH158": "invalid_otp", # Invalid OTP
"AUTH158": "authorization_failed", # GSTR1
"AUTH4033": "invalid_otp", # Invalid Session
# "AUTH4034": "invalid_otp", # Invalid OTP
"AUTH4038": "authorization_failed", # Session Expired
Expand All @@ -104,12 +130,17 @@ def request_otp(self):
if response.status_cd != 1:
return

return response.update(
{"error_type": "otp_requested", "gstin": self.company_gstin}
)
response.update({"error_type": "otp_requested", "gstin": self.company_gstin})

raise OTPRequestedError(response=response)

def autheticate_with_otp(self, otp=None):
if not otp:
# in enqueue / cron job
if getattr(frappe.local, "job", None):
frappe.local.job.after_job.add(self.reset_auth_token)
raise InvalidAuthTokenError

# reset auth token
frappe.db.set_value(
"GST Credential",
Expand All @@ -124,7 +155,7 @@ def autheticate_with_otp(self, otp=None):
self.auth_token = None
return self.request_otp()

return super().post(
response = super().post(
json={
"action": "AUTHTOKEN",
"app_key": self.app_key,
Expand All @@ -134,6 +165,14 @@ def autheticate_with_otp(self, otp=None):
endpoint="authenticate",
)

frappe.cache.set_value(
f"authenticated_gstin:{self.company_gstin}",
True,
expires_in_sec=60 * 15,
)

return response

def refresh_auth_token(self):
auth_token = self.get_auth_token()

Expand Down Expand Up @@ -227,10 +266,31 @@ def get_auth_token(self):

return self.auth_token

def reset_auth_token(self):
"""
Reset after job to clear the auth token
"""
frappe.db.set_value(
"GST Credential",
{
"gstin": self.company_gstin,
"username": self.username,
"service": "Returns",
},
{"auth_token": None},
)

frappe.db.commit() # nosemgrep - executed in after enqueue


class TaxpayerBaseAPI(TaxpayerAuthenticate):
BASE_PATH = "standard/gstn"

IGNORED_ERROR_CODES = {
**TaxpayerAuthenticate.IGNORED_ERROR_CODES,
"RT-R1R3BAV-1007": "authorization_failed", # Either auth-token or username is invalid. Raised in get_filing_preference
}

def setup(self, company_gstin):
if self.sandbox_mode:
frappe.throw(_("Sandbox mode not supported for Returns API"))
Expand Down Expand Up @@ -347,6 +407,13 @@ def is_ignored_error(self, response):
if error_code in self.IGNORED_ERROR_CODES:
response.error_type = self.IGNORED_ERROR_CODES[error_code]
response.gstin = self.company_gstin

if response.error_type == "otp_requested":
raise OTPRequestedError(response=response)

if response.error_type == "invalid_otp":
raise InvalidOTPError(response=response)

return True

def generate_app_key(self):
Expand Down Expand Up @@ -376,3 +443,39 @@ def get_files(self, return_period, token, action, endpoint, otp=None):
return response

return FilesAPI().get_all(response)

def validate_auth_token(self):
"""
Try refreshing the auth token without error
to check if the auth token is valid
Generates a new OTP if the auth token is invalid
"""
if frappe.cache.get_value(f"authenticated_gstin:{self.company_gstin}"):
return

# Dummy request
self.get_filing_preference()

frappe.cache.set_value(
f"authenticated_gstin:{self.company_gstin}",
True,
expires_in_sec=60 * 15,
)

return

def get_filing_preference(self):
return self.get(
action="GETPREF", params={"fy": self.get_fy()}, endpoint="returns"
)

@staticmethod
def get_fy():
date = frappe.utils.getdate()

# Standard for India as per GST
if date.month < 4:
return f"{date.year - 1}-{str(date.year)[2:]}"

return f"{date.year}-{str(date.year + 1)[2:]}"
54 changes: 54 additions & 0 deletions india_compliance/gst_india/client_scripts/e_invoice_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ frappe.ui.form.on("Sales Invoice", {
},
"e-Invoice"
);

frm.add_custom_button(
__("Mark as Generated"),
() => show_mark_e_invoice_as_generated_dialog(frm),
"e-Invoice"
);
}
if (
frm.doc.irn &&
Expand Down Expand Up @@ -189,6 +195,54 @@ function show_cancel_e_invoice_dialog(frm, callback) {
`).prependTo(d.wrapper);
}

function show_mark_e_invoice_as_generated_dialog(frm) {
const d = new frappe.ui.Dialog({
title: __("Update e-Invoice Details"),
fields: get_generated_e_invoice_dialog_fields(),
primary_action_label: __("Update"),
primary_action(values) {
frappe.call({
method: "india_compliance.gst_india.utils.e_invoice.mark_e_invoice_as_generated",
args: {
doctype: frm.doctype,
docname: frm.doc.name,
values,
},
callback: () => {
d.hide();
frm.refresh();
},
});
},
});

d.show();
}

function get_generated_e_invoice_dialog_fields() {
let fields = [
{
label: "IRN Number",
fieldname: "irn",
fieldtype: "Data",
reqd: 1,
},
{
label: "Acknowledgement Number",
fieldname: "ack_no",
fieldtype: "Data",
reqd: 1,
},
{
label: "Acknowledged On",
fieldname: "ack_dt",
fieldtype: "Datetime",
reqd: 1,
},
];
return fields;
}

function show_mark_e_invoice_as_cancelled_dialog(frm) {
const d = new frappe.ui.Dialog({
title: __("Update Cancelled e-Invoice Details"),
Expand Down
1 change: 1 addition & 0 deletions india_compliance/gst_india/client_scripts/party.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ async function set_gstin_options(frm) {

frm._gstin_options_set_for = frm.doc.name;
const field = frm.get_field("gstin");
if (!field || field.df.fieldtype != "Autocomplete") return;
field.df.ignore_validation = true;
field.set_data(await india_compliance.get_gstin_options(frm.doc.name, frm.doctype));
}
Expand Down
20 changes: 18 additions & 2 deletions india_compliance/gst_india/client_scripts/purchase_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ frappe.ui.form.on(DOCTYPE, {

onload: toggle_reverse_charge,

gst_category: toggle_reverse_charge,
gst_category(frm) {
validate_gst_hsn_code(frm);
toggle_reverse_charge(frm);
},

after_save(frm) {
if (
Expand Down Expand Up @@ -90,9 +93,14 @@ frappe.ui.form.on(DOCTYPE, {
});

frappe.ui.form.on("Purchase Invoice Item", {
item_code: toggle_reverse_charge,
item_code(frm) {
validate_gst_hsn_code(frm);
toggle_reverse_charge(frm);
},

items_remove: toggle_reverse_charge,

gst_hsn_code: validate_gst_hsn_code,
});

function toggle_reverse_charge(frm) {
Expand All @@ -104,3 +112,11 @@ function toggle_reverse_charge(frm) {

frm.set_df_property("is_reverse_charge", "read_only", is_read_only);
}

function validate_gst_hsn_code(frm) {
if (frm.doc.gst_category !== "Overseas") return;

if (frm.doc.items.some(item => !item.gst_hsn_code)) {
frappe.throw(__("GST HSN Code is mandatory for Overseas Purchase Invoice."));
}
}
2 changes: 1 addition & 1 deletion india_compliance/gst_india/constants/custom_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,7 @@
"label": "e-Invoice Status",
"fieldtype": "Select",
"insert_after": "status",
"options": "\nPending\nGenerated\nAuto-Retry\nCancelled\nManually Cancelled\nFailed\nNot Applicable\nPending Cancellation",
"options": "\nPending\nGenerated\nManually Generated\nAuto-Retry\nCancelled\nManually Cancelled\nFailed\nNot Applicable\nPending Cancellation",
"default": None,
"hidden": 1,
"no_copy": 1,
Expand Down
Loading

0 comments on commit 2f7fc8d

Please sign in to comment.