diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
new file mode 100644
index 00000000000..23924c5fb66
--- /dev/null
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -0,0 +1,1076 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+
+import json
+from datetime import date
+
+import frappe
+from frappe import _
+from frappe.utils import flt, formatdate, getdate
+from six import iteritems
+
+from erpnext.regional.india.utils import get_gst_accounts
+
+
+def execute(filters=None):
+ return Gstr1Report(filters).run()
+
+class Gstr1Report(object):
+ def __init__(self, filters=None):
+ self.filters = frappe._dict(filters or {})
+ self.columns = []
+ self.data = []
+ self.doctype = "Sales Invoice"
+ self.tax_doctype = "Sales Taxes and Charges"
+ self.select_columns = """
+ name as invoice_number,
+ customer_name,
+ posting_date,
+ base_grand_total,
+ base_rounded_total,
+ COALESCE(NULLIF(customer_gstin,''), NULLIF(billing_address_gstin, '')) as customer_gstin,
+ place_of_supply,
+ ecommerce_gstin,
+ reverse_charge,
+ return_against,
+ is_return,
+ is_debit_note,
+ gst_category,
+ export_type,
+ port_code,
+ shipping_bill_number,
+ shipping_bill_date,
+ reason_for_issuing_document
+ """
+
+ def run(self):
+ self.get_columns()
+ self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1)
+ self.get_invoice_data()
+
+ if self.invoices:
+ self.get_invoice_items()
+ self.get_items_based_on_tax_rate()
+ self.invoice_fields = [d["fieldname"] for d in self.invoice_columns]
+
+ self.get_data()
+
+ return self.columns, self.data
+
+ def get_data(self):
+ if self.filters.get("type_of_business") in ("B2C Small", "B2C Large"):
+ self.get_b2c_data()
+ elif self.filters.get("type_of_business") == "Advances":
+ self.get_advance_data()
+ elif self.invoices:
+ for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
+ invoice_details = self.invoices.get(inv)
+ for rate, items in items_based_on_rate.items():
+ row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
+
+ if self.filters.get("type_of_business") in ("CDNR-REG", "CDNR-UNREG"):
+ row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
+ row.append("C" if invoice_details.is_return else "D")
+
+ if taxable_value:
+ self.data.append(row)
+
+ def get_advance_data(self):
+ advances_data = {}
+ advances = self.get_advance_entries()
+ for entry in advances:
+ # only consider IGST and SGST so as to avoid duplication of taxable amount
+ if entry.account_head in self.gst_accounts.igst_account or \
+ entry.account_head in self.gst_accounts.sgst_account:
+ advances_data.setdefault((entry.place_of_supply, entry.rate), [0.0, 0.0])
+ advances_data[(entry.place_of_supply, entry.rate)][0] += (entry.amount * 100 / entry.rate)
+ elif entry.account_head in self.gst_accounts.cess_account:
+ advances_data[(entry.place_of_supply, entry.rate)][1] += entry.amount
+
+ for key, value in advances_data.items():
+ row= [key[0], key[1], value[0], value[1]]
+ self.data.append(row)
+
+ def get_b2c_data(self):
+ b2cs_output = {}
+
+ if self.invoices:
+ for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
+ invoice_details = self.invoices.get(inv)
+ for rate, items in items_based_on_rate.items():
+ place_of_supply = invoice_details.get("place_of_supply")
+ ecommerce_gstin = invoice_details.get("ecommerce_gstin")
+
+ b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin), {
+ "place_of_supply": "",
+ "ecommerce_gstin": "",
+ "rate": "",
+ "taxable_value": 0,
+ "cess_amount": 0,
+ "type": "",
+ "invoice_number": invoice_details.get("invoice_number"),
+ "posting_date": invoice_details.get("posting_date"),
+ "invoice_value": invoice_details.get("base_grand_total"),
+ })
+
+ row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
+ row["place_of_supply"] = place_of_supply
+ row["ecommerce_gstin"] = ecommerce_gstin
+ row["rate"] = rate
+ row["taxable_value"] += sum([abs(net_amount)
+ for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items])
+ row["cess_amount"] += flt(self.invoice_cess.get(inv), 2)
+ row["type"] = "E" if ecommerce_gstin else "OE"
+
+ for key, value in iteritems(b2cs_output):
+ self.data.append(value)
+
+ def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items):
+ row = []
+ for fieldname in self.invoice_fields:
+ if self.filters.get("type_of_business") in ("CDNR-REG", "CDNR-UNREG") and fieldname == "invoice_value":
+ row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
+ elif fieldname == "invoice_value":
+ row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
+ elif fieldname in ('posting_date', 'shipping_bill_date'):
+ row.append(formatdate(invoice_details.get(fieldname), 'dd-MMM-YY'))
+ elif fieldname == "export_type":
+ export_type = "WPAY" if invoice_details.get(fieldname)=="With Payment of Tax" else "WOPAY"
+ row.append(export_type)
+ else:
+ row.append(invoice_details.get(fieldname))
+ taxable_value = 0
+
+ if invoice in self.cgst_sgst_invoices:
+ division_factor = 2
+ else:
+ division_factor = 1
+
+ for item_code, net_amount in self.invoice_items.get(invoice).items():
+ if item_code in items:
+ if self.item_tax_rate.get(invoice) and tax_rate/division_factor in self.item_tax_rate.get(invoice, {}).get(item_code, []):
+ taxable_value += abs(net_amount)
+ elif not self.item_tax_rate.get(invoice):
+ taxable_value += abs(net_amount)
+ elif tax_rate:
+ taxable_value += abs(net_amount)
+ elif not tax_rate and self.filters.get('type_of_business') == 'EXPORT' \
+ and invoice_details.get('export_type') == "Without Payment of Tax":
+ taxable_value += abs(net_amount)
+
+ row += [tax_rate or 0, taxable_value]
+
+ for column in self.other_columns:
+ if column.get('fieldname') == 'cess_amount':
+ row.append(flt(self.invoice_cess.get(invoice), 2))
+
+ return row, taxable_value
+
+ def get_invoice_data(self):
+ self.invoices = frappe._dict()
+ conditions = self.get_conditions()
+
+ company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True)
+
+ if company_gstins:
+ self.filters.update({
+ 'company_gstins': company_gstins
+ })
+
+ invoice_data = frappe.db.sql("""
+ select
+ {select_columns}
+ from `tab{doctype}`
+ where docstatus = 1 {where_conditions}
+ and is_opening = 'No'
+ order by posting_date desc
+ """.format(select_columns=self.select_columns, doctype=self.doctype,
+ where_conditions=conditions), self.filters, as_dict=1)
+
+ for d in invoice_data:
+ self.invoices.setdefault(d.invoice_number, d)
+
+ def get_advance_entries(self):
+ return frappe.db.sql("""
+ SELECT SUM(a.base_tax_amount) as amount, a.account_head, a.rate, p.place_of_supply
+ FROM `tabPayment Entry` p, `tabAdvance Taxes and Charges` a
+ WHERE p.docstatus = 1
+ AND p.name = a.parent
+ AND posting_date between %s and %s
+ GROUP BY a.account_head, p.place_of_supply, a.rate
+ """, (self.filters.get('from_date'), self.filters.get('to_date')), as_dict=1)
+
+ def get_conditions(self):
+ conditions = ""
+
+ for opts in (("company", " and company=%(company)s"),
+ ("from_date", " and posting_date>=%(from_date)s"),
+ ("to_date", " and posting_date<=%(to_date)s"),
+ ("company_address", " and company_address=%(company_address)s")):
+ if self.filters.get(opts[0]):
+ conditions += opts[1]
+
+
+ if self.filters.get("type_of_business") == "B2B":
+ conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1 AND is_debit_note !=1"
+
+ if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
+ b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
+ if not b2c_limit:
+ frappe.throw(_("Please set B2C Limit in GST Settings."))
+
+ 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 AND gst_category ='Unregistered' """.format(flt(b2c_limit))
+
+ 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 is_return != 1 AND gst_category ='Unregistered' """.format(flt(b2c_limit))
+
+ elif self.filters.get("type_of_business") == "CDNR-REG":
+ conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')"""
+
+ elif self.filters.get("type_of_business") == "CDNR-UNREG":
+ b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
+ conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
+ AND (is_return = 1 OR is_debit_note = 1)
+ AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')"""
+
+ elif self.filters.get("type_of_business") == "EXPORT":
+ conditions += """ AND is_return !=1 and gst_category = 'Overseas' """
+
+ conditions += " AND IFNULL(billing_address_gstin, '') NOT IN %(company_gstins)s"
+
+ return conditions
+
+ def get_invoice_items(self):
+ self.invoice_items = frappe._dict()
+ self.item_tax_rate = frappe._dict()
+
+ items = frappe.db.sql("""
+ select item_code, parent, taxable_value, base_net_amount, item_tax_rate
+ from `tab%s Item`
+ where parent in (%s)
+ """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
+
+ for d in items:
+ if d.item_code not in self.invoice_items.get(d.parent, {}):
+ self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
+ self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
+
+ item_tax_rate = {}
+
+ if d.item_tax_rate:
+ item_tax_rate = json.loads(d.item_tax_rate)
+
+ for account, rate in item_tax_rate.items():
+ tax_rate_dict = self.item_tax_rate.setdefault(d.parent, {}).setdefault(d.item_code, [])
+ tax_rate_dict.append(rate)
+
+ def get_items_based_on_tax_rate(self):
+ self.tax_details = frappe.db.sql("""
+ select
+ parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
+ from `tab%s`
+ where
+ parenttype = %s and docstatus = 1
+ and parent in (%s)
+ order by account_head
+ """ % (self.tax_doctype, '%s', ', '.join(['%s']*len(self.invoices.keys()))),
+ tuple([self.doctype] + list(self.invoices.keys())))
+
+ self.items_based_on_tax_rate = {}
+ self.invoice_cess = frappe._dict()
+ self.cgst_sgst_invoices = []
+
+ unidentified_gst_accounts = []
+ unidentified_gst_accounts_invoice = []
+ for parent, account, item_wise_tax_detail, tax_amount in self.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)
+ unidentified_gst_accounts_invoice.append(parent)
+ continue
+
+ for item_code, tax_amounts in item_wise_tax_detail.items():
+ tax_rate = tax_amounts[0]
+ if tax_rate:
+ if cgst_or_sgst:
+ tax_rate *= 2
+ if parent not in self.cgst_sgst_invoices:
+ self.cgst_sgst_invoices.append(parent)
+
+ 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
+ if unidentified_gst_accounts:
+ frappe.msgprint(_("Following accounts might be selected in GST Settings:")
+ + "
" + "
".join(unidentified_gst_accounts), alert=True)
+
+ # Build itemised tax for export invoices where tax table is blank
+ for invoice, items in iteritems(self.invoice_items):
+ if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
+ and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
+ and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
+ self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
+
+ 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
+ }
+ ]
+ self.other_columns = []
+
+ if self.filters.get("type_of_business") == "B2B":
+ self.invoice_columns = [
+ {
+ "fieldname": "customer_gstin",
+ "label": "GSTIN/UIN of Recipient",
+ "fieldtype": "Data",
+ "width": 150
+ },
+ {
+ "fieldname": "customer_name",
+ "label": "Receiver Name",
+ "fieldtype": "Data",
+ "width":100
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Invoice Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":100
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice date",
+ "fieldtype": "Data",
+ "width":80
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width":100
+ },
+ {
+ "fieldname": "place_of_supply",
+ "label": "Place Of Supply",
+ "fieldtype": "Data",
+ "width":100
+ },
+ {
+ "fieldname": "reverse_charge",
+ "label": "Reverse Charge",
+ "fieldtype": "Data"
+ },
+ {
+ "fieldname": "gst_category",
+ "label": "Invoice Type",
+ "fieldtype": "Data"
+ },
+ {
+ "fieldname": "ecommerce_gstin",
+ "label": "E-Commerce GSTIN",
+ "fieldtype": "Data",
+ "width":120
+ }
+ ]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
+
+ 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": "Data",
+ "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
+ }
+ ]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
+ elif self.filters.get("type_of_business") == "CDNR-REG":
+ self.invoice_columns = [
+ {
+ "fieldname": "customer_gstin",
+ "label": "GSTIN/UIN of Recipient",
+ "fieldtype": "Data",
+ "width": 150
+ },
+ {
+ "fieldname": "customer_name",
+ "label": "Receiver Name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "return_against",
+ "label": "Invoice/Advance Receipt Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice/Advance Receipt date",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Invoice/Advance Receipt Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":120
+ },
+ {
+ "fieldname": "reverse_charge",
+ "label": "Reverse Charge",
+ "fieldtype": "Data"
+ },
+ {
+ "fieldname": "export_type",
+ "label": "Export Type",
+ "fieldtype": "Data",
+ "hidden": 1
+ },
+ {
+ "fieldname": "reason_for_issuing_document",
+ "label": "Reason For Issuing document",
+ "fieldtype": "Data",
+ "width": 140
+ },
+ {
+ "fieldname": "place_of_supply",
+ "label": "Place Of Supply",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "gst_category",
+ "label": "GST Category",
+ "fieldtype": "Data"
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width": 120
+ }
+ ]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ },
+ {
+ "fieldname": "pre_gst",
+ "label": "PRE GST",
+ "fieldtype": "Data",
+ "width": 80
+ },
+ {
+ "fieldname": "document_type",
+ "label": "Document Type",
+ "fieldtype": "Data",
+ "width": 80
+ }
+ ]
+ elif self.filters.get("type_of_business") == "CDNR-UNREG":
+ self.invoice_columns = [
+ {
+ "fieldname": "customer_name",
+ "label": "Receiver Name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "return_against",
+ "label": "Issued Against",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Note Date",
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Note Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":120
+ },
+ {
+ "fieldname": "export_type",
+ "label": "Export Type",
+ "fieldtype": "Data",
+ "hidden": 1
+ },
+ {
+ "fieldname": "reason_for_issuing_document",
+ "label": "Reason For Issuing document",
+ "fieldtype": "Data",
+ "width": 140
+ },
+ {
+ "fieldname": "place_of_supply",
+ "label": "Place Of Supply",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "gst_category",
+ "label": "GST Category",
+ "fieldtype": "Data"
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width": 120
+ }
+ ]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ },
+ {
+ "fieldname": "pre_gst",
+ "label": "PRE GST",
+ "fieldtype": "Data",
+ "width": 80
+ },
+ {
+ "fieldname": "document_type",
+ "label": "Document Type",
+ "fieldtype": "Data",
+ "width": 80
+ }
+ ]
+ 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": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ },
+ {
+ "fieldname": "type",
+ "label": "Type",
+ "fieldtype": "Data",
+ "width": 50
+ }
+ ]
+ elif self.filters.get("type_of_business") == "EXPORT":
+ self.invoice_columns = [
+ {
+ "fieldname": "export_type",
+ "label": "Export Type",
+ "fieldtype": "Data",
+ "width":120
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Invoice Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice date",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width": 120
+ },
+ {
+ "fieldname": "port_code",
+ "label": "Port Code",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "shipping_bill_number",
+ "label": "Shipping Bill Number",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "shipping_bill_date",
+ "label": "Shipping Bill Date",
+ "fieldtype": "Data",
+ "width": 120
+ }
+ ]
+ elif self.filters.get("type_of_business") == "Advances":
+ self.invoice_columns = [
+ {
+ "fieldname": "place_of_supply",
+ "label": "Place Of Supply",
+ "fieldtype": "Data",
+ "width": 120
+ }
+ ]
+
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
+
+ self.columns = self.invoice_columns + self.tax_columns + self.other_columns
+
+@frappe.whitelist()
+def get_json(filters, report_name, data):
+ filters = json.loads(filters)
+ report_data = json.loads(data)
+ gstin = get_company_gstin_number(filters.get("company"), filters.get("company_address"))
+
+ fp = "%02d%s" % (getdate(filters["to_date"]).month, getdate(filters["to_date"]).year)
+
+ gst_json = {"version": "GST3.0.4",
+ "hash": "hash", "gstin": gstin, "fp": fp}
+
+ res = {}
+ if filters["type_of_business"] == "B2B":
+ for item in report_data[:-1]:
+ res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
+
+ out = get_b2b_json(res, gstin)
+ gst_json["b2b"] = out
+
+ elif filters["type_of_business"] == "B2C Large":
+ for item in report_data[:-1]:
+ res.setdefault(item["place_of_supply"], []).append(item)
+
+ out = get_b2cl_json(res, gstin)
+ gst_json["b2cl"] = out
+
+ elif filters["type_of_business"] == "B2C Small":
+ out = get_b2cs_json(report_data[:-1], gstin)
+ gst_json["b2cs"] = out
+
+ elif filters["type_of_business"] == "EXPORT":
+ for item in report_data[:-1]:
+ res.setdefault(item["export_type"], []).append(item)
+
+ out = get_export_json(res)
+ gst_json["exp"] = out
+ elif filters["type_of_business"] == "CDNR-REG":
+ for item in report_data[:-1]:
+ res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
+
+ out = get_cdnr_reg_json(res, gstin)
+ gst_json["cdnr"] = out
+ elif filters["type_of_business"] == "CDNR-UNREG":
+ for item in report_data[:-1]:
+ res.setdefault(item["invoice_number"],[]).append(item)
+
+ out = get_cdnr_unreg_json(res, gstin)
+ gst_json["cdnur"] = out
+
+ elif filters["type_of_business"] == "Advances":
+ for item in report_data[:-1]:
+ if not item.get("place_of_supply"):
+ frappe.throw(_("""{0} not entered in some entries.
+ Please update and try again""").format(frappe.bold("Place Of Supply")))
+
+ res.setdefault(item["place_of_supply"],[]).append(item)
+
+ out = get_advances_json(res, gstin)
+ gst_json["at"] = out
+
+ return {
+ 'report_name': report_name,
+ 'report_type': filters['type_of_business'],
+ 'data': gst_json
+ }
+
+def get_b2b_json(res, gstin):
+ inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, []
+ for gst_in in res:
+ b2b_item, inv = {"ctin": gst_in, "inv": []}, []
+ if not gst_in: continue
+
+ for number, invoice in iteritems(res[gst_in]):
+ if not invoice[0]["place_of_supply"]:
+ frappe.throw(_("""{0} not entered in Invoice {1}.
+ Please update and try again""").format(frappe.bold("Place Of Supply"),
+ frappe.bold(invoice[0]['invoice_number'])))
+
+ inv_item = get_basic_invoice_detail(invoice[0])
+ inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0])
+ inv_item["rchrg"] = invoice[0]["reverse_charge"]
+ inv_item["inv_typ"] = inv_type.get(invoice[0].get("gst_category", ""),"")
+
+ if inv_item["pos"]=="00": continue
+ inv_item["itms"] = []
+
+ for item in invoice:
+ inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
+
+ inv.append(inv_item)
+
+ if not inv: continue
+ b2b_item["inv"] = inv
+ out.append(b2b_item)
+
+ return out
+
+def get_b2cs_json(data, gstin):
+ company_state_number = gstin[0:2]
+
+ out = []
+ for d in data:
+ if not d.get("place_of_supply"):
+ frappe.throw(_("""{0} not entered in some invoices.
+ Please update and try again""").format(frappe.bold("Place Of Supply")))
+
+ pos = d.get('place_of_supply').split('-')[0]
+ tax_details = {}
+
+ rate = d.get('rate', 0)
+ tax = flt((d["taxable_value"]*rate)/100.0, 2)
+
+ if company_state_number == pos:
+ tax_details.update({"camt": flt(tax/2.0, 2), "samt": flt(tax/2.0, 2)})
+ else:
+ tax_details.update({"iamt": tax})
+
+ inv = {
+ "sply_ty": "INTRA" if company_state_number == pos else "INTER",
+ "pos": pos,
+ "typ": d.get('type'),
+ "txval": flt(d.get('taxable_value'), 2),
+ "rt": rate,
+ "iamt": flt(tax_details.get('iamt'), 2),
+ "camt": flt(tax_details.get('camt'), 2),
+ "samt": flt(tax_details.get('samt'), 2),
+ "csamt": flt(d.get('cess_amount'), 2)
+ }
+
+ if d.get('type') == "E" and d.get('ecommerce_gstin'):
+ inv.update({
+ "etin": d.get('ecommerce_gstin')
+ })
+
+ out.append(inv)
+
+ return out
+
+def get_advances_json(data, gstin):
+ company_state_number = gstin[0:2]
+ out = []
+ for place_of_supply, items in iteritems(data):
+ supply_type = "INTRA" if company_state_number == place_of_supply.split('-')[0] else "INTER"
+ row = {
+ "pos": place_of_supply.split('-')[0],
+ "itms": [],
+ "sply_ty": supply_type
+ }
+
+ for item in items:
+ itms = {
+ 'rt': item['rate'],
+ 'ad_amount': flt(item.get('taxable_value')),
+ 'csamt': flt(item.get('cess_amount'))
+ }
+
+ if supply_type == "INTRA":
+ itms.update({
+ "samt": flt((itms["ad_amount"] * itms["rt"]) / 100),
+ "camt": flt((itms["ad_amount"] * itms["rt"]) / 100),
+ "rt": itms["rt"] * 2
+ })
+ else:
+ itms.update({
+ "iamt": flt((itms["ad_amount"] * itms["rt"]) / 100)
+ })
+
+ row['itms'].append(itms)
+ out.append(row)
+
+ return out
+
+def get_b2cl_json(res, gstin):
+ out = []
+ for pos in res:
+ if not pos:
+ frappe.throw(_("""{0} not entered in some invoices.
+ Please update and try again""").format(frappe.bold("Place Of Supply")))
+
+ b2cl_item, inv = {"pos": "%02d" % int(pos.split('-')[0]), "inv": []}, []
+
+ for row in res[pos]:
+ inv_item = get_basic_invoice_detail(row)
+ if row.get("sale_from_bonded_wh"):
+ inv_item["inv_typ"] = "CBW"
+
+ inv_item["itms"] = [get_rate_and_tax_details(row, gstin)]
+
+ inv.append(inv_item)
+
+ b2cl_item["inv"] = inv
+ out.append(b2cl_item)
+
+ return out
+
+def get_export_json(res):
+ out = []
+ for exp_type in res:
+ exp_item, inv = {"exp_typ": exp_type, "inv": []}, []
+
+ for row in res[exp_type]:
+ inv_item = get_basic_invoice_detail(row)
+ inv_item["itms"] = [{
+ "txval": flt(row["taxable_value"], 2),
+ "rt": row["rate"] or 0,
+ "iamt": 0,
+ "csamt": 0
+ }]
+
+ inv.append(inv_item)
+
+ exp_item["inv"] = inv
+ out.append(exp_item)
+
+ return out
+
+def get_cdnr_reg_json(res, gstin):
+ out = []
+
+ for gst_in in res:
+ cdnr_item, inv = {"ctin": gst_in, "nt": []}, []
+ if not gst_in: continue
+
+ for number, invoice in iteritems(res[gst_in]):
+ if not invoice[0]["place_of_supply"]:
+ frappe.throw(_("""{0} not entered in Invoice {1}.
+ Please update and try again""").format(frappe.bold("Place Of Supply"),
+ frappe.bold(invoice[0]['invoice_number'])))
+
+ inv_item = {
+ "nt_num": invoice[0]["invoice_number"],
+ "nt_dt": getdate(invoice[0]["posting_date"]).strftime('%d-%m-%Y'),
+ "val": abs(flt(invoice[0]["invoice_value"])),
+ "ntty": invoice[0]["document_type"],
+ "pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]),
+ "rchrg": invoice[0]["reverse_charge"],
+ "inv_typ": get_invoice_type_for_cdnr(invoice[0])
+ }
+
+ inv_item["itms"] = []
+ for item in invoice:
+ inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
+
+ inv.append(inv_item)
+
+ if not inv: continue
+ cdnr_item["nt"] = inv
+ out.append(cdnr_item)
+
+ return out
+
+def get_cdnr_unreg_json(res, gstin):
+ out = []
+
+ for invoice, items in iteritems(res):
+ inv_item = {
+ "nt_num": items[0]["invoice_number"],
+ "nt_dt": getdate(items[0]["posting_date"]).strftime('%d-%m-%Y'),
+ "val": abs(flt(items[0]["invoice_value"])),
+ "ntty": items[0]["document_type"],
+ "pos": "%02d" % int(items[0]["place_of_supply"].split('-')[0]),
+ "typ": get_invoice_type_for_cdnrur(items[0])
+ }
+
+ inv_item["itms"] = []
+ for item in items:
+ inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
+
+ out.append(inv_item)
+
+ return out
+
+def get_invoice_type_for_cdnr(row):
+ if row.get('gst_category') == 'SEZ':
+ if row.get('export_type') == 'WPAY':
+ invoice_type = 'SEWP'
+ else:
+ invoice_type = 'SEWOP'
+ elif row.get('gst_category') == 'Deemed Export':
+ invoice_type = 'DE'
+ elif row.get('gst_category') == 'Registered Regular':
+ invoice_type = 'R'
+
+ return invoice_type
+
+def get_invoice_type_for_cdnrur(row):
+ if row.get('gst_category') == 'Overseas':
+ if row.get('export_type') == 'WPAY':
+ invoice_type = 'EXPWP'
+ else:
+ invoice_type = 'EXPWOP'
+ elif row.get('gst_category') == 'Unregistered':
+ invoice_type = 'B2CL'
+
+ return invoice_type
+
+def get_basic_invoice_detail(row):
+ return {
+ "inum": row["invoice_number"],
+ "idt": getdate(row["posting_date"]).strftime('%d-%m-%Y'),
+ "val": flt(row["invoice_value"], 2)
+ }
+
+def get_rate_and_tax_details(row, gstin):
+ itm_det = {"txval": flt(row["taxable_value"], 2),
+ "rt": row["rate"],
+ "csamt": (flt(row.get("cess_amount"), 2) or 0)
+ }
+
+ # calculate rate
+ num = 1 if not row["rate"] else "%d%02d" % (row["rate"], 1)
+ rate = row.get("rate") or 0
+
+ # calculate tax amount added
+ tax = flt((row["taxable_value"]*rate)/100.0, 2)
+ frappe.errprint([tax, tax/2])
+ if row.get("customer_gstin") and gstin[0:2] == row["customer_gstin"][0:2]:
+ itm_det.update({"camt": flt(tax/2.0, 2), "samt": flt(tax/2.0, 2)})
+ else:
+ itm_det.update({"iamt": tax})
+
+ return {"num": int(num), "itm_det": itm_det}
+
+def get_company_gstin_number(company, address=None, all_gstins=False):
+ gstin = ''
+ if address:
+ gstin = frappe.db.get_value("Address", address, "gstin")
+
+ if not gstin:
+ filters = [
+ ["is_your_company_address", "=", 1],
+ ["Dynamic Link", "link_doctype", "=", "Company"],
+ ["Dynamic Link", "link_name", "=", company],
+ ["Dynamic Link", "parenttype", "=", "Address"],
+ ["gstin", "!=", '']
+ ]
+ gstin = frappe.get_all("Address", filters=filters, pluck="gstin", order_by="is_primary_address desc")
+ if gstin and not all_gstins:
+ gstin = gstin[0]
+
+ if not gstin:
+ address = frappe.bold(address) if address else ""
+ frappe.throw(_("Please set valid GSTIN No. in Company Address {} for company {}").format(
+ address, frappe.bold(company)
+ ))
+
+ return gstin
+
+@frappe.whitelist()
+def download_json_file():
+ ''' download json content in a file '''
+ data = frappe._dict(frappe.local.form_dict)
+ frappe.response['filename'] = frappe.scrub("{0} {1}".format(data['report_name'], data['report_type'])) + '.json'
+ frappe.response['filecontent'] = data['data']
+ frappe.response['content_type'] = 'application/json'
+ frappe.response['type'] = 'download'