Skip to content

Commit

Permalink
feat: GST Invoice Management System (IMS) Implementation (#2638)
Browse files Browse the repository at this point in the history
Co-authored-by: Smit Vora <[email protected]>
  • Loading branch information
Ninad1306 and vorasmit authored Jan 22, 2025
1 parent 21423a3 commit 01bbdf9
Show file tree
Hide file tree
Showing 35 changed files with 4,064 additions and 848 deletions.
3 changes: 1 addition & 2 deletions india_compliance/gst_india/api_classes/taxpayer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,13 +466,12 @@ def generate_app_key(self):

return app_key

def get_files(self, return_period, token, action, endpoint, otp=None):
def get_files(self, return_period, token, action, endpoint):
response = self.get(
action=action,
return_period=return_period,
params={"ret_period": return_period, "token": token},
endpoint=endpoint,
otp=otp,
)

if response.error_type == "queued":
Expand Down
243 changes: 239 additions & 4 deletions india_compliance/gst_india/api_classes/taxpayer_returns.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import frappe
from frappe import _

from india_compliance.gst_india.api_classes.taxpayer_base import TaxpayerBaseAPI
from india_compliance.gst_india.api_classes.taxpayer_base import (
FilesAPI,
TaxpayerBaseAPI,
)


class ReturnsAPI(TaxpayerBaseAPI):
Expand All @@ -23,9 +26,9 @@ class ReturnsAPI(TaxpayerBaseAPI):
"RET2B1010": "authorization_failed", # API Authorization Failed for 2B
}

def download_files(self, return_period, token, otp=None):
def download_files(self, return_period, token):
return super().get_files(
return_period, token, action="FILEDET", endpoint="returns", otp=otp
return_period, token, action="FILEDET", endpoint="returns"
)

def get_return_status(self, return_period, reference_id, otp=None):
Expand Down Expand Up @@ -57,6 +60,7 @@ def proceed_to_file(self, return_type, return_period, is_nil_return, otp=None):

class GSTR2bAPI(ReturnsAPI):
API_NAME = "GSTR-2B"
END_POINT = "returns/gstr2b"

def get_data(self, return_period, otp=None, file_num=None):
params = {"rtnprd": return_period}
Expand All @@ -67,10 +71,29 @@ def get_data(self, return_period, otp=None, file_num=None):
action="GET2B",
return_period=return_period,
params=params,
endpoint="returns/gstr2b",
endpoint=self.END_POINT,
otp=otp,
)

def regenerate_2b(self, return_period):
return self.put(
json={
"action": "GEN2B",
"data": {"rtin": self.company_gstin, "itcprd": return_period},
},
endpoint=self.END_POINT,
)

def get_2b_gen_status(self, transaction_id):
return self.get(
action="GENSTS2B",
params={
"gstin": self.company_gstin,
"int_tran_id": transaction_id,
},
endpoint=self.END_POINT,
)


class GSTR2aAPI(ReturnsAPI):
API_NAME = "GSTR-2A"
Expand Down Expand Up @@ -153,3 +176,215 @@ def file_gstr_1(self, return_period, summary_data, pan, evc_otp):
},
endpoint="returns/gstr1",
)


class GSTR3bAPI(ReturnsAPI):
END_POINT = "returns/gstr3b"

def setup(self, company_gstin, return_period):
self.return_period = return_period
super().setup(company_gstin=company_gstin)

def get_data(self):
return self.get(
action="RETSUM",
return_period=self.return_period,
params={"gstin": self.company_gstin, "ret_period": self.return_period},
endpoint=self.END_POINT,
)

def save_gstr3b(self, data):
return self.put(
return_period=self.return_period,
json={
"action": "RETSAVE",
"data": data,
},
endpoint=self.END_POINT,
)

def submit_gstr3b(self, data):
return self.post(
return_period=self.return_period,
json={
"action": "RETSUBMIT",
"data": data,
},
endpoint=self.END_POINT,
)

def save_offset_liability_gstr3b(self, data):
return self.put(
return_period=self.return_period,
json={
"action": "RETOFFSET",
"data": data,
},
endpoint=self.END_POINT,
)

def file_gstr_3b(self, data, pan, evc_otp):
return self.post(
return_period=self.return_period,
json={
"action": "RETFILE",
"data": data,
"st": "EVC",
"sid": f"{pan}|{evc_otp}",
},
endpoint=self.END_POINT,
)

def get_itc_liab_data(self):
return self.get(
action="AUTOLIAB",
return_period=self.return_period,
params={"gstin": self.company_gstin, "ret_period": self.return_period},
endpoint=self.END_POINT,
)

def validate_3b_against_auto_calc(self, data):
return self.post(
return_period=self.return_period,
json={
"action": "VALID",
"data": data,
},
endpoint=self.END_POINT,
)

