Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: JSON mapping for ITC-04 filing #2273

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,67 @@ frappe.query_reports["GST Job Work Stock Movement"] = {
reqd: 1,
},
],
onload: function (query_report) {
query_report.page.add_inner_button(__("Export JSON"), function () {
this.dialog = new frappe.ui.Dialog({
title: __("Export JSON"),
fields: [
{
fieldname: "year",
label: __("Year"),
fieldtype: "Select",
options: get_options_for_year(),
onchange: () => {
if (this.dialog.get_value("year") === "2017") {
this.dialog.fields_dict.period.df.options = [
"Jul - Sep",
"Oct - Dec",
"Jan - Mar",
];
this.dialog.refresh();
}
},
},
{
fieldname: "period",
label: __("Return Filing Period"),
fieldtype: "Select",
options: ["Apr - Jun", "Jul - Sep", "Oct - Dec", "Jan - Mar"],
},
],
primary_action_label: "Export JSON",
primary_action: () => {
frappe.call({
method: "india_compliance.gst_india.utils.itc_04.itc_04_export.download_itc_04_json",
args: {
company: frappe.query_report.get_filter_value("company"),
company_gstin:
frappe.query_report.get_filter_value("company_gstin"),
period: this.dialog.get_value("period"),
year: this.dialog.get_value("year"),
},
callback: r => {
this.dialog.hide();
india_compliance.trigger_file_download(
JSON.stringify(r.message.data),
r.message.filename
);
},
});
},
});

dialog.show();
});
query_report.refresh();
},
};

function get_options_for_year() {
const today = new Date();
const current_year = today.getFullYear();
const start_year = 2017;
const year_range = current_year - start_year + 1;
let options = Array.from({ length: year_range }, (_, index) => start_year + index);
return options.reverse().map(year => year.toString());
}
134 changes: 20 additions & 114 deletions india_compliance/gst_india/utils/gstr_1/gstr_1_json_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import frappe
from frappe.utils import flt

from india_compliance.gst_india.constants import STATE_NUMBERS, UOM_MAP
from india_compliance.gst_india.constants import UOM_MAP
from india_compliance.gst_india.report.gstr_1.gstr_1 import (
GSTR1DocumentIssuedSummary,
GSTR11A11BData,
)
from india_compliance.gst_india.utils import get_gst_accounts_by_type
from india_compliance.gst_india.utils.__init__ import get_party_for_gstin
from india_compliance.gst_india.utils import (
get_gst_accounts_by_type,
get_party_for_gstin,
)
from india_compliance.gst_india.utils.gstr_1 import (
CATEGORY_SUB_CATEGORY_MAPPING,
SUB_CATEGORY_GOV_CATEGORY_MAPPING,
Expand All @@ -24,20 +26,20 @@
GSTR1_SubCategory,
)
from india_compliance.gst_india.utils.gstr_1.gstr_1_data import GSTR1Invoices
from india_compliance.gst_india.utils.gstr_mapper_utils import GovDataMapper

############################################################################################################
### Map Govt JSON to Internal Data Structure ###############################################################
############################################################################################################


class GovDataMapper:
class GSTR1DataMapper(GovDataMapper):
"""
GST Developer API Documentation for Returns - https://developer.gst.gov.in/apiportal/taxpayer/returns

GSTR-1 JSON format - https://developer.gst.gov.in/pages/apiportal/data/Returns/GSTR1%20-%20Save%20GSTR1%20data/v4.0/GSTR1%20-%20Save%20GSTR1%20data%20attributes.xlsx
"""

KEY_MAPPING = {}
# default item amounts
DEFAULT_ITEM_AMOUNTS = {
GSTR1_ItemField.TAXABLE_VALUE.value: 0,
Expand Down Expand Up @@ -68,106 +70,10 @@ class GovDataMapper:
}

def __init__(self):
self.set_total_defaults()

self.value_formatters_for_internal = {}
self.value_formatters_for_gov = {}
super().__init__()
self.gstin_party_map = {}
# value formatting constants

self.STATE_NUMBERS = self.reverse_dict(STATE_NUMBERS)

def format_data(
self, data: dict, default_data: dict = None, for_gov: bool = False
) -> dict:
"""
Objective: Convert Object from one format to another.
eg: Govt JSON to Internal Data Structure

Args:
data (dict): Data to be converted
default_data (dict, optional): Default Data to be added. Hardcoded values.
for_gov (bool, optional): If the data is to be converted to Govt JSON. Defaults to False.
else it will be converted to Internal Data Structure.

Steps:
1. Use key mapping to map the keys from one format to another.
2. Use value formatters to format the values of the keys.
3. Round values
"""
output = {}

if default_data:
output.update(default_data)

key_mapping = self.KEY_MAPPING.copy()

if for_gov:
key_mapping = self.reverse_dict(key_mapping)

value_formatters = (
self.value_formatters_for_gov
if for_gov
else self.value_formatters_for_internal
)

