From 22b61607c6fe16f9a76fad03dc854603e27f3afe Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 21 Mar 2019 20:47:47 +0530 Subject: [PATCH] feat: GSTR3B Report JSON creation and Print Format (#16595) * feat: Created doctype for GSTR3B report and added boilerplate code * feat: Updated gst_fields and patches for gst_category * feat: Functions for calculating itc amount * fix: Patched eligibility_for_itc_field * fix: Updated set_category for gst * fix: Function for setting iter_state supplies * fix: Changed route to regional module, minor fix in inster_state_supply grouping and fixes in print format * fix(style): Added missing semicolon and removed unused imports * fix: Patch field only if column is available * fix: Make custom fields only for india sepecific company * fix: Add intro to gstr3b report * fix: Updated patch in patches.txt * fix: Update patches.txt * fix: Update patch to set GST Category * fix: Add fields for nil rated and non gst in item master * fix: Added logic for nil rated and non gst inward flow * fix: Initial test case for GSTR3B Report * fix: Codacy fixes * fix: Test Case fixes * fix: Add link for gstr_3b_report in accounting module * fix: Updated report template * fix: Changes in GSTR3B Report doctype * fix: Added function to get missing field invoices * fix: Added more test cases * fix: Item not found error in test case * fix: Key error in state numbers * fix: Changes in GSTR3b Doctype * fix: Changed functions to method * fix: Minor fix in patch * fix: Add gst_ctegory in GST Reports * fix: Minor fixes in patch and itc_mapping * fix: Query to patch itc field * fix: Patch registered customers and fix for multiple gst accounts * fix: Test case * fix: Total taxable calculation logic fix and template enhancement * fix: Calculate txval seperately * fix: itc amount calculation fix and patch improvement * fix: Updated test_cases for itc calculation * fix: Missing field query * fix: Multiple minor fixes inreport * fix: Added transalations in GSTR3B-Form * fix: Use double underscore for translation * fix: GST fields ordering fix * fix: Print form precision fix and get_period function fix --- erpnext/config/accounting.py | 4 + erpnext/patches.txt | 1 + erpnext/patches/v12_0/set_gst_category.py | 50 ++ .../doctype/gstr_3b_report/__init__.py | 0 .../gstr_3b_report/gstr_3b_report.html | 297 ++++++++++++ .../doctype/gstr_3b_report/gstr_3b_report.js | 59 +++ .../gstr_3b_report/gstr_3b_report.json | 259 ++++++++++ .../doctype/gstr_3b_report/gstr_3b_report.py | 459 ++++++++++++++++++ .../gstr_3b_report/test_gstr_3b_report.py | 380 +++++++++++++++ erpnext/regional/india/setup.py | 87 +++- .../gst_itemised_purchase_register.py | 4 +- .../gst_itemised_sales_register.py | 4 +- .../gst_purchase_register.py | 4 +- .../gst_sales_register/gst_sales_register.py | 4 +- erpnext/regional/report/gstr_1/gstr_1.py | 12 +- erpnext/regional/report/gstr_2/gstr_2.py | 8 +- 16 files changed, 1590 insertions(+), 42 deletions(-) create mode 100644 erpnext/patches/v12_0/set_gst_category.py create mode 100644 erpnext/regional/doctype/gstr_3b_report/__init__.py create mode 100644 erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html create mode 100644 erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js create mode 100644 erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.json create mode 100644 erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py create mode 100644 erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py diff --git a/erpnext/config/accounting.py b/erpnext/config/accounting.py index 9de5b368db..afe35f8078 100644 --- a/erpnext/config/accounting.py +++ b/erpnext/config/accounting.py @@ -563,6 +563,10 @@ def get_data(): "name": "GSTR-2", "is_query_report": True }, + { + "type": "doctype", + "name": "GSTR 3B Report", + }, { "type": "report", "name": "GST Sales Register", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 79efe1be42..6643982eb1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -583,6 +583,7 @@ erpnext.patches.v11_0.renamed_from_to_fields_in_project erpnext.patches.v11_0.add_permissions_in_gst_settings erpnext.patches.v11_1.setup_guardian_role execute:frappe.delete_doc('DocType', 'Notification Control') +erpnext.patches.v12_0.set_gst_category erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants erpnext.patches.v12_0.set_task_status erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019 diff --git a/erpnext/patches/v12_0/set_gst_category.py b/erpnext/patches/v12_0/set_gst_category.py new file mode 100644 index 0000000000..54bc5b3c74 --- /dev/null +++ b/erpnext/patches/v12_0/set_gst_category.py @@ -0,0 +1,50 @@ +import frappe +from erpnext.regional.india.setup import make_custom_fields + +def execute(): + + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + make_custom_fields() + + for doctype in ['Sales Invoice', 'Purchase Invoice']: + has_column = frappe.db.has_column(doctype,'invoice_type') + + if has_column: + update_map = { + 'Regular': 'Registered Regular', + 'Export': 'Overseas', + 'SEZ': 'SEZ', + 'Deemed Export': 'Deemed Export', + } + + for old, new in update_map.items(): + frappe.db.sql("UPDATE `tab{doctype}` SET gst_category = %s where invoice_type = %s".format(doctype=doctype), (new, old)) #nosec + + frappe.delete_doc('Custom Field', 'Sales Invoice-invoice_type') + frappe.delete_doc('Custom Field', 'Purchase Invoice-invoice_type') + + itc_update_map = { + "ineligible": "Ineligible", + "input service": "Input Service Distributor", + "capital goods": "Import Of Capital Goods", + "input": "All Other ITC" + } + + has_gst_fields = frappe.db.has_column('Purchase Invoice','eligibility_for_itc') + + if has_gst_fields: + for old, new in itc_update_map.items(): + frappe.db.sql("UPDATE `tabPurchase Invoice` SET eligibility_for_itc = %s where eligibility_for_itc = %s ", (new, old)) + + for doctype in ["Customer", "Supplier"]: + + frappe.db.sql(""" UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Registered Regular" + where t3.link_name = t1.name and t3.parent = t2.name and t2.gstin IS NOT NULL and t2.gstin != '' """.format(doctype=doctype)) #nosec + + frappe.db.sql(""" UPDATE `tab{doctype}` t1, `tabAddress` t2, `tabDynamic Link` t3 SET t1.gst_category = "Overseas" + where t3.link_name = t1.name and t3.parent = t2.name and t2.country != 'India' """.format(doctype=doctype)) #nosec + + diff --git a/erpnext/regional/doctype/gstr_3b_report/__init__.py b/erpnext/regional/doctype/gstr_3b_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html new file mode 100644 index 0000000000..4bf0de1ad2 --- /dev/null +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html @@ -0,0 +1,297 @@ + +
+

{{ __("GSTR3B-Form")}}

+
{{__("GSTIN")}}:   {{ data.gstin }}
+
{{__("Period")}}:   {{ data.ret_period }}
+
+ +
3.1  {{__("Details of Outward Supplies and inward supplies liable to reverse charge")}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{__("Nature Of Supplies")}}{{__("Total Taxable value")}}{{__("Integrated Tax")}}{{__("Central Tax")}}{{__("State/UT Tax")}}{{__("Cess")}}
(a) {{__("Outward taxable supplies(other than zero rated, nil rated and exempted")}}{{ flt(data.sup_details.osup_det.txval, 2) }}{{ flt(data.sup_details.osup_det.iamt, 2) }}{{ flt(data.sup_details.osup_det.camt, 2) }}{{ flt(data.sup_details.osup_det.samt, 2) }}{{ flt(data.sup_details.osup_det.csamt, 2) }}
(b) {{__("Outward taxable supplies(zero rated)")}}{{ flt(data.sup_details.osup_zero.txval, 2) }}{{ flt(data.sup_details.osup_zero.iamt, 2) }}{{ flt(data.sup_details.osup_zero.csamt, 2) }}
(b) {{__("Other outward supplies(Nil rated,Exempted)")}}{{ data.sup_details.osup_nil_exmp.txval }}
(d) {{__("Inward Supplies(liable to reverse charge")}}{{ flt(data.sup_details.isup_rev.txval, 2) }}{{ flt(data.sup_details.isup_rev.iamt, 2) }}{{ flt(data.sup_details.isup_rev.camt, 2) }}{{ flt(data.sup_details.isup_rev.samt, 2) }}{{ flt(data.sup_details.isup_rev.csamt,2) }}
(e) {{__("Non-GST outward supplies")}}{{ data.sup_details.osup_nongst.txval }}
+ +
+ 3.2  {{__("Of the supplies shown in 3.1 (a) above, details of inter-State supplies made to unregisterd + persons, composition taxable persons and UIN holders")}} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{__("Place Of Supply (State/UT)")}}{{__("Total Taxable Value")}}{{__("Amount of Integrated Tax")}}
{{__("Supplies made to Unregistered Persons")}} + {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ row.pos }}
+ {% endif %} + {% endfor %} +
+ {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ flt(row.txval, 2) }}
+ {% endif %} + {% endfor %} +
+ {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ flt(row.iamt, 2) }}
+ {% endif %} + {% endfor %} +
{{__("Suppliies made to Composition Taxable Persons")}} + {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ row.pos }}
+ {% endif %} + {% endfor %} +
+ {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ flt(row.txval, 2) }}
+ {% endif %} + {% endfor %} +
+ {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ flt(row.iamt, 2) }}
+ {% endif %} + {% endfor %} +
{{__("Supplies made to UIN holders")}} + {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ row.pos }}
+ {% endif %} + {% endfor %} +
+ {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ flt(row.txval, 2) }}
+ {% endif %} + {% endfor %} +
+ {% for row in data.inter_sup.unreg_details %} + {% if row %} + {{ flt(row.iamt, 2) }}
+ {% endif %} + {% endfor %} +
+ +
4.   {{__("Eligible ITC")}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DetailsIntegrated TaxCentral TaxState/UT taxCess
(A) {{__("ITC Available (whether in full op part)")}}
  (1) {{__("Import of goods")}} {{ flt(data.itc_elg.itc_avl[0].iamt, 2) }}{{ flt(data.itc_elg.itc_avl[0].camt, 2) }}{{ flt(data.itc_elg.itc_avl[0].samt, 2) }}{{ flt(data.itc_elg.itc_avl[0].csamt, 2) }}
  (2) {{__("Import of services")}}{{ flt(data.itc_elg.itc_avl[1].iamt, 2) }}{{ flt(data.itc_elg.itc_avl[1].camt, 2) }}{{ flt(data.itc_elg.itc_avl[1].samt, 2) }}{{ flt(data.itc_elg.itc_avl[1].csamt, 2) }}
  (3) {{__("Inward supplies liable to reverse charge (other than 1 & 2 above)")}}{{ flt(data.itc_elg.itc_avl[2].iamt, 2) }}{{ flt(data.itc_elg.itc_avl[2].camt, 2) }}{{ flt(data.itc_elg.itc_avl[2].samt, 2) }}{{ flt(data.itc_elg.itc_avl[2].csamt, 2) }}
  (4) {{__("Inward supplies from ISD")}}{{ flt(data.itc_elg.itc_avl[3].iamt, 2) }}{{ flt(data.itc_elg.itc_avl[3].camt, 2) }}{{ flt(data.itc_elg.itc_avl[3].samt, 2) }}{{ flt(data.itc_elg.itc_avl[3].csamt, 2) }}
  (5) {{__("All other ITC")}}{{ flt(data.itc_elg.itc_avl[4].iamt, 2) }}{{ flt(data.itc_elg.itc_avl[4].camt, 2) }}{{ flt(data.itc_elg.itc_avl[4].samt, 2) }}{{ flt(data.itc_elg.itc_avl[4].csamt, 2) }}
(B) {{__("ITC Reversed")}}
  (1) {{__("As per rules 42 & 43 of CGST Rules")}}
  (2) {{__("Others")}}
(C) {{__("Net ITC Available(A) - (B)")}}{{ flt(data.itc_elg.itc_net.iamt, 2) }}{{ flt(data.itc_elg.itc_net.camt, 2) }}{{ flt(data.itc_elg.itc_net.samt, 2) }}{{ flt(data.itc_elg.itc_net.csamt, 2) }}
(D) {{__("Ineligible ITC")}}
  (1) {{__("As per section 17(5)")}}{{ flt(data.itc_elg.itc_inelg[0].iamt, 2) }}{{ flt(data.itc_elg.itc_inelg[0].camt, 2) }}{{ flt(data.itc_elg.itc_inelg[0].samt, 2) }}{{ flt(data.itc_elg.itc_inelg[0].csamt, 2) }}
  (2) {{__("Others")}}{{ flt(data.itc_elg.itc_inelg[1].iamt, 2) }}{{ flt(data.itc_elg.itc_inelg[1].camt, 2) }}{{ flt(data.itc_elg.itc_inelg[1].samt, 2) }}{{ flt(data.itc_elg.itc_inelg[1].csamt, 2) }}
+ +
5.    {{__("Values of exempt, nil rated and non-GST inward supplies")}}
+ + + + + + + + + + + + + + + + + + + + +
{{__("Nature of Supplies")}}{{__("Inter-State Supplies")}}{{__("Intra-State Supplies")}}
{{__("From a supplier under composition scheme, Exempt and Nil rated")}}{{ flt(data.inward_sup.isup_details[0].inter, 2) }}{{ flt(data.inward_sup.isup_details[0].intra, 2) }}
{{__("Non GST Inward Supplies")}}{{ flt(data.inward_sup.isup_details[1].inter, 2) }}{{ flt(data.inward_sup.isup_details[1].intra, 2) }}
+ + \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js new file mode 100644 index 0000000000..0d6cef0792 --- /dev/null +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.js @@ -0,0 +1,59 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('GSTR 3B Report', { + refresh : function(frm){ + if(!frm.is_new()) { + frm.set_intro(__("Please save the report again to rebuild or update")); + frm.add_custom_button(__('Download JSON'), function() { + var w = window.open( + frappe.urllib.get_full_url( + "/api/method/erpnext.regional.doctype.gstr_3b_report.gstr_3b_report.make_json?" + +"name="+encodeURIComponent(frm.doc.name))); + + if(!w) { + frappe.msgprint(__("Please enable pop-ups")); return; + } + }); + frm.add_custom_button(__('View Form'), function() { + frappe.call({ + "method" : "erpnext.regional.doctype.gstr_3b_report.gstr_3b_report.view_report", + "args" : { + name : frm.doc.name, + }, + "callback" : function(r){ + + let data = r.message; + + frappe.ui.get_print_settings(false, print_settings => { + + frappe.render_grid({ + template: 'gstr_3b_report', + title: __(this.doctype), + print_settings: print_settings, + data: data, + columns:[] + }); + }); + } + }); + }); + } + }, + + setup: function(frm){ + frm.set_query('company_address', function(doc) { + if(!doc.company) { + frappe.throw(__('Please set Company')); + } + + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { + link_doctype: 'Company', + link_name: doc.company + } + }; + }); + }, +}); diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.json b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.json new file mode 100644 index 0000000000..7b0462fd4b --- /dev/null +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.json @@ -0,0 +1,259 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "format:GSTR3B-{month}-{year}-{company_address}", + "beta": 0, + "creation": "2019-02-04 11:35:55.964639", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company_address", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company Address", + "length": 0, + "no_copy": 0, + "options": "Address", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "year", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Year", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "month", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Month", + "length": 0, + "no_copy": 0, + "options": "January\nFebruary\nMarch\nApril\nMay\nJune\nJuly\nAugust\nSeptember\nOctober\nNovember\nDecember", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "json_output", + "fieldtype": "Code", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "JSON Output", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "missing_field_invoices", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Invoices with no Place Of Supply", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-03-04 10:04:44.767655", + "modified_by": "Administrator", + "module": "Regional", + "name": "GSTR 3B Report", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py new file mode 100644 index 0000000000..a9aa1d58be --- /dev/null +++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py @@ -0,0 +1,459 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +import json +from six import iteritems +from frappe.utils import flt, getdate +from erpnext.regional.india import state_numbers + +class GSTR3BReport(Document): + def before_save(self): + + self.get_data() + + def get_data(self): + + self.report_dict = { + "gstin": "", + "ret_period": "", + "inward_sup": { + "isup_details": [ + { + "ty": "GST", + "intra": 0, + "inter": 0 + }, + { + "ty": "NONGST", + "inter": 0, + "intra": 0 + } + ] + }, + "sup_details": { + "osup_zero": { + "csamt": 0, + "txval": 0, + "iamt": 0 + }, + "osup_nil_exmp": { + "txval": 0 + }, + "osup_det": { + "samt": 0, + "csamt": 0, + "txval": 0, + "camt": 0, + "iamt": 0 + }, + "isup_rev": { + "samt": 0, + "csamt": 0, + "txval": 0, + "camt": 0, + "iamt": 0 + }, + "osup_nongst": { + "txval": 0, + } + }, + "inter_sup": { + "unreg_details": [], + "comp_details": [], + "uin_details": [] + }, + "itc_elg": { + "itc_avl": [ + { + "csamt": 0, + "samt": 0, + "ty": "IMPG", + "camt": 0, + "iamt": 0 + }, + { + "csamt": 0, + "samt": 0, + "ty": "IMPS", + "camt": 0, + "iamt": 0 + }, + { + "samt": 0, + "csamt": 0, + "ty": "ISRC", + "camt": 0, + "iamt": 0 + }, + { + "ty": "ISD", + "iamt": 1, + "camt": 1, + "samt": 1, + "csamt": 1 + }, + { + "samt": 0, + "csamt": 0, + "ty": "OTH", + "camt": 0, + "iamt": 0 + } + ], + "itc_net": { + "samt": 0, + "csamt": 0, + "camt": 0, + "iamt": 0 + }, + "itc_inelg": [ + { + "ty": "RUL", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + }, + { + "ty": "OTH", + "iamt": 0, + "camt": 0, + "samt": 0, + "csamt": 0 + } + ] + } + } + + self.gst_details = self.get_company_gst_details() + self.report_dict["gstin"] = self.gst_details.get("gstin") + self.report_dict["ret_period"] = get_period(self.month, self.year) + self.month_no = get_period(self.month) + self.account_heads = self.get_account_heads() + + outward_supply_tax_amounts = self.get_tax_amounts("Sales Invoice") + inward_supply_tax_amounts = self.get_tax_amounts("Purchase Invoice", reverse_charge="Y") + itc_details = self.get_itc_details() + inter_state_supplies = self.get_inter_state_supplies(self.gst_details.get("gst_state")) + inward_nil_exempt = self.get_inward_nil_exempt(self.gst_details.get("gst_state")) + + self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"]) + self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"]) + self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Registered Regular"], reverse_charge="Y") + self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2) + self.set_itc_details(itc_details) + self.set_inter_state_supply(inter_state_supplies) + self.set_inward_nil_exempt(inward_nil_exempt) + + self.missing_field_invoices = self.get_missing_field_invoices() + + self.json_output = frappe.as_json(self.report_dict) + + def set_inward_nil_exempt(self, inward_nil_exempt): + + self.report_dict["inward_sup"]["isup_details"][0]["inter"] = flt(inward_nil_exempt.get("gst").get("inter"), 2) + self.report_dict["inward_sup"]["isup_details"][0]["intra"] = flt(inward_nil_exempt.get("gst").get("intra"), 2) + self.report_dict["inward_sup"]["isup_details"][1]["inter"] = flt(inward_nil_exempt.get("non_gst").get("inter"), 2) + self.report_dict["inward_sup"]["isup_details"][1]["intra"] = flt(inward_nil_exempt.get("non_gst").get("intra"), 2) + + def set_itc_details(self, itc_details): + + itc_type_map = { + 'IMPG': 'Import Of Capital Goods', + 'IMPS': 'Import Of Service', + 'ISD': 'Input Service Distributor', + 'OTH': 'All Other ITC' + } + + net_itc = self.report_dict["itc_elg"]["itc_net"] + + for d in self.report_dict["itc_elg"]["itc_avl"]: + if d["ty"] == 'ISRC': + reverse_charge = "Y" + else: + reverse_charge = "N" + + for account_head in self.account_heads: + + d["iamt"] = flt(itc_details.get((itc_type_map.get(d["ty"]), reverse_charge, account_head.get('igst_account')), {}).get("amount"), 2) + net_itc["iamt"] += flt(d["iamt"], 2) + + d["camt"] = flt(itc_details.get((itc_type_map.get(d["ty"]), reverse_charge, account_head.get('cgst_account')), {}).get("amount"), 2) + net_itc["camt"] += flt(d["camt"], 2) + + d["samt"] = flt(itc_details.get((itc_type_map.get(d["ty"]), reverse_charge, account_head.get('sgst_account')), {}).get("amount"), 2) + net_itc["samt"] += flt(d["samt"], 2) + + d["csamt"] = flt(itc_details.get((itc_type_map.get(d["ty"]), reverse_charge, account_head.get('cess_account')), {}).get("amount"), 2) + net_itc["csamt"] += flt(d["csamt"], 2) + + for account_head in self.account_heads: + + self.report_dict["itc_elg"]["itc_inelg"][1]["iamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("igst_account")), {}).get("amount"), 2) + self.report_dict["itc_elg"]["itc_inelg"][1]["camt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cgst_account")), {}).get("amount"), 2) + self.report_dict["itc_elg"]["itc_inelg"][1]["samt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("sgst_account")), {}).get("amount"), 2) + self.report_dict["itc_elg"]["itc_inelg"][1]["csamt"] = flt(itc_details.get(("Ineligible", "N", account_head.get("cess_account")), {}).get("amount"), 2) + + def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"): + + account_map = { + 'sgst_account': 'samt', + 'cess_account': 'csamt', + 'cgst_account': 'camt', + 'igst_account': 'iamt' + } + + txval = 0 + total_taxable_value = self.get_total_taxable_value(doctype, reverse_charge) + + for gst_category in gst_category_list: + txval += total_taxable_value.get(gst_category,0) + for account_head in self.account_heads: + for account_type, account_name in iteritems(account_head): + if account_map.get(account_type) in self.report_dict.get(supply_type).get(supply_category): + self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \ + flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2) + + for k, v in iteritems(account_map): + txval -= self.report_dict.get(supply_type, {}).get(supply_category, {}).get(v, 0) + + self.report_dict[supply_type][supply_category]["txval"] = flt(txval, 2) + + def set_inter_state_supply(self, inter_state_supply): + + for d in inter_state_supply.get("Unregistered", []): + self.report_dict["inter_sup"]["unreg_details"].append(d) + + for d in inter_state_supply.get("Registered Composition", []): + self.report_dict["inter_sup"]["comp_details"].append(d) + + for d in inter_state_supply.get("UIN Holders", []): + self.report_dict["inter_sup"]["uin_details"].append(d) + + def get_total_taxable_value(self, doctype, reverse_charge): + + return frappe._dict(frappe.db.sql(""" + select gst_category, sum(base_grand_total) as total + from `tab{doctype}` + where docstatus = 1 and month(posting_date) = %s + and year(posting_date) = %s and reverse_charge = %s + and company = %s and company_gstin = %s + group by gst_category + """ #nosec + .format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin")))) + + def get_itc_details(self, reverse_charge='N'): + + itc_amount = frappe.db.sql(""" + select s.gst_category, sum(t.tax_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge + from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t + where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s + and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s + and s.company_gstin = %s + group by t.account_head, s.gst_category, s.eligibility_for_itc + """, + (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + + itc_details = {} + + for d in itc_amount: + itc_details.setdefault((d.eligibility_for_itc, d.reverse_charge, d.account_head),{ + "amount": d.tax_amount + }) + + return itc_details + + def get_nil_rated_supply_value(self): + + return frappe.db.sql(""" + select sum(i.base_amount) as total from + `tabSales Invoice Item` i, `tabSales Invoice` s + where s.docstatus = 1 and i.parent = s.name and i.is_nil_exempt = 1 + and month(s.posting_date) = %s and year(s.posting_date) = %s + and s.company = %s and s.company_gstin = %s""", + (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total + + def get_inter_state_supplies(self, state): + + inter_state_supply = frappe.db.sql(""" select sum(s.grand_total) as total, t.tax_amount, a.gst_state, s.gst_category + from `tabSales Invoice` s, `tabSales Taxes and Charges` t, `tabAddress` a + where t.parent = s.name and s.customer_address = a.name and + s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s and + a.gst_state <> %s and s.company = %s and s.company_gstin = %s and + s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders') + group by s.gst_category, a.state""", (self.month_no, self.year, state, self.company, self.gst_details.get("gstin")), as_dict=1) + + inter_state_supply_details = {} + + for d in inter_state_supply: + inter_state_supply_details.setdefault( + d.gst_category, [] + ) + + inter_state_supply_details[d.gst_category].append({ + "pos": get_state_code(d.gst_state), + "txval": d.total, + "iamt": d.tax_amount + }) + + return inter_state_supply_details + + def get_inward_nil_exempt(self, state): + + inward_nil_exempt = frappe.db.sql(""" select a.gst_state, sum(i.base_amount) as base_amount, + i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i, `tabAddress` a + where p.docstatus = 1 and p.name = i.parent and p.supplier_address = a.name + and i.is_nil_exempt = 1 or i.is_non_gst = 1 and + month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s + group by a.gst_state """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + + inward_nil_exempt_details = { + "gst": { + "intra": 0.0, + "inter": 0.0 + }, + "non_gst": { + "intra": 0.0, + "inter": 0.0 + } + } + + for d in inward_nil_exempt: + if d.is_nil_exempt == 1 and state == d.gst_state: + inward_nil_exempt_details["gst"]["intra"] += d.base_amount + elif d.is_nil_exempt == 1 and state != d.gst_state: + inward_nil_exempt_details["gst"]["inter"] += d.base_amount + elif d.is_non_gst == 1 and state == d.gst_state: + inward_nil_exempt_details["non_gst"]["inter"] += d.base_amount + elif d.is_non_gst == 1 and state != d.gst_state: + inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount + + return inward_nil_exempt_details + + def get_tax_amounts(self, doctype, reverse_charge="N"): + + if doctype == "Sales Invoice": + tax_template = 'Sales Taxes and Charges' + elif doctype == "Purchase Invoice": + tax_template = 'Purchase Taxes and Charges' + + tax_amounts = frappe.db.sql(""" + select s.gst_category, sum(t.tax_amount) as tax_amount, t.account_head + from `tab{doctype}` s , `tab{template}` t + where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s + and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s + and s.company_gstin = %s + group by t.account_head, s.gst_category + """ #nosec + .format(doctype=doctype, template=tax_template), + (reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) + + tax_details = {} + + for d in tax_amounts: + tax_details.setdefault( + (d.account_head,d.gst_category),{ + "amount": d.get("tax_amount"), + } + ) + + return tax_details + + def get_company_gst_details(self): + + gst_details = frappe.get_all("Address", + fields=["gstin", "gst_state", "gst_state_number"], + filters={ + "name":self.company_address + }) + + if gst_details: + return gst_details[0] + else: + frappe.throw("Please enter GSTIN and state for the Company Address {0}".format(self.company_address)) + + def get_account_heads(self): + + account_heads = frappe.get_all("GST Account", + fields=["cgst_account", "sgst_account", "igst_account", "cess_account"], + filters={ + "company":self.company + }) + + if account_heads: + return account_heads + else: + frappe.throw("Please set account heads in GST Settings for Compnay {0}".format(self.company)) + + def get_missing_field_invoices(self): + + missing_field_invoices = [] + + for doctype in ["Sales Invoice", "Purchase Invoice"]: + + if doctype == "Sales Invoice": + party_type = 'Customer' + party = 'customer' + else: + party_type = 'Supplier' + party = 'supplier' + + docnames = frappe.db.sql(""" + select t1.name from `tab{doctype}` t1, `tab{party_type}` t2 + where t1.docstatus = 1 and month(t1.posting_date) = %s and year(t1.posting_date) = %s + and t1.company = %s and t1.place_of_supply IS NULL and t1.{party} = t2.name and + t2.gst_category != 'Overseas' + """.format(doctype = doctype, party_type = party_type, party=party), (self.month_no, self.year, self.company), as_dict=1) #nosec + + for d in docnames: + missing_field_invoices.append(d.name) + + return ",".join(missing_field_invoices) + +def get_state_code(state): + + state_code = state_numbers.get(state) + + return state_code + +def get_period(month, year=None): + + month_no = { + "January": 1, + "February": 2, + "March": 3, + "April": 4, + "May": 5, + "June": 6, + "July": 7, + "August": 8, + "September": 9, + "October": 10, + "November": 11, + "December": 12 + }.get(month) + + if year: + return str(month_no).zfill(2) + str(year) + else: + return month_no + + +@frappe.whitelist() +def view_report(name): + + json_data = frappe.get_value("GSTR 3B Report", name, 'json_output') + return json.loads(json_data) + +@frappe.whitelist() +def make_json(name): + + json_data = frappe.get_value("GSTR 3B Report", name, 'json_output') + file_name = "GST3B.json" + frappe.local.response.filename = file_name + frappe.local.response.filecontent = json_data + frappe.local.response.type = "download" diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py new file mode 100644 index 0000000000..2e9f53685e --- /dev/null +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -0,0 +1,380 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.stock.doctype.item.test_item import make_item +import json + +class TestGSTR3BReport(unittest.TestCase): + def test_gstr_3b_report(self): + frappe.set_user("Administrator") + + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'") + frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'") + + make_company() + make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000}) + set_account_heads() + make_customers() + make_suppliers() + make_sales_invoice() + create_purchase_invoices() + + if frappe.db.exists("GSTR 3B Report", "GSTR3B-March-2019-_Test Address-Billing"): + report = frappe.get_doc("GSTR 3B Report", "GSTR3B-March-2019-_Test Address-Billing") + report.save() + else: + report = frappe.get_doc({ + "doctype": "GSTR 3B Report", + "company": "_Test Company GST", + "company_address": "_Test Address-Billing", + "year": "2019", + "month": "March" + }).insert() + + output = json.loads(report.json_output) + + self.assertEqual(output["sup_details"]["osup_det"]["iamt"], 18), + self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18), + self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18), + self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100), + self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250) + self.assertEqual(output["itc_elg"]["itc_avl"][4]["iamt"], 45) + +def make_sales_invoice(): + si = create_sales_invoice(company="_Test Company GST", + customer = '_Test GST Customer', + currency = 'INR', + warehouse = 'Finished Goods - _GST', + debit_to = 'Debtors - _GST', + income_account = 'Sales - _GST', + expense_account = 'Cost of Goods Sold - _GST', + cost_center = 'Main - _GST', + posting_date = '2019-03-10', + do_not_save=1 + ) + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": "IGST - _GST", + "cost_center": "Main - _GST", + "description": "IGST @ 18.0", + "rate": 18 + }) + + si.submit() + + si1 = create_sales_invoice(company="_Test Company GST", + customer = '_Test GST SEZ Customer', + currency = 'INR', + warehouse = 'Finished Goods - _GST', + debit_to = 'Debtors - _GST', + income_account = 'Sales - _GST', + expense_account = 'Cost of Goods Sold - _GST', + cost_center = 'Main - _GST', + posting_date = '2019-03-10', + do_not_save=1 + ) + + si1.append("taxes", { + "charge_type": "On Net Total", + "account_head": "IGST - _GST", + "cost_center": "Main - _GST", + "description": "IGST @ 18.0", + "rate": 18 + }) + + si1.submit() + + si2 = create_sales_invoice(company="_Test Company GST", + customer = '_Test Unregistered Customer', + currency = 'INR', + warehouse = 'Finished Goods - _GST', + debit_to = 'Debtors - _GST', + income_account = 'Sales - _GST', + expense_account = 'Cost of Goods Sold - _GST', + cost_center = 'Main - _GST', + posting_date = '2019-03-10', + do_not_save=1 + ) + + si2.append("taxes", { + "charge_type": "On Net Total", + "account_head": "IGST - _GST", + "cost_center": "Main - _GST", + "description": "IGST @ 18.0", + "rate": 18 + }) + + si2.submit() + + si3 = create_sales_invoice(company="_Test Company GST", + customer = '_Test GST Customer', + currency = 'INR', + item = 'Milk', + warehouse = 'Finished Goods - _GST', + debit_to = 'Debtors - _GST', + income_account = 'Sales - _GST', + expense_account = 'Cost of Goods Sold - _GST', + cost_center = 'Main - _GST', + posting_date = '2019-03-10', + do_not_save=1 + ) + si3.submit() + +def create_purchase_invoices(): + + pi = make_purchase_invoice( + company="_Test Company GST", + supplier = '_Test Registered Supplier', + currency = 'INR', + warehouse = 'Finished Goods - _GST', + cost_center = 'Main - _GST', + posting_date = '2019-03-10', + do_not_save=1, + ) + + pi.eligibility_for_itc = "All Other ITC" + + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "IGST - _GST", + "cost_center": "Main - _GST", + "description": "IGST @ 18.0", + "rate": 18 + }) + + pi.submit() + + pi1 = make_purchase_invoice( + company="_Test Company GST", + supplier = '_Test Registered Supplier', + currency = 'INR', + warehouse = 'Finished Goods - _GST', + cost_center = 'Main - _GST', + posting_date = '2019-03-10', + item = "Milk", + do_not_save=1 + ) + + pi1.submit() + +def make_suppliers(): + + if not frappe.db.exists("Supplier", "_Test Registered Supplier"): + frappe.get_doc({ + "supplier_group": "_Test Supplier Group", + "supplier_name": "_Test Registered Supplier", + "gst_category": "Registered Regular", + "supplier_type": "Individual", + "doctype": "Supplier", + }).insert() + + if not frappe.db.exists("Supplier", "_Test Unregistered Supplier"): + frappe.get_doc({ + "supplier_group": "_Test Supplier Group", + "supplier_name": "_Test Unregistered Supplier", + "gst_category": "Unregistered", + "supplier_type": "Individual", + "doctype": "Supplier", + }).insert() + + if not frappe.db.exists('Address', '_Test Supplier GST-1-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Supplier GST-1", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gstin": "29AACCV0498C1Z9", + "gst_state": "Karnataka", + }).insert() + + address.append("links", { + "link_doctype": "Supplier", + "link_name": "_Test Registered Supplier" + }) + + address.save() + + if not frappe.db.exists('Address', '_Test Supplier GST-2-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Supplier GST-2", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gst_state": "Karnataka", + }).insert() + + address.append("links", { + "link_doctype": "Supplier", + "link_name": "_Test Unregistered Supplier" + }) + + address.save() + +def make_customers(): + + if not frappe.db.exists("Customer", "_Test GST Customer"): + frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "_Test GST Customer", + "gst_category": "Registered Regular", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + if not frappe.db.exists("Customer", "_Test GST SEZ Customer"): + frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "_Test GST SEZ Customer", + "gst_category": "SEZ", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + if not frappe.db.exists("Customer", "_Test Unregistered Customer"): + frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "_Test Unregistered Customer", + "gst_category": "Unregistered", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + if not frappe.db.exists('Address', '_Test GST-1-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test GST-1", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gstin": "29AZWPS7135H1ZG", + "gst_state": "Karnataka", + "gst_state_number": "29" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test GST Customer" + }) + + address.save() + + if not frappe.db.exists('Address', '_Test GST-2-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test GST-2", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gst_state": "Haryana", + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Unregistered Customer" + }) + + address.save() + + if not frappe.db.exists('Address', '_Test GST-3-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test GST-3", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gst_state": "Gujarat", + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test GST SEZ Customer" + }) + + address.save() + +def make_company(): + + if frappe.db.exists("Company", "_Test Company GST"): + return + company = frappe.new_doc("Company") + company.company_name = "_Test Company GST" + company.abbr = "_GST" + company.default_currency = "INR" + company.country = "India" + company.insert() + + if not frappe.db.exists('Address', '_Test Address-Billing'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_title": "_Test Address", + "address_type": "Billing", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+91 0000000000", + "gstin": "27AAECE4835E1ZR", + "gst_state": "Maharashtra", + "gst_state_number": "27" + }).insert() + + address.append("links", { + "link_doctype": "Company", + "link_name": "_Test Company GST" + }) + + address.save() + +def set_account_heads(): + + gst_settings = frappe.get_doc("GST Settings") + + gst_account = frappe.get_all( + "GST Account", + fields=["cgst_account", "sgst_account", "igst_account"], + filters = {"company": "_Test Company GST"}) + + if not gst_account: + gst_settings.append("gst_accounts", { + "company": "_Test Company GST", + "cgst_account": "CGST - _GST", + "sgst_account": "SGST - _GST", + "igst_account": "IGST - _GST", + }) + + gst_settings.save() + + diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index c5498c7770..c0d44b2a46 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -94,21 +94,39 @@ def make_custom_fields(update=True): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', allow_on_submit=1, print_hide=1) - invoice_gst_fields = [ + nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is nil rated or exempted', + fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code', + print_hide=1) + is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST', + fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt', + print_hide=1) + + purchase_invoice_gst_category = [ dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='language', print_hide=1, collapsible=1), + dict(fieldname='gst_category', label='GST Category', + fieldtype='Data', insert_after='gst_section', print_hide=1, + fetch_from='supplier.gst_category') + ] + + sales_invoice_gst_category = [ + dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', + insert_after='language', print_hide=1, collapsible=1), + dict(fieldname='gst_category', label='GST Category', + fieldtype='Data', insert_after='gst_section', print_hide=1, + fetch_from='customer.gst_category') + ] + + invoice_gst_fields = [ dict(fieldname='invoice_copy', label='Invoice Copy', - fieldtype='Select', insert_after='gst_section', print_hide=1, allow_on_submit=1, + fieldtype='Select', insert_after='gst_category', print_hide=1, allow_on_submit=1, options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier'), dict(fieldname='reverse_charge', label='Reverse Charge', fieldtype='Select', insert_after='invoice_copy', print_hide=1, options='Y\nN', default='N'), - dict(fieldname='invoice_type', label='Invoice Type', - fieldtype='Select', insert_after='invoice_copy', print_hide=1, - options='Regular\nSEZ\nExport\nDeemed Export', default='Regular'), dict(fieldname='export_type', label='Export Type', - fieldtype='Select', insert_after='invoice_type', print_hide=1, - depends_on='eval:in_list(["SEZ", "Export", "Deemed Export"], doc.invoice_type)', + fieldtype='Select', insert_after='reverse_charge', print_hide=1, + depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', options='\nWith Payment of Tax\nWithout Payment of Tax'), dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', fieldtype='Data', insert_after='export_type', print_hide=1), @@ -134,7 +152,7 @@ def make_custom_fields(update=True): purchase_invoice_itc_fields = [ dict(fieldname='eligibility_for_itc', label='Eligibility For ITC', fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1, - options='input\ninput service\ncapital goods\nineligible', default="ineligible"), + options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nIneligible\nAll Other ITC', default="All Other ITC"), dict(fieldname='itc_integrated_tax', label='Availed ITC Integrated Tax', fieldtype='Data', insert_after='eligibility_for_itc', print_hide=1), dict(fieldname='itc_central_tax', label='Availed ITC Central Tax', @@ -163,13 +181,13 @@ def make_custom_fields(update=True): sales_invoice_shipping_fields = [ dict(fieldname='port_code', label='Port Code', fieldtype='Data', insert_after='reason_for_issuing_document', print_hide=1, - depends_on="eval:doc.invoice_type=='Export' "), + depends_on="eval:doc.gst_category=='Overseas' "), dict(fieldname='shipping_bill_number', label=' Shipping Bill Number', fieldtype='Data', insert_after='port_code', print_hide=1, - depends_on="eval:doc.invoice_type=='Export' "), + depends_on="eval:doc.gst_category=='Overseas' "), dict(fieldname='shipping_bill_date', label='Shipping Bill Date', fieldtype='Date', insert_after='shipping_bill_number', print_hide=1, - depends_on="eval:doc.invoice_type=='Export' ") + depends_on="eval:doc.gst_category=='Overseas' "), ] inter_state_gst_field = [ @@ -223,26 +241,30 @@ def make_custom_fields(update=True): dict(fieldname='gst_state_number', label='GST State Number', fieldtype='Data', insert_after='gst_state', read_only=1), ], - 'Purchase Invoice': invoice_gst_fields + purchase_invoice_gst_fields + purchase_invoice_itc_fields, + 'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields, 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, - 'Sales Invoice': invoice_gst_fields + sales_invoice_gst_fields + sales_invoice_shipping_fields, - 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields, + 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields, + 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields, 'Sales Order': sales_invoice_gst_fields, 'Sales Taxes and Charges Template': inter_state_gst_field, 'Purchase Taxes and Charges Template': inter_state_gst_field, 'Item': [ dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Link', options='GST HSN Code', insert_after='item_group'), + dict(fieldname='is_nil_exempt', label='Is nil rated or exempted', + fieldtype='Check', insert_after='gst_hsn_code'), + dict(fieldname='is_non_gst', label='Is Non GST ', + fieldtype='Check', insert_after='is_nil_exempt') ], - 'Quotation Item': [hsn_sac_field], - 'Supplier Quotation Item': [hsn_sac_field], - 'Sales Order Item': [hsn_sac_field], - 'Delivery Note Item': [hsn_sac_field], - 'Sales Invoice Item': [hsn_sac_field], - 'Purchase Order Item': [hsn_sac_field], - 'Purchase Receipt Item': [hsn_sac_field], - 'Purchase Invoice Item': [hsn_sac_field], + 'Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], + 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Employee': [ dict(fieldname='ifsc_code', label='IFSC Code', fieldtype='Data', insert_after='bank_ac_no', print_hide=1, @@ -301,11 +323,28 @@ def make_custom_fields(update=True): 'fieldtype': 'Data', 'insert_after': 'supplier_type', 'depends_on': 'eval:doc.is_transporter' + }, + { + 'fieldname': 'gst_category', + 'label': 'GST Category', + 'fieldtype': 'Select', + 'insert_after': 'gst_transporter_id', + 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nUIN Holders', + 'default': 'Unregistered' + } + ], + 'Customer': [ + { + 'fieldname': 'gst_category', + 'label': 'GST Category', + 'fieldtype': 'Select', + 'insert_after': 'customer_type', + 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', + 'default': 'Unregistered' } ] } - - create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=update) + create_custom_fields(custom_fields, update=update) def make_fixtures(company=None): docs = [] diff --git a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py index 34ebb72a9f..3ce2547b20 100644 --- a/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py +++ b/erpnext/regional/report/gst_itemised_purchase_register/gst_itemised_purchase_register.py @@ -10,7 +10,7 @@ def execute(filters=None): dict(fieldtype='Data', label='Supplier GSTIN', fieldname="supplier_gstin", width=120), dict(fieldtype='Data', label='Company GSTIN', fieldname="company_gstin", width=120), dict(fieldtype='Data', label='Reverse Charge', fieldname="reverse_charge", width=120), - dict(fieldtype='Data', label='Invoice Type', fieldname="invoice_type", width=120), + dict(fieldtype='Data', label='GST Category', fieldname="gst_category", width=120), dict(fieldtype='Data', label='Export Type', fieldname="export_type", width=120), dict(fieldtype='Data', label='E-Commerce GSTIN', fieldname="ecommerce_gstin", width=130), dict(fieldtype='Data', label='HSN Code', fieldname="hsn_code", width=120), @@ -20,7 +20,7 @@ def execute(filters=None): 'supplier_gstin', 'company_gstin', 'reverse_charge', - 'invoice_type', + 'gst_category', 'export_type', 'ecommerce_gstin', 'gst_hsn_code', diff --git a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py index d0b1163547..ab523e74e6 100644 --- a/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py +++ b/erpnext/regional/report/gst_itemised_sales_register/gst_itemised_sales_register.py @@ -12,7 +12,7 @@ def execute(filters=None): dict(fieldtype='Data', label='Company GSTIN', fieldname="company_gstin", width=120), dict(fieldtype='Data', label='Place of Supply', fieldname="place_of_supply", width=120), dict(fieldtype='Data', label='Reverse Charge', fieldname="reverse_charge", width=120), - dict(fieldtype='Data', label='Invoice Type', fieldname="invoice_type", width=120), + dict(fieldtype='Data', label='GST Category', fieldname="gst_category", width=120), dict(fieldtype='Data', label='Export Type', fieldname="export_type", width=120), dict(fieldtype='Data', label='E-Commerce GSTIN', fieldname="ecommerce_gstin", width=130), dict(fieldtype='Data', label='HSN Code', fieldname="hsn_code", width=120) @@ -22,7 +22,7 @@ def execute(filters=None): 'company_gstin', 'place_of_supply', 'reverse_charge', - 'invoice_type', + 'gst_category', 'export_type', 'ecommerce_gstin', 'gst_hsn_code' diff --git a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py index 59df553e5a..7274e0acce 100644 --- a/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py +++ b/erpnext/regional/report/gst_purchase_register/gst_purchase_register.py @@ -10,14 +10,14 @@ def execute(filters=None): dict(fieldtype='Data', label='Supplier GSTIN', fieldname="supplier_gstin", width=120), dict(fieldtype='Data', label='Company GSTIN', fieldname="company_gstin", width=120), dict(fieldtype='Data', label='Reverse Charge', fieldname="reverse_charge", width=120), - dict(fieldtype='Data', label='Invoice Type', fieldname="invoice_type", width=120), + dict(fieldtype='Data', label='GST Category', fieldname="gst_category", width=120), dict(fieldtype='Data', label='Export Type', fieldname="export_type", width=120), dict(fieldtype='Data', label='E-Commerce GSTIN', fieldname="ecommerce_gstin", width=130) ], additional_query_columns=[ 'supplier_gstin', 'company_gstin', 'reverse_charge', - 'invoice_type', + 'gst_category', 'export_type', 'ecommerce_gstin' ]) diff --git a/erpnext/regional/report/gst_sales_register/gst_sales_register.py b/erpnext/regional/report/gst_sales_register/gst_sales_register.py index 4b320817e0..075bd483cf 100644 --- a/erpnext/regional/report/gst_sales_register/gst_sales_register.py +++ b/erpnext/regional/report/gst_sales_register/gst_sales_register.py @@ -12,7 +12,7 @@ def execute(filters=None): dict(fieldtype='Data', label='Company GSTIN', fieldname="company_gstin", width=120), dict(fieldtype='Data', label='Place of Supply', fieldname="place_of_supply", width=120), dict(fieldtype='Data', label='Reverse Charge', fieldname="reverse_charge", width=120), - dict(fieldtype='Data', label='Invoice Type', fieldname="invoice_type", width=120), + dict(fieldtype='Data', label='GST Category', fieldname="gst_category", width=120), dict(fieldtype='Data', label='Export Type', fieldname="export_type", width=120), dict(fieldtype='Data', label='E-Commerce GSTIN', fieldname="ecommerce_gstin", width=130) ], additional_query_columns=[ @@ -21,7 +21,7 @@ def execute(filters=None): 'company_gstin', 'place_of_supply', 'reverse_charge', - 'invoice_type', + 'gst_category', 'export_type', 'ecommerce_gstin' ]) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 906e90d7d2..b01abce3cd 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -28,10 +28,10 @@ class Gstr1Report(object): place_of_supply, ecommerce_gstin, reverse_charge, - invoice_type, + gst_category, return_against, is_return, - invoice_type, + gst_category, export_type, port_code, shipping_bill_number, @@ -116,7 +116,7 @@ class Gstr1Report(object): customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type}) if self.filters.get("type_of_business") == "B2B": - conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1 + conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1 and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers])) if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): @@ -138,7 +138,7 @@ class Gstr1Report(object): conditions += """ and is_return = 1 """ elif self.filters.get("type_of_business") == "EXPORT": - conditions += """ and is_return !=1 and invoice_type = 'Export' """ + conditions += """ and is_return !=1 and gst_category = 'Overseas' """ return conditions def get_invoice_items(self): @@ -283,8 +283,8 @@ class Gstr1Report(object): "fieldtype": "Data" }, { - "fieldname": "invoice_type", - "label": "Invoice Type", + "fieldname": "gst_category", + "label": "GST Category", "fieldtype": "Data" }, { diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py index 06056956b0..a362269007 100644 --- a/erpnext/regional/report/gstr_2/gstr_2.py +++ b/erpnext/regional/report/gstr_2/gstr_2.py @@ -26,10 +26,10 @@ class Gstr2Report(Gstr1Report): place_of_supply, ecommerce_gstin, reverse_charge, - invoice_type, + gst_category, return_against, is_return, - invoice_type, + gst_category, export_type, reason_for_issuing_document, eligibility_for_itc, @@ -82,7 +82,7 @@ class Gstr2Report(Gstr1Report): conditions += opts[1] if self.filters.get("type_of_business") == "B2B": - conditions += "and ifnull(invoice_type, '') != 'Export' and is_return != 1 " + conditions += "and ifnull(gst_category, '') != 'Overseas' and is_return != 1 " elif self.filters.get("type_of_business") == "CDNR": conditions += """ and is_return = 1 """ @@ -200,7 +200,7 @@ class Gstr2Report(Gstr1Report): "width": 80 }, { - "fieldname": "invoice_type", + "fieldname": "gst_category", "label": "Invoice Type", "fieldtype": "Data", "width": 80