GSTR1 for B2B, B2CL and B2CS (#12459)
This commit is contained in:
parent
b76202f1a4
commit
a49f720ee3
@ -1,5 +1,6 @@
|
|||||||
import frappe, re
|
import frappe, re
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.utils import cstr
|
||||||
from erpnext.regional.india import states, state_numbers
|
from erpnext.regional.india import states, state_numbers
|
||||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
|
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
|
||||||
|
|
||||||
@ -61,12 +62,10 @@ def get_itemised_tax_breakup_data(doc):
|
|||||||
return hsn_tax, hsn_taxable_amount
|
return hsn_tax, hsn_taxable_amount
|
||||||
|
|
||||||
def set_place_of_supply(doc, method):
|
def set_place_of_supply(doc, method):
|
||||||
if not hasattr(doc, 'customer_gstin'):
|
|
||||||
return
|
|
||||||
|
|
||||||
address_name = doc.shipping_address_name or doc.customer_address
|
address_name = doc.shipping_address_name or doc.customer_address
|
||||||
address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1)
|
if address_name:
|
||||||
doc.place_of_supply = str(address.gst_state_number) + "-" + address.gst_state
|
address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1)
|
||||||
|
doc.place_of_supply = cstr(address.gst_state_number) + "-" + address.gst_state
|
||||||
|
|
||||||
# don't remove this function it is used in tests
|
# don't remove this function it is used in tests
|
||||||
def test_method():
|
def test_method():
|
||||||
|
@ -6,167 +6,299 @@ import frappe, json
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
columns, data = get_columns(filters), get_data(filters)
|
return Gstr1Report(filters).run()
|
||||||
return columns, data
|
|
||||||
|
|
||||||
def get_columns(filters):
|
class Gstr1Report(object):
|
||||||
return [
|
def __init__(self, filters=None):
|
||||||
"GSTIN/UIN of Recipient::150",
|
self.filters = frappe._dict(filters or {})
|
||||||
"Receiver Name::120",
|
self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual"
|
||||||
"Invoice Number:Link/Sales Invoice:120",
|
|
||||||
"Invoice date:Date:120",
|
def run(self):
|
||||||
"Invoice Value:Currency:120",
|
self.get_columns()
|
||||||
"Place of Supply::120",
|
self.get_data()
|
||||||
"Reverse Charge::120",
|
return self.columns, self.data
|
||||||
"Invoice Type::120",
|
|
||||||
"E-Commerce GSTIN::120",
|
|
||||||
"Rate:Int:80",
|
|
||||||
"Taxable Value:Currency:120",
|
|
||||||
"Cess Amount:Currency:120"
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(self):
|
||||||
gst_accounts = get_gst_accounts(filters)
|
self.data = []
|
||||||
invoices = get_invoice_data(filters)
|
self.get_gst_accounts()
|
||||||
invoice_items = get_invoice_items(invoices)
|
self.get_invoice_data()
|
||||||
items_based_on_tax_rate, invoice_cess = get_items_based_on_tax_rate(invoices.keys(), gst_accounts)
|
|
||||||
|
|
||||||
data = []
|
if not self.invoices: return
|
||||||
for inv, items_based_on_rate in items_based_on_tax_rate.items():
|
|
||||||
invoice_details = invoices.get(inv)
|
|
||||||
for rate, items in items_based_on_rate.items():
|
|
||||||
row = [
|
|
||||||
invoice_details.customer_gstin,
|
|
||||||
invoice_details.customer_name,
|
|
||||||
inv,
|
|
||||||
invoice_details.posting_date,
|
|
||||||
invoice_details.base_rounded_total or invoice_details.base_grand_total,
|
|
||||||
invoice_details.place_of_supply,
|
|
||||||
invoice_details.reverse_charge,
|
|
||||||
invoice_details.invoice_type,
|
|
||||||
invoice_details.ecommerce_gstin,
|
|
||||||
rate,
|
|
||||||
sum([net_amount for item_code, net_amount in invoice_items.get(inv).items()
|
|
||||||
if item_code in items]),
|
|
||||||
invoice_cess.get(inv)
|
|
||||||
]
|
|
||||||
data.append(row)
|
|
||||||
|
|
||||||
return data
|
self.get_invoice_items()
|
||||||
|
self.get_items_based_on_tax_rate()
|
||||||
|
invoice_fields = [d["fieldname"] for d in self.invoice_columns]
|
||||||
|
|
||||||
def get_gst_accounts(filters):
|
|
||||||
gst_accounts = frappe._dict()
|
|
||||||
gst_settings_accounts = frappe.get_list("GST Account",
|
|
||||||
filters={"parent": "GST Settings", "company": filters.company},
|
|
||||||
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
|
|
||||||
|
|
||||||
if not gst_settings_accounts:
|
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||||
frappe.throw(_("Please set GST Accounts in GST Settings"))
|
invoice_details = self.invoices.get(inv)
|
||||||
|
for rate, items in items_based_on_rate.items():
|
||||||
|
row = []
|
||||||
|
for fieldname in invoice_fields:
|
||||||
|
if fieldname == "invoice_value":
|
||||||
|
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
|
||||||
|
else:
|
||||||
|
row.append(invoice_details.get(fieldname))
|
||||||
|
|
||||||
for d in gst_settings_accounts:
|
row += [rate,
|
||||||
for acc, val in d.items():
|
sum([net_amount for item_code, net_amount in self.invoice_items.get(inv).items()
|
||||||
gst_accounts.setdefault(acc, []).append(val)
|
if item_code in items]),
|
||||||
|
self.invoice_cess.get(inv)
|
||||||
|
]
|
||||||
|
|
||||||
return gst_accounts
|
if self.filters.get("type_of_business") == "B2C Small":
|
||||||
|
row.append("E" if invoice_details.ecommerce_gstin else "OE")
|
||||||
|
|
||||||
def get_invoice_data(filters):
|
self.data.append(row)
|
||||||
invoices = frappe._dict()
|
|
||||||
conditions = get_conditions(filters)
|
|
||||||
match_conditions = frappe.build_match_conditions("Sales Invoice")
|
|
||||||
|
|
||||||
if match_conditions:
|
def get_invoice_data(self):
|
||||||
match_conditions = " and {0} ".format(match_conditions)
|
self.invoices = frappe._dict()
|
||||||
|
conditions = self.get_conditions()
|
||||||
|
|
||||||
invoice_data = frappe.db.sql("""
|
invoice_data = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
`tabSales Invoice`.name,
|
name as invoice_number,
|
||||||
`tabSales Invoice`.customer_name,
|
customer_name,
|
||||||
`tabSales Invoice`.posting_date,
|
posting_date,
|
||||||
`tabSales Invoice`.base_grand_total,
|
base_grand_total,
|
||||||
`tabSales Invoice`.base_rounded_total,
|
base_rounded_total,
|
||||||
`tabSales Invoice`.customer_gstin,
|
customer_gstin,
|
||||||
`tabSales Invoice`.place_of_supply,
|
place_of_supply,
|
||||||
`tabSales Invoice`.ecommerce_gstin,
|
ecommerce_gstin,
|
||||||
`tabSales Invoice`.reverse_charge,
|
reverse_charge,
|
||||||
`tabSales Invoice`.invoice_type
|
invoice_type
|
||||||
from `tabSales Invoice`
|
from `tabSales Invoice`
|
||||||
where `tabSales Invoice`.docstatus = 1 %s %s
|
where docstatus = 1 %s
|
||||||
order by `tabSales Invoice`.posting_date desc
|
order by posting_date desc
|
||||||
""" % (conditions, match_conditions), filters, as_dict=1)
|
""" % (conditions), self.filters, as_dict=1)
|
||||||
|
|
||||||
for d in invoice_data:
|
for d in invoice_data:
|
||||||
invoices.setdefault(d.name, d)
|
self.invoices.setdefault(d.invoice_number, d)
|
||||||
return invoices
|
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(self):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
for opts in (("company", " and company=%(company)s"),
|
for opts in (("company", " and company=%(company)s"),
|
||||||
("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
|
("from_date", " and posting_date>=%(from_date)s"),
|
||||||
("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s")):
|
("to_date", " and posting_date<=%(to_date)s")):
|
||||||
if filters.get(opts[0]):
|
if self.filters.get(opts[0]):
|
||||||
conditions += opts[1]
|
conditions += opts[1]
|
||||||
|
|
||||||
return conditions
|
customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type})
|
||||||
|
conditions += " and customer in ('{0}')".format("', '".join([frappe.db.escape(c.name)
|
||||||
|
for c in customers]))
|
||||||
|
|
||||||
def get_invoice_items(invoices):
|
if self.filters.get("type_of_business") == "B2C Large":
|
||||||
invoice_items = frappe._dict()
|
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
|
||||||
items = frappe.db.sql("""
|
and grand_total > 250000"""
|
||||||
select item_code, parent, base_net_amount
|
elif self.filters.get("type_of_business") == "B2C Small":
|
||||||
from `tabSales Invoice Item`
|
conditions += """ and (
|
||||||
where parent in (%s)
|
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
|
||||||
""" % (', '.join(['%s']*len(invoices))), tuple(invoices), as_dict=1)
|
or grand_total <= 250000
|
||||||
|
)"""
|
||||||
|
|
||||||
for d in items:
|
return conditions
|
||||||
invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, d.base_net_amount)
|
|
||||||
return invoice_items
|
|
||||||
|
|
||||||
def get_items_based_on_tax_rate(invoices, gst_accounts):
|
def get_invoice_items(self):
|
||||||
tax_details = frappe.db.sql("""
|
self.invoice_items = frappe._dict()
|
||||||
select
|
items = frappe.db.sql("""
|
||||||
parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
|
select item_code, parent, base_net_amount
|
||||||
from `tabSales Taxes and Charges`
|
from `tabSales Invoice Item`
|
||||||
where
|
where parent in (%s)
|
||||||
parenttype = 'Sales Invoice' and docstatus = 1
|
""" % (', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||||
and parent in (%s)
|
|
||||||
and tax_amount_after_discount_amount > 0
|
|
||||||
order by account_head
|
|
||||||
""" % (', '.join(['%s']*len(invoices))), tuple(invoices))
|
|
||||||
|
|
||||||
items_based_on_tax_rate = {}
|
for d in items:
|
||||||
invoice_cess = frappe._dict()
|
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, d.base_net_amount)
|
||||||
unidentified_gst_accounts = []
|
|
||||||
|
|
||||||
for parent, account, item_wise_tax_detail, tax_amount in tax_details:
|
def get_items_based_on_tax_rate(self):
|
||||||
if account in gst_accounts.cess_account:
|
tax_details = frappe.db.sql("""
|
||||||
invoice_cess.setdefault(parent, tax_amount)
|
select
|
||||||
else:
|
parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
|
||||||
if item_wise_tax_detail:
|
from `tabSales Taxes and Charges`
|
||||||
try:
|
where
|
||||||
item_wise_tax_detail = json.loads(item_wise_tax_detail)
|
parenttype = 'Sales Invoice' and docstatus = 1
|
||||||
cgst_or_sgst = False
|
and parent in (%s)
|
||||||
if account in gst_accounts.cgst_account or account in gst_accounts.sgst_account:
|
and tax_amount_after_discount_amount > 0
|
||||||
cgst_or_sgst = True
|
order by account_head
|
||||||
|
""" % (', '.join(['%s']*len(self.invoices.keys()))), tuple(self.invoices.keys()))
|
||||||
|
|
||||||
if not (cgst_or_sgst or account in gst_accounts.igst_account):
|
self.items_based_on_tax_rate = {}
|
||||||
if "gst" in account.lower() and account not in unidentified_gst_accounts:
|
self.invoice_cess = frappe._dict()
|
||||||
unidentified_gst_accounts.append(account)
|
unidentified_gst_accounts = []
|
||||||
|
|
||||||
|
for parent, account, item_wise_tax_detail, tax_amount in tax_details:
|
||||||
|
if account in self.gst_accounts.cess_account:
|
||||||
|
self.invoice_cess.setdefault(parent, tax_amount)
|
||||||
|
else:
|
||||||
|
if item_wise_tax_detail:
|
||||||
|
try:
|
||||||
|
item_wise_tax_detail = json.loads(item_wise_tax_detail)
|
||||||
|
cgst_or_sgst = False
|
||||||
|
if account in self.gst_accounts.cgst_account \
|
||||||
|
or account in self.gst_accounts.sgst_account:
|
||||||
|
cgst_or_sgst = True
|
||||||
|
|
||||||
|
if not (cgst_or_sgst or account in self.gst_accounts.igst_account):
|
||||||
|
if "gst" in account.lower() and account not in unidentified_gst_accounts:
|
||||||
|
unidentified_gst_accounts.append(account)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for item_code, tax_amounts in item_wise_tax_detail.items():
|
||||||
|
tax_rate = tax_amounts[0]
|
||||||
|
if cgst_or_sgst:
|
||||||
|
tax_rate *= 2
|
||||||
|
|
||||||
|
rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {})\
|
||||||
|
.setdefault(tax_rate, [])
|
||||||
|
if item_code not in rate_based_dict:
|
||||||
|
rate_based_dict.append(item_code)
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
|
if unidentified_gst_accounts:
|
||||||
|
frappe.msgprint(_("Following accounts might be selected in GST Settings:")
|
||||||
|
+ "<br>" + "<br>".join(unidentified_gst_accounts), alert=True)
|
||||||
|
|
||||||
for item_code, tax_amounts in item_wise_tax_detail.items():
|
def get_gst_accounts(self):
|
||||||
tax_rate = tax_amounts[0]
|
self.gst_accounts = frappe._dict()
|
||||||
if cgst_or_sgst:
|
gst_settings_accounts = frappe.get_list("GST Account",
|
||||||
tax_rate *= 2
|
filters={"parent": "GST Settings", "company": self.filters.company},
|
||||||
|
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
|
||||||
|
|
||||||
rate_based_dict = items_based_on_tax_rate.setdefault(parent, {})\
|
if not gst_settings_accounts:
|
||||||
.setdefault(tax_rate, [])
|
frappe.throw(_("Please set GST Accounts in GST Settings"))
|
||||||
if item_code not in rate_based_dict:
|
|
||||||
rate_based_dict.append(item_code)
|
|
||||||
|
|
||||||
except ValueError:
|
for d in gst_settings_accounts:
|
||||||
continue
|
for acc, val in d.items():
|
||||||
if unidentified_gst_accounts:
|
self.gst_accounts.setdefault(acc, []).append(val)
|
||||||
frappe.msgprint(_("Following accounts might be selected in GST Settings:")
|
|
||||||
+ "<br>" + "<br>".join(unidentified_gst_accounts), alert=True)
|
|
||||||
|
|
||||||
return items_based_on_tax_rate, invoice_cess
|
def get_columns(self):
|
||||||
|
self.tax_columns = [
|
||||||
|
{
|
||||||
|
"fieldname": "rate",
|
||||||
|
"label": "Rate",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"width": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "taxable_value",
|
||||||
|
"label": "Taxable Value",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cess_amount",
|
||||||
|
"label": "Cess Amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.other_columns = []
|
||||||
|
|
||||||
|
if self.filters.get("type_of_business") == "B2B":
|
||||||
|
self.invoice_columns = [
|
||||||
|
{
|
||||||
|
"fieldname": "customer_gstin",
|
||||||
|
"label": "GSTIN/UIN of Recipient",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_name",
|
||||||
|
"label": "Receiver Name",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_number",
|
||||||
|
"label": "Invoice Number",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Sales Invoice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"label": "Invoice date",
|
||||||
|
"fieldtype": "Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_value",
|
||||||
|
"label": "Invoice Value",
|
||||||
|
"fieldtype": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "place_of_supply",
|
||||||
|
"label": "Place of Supply",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reverse_charge",
|
||||||
|
"label": "Reverse Charge",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_type",
|
||||||
|
"label": "Invoice Type",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ecommerce_gstin",
|
||||||
|
"label": "E-Commerce GSTIN",
|
||||||
|
"fieldtype": "Data"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
elif self.filters.get("type_of_business") == "B2C Large":
|
||||||
|
self.invoice_columns = [
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_number",
|
||||||
|
"label": "Invoice Number",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Sales Invoice",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"label": "Invoice date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "invoice_value",
|
||||||
|
"label": "Invoice Value",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"width": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "place_of_supply",
|
||||||
|
"label": "Place of Supply",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ecommerce_gstin",
|
||||||
|
"label": "E-Commerce GSTIN",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 130
|
||||||
|
}
|
||||||
|
]
|
||||||
|
elif self.filters.get("type_of_business") == "B2C Small":
|
||||||
|
self.invoice_columns = [
|
||||||
|
{
|
||||||
|
"fieldname": "place_of_supply",
|
||||||
|
"label": "Place of Supply",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ecommerce_gstin",
|
||||||
|
"label": "E-Commerce GSTIN",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 130
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.other_columns = [
|
||||||
|
{
|
||||||
|
"fieldname": "type",
|
||||||
|
"label": "Type",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 50
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.columns = self.invoice_columns + self.tax_columns + self.other_columns
|
Loading…
x
Reference in New Issue
Block a user