From 1dfb5eb5355eef7dbfbc511288be54c012765e12 Mon Sep 17 00:00:00 2001 From: Frappe Date: Wed, 14 Jul 2021 12:33:45 +0530 Subject: [PATCH] feat: South Africa VAT report --- .../south_africa_vat_account/__init__.py | 0 .../south_africa_vat_account.json | 34 +++ .../south_africa_vat_account.py | 8 + .../south_africa_vat_settings/__init__.py | 0 .../south_africa_vat_settings.js | 25 ++ .../south_africa_vat_settings.json | 76 ++++++ .../south_africa_vat_settings.py | 8 + .../test_south_africa_vat_settings.py | 8 + .../report/vat_audit_report/__init__.py | 0 .../vat_audit_report/vat_audit_report.js | 31 +++ .../vat_audit_report/vat_audit_report.json | 32 +++ .../vat_audit_report/vat_audit_report.py | 246 ++++++++++++++++++ erpnext/regional/south_africa/__init__.py | 0 erpnext/regional/south_africa/setup.py | 19 ++ 14 files changed, 487 insertions(+) create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/__init__.py create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json create mode 100644 erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/__init__.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py create mode 100644 erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py create mode 100644 erpnext/regional/report/vat_audit_report/__init__.py create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.js create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.json create mode 100644 erpnext/regional/report/vat_audit_report/vat_audit_report.py create mode 100644 erpnext/regional/south_africa/__init__.py create mode 100644 erpnext/regional/south_africa/setup.py diff --git a/erpnext/accounts/doctype/south_africa_vat_account/__init__.py b/erpnext/accounts/doctype/south_africa_vat_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json new file mode 100644 index 0000000000..fa1aa7da59 --- /dev/null +++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json @@ -0,0 +1,34 @@ +{ + "actions": [], + "autoname": "account", + "creation": "2021-07-08 22:04:24.634967", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "in_preview": 1, + "label": "Account", + "options": "Account" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-07-08 22:35:33.202911", + "modified_by": "Administrator", + "module": "Accounts", + "name": "South Africa VAT Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py new file mode 100644 index 0000000000..4bd8c65a04 --- /dev/null +++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SouthAfricaVATAccount(Document): + pass diff --git a/erpnext/regional/doctype/south_africa_vat_settings/__init__.py b/erpnext/regional/doctype/south_africa_vat_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js new file mode 100644 index 0000000000..3f0b466d82 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js @@ -0,0 +1,25 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('South Africa VAT Settings', { + refresh: function(frm) { + frm.set_query("company", function() { + return { + filters: { + country: "South Africa", + } + } + }); + frm.set_query("account", "vat_accounts", function(doc, cdt, cdn) { + var row = locals[cdt][cdn]; + console.log(row); + return { + filters: { + company: frm.doc.company, + account_type: "Tax", + is_group: 0 + } + } + }) + } +}); diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json new file mode 100644 index 0000000000..8a51829c41 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json @@ -0,0 +1,76 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-08 22:34:33.668015", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "vat_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "vat_accounts", + "fieldtype": "Table", + "label": "VAT Accounts", + "options": "South Africa VAT Account", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-07-14 02:17:52.476762", + "modified_by": "Administrator", + "module": "Regional", + "name": "South Africa VAT Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Auditor", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py new file mode 100644 index 0000000000..d74154bfe7 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class SouthAfricaVATSettings(Document): + pass diff --git a/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py new file mode 100644 index 0000000000..1c36652ad6 --- /dev/null +++ b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + +class TestSouthAfricaVATSettings(unittest.TestCase): + pass diff --git a/erpnext/regional/report/vat_audit_report/__init__.py b/erpnext/regional/report/vat_audit_report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.js b/erpnext/regional/report/vat_audit_report/vat_audit_report.js new file mode 100644 index 0000000000..39ef9b563a --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.js @@ -0,0 +1,31 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["VAT Audit Report"] = { + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -2), + "width": "80" + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ] +}; diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.json b/erpnext/regional/report/vat_audit_report/vat_audit_report.json new file mode 100644 index 0000000000..8917e8f3c7 --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-09 11:07:43.473518", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-09 11:07:43.473518", + "modified_by": "Administrator", + "module": "Regional", + "name": "VAT Audit Report", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "GL Entry", + "report_name": "VAT Audit Report", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts User" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Auditor" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py new file mode 100644 index 0000000000..e66212689e --- /dev/null +++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py @@ -0,0 +1,246 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, json +from frappe import _ +from frappe.utils import flt, formatdate, now_datetime, getdate +from datetime import date + +def execute(filters=None): + return VATAuditReport(filters).run() + +class VATAuditReport(object): + + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + self.columns = [] + self.data = [] + self.doctypes = ["Purchase Invoice", "Sales Invoice"] + + def run(self): + self.get_sa_vat_accounts() + for doctype in self.doctypes: + self.get_columns(doctype) + self.select_columns = """ + name as invoice_number, + posting_date, remarks""" + columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \ + else ", customer as party, debit_to as account" + self.select_columns += columns + + self.get_invoice_data(doctype) + + if self.invoices: + self.get_invoice_items(doctype) + self.get_items_based_on_tax_rate(doctype) + self.get_data(doctype) + + return self.columns, self.data + + def get_sa_vat_accounts(self): + self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", \ + filters = {"parent":self.filters.company}, pluck="account") + if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: + frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings")) + + def get_invoice_data(self,doctype): + conditions = self.get_conditions() + self.invoices = frappe._dict() + + 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=doctype, + where_conditions=conditions), self.filters, as_dict=1) + + for d in invoice_data: + self.invoices.setdefault(d.invoice_number, d) + + def get_invoice_items(self,doctype): + 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) + """ % (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, + sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items + if i.item_code == d.item_code and i.parent == d.parent)) + + def get_items_based_on_tax_rate(self,doctype): + self.items_based_on_tax_rate = frappe._dict() + self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \ + else "Sales Taxes and Charges" + 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([doctype] + list(self.invoices.keys()))) + + for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: + if item_wise_tax_detail: + try: + if account in self.sa_vat_accounts: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + else: + continue + for item_code, taxes in item_wise_tax_detail.items(): + tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes) + + if tax_rate is not None: + 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 + + def get_item_amount_map(self, parent, item_code, taxes): + net_amount = abs(self.invoice_items.get(parent).get(item_code)) + tax_rate = taxes[0] + tax_amount = taxes[1] + gross_amount = net_amount + tax_amount + item_amount_map = self.item_tax_rate.setdefault(parent, {}) \ + .setdefault(item_code, []) + amount_dict = { + "tax_rate": tax_rate, + "gross_amount": gross_amount, + "tax_amount": tax_amount, + "net_amount": net_amount + } + item_amount_map.append(amount_dict) + + return tax_rate, item_amount_map + + 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")): + if self.filters.get(opts[0]): + conditions += opts[1] + + return conditions + + def get_data(self,doctype): + consolidated_data = self.get_consolidated_data() + section_name = _("Purchases ") if doctype == "Purchase Invoice" else _("Sales ") + + for rate, section in consolidated_data.items(): + rate = int(rate) + label = frappe.bold(_("Standard Rate ") + section_name + str(rate) + "%") + section_head = {"posting_date": label } + total_gross = total_tax = total_net = 0 + self.data.append(section_head) + for row in section.get("data"): + self.data.append(row) + total_gross += row["gross_amount"] + total_tax += row["tax_amount"] + total_net += row["net_amount"] + + total = { + "posting_date": frappe.bold(_("Total")), + "gross_amount": total_gross, + "tax_amount": total_tax, + "net_amount": total_net, + "bold":1 + } + self.data.append(total) + self.data.append({}) + + def get_consolidated_data(self): + consolidated_data_map={} + for inv, inv_data in self.invoices.items(): + for rate, items in self.items_based_on_tax_rate.get(inv).items(): + consolidated_data_map.setdefault(rate, {"data": []}) + for item in items: + row = {} + item_details = self.item_tax_rate.get(inv).get(item) + row["account"] = inv_data.get("account") + row["posting_date"] = formatdate(inv_data.get("posting_date"), 'dd-mm-yyyy') + row["invoice_number"] = inv + row["party"] = inv_data.get("party") + row["remarks"] = inv_data.get("remarks") + row["gross_amount"]= item_details[0].get("gross_amount") + row["tax_amount"]= item_details[0].get("tax_amount") + row["net_amount"]= item_details[0].get("net_amount") + consolidated_data_map[rate]["data"].append(row) + + return consolidated_data_map + + def get_columns(self,doctype): + self.columns = [ + { + "fieldname": "posting_date", + "label": "Posting Date", + "fieldtype": "Data", + "width": 200 + }, + { + "fieldname": "account", + "label": "Account", + "fieldtype": "Link", + "options": "Account", + "width": 140 + }, + { + "fieldname": "invoice_number", + "label": "Reference", + "fieldtype": "Link", + "options": doctype, + "width": 140 + }, + { + "fieldname": "party", + "label": "Party", + "fieldtype": "Link", + "options": "Supplier" if doctype == "Purchase Invoice" else "Customer", + "width": 140 + }, + { + "fieldname": "remarks", + "label": "Details", + "fieldtype": "Data", + "width": 140 + }, + { + "fieldname": "net_amount", + "label": "Net Amount", + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "tax_amount", + "label": "Tax Amount", + "fieldtype": "Currency", + "width": 140 + }, + { + "fieldname": "gross_amount", + "label": "Gross Amount", + "fieldtype": "Currency", + "width": 140 + }, + ] diff --git a/erpnext/regional/south_africa/__init__.py b/erpnext/regional/south_africa/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py new file mode 100644 index 0000000000..65e9f5bc33 --- /dev/null +++ b/erpnext/regional/south_africa/setup.py @@ -0,0 +1,19 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe, os, json +from frappe.permissions import add_permission, update_permission_property + +def setup(): + add_permissions() + +def add_permissions(): + """Add Permissions for South Africa VAT Settings and South Africa VAT Account""" + for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'): + add_permission(doctype, 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission(doctype, role, 0) + update_permission_property(doctype, role, 0, 'write', 1) + update_permission_property(doctype, role, 0, 'create', 1) \ No newline at end of file