def get_system_calc_interest(self):
return self.get(
action="RETINT",
return_period=self.return_period,
params={"gstin": self.company_gstin, "ret_period": self.return_period},
endpoint=self.END_POINT,
)

def recompute_interest(self):
return self.post(
return_period=self.return_period,
json={
"action": "CMPINT",
"data": {"gstn": self.company_gstin, "ret_period": self.return_period},
},
endpoint=self.END_POINT,
)

def save_past_liab(self, data):
return self.put(
return_period=self.return_period,
json={"action": "RETBKP", "data": data},
endpoint=self.END_POINT,
)

def get_itc_reversal_bal(self):
return self.get(
action="CLOSINGBAL",
return_period=self.return_period,
params={"gstin": self.company_gstin},
endpoint=self.END_POINT,
)

def get_rcm_bal(self):
return self.get(
action="RCMCLOSINGBAL",
return_period=self.return_period,
params={"gstin": self.company_gstin},
endpoint=self.END_POINT,
)

def get_opening_bal(self):
return self.get(
action="OPENINGBAL",
return_period=self.return_period,
params={"gstin": self.company_gstin},
endpoint=self.END_POINT,
)

def get_rcm_opening_bal(self):
return self.get(
action="RCMOPNBAL",
return_period=self.return_period,
params={"gstin": self.company_gstin},
endpoint=self.END_POINT,
)

def save_opening_bal(self, data):
return self.post(
return_period=self.return_period,
json={"action": "SAVEOB", "data": data},
endpoint=self.END_POINT,
)

def submit_rcm_opening_bal(self, data):
return self.post(
return_period=self.return_period,
json={
"action": "SAVERCMOPNBAL",
"data": data,
},
endpoint=self.END_POINT,
)


class IMSAPI(ReturnsAPI):
API_NAME = "IMS"
END_POINT = "returns/ims"

def get_data(self, section):
return self.get(
action="GETINV",
params={
"gstin": self.company_gstin,
"section": section,
},
endpoint=self.END_POINT,
)

def download_files(self, return_period, token):
return self.get_files(
return_period, token, action="FILEDET", endpoint=self.END_POINT
)

def get_files(self, return_period, token, action, endpoint):
response = self.get(
action=action,
return_period=return_period,
params={"gstin": self.company_gstin, "token": token},
endpoint=endpoint,
)

if response.error_type == "queued":
return response

return FilesAPI().get_all(response)

def save(self, data):
return self.put(
endpoint=self.END_POINT,
json={
"action": "SAVE",
"data": {"rtin": self.company_gstin, "reqtyp": "SAVE", "invdata": data},
},
)

def reset(self, data):
return self.put(
endpoint=self.END_POINT,
json={
"action": "RESETIMS",
"data": {
"rtin": self.company_gstin,
"reqtyp": "RESET",
"invdata": data,
},
},
)

def get_request_status(self, transaction_id):
return self.get(
action="REQSTS",
endpoint=self.END_POINT,
params={"gstin": self.company_gstin, "int_tran_id": transaction_id},
)
11 changes: 5 additions & 6 deletions india_compliance/gst_india/client_scripts/purchase_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,19 @@ frappe.ui.form.on(DOCTYPE, {

on_submit: function (frm) {
if (!frm._inward_supply) return;

// go back to previous page and match the invoice with the inward supply
setTimeout(() => {
frappe.route_hooks.after_load = reco_frm => {
if (!reco_frm.purchase_reconciliation_tool) return;
purchase_reconciliation_tool.link_documents(
reco_frm,
frappe.route_hooks.after_load = source_frm => {
if (!source_frm.reconciliation_tabs) return;
reconciliation.link_documents(
source_frm,
frm.doc.name,
frm._inward_supply.name,
"Purchase Invoice",
false
);
};
frappe.set_route("Form", "Purchase Reconciliation Tool");
frappe.set_route("Form", frm._inward_supply.source_doc);
}, 2000);
},
});
Expand Down
17 changes: 17 additions & 0 deletions india_compliance/gst_india/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@
"Input Service Distributor": "B2B",
}

GST_CATEGORY_MAP = {
"R": "Regular",
"SEZWP": "SEZ supplies with payment of tax",
"SEZWOP": "SEZ supplies with out payment of tax",
"DE": "Deemed exports",
"CBW": "Intra-State Supplies attracting IGST",
}

ACTION_MAP = {"A": "Accepted", "R": "Rejected", "P": "Pending", "N": "No Action"}

STATUS_CODE_MAP = {
"P": "Processed",
"PE": "Processed with Errors",
"ER": "Error",
"IP": "In Progress",
}

EXPORT_TYPES = (
"WOP", # Without Payment of Tax [0]
"WP", # With Payment of Tax [1]
Expand Down
Loading

0 comments on commit 01bbdf9

Please sign in to comment.