for old_key, new_key in key_mapping.items():
invoice_data_value = data.get(old_key, "")

if not for_gov and old_key == "flag":
continue

if new_key in self.DISCARD_IF_ZERO_FIELDS and not invoice_data_value:
continue

if not (invoice_data_value or invoice_data_value == 0):
# continue if value is None or empty object
continue

value_formatter = value_formatters.get(old_key)

if callable(value_formatter):
output[new_key] = value_formatter(invoice_data_value, data)
else:
output[new_key] = invoice_data_value

if new_key in self.FLOAT_FIELDS:
output[new_key] = flt(output[new_key], 2)

return output

# common utils

def update_totals(self, invoice, items):
"""
Update item totals to the invoice row
"""
total_data = self.TOTAL_DEFAULTS.copy()

for item in items:
for field, value in item.items():
total_field = f"total_{field}"

if total_field not in total_data:
continue

invoice[total_field] = invoice.setdefault(total_field, 0) + value

def set_total_defaults(self):
self.TOTAL_DEFAULTS = {
f"total_{key}": 0 for key in self.DEFAULT_ITEM_AMOUNTS.keys()
}

def reverse_dict(self, data):
return {v: k for k, v in data.items()}

# common value formatters
def map_place_of_supply(self, pos, *args):
if pos.isnumeric():
return f"{pos}-{self.STATE_NUMBERS.get(pos)}"

return pos.split("-")[0]

def format_item_for_internal(self, items, *args):
return [
{
Expand Down Expand Up @@ -201,7 +107,7 @@ def format_date_for_gov(self, date, *args):
return datetime.strptime(date, "%Y-%m-%d").strftime("%d-%m-%Y")


class B2B(GovDataMapper):
class B2B(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -376,7 +282,7 @@ def document_category_mapping(self, sub_category, data):
return self.DOCUMENT_CATEGORIES.get(sub_category, sub_category)


class B2CL(GovDataMapper):
class B2CL(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -502,7 +408,7 @@ def convert_to_gov_data_format(self, input_data, **kwargs):
return list(pos_data.values())


class Exports(GovDataMapper):
class Exports(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -648,7 +554,7 @@ def format_item_for_gov(self, items, *args):
return [self.format_data(item, for_gov=True) for item in items]


class B2CS(GovDataMapper):
class B2CS(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -744,7 +650,7 @@ def format_data(self, data, default_data=None, for_gov=False):
return data


class NilRated(GovDataMapper):
class NilRated(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -849,7 +755,7 @@ def document_category_mapping(self, doc_category, data):
return self.DOCUMENT_CATEGORIES.get(doc_category, doc_category)


class CDNR(GovDataMapper):
class CDNR(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -1037,7 +943,7 @@ def format_doc_value(self, value, data):
return value * -1 if data[GovDataField.NOTE_TYPE.value] == "C" else value


class CDNUR(GovDataMapper):
class CDNUR(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -1175,7 +1081,7 @@ def format_doc_value(self, value, data):
return value * -1 if data[GovDataField.NOTE_TYPE.value] == "C" else value


class HSNSUM(GovDataMapper):
class HSNSUM(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -1295,7 +1201,7 @@ def map_uom(self, uom, data=None):
return f"OTH-{UOM_MAP.get('OTH')}"


class AT(GovDataMapper):
class AT(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -1476,7 +1382,7 @@ class TXPD(AT):
MULTIPLIER = -1


class DOC_ISSUE(GovDataMapper):
class DOC_ISSUE(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -1612,7 +1518,7 @@ def get_document_nature(self, doc_nature, *args):
return self.DOCUMENT_NATURE.get(doc_nature, doc_nature)


class SUPECOM(GovDataMapper):
class SUPECOM(GSTR1DataMapper):
"""
GST API Version - v4.0

Expand Down Expand Up @@ -1689,7 +1595,7 @@ def convert_to_gov_data_format(self, input_data, **kwargs):
return output


class RETSUM(GovDataMapper):
class RETSUM(GSTR1DataMapper):
"""
Convert GSTR-1 Summary as returned by the API to the internal format

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
)
from india_compliance.gst_india.utils import get_party_for_gstin as _get_party_for_gstin
from india_compliance.gst_india.utils.gstr_1 import (
SUB_CATEGORY_GOV_CATEGORY_MAPPING,
GovDataField,
GSTR1_B2B_InvoiceType,
GSTR1_DataField,
Expand Down Expand Up @@ -40,11 +39,7 @@ def normalize_data(data):


def process_mapped_data(data):
return list(
get_category_wise_data(
normalize_data(copy.deepcopy(data)), SUB_CATEGORY_GOV_CATEGORY_MAPPING
).values()
)[0]
return list(get_category_wise_data(normalize_data(copy.deepcopy(data))).values())[0]


class TestB2B(FrappeTestCase):
Expand Down
Loading
Loading