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

chore: release v14 #2717

Open
wants to merge 15 commits into
base: version-14
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/server-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ jobs:
BRANCH_TO_CLONE: ${{ env.BRANCH }}

- name: Run Tests
run: cd ~/frappe-bench/ && bench --site test_site run-tests --app ${{ env.APP_NAME }} --coverage
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app ${{ env.APP_NAME }} --with-coverage
env:
TYPE: server

Expand Down
1 change: 1 addition & 0 deletions india_compliance/gst_india/api_classes/e_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class EInvoiceAPI(BaseAPI):
# Invalid GSTIN error
"3028": "GSTIN is invalid",
"3029": "GSTIN is not active",
"3001": "Requested data is not available",
}

def setup(self, doc=None, *, company_gstin=None):
Expand Down
11 changes: 11 additions & 0 deletions india_compliance/gst_india/api_classes/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def setup(self):
self.default_headers.update({"requestid": self.generate_request_id()})

def get_gstin_info(self, gstin):
self.gstin = gstin
response = self.get("search", params={"action": "TP", "gstin": gstin})
if self.sandbox_mode:
response.update(
Expand All @@ -33,3 +34,13 @@ def get_returns_info(self, gstin, fy):
return self.get(
"returns", params={"action": "RETTRACK", "gstin": gstin, "fy": fy}
)

def is_ignored_error(self, response_json):
if response_json.get("errorCode") == "FO8000":
response_json.update(
{
"sts": "Invalid",
"gstin": self.gstin,
}
)
return True
2 changes: 1 addition & 1 deletion india_compliance/gst_india/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
"Jammu and Kashmir": (180, 194),
"Himachal Pradesh": (171, 177),
"Punjab": (140, 160),
"Chandigarh": (160, 160),
"Chandigarh": ((140, 140), (160, 160)),
"Uttarakhand": (244, 263),
"Haryana": (121, 136),
"Delhi": (110, 110),
Expand Down
20 changes: 20 additions & 0 deletions india_compliance/gst_india/overrides/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,3 +1151,23 @@ def test_so_and_po_after_item_update(self):
},
doc.items[1],
)


class TestPlaceOfSupply(FrappeTestCase):
def test_pos_sales_invoice(self):
doc_args = {
"doctype": "Sales Invoice",
"customer": "_Test Registered Composition Customer",
}

settings = ["Accounts Settings", None, "determine_address_tax_category_from"]

# Shipping Address
frappe.db.set_value(*settings, "Shipping Address")
doc = create_transaction(**doc_args)
self.assertEqual(doc.place_of_supply, "24-Gujarat")

# Billing Address
frappe.db.set_value(*settings, "Billing Address")
doc = create_transaction(**doc_args)
self.assertEqual(doc.place_of_supply, "29-Karnataka")
48 changes: 32 additions & 16 deletions india_compliance/gst_india/report/gstr_1/gstr_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from frappe.query_builder.functions import Date, IfNull, Sum
from frappe.utils import cint, flt, formatdate, getdate

from india_compliance.gst_india.constants.__init__ import GST_TAX_TYPES
from india_compliance.gst_india.constants import GST_TAX_TYPES
from india_compliance.gst_india.report.hsn_wise_summary_of_outward_supplies.hsn_wise_summary_of_outward_supplies import (
get_columns as get_hsn_columns,
)
Expand All @@ -26,11 +26,10 @@
get_escaped_name,
get_gst_accounts_by_type,
get_gstin_list,
validate_invoice_number,
)
from india_compliance.gst_india.utils.exporter import ExcelExporter
from india_compliance.gst_india.utils.gstr_1 import SUPECOM

B2C_LIMIT = 2_50_000
from india_compliance.gst_india.utils.gstr_1 import SUPECOM, get_b2c_limit

TYPES_OF_BUSINESS = {
"B2B": "b2b",
Expand Down Expand Up @@ -259,7 +258,8 @@ def is_b2cl_cdn(self, invoice):
grand_total = invoice.return_against_invoice_total or abs(
invoice.base_grand_total
)
return grand_total > B2C_LIMIT

return grand_total > get_b2c_limit(invoice.posting_date)

def get_row_data_for_invoice(self, invoice_details, tax_rate, item_detail):
"""
Expand Down Expand Up @@ -378,20 +378,36 @@ def get_conditions(self):
)

if self.filters.get("type_of_business") == "B2C Large":
conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
AND grand_total > {0} AND is_return != 1 AND is_debit_note !=1
# get_b2c_limit hardcoded
conditions += """
AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
AND grand_total > (
CASE
WHEN posting_date <= '2024-07-31' THEN 250000
ELSE 100000
END
)
AND is_return != 1
AND is_debit_note !=1
AND IFNULL(gst_category, "") in ('Unregistered', 'Overseas')
AND SUBSTR(place_of_supply, 1, 2) != '96'""".format(
B2C_LIMIT
)
AND SUBSTR(place_of_supply, 1, 2) != '96'
"""

elif self.filters.get("type_of_business") == "B2C Small":
conditions += """ AND (
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
OR grand_total <= {0}) AND IFNULL(gst_category, "") in ('Unregistered', 'Overseas')
AND SUBSTR(place_of_supply, 1, 2) != '96' """.format(
B2C_LIMIT
)
# get_b2c_limit hardcoded
conditions += """
AND (
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
OR grand_total <= (
CASE
WHEN posting_date <= '2024-07-31' THEN 250000
ELSE 100000
END
)
)
AND IFNULL(gst_category, "") in ('Unregistered', 'Overseas')
AND SUBSTR(place_of_supply, 1, 2) != '96'
"""

elif self.filters.get("type_of_business") == "CDNR-REG":
conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') not in ('Unregistered', 'Overseas')"""
Expand Down
10 changes: 1 addition & 9 deletions india_compliance/gst_india/setup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,6 @@ def set_default_accounts_settings():
"""
Accounts Settings overridden by India Compliance

- Determine Address Tax Category From:
This is overriden to be Billing Address, since that's the correct
address for determining GST applicablility

- Automatically Add Taxes and Charges from Item Tax Template:
This is overriden to be "No". Item Tax Templates are designed to have
all GST Accounts and are primarily used for selection of tax rate.
Expand All @@ -250,11 +246,7 @@ def set_default_accounts_settings():
show_accounts_settings_override_warning()

frappe.db.set_single_value(
"Accounts Settings",
{
"determine_address_tax_category_from": "Billing Address",
"add_taxes_from_item_tax_template": 0,
},
"Accounts Settings", "add_taxes_from_item_tax_template", 0
)

frappe.db.set_default("add_taxes_from_item_tax_template", 0)
Expand Down
2 changes: 1 addition & 1 deletion india_compliance/gst_india/setup/property_setters.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def get_property_setters(*, include_defaults=False):
"doctype": "Accounts Settings",
"fieldname": "determine_address_tax_category_from",
"property": "read_only",
"value": "1",
"value": "0",
},
{
"doctype": "Accounts Settings",
Expand Down
22 changes: 17 additions & 5 deletions india_compliance/gst_india/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,10 +386,18 @@ def get_place_of_supply(party_details, doctype):
:param party_details: A frappe._dict or document containing fields related to party
"""

pos_basis = frappe.get_cached_value(
"Accounts Settings", "Accounts Settings", "determine_address_tax_category_from"
)

if pos_basis == "Shipping Address" and doctype in SALES_DOCTYPES:
# POS Basis Shipping Address is only applicable for Sales
pos_gstin = party_details.company_gstin

# fallback to company GSTIN for sales or supplier GSTIN for purchases
# (in retail scenarios, customer / company GSTIN may not be set)

if doctype in SALES_DOCTYPES or doctype == "Payment Entry":
elif doctype in SALES_DOCTYPES or doctype == "Payment Entry":
# for exports, Place of Supply is set using GST category in absence of GSTIN
if party_details.gst_category == "Overseas":
return get_overseas_place_of_supply(party_details)
Expand All @@ -406,14 +414,18 @@ def get_place_of_supply(party_details, doctype):
if gst_state_number and gst_state:
return f"{gst_state_number}-{gst_state}"

party_gstin = party_details.billing_address_gstin or party_details.company_gstin
pos_gstin = party_details.billing_address_gstin or party_details.company_gstin

elif doctype == "Stock Entry":
pos_gstin = party_details.bill_to_gstin or party_details.bill_from_gstin
else:
party_gstin = party_details.company_gstin or party_details.supplier_gstin
# for purchase, subcontracting order and receipt
pos_gstin = party_details.company_gstin or party_details.supplier_gstin

if not party_gstin:
if not pos_gstin:
return

state_code = party_gstin[:2]
state_code = pos_gstin[:2]

if state := get_state(state_code):
return f"{state_code}-{state}"
Expand Down
2 changes: 1 addition & 1 deletion india_compliance/gst_india/utils/e_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def generate_e_invoice(docname, throw=True, force=False):
result = result.Desc if response.error_code == "2283" else response

# Handle Invalid GSTIN Error
if result.error_code in ("3028", "3029"):
if result.error_code in ("3028", "3029", "3001"):
gstin = data.get("BuyerDtls").get("Gstin")
response = api.sync_gstin_info(gstin)

Expand Down
3 changes: 2 additions & 1 deletion india_compliance/gst_india/utils/gstin_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def _get_gstin_info(gstin, *, throw_error=True):
queue="long",
response=get_formatted_response_for_status(response),
)

except Exception as exc:
if isinstance(exc, GSPServerError):
frappe.cache().set_value("gst_server_error", True, expires_in_sec=60)
Expand All @@ -75,7 +76,7 @@ def _get_gstin_info(gstin, *, throw_error=True):

gstin_info = frappe._dict(
gstin=response.gstin,
business_name=titlecase(business_name),
business_name=titlecase(business_name or ""),
gst_category=GST_CATEGORIES.get(response.dty, ""),
status=response.sts,
)
Expand Down
19 changes: 19 additions & 0 deletions india_compliance/gst_india/utils/gstr_1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from enum import Enum

from frappe.utils import getdate


class GSTR1_Category(Enum):
"""
Expand Down Expand Up @@ -327,3 +329,20 @@ class GSTR1_B2B_InvoiceType(Enum):
GSTR1_SubCategory.B2B_REVERSE_CHARGE.value,
*SUBCATEGORIES_NOT_CONSIDERED_IN_TOTAL_TAXABLE_VALUE,
]


B2C_LIMIT = [
("2024-07-31", 2_50_000),
("2099-03-31", 1_00_000),
]


def get_b2c_limit(date):
if isinstance(date, str):
date = getdate(date)

for limit_date, limit in B2C_LIMIT:
if date <= getdate(limit_date):
return limit

return B2C_LIMIT[-1][1]
11 changes: 7 additions & 4 deletions india_compliance/gst_india/utils/gstr_1/gstr_1_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@
GSTR1_B2B_InvoiceType,
GSTR1_Category,
GSTR1_SubCategory,
get_b2c_limit,
)

B2C_LIMIT = 2_50_000

CATEGORY_CONDITIONS = {
GSTR1_Category.B2B.value: {
"category": "is_b2b_invoice",
Expand Down Expand Up @@ -234,11 +233,15 @@ def is_b2cl_cn_dn(self, invoice):
else invoice.invoice_total
)

return (abs(invoice_total) > B2C_LIMIT) and self.is_inter_state(invoice)
return (
abs(invoice_total) > get_b2c_limit(invoice.posting_date)
) and self.is_inter_state(invoice)

@cache_invoice_condition
def is_b2cl_inv(self, invoice):
return abs(invoice.invoice_total) > B2C_LIMIT and self.is_inter_state(invoice)
return abs(invoice.invoice_total) > get_b2c_limit(
invoice.posting_date
) and self.is_inter_state(invoice)


class GSTR1CategoryConditions(GSTR1Conditions):
Expand Down
29 changes: 29 additions & 0 deletions india_compliance/gst_india/utils/test_gstin_info.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import unittest
from unittest.mock import Mock, patch

import responses
from responses import matchers

import frappe
from frappe.tests.utils import FrappeTestCase, change_settings

from india_compliance.gst_india.utils.gstin_info import get_gstin_info

Expand Down Expand Up @@ -189,3 +193,28 @@ def test_tcs_gstin_info(self):
},
},
)


class TestGstinInvalidInfo(FrappeTestCase):
@responses.activate
@change_settings("GST Settings", {"validate_gstin_status": 1, "sandbox_mode": 0})
def test_invalid_gstin(self):
gstin = "24AQTPC8950E1ZO"
url = "https://asp.resilient.tech/commonapi/search"

responses.add(
responses.GET,
url,
json={
"errorCode": "FO8000",
"gstin": "24AQTPC8950E1ZO",
"message": "No records found",
"sts": "Invalid",
"success": False,
},
match=[matchers.query_param_matcher({"action": "TP", "gstin": gstin})],
)

gstin_info = get_gstin_info(gstin)
self.assertEqual(gstin_info.status, "Invalid")
self.assertEqual(gstin_info.business_name, "")
Loading
Loading