From eaba934c770706d569ad8245984dbe292359af79 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 3 Apr 2024 14:36:35 +0530 Subject: [PATCH 01/23] fix(ux): better error message --- india_compliance/gst_india/client_scripts/e_invoice_actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/client_scripts/e_invoice_actions.js b/india_compliance/gst_india/client_scripts/e_invoice_actions.js index fec3779c26..c761dc82b1 100644 --- a/india_compliance/gst_india/client_scripts/e_invoice_actions.js +++ b/india_compliance/gst_india/client_scripts/e_invoice_actions.js @@ -304,7 +304,7 @@ function is_e_invoice_applicable(frm, show_message = false) { ) { is_einv_applicable = false; message_list.push( - "At least one item must be taxable or transaction is categorized as export." + "All items are either Nil-Rated/Exempted/Non-GST. At least one item must be taxable or the transaction should be categorised as export." ); } From 5e5b6b1d2fd4d0d3de3938a8b85521d8c8d412df Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 4 Apr 2024 10:10:52 +0530 Subject: [PATCH 02/23] fix(minor): allow 673 like pin code for puducherry --- india_compliance/gst_india/constants/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/constants/__init__.py b/india_compliance/gst_india/constants/__init__.py index 903688f7c2..52dab78965 100644 --- a/india_compliance/gst_india/constants/__init__.py +++ b/india_compliance/gst_india/constants/__init__.py @@ -167,7 +167,7 @@ "Lakshadweep Islands": (682, 682), "Kerala": (670, 695), "Tamil Nadu": (600, 643), - "Puducherry": ((533, 533), (605, 605), (607, 607), (609, 609)), + "Puducherry": ((533, 533), (605, 605), (607, 607), (609, 609), (673, 673)), "Andaman and Nicobar Islands": (744, 744), "Andhra Pradesh": (500, 535), } From 8166d3ad3721f188dbdbd267fc0478fdc5206301 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 5 Apr 2024 17:08:10 +0530 Subject: [PATCH 03/23] fix: rounding of other charges for e-Invoice (#1980) --- india_compliance/gst_india/utils/e_invoice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index a0ac1a976d..930a5e068d 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -511,8 +511,9 @@ def update_other_charges(self): """ Non Taxable Value should be added to other charges. """ - self.transaction_details.other_charges += self.rounded( - self.transaction_details.total_non_taxable_value + self.transaction_details.other_charges = self.rounded( + self.transaction_details.other_charges + + self.transaction_details.total_non_taxable_value ) def validate_transaction(self): From a94fa977557fa11e310d7b8331691d731c3be886 Mon Sep 17 00:00:00 2001 From: SherinKR Date: Fri, 5 Apr 2024 17:14:05 +0530 Subject: [PATCH 04/23] fix: ReferenceError: taxes_table is not defined in Journal Entry (cherry picked from commit f5b1b862694b8c58273fd4f0431f13509a3ea06c) --- india_compliance/gst_india/client_scripts/journal_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/client_scripts/journal_entry.js b/india_compliance/gst_india/client_scripts/journal_entry.js index 48b1d3c004..235184a4e3 100644 --- a/india_compliance/gst_india/client_scripts/journal_entry.js +++ b/india_compliance/gst_india/client_scripts/journal_entry.js @@ -15,7 +15,7 @@ frappe.ui.form.on("Journal Entry Account", { }); function toggle_gstin_for_journal_entry(frm) { - toggle_company_gstin(frm, taxes_table="accounts", account_head="account"); + toggle_company_gstin(frm, "accounts", "account"); } async function toggle_company_gstin(frm, taxes_table, account_head) { From 9fd3eacd235edbbcca632de53d651fbead2138f5 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sat, 6 Apr 2024 09:07:45 +0530 Subject: [PATCH 05/23] fix(minor): correct function call for js (cherry picked from commit 7600d8ae66d0fbe119abcfd519409b0dc4d49b92) --- india_compliance/gst_india/client_scripts/expense_claim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/gst_india/client_scripts/expense_claim.js b/india_compliance/gst_india/client_scripts/expense_claim.js index abe58138cb..3c0d4167b3 100644 --- a/india_compliance/gst_india/client_scripts/expense_claim.js +++ b/india_compliance/gst_india/client_scripts/expense_claim.js @@ -8,5 +8,5 @@ frappe.ui.form.on("Expense Taxes and Charges", { }); function toggle_gstin_for_expense_claim(frm) { - toggle_company_gstin(frm, taxes_table="taxes", account_field="account_head"); + toggle_company_gstin(frm, "taxes", "account_head"); } \ No newline at end of file From efbcb672a2965f4e16d22bea5766ed5292fa8e16 Mon Sep 17 00:00:00 2001 From: Abdeali Chharchhodawala <99460106+Abdeali099@users.noreply.github.com> Date: Sat, 6 Apr 2024 11:06:32 +0530 Subject: [PATCH 06/23] fix: Handle If `response_json` Is `None` (#1968) * fix: handle if `response_json` is `None` * fix: optimise `mask_sensitive_info` * test: fix in make_sensitive_info --------- Co-authored-by: Sagar Vora --- .../gst_india/api_classes/base.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py index d4671f9559..c12e3d7c4e 100644 --- a/india_compliance/gst_india/api_classes/base.py +++ b/india_compliance/gst_india/api_classes/base.py @@ -161,7 +161,9 @@ def _make_request( raise e finally: - log.output = response_json.copy() + if response_json: + log.output = response_json.copy() + self.mask_sensitive_info(log) enqueue_integration_request(**log) @@ -252,21 +254,26 @@ def generate_request_id(self, length=12): return frappe.generate_hash(length=length) def mask_sensitive_info(self, log): + request_headers = log.request_headers + output = log.output + data = log.data + request_body = data and data.get("body") + for key in self.SENSITIVE_INFO: - if key in log.request_headers: - log.request_headers[key] = "*****" + if key in request_headers: + request_headers[key] = "*****" - if key in log.output: - log.output[key] = "*****" + if output and key in output: + output[key] = "*****" - if not log.data: - return + if not data: + continue - if key in log.get("data", {}): - log.data[key] = "*****" + if key in data: + data[key] = "*****" - if key in log.get("data", {}).get("body", {}): - log.data["body"][key] = "*****" + if request_body and key in request_body: + request_body[key] = "*****" def get_public_ip(): From d473f80d6bdcb27a32793a67f5d21c4f9ae35c51 Mon Sep 17 00:00:00 2001 From: priyanshshah2442 Date: Mon, 8 Apr 2024 12:04:36 +0530 Subject: [PATCH 07/23] fix: overlap count for nil-rated , non-gst and exempted invoices --- .../gst_sales_register_beta.py | 2 +- .../gst_india/utils/gstr/gstr_1.py | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py index 423cd02ff3..416be606b5 100644 --- a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py +++ b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py @@ -64,7 +64,7 @@ def get_columns(filters): "label": _("No. of records"), "fieldname": "no_of_records", "width": "120", - "fieldtype": "Int", + "fieldtype": "HTML", }, { "label": _("Taxable Value"), diff --git a/india_compliance/gst_india/utils/gstr/gstr_1.py b/india_compliance/gst_india/utils/gstr/gstr_1.py index 6d850b88bb..40854c6e3c 100644 --- a/india_compliance/gst_india/utils/gstr/gstr_1.py +++ b/india_compliance/gst_india/utils/gstr/gstr_1.py @@ -504,6 +504,9 @@ def get_overview(self): **amount_fields, } + nne_invoices = {"Nil-Rated": set(), "Non-GST": set(), "Exempted": set()} + overlap_invoices = {"Nil-Rated": set(), "Non-GST": set(), "Exempted": set()} + for row in invoices: category_key = summary[ row.get("invoice_sub_category", row["invoice_category"]) @@ -514,7 +517,22 @@ def get_overview(self): category_key["unique_records"].add(row.invoice_no) - for category_key in summary.values(): - category_key["no_of_records"] = len(category_key["unique_records"]) + if category_key["description"] in ["Nil-Rated", "Non-GST", "Exempted"]: + nne_invoices[category_key["description"]].add(row.invoice_no) + + for row in summary.values(): + for category in ["Nil-Rated", "Non-GST", "Exempted"]: + if row["description"] != category: + overlap_invoices[category].update( + nne_invoices[category].intersection(row["unique_records"]) + ) + + for row in summary.values(): + row["no_of_records"] = len(row["unique_records"]) + + if row["description"] in ["Nil-Rated", "Non-GST", "Exempted"]: + row["no_of_records"] = ( + f"{row['no_of_records']} ({len(overlap_invoices[row['description']])})" + ) return list(summary.values()) From 648e5f723956bebc017635f942d25676450ad192 Mon Sep 17 00:00:00 2001 From: Priyansh Shah <108476017+priyanshshah2442@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:09:14 +0530 Subject: [PATCH 08/23] test: test cases for `update item after submit` in Sales Order and Purchase Order (#1979) --- .../gst_india/overrides/test_transaction.py | 75 +++++++++++++++++++ .../gst_india/overrides/transaction.py | 18 ++++- india_compliance/hooks.py | 4 +- 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/india_compliance/gst_india/overrides/test_transaction.py b/india_compliance/gst_india/overrides/test_transaction.py index 5e0a5a602d..64b26d2633 100644 --- a/india_compliance/gst_india/overrides/test_transaction.py +++ b/india_compliance/gst_india/overrides/test_transaction.py @@ -8,6 +8,7 @@ from frappe.utils import today from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return from erpnext.accounts.party import _get_party_details +from erpnext.controllers.accounts_controller import update_child_qty_rate from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from india_compliance.gst_india.constants import SALES_DOCTYPES @@ -932,3 +933,77 @@ def create_tax_accounts(account_name): **defaults, } ).insert(ignore_if_duplicate=True) + + +class TestItemUpdate(FrappeTestCase): + DATA = { + "customer": "_Test Unregistered Customer", + "item_code": "_Test Trading Goods 1", + "qty": 1, + "rate": 100, + "is_in_state": 1, + } + + @classmethod + def setUpClass(cls): + super().setUpClass() + + def create_order(self, doctype): + self.DATA["doctype"] = doctype + doc = create_transaction(**self.DATA) + return doc + + def test_so_and_po_after_item_update(self): + for doctype in ["Sales Order", "Purchase Order"]: + doc = self.create_order(doctype) + + self.assertDocumentEqual( + { + "taxable_value": 100, + "cgst_amount": 9, + "sgst_amount": 9, + }, + doc.items[0], + ) + + # Update Item Rate + item = doc.items[0] + item_to_update = [ + { + "item_code": item.item_code, + "qty": item.qty, + "rate": 200, + "docname": item.name, + "name": item.name, + "idx": item.idx, + } + ] + + update_child_qty_rate(doctype, json.dumps(item_to_update), doc.name) + doc = frappe.get_doc(doctype, doc.name) + + self.assertDocumentEqual( + { + "taxable_value": 200, + "cgst_amount": 18, + "sgst_amount": 18, + }, + doc.items[0], + ) + + # Insert New Item + item_to_update.append( + {"item_code": "_Test Trading Goods 1", "qty": 1, "rate": 50, "idx": 2} + ) + + update_child_qty_rate(doctype, json.dumps(item_to_update), doc.name) + doc = frappe.get_doc(doctype, doc.name) + + self.assertDocumentEqual( + { + "taxable_value": 50, + "cgst_amount": 4.5, + "sgst_amount": 4.5, + }, + doc.items[1], + ) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 270cbf4afc..8edb0d75bc 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -1400,8 +1400,22 @@ def ignore_gst_validations(doc): return True -def before_update_after_submit_item(doc, method=None): - frappe.flags.through_update_item = True +def on_change_item(doc, method=None): + """ + Objective: + Child item is saved before trying to update parent doc. + Hence we can't verify has_value_changed for items in the parent doc. + + Solution: + - Set a flag in on_change of item + - Runs for both insert and save (update after submit) + - Set flag only if `ignore_validate_update_after_submit` is set + + Reference: + erpnext.controllers.accounts_controller.update_child_qty_rate + """ + if doc.flags.ignore_validate_update_after_submit: + frappe.flags.through_update_item = True def before_update_after_submit(doc, method=None): diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 8a68cd9543..42c0ef1133 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -154,7 +154,7 @@ "before_update_after_submit": "india_compliance.gst_india.overrides.transaction.before_update_after_submit", }, "Purchase Order Item": { - "before_update_after_submit": "india_compliance.gst_india.overrides.transaction.before_update_after_submit_item", + "on_change": "india_compliance.gst_india.overrides.transaction.on_change_item", }, "Purchase Receipt": { "onload": [ @@ -200,7 +200,7 @@ "before_update_after_submit": "india_compliance.gst_india.overrides.transaction.before_update_after_submit", }, "Sales Order Item": { - "before_update_after_submit": "india_compliance.gst_india.overrides.transaction.before_update_after_submit_item", + "on_change": "india_compliance.gst_india.overrides.transaction.on_change_item", }, "Supplier": { "validate": [ From e78d3cf9e1dd3df9d8f66c718ca65a7e288cf17f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 8 Apr 2024 15:28:22 +0530 Subject: [PATCH 09/23] fix: group tax values and amounts for invoice print formats --- india_compliance/gst_india/overrides/transaction.py | 3 --- india_compliance/hooks.py | 9 +++++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 270cbf4afc..6f6e57de36 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -1311,9 +1311,6 @@ def before_print(doc, method=None, print_settings=None): if ignore_gst_validations(doc) or not doc.place_of_supply or not doc.company_gstin: return - if doc.get("group_same_items"): - ItemGSTDetails().update(doc) - set_gst_breakup(doc) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 8a68cd9543..1bf1d8c8a0 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -390,6 +390,15 @@ } } +fields_for_group_similar_items = [ + "taxable_value", + "cgst_amount", + "sgst_amount", + "igst_amount", + "cess_amount", + "cess_non_advol_amount", +] + # Includes in # ------------------ From 6402c92e5af62663d7d2f580261e00edb6984d92 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 8 Apr 2024 19:02:51 +0530 Subject: [PATCH 10/23] fix: missing gst amounts in hsn json --- .../hsn_wise_summary_of_outward_supplies.py | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py b/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py index 5d6a52b297..b2f2691a50 100644 --- a/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py +++ b/india_compliance/gst_india/report/hsn_wise_summary_of_outward_supplies/hsn_wise_summary_of_outward_supplies.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.model.meta import get_field_precision -from frappe.utils import cstr, flt, getdate +from frappe.utils import flt, getdate import erpnext from india_compliance.gst_india.utils import get_gst_accounts_by_type, get_gst_uom @@ -349,7 +349,6 @@ def download_json_file(): def get_hsn_wise_json_data(filters, report_data): filters = frappe._dict(filters) - gst_accounts = get_gst_accounts_by_type(filters.company, "Output") data = [] count = 1 @@ -372,21 +371,10 @@ def get_hsn_wise_json_data(filters, report_data): if hsn_description := hsn.get("description"): row["desc"] = hsn_description[:30] - row["iamt"] += flt( - hsn.get(frappe.scrub(cstr(gst_accounts.get("igst_account"))), 0.0), 2 - ) - - row["camt"] += flt( - hsn.get(frappe.scrub(cstr(gst_accounts.get("cgst_account"))), 0.0), 2 - ) - - row["samt"] += flt( - hsn.get(frappe.scrub(cstr(gst_accounts.get("sgst_account"))), 0.0), 2 - ) - - row["csamt"] += flt( - hsn.get(frappe.scrub(cstr(gst_accounts.get("cess_account"))), 0.0), 2 - ) + row["iamt"] += flt(hsn.get("igst_account"), 2) + row["camt"] += flt(hsn.get("cgst_account"), 2) + row["samt"] += flt(hsn.get("sgst_account"), 2) + row["csamt"] += flt(hsn.get("cess_account"), 2) data.append(row) count += 1 From 119bb5830d32b29e9f10665c841aa7d3e26e61e6 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 8 Apr 2024 19:07:12 +0530 Subject: [PATCH 11/23] chore: update compatible version --- india_compliance/patches/check_version_compatibility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/india_compliance/patches/check_version_compatibility.py b/india_compliance/patches/check_version_compatibility.py index 4cd0e323d2..deede46cbe 100644 --- a/india_compliance/patches/check_version_compatibility.py +++ b/india_compliance/patches/check_version_compatibility.py @@ -18,7 +18,7 @@ { "app_name": "ERPNext", "current_version": version.parse(erpnext.__version__), - "required_versions": {"version-14": "14.64.0", "version-15": "15.15.0"}, + "required_versions": {"version-14": "14.66.5", "version-15": "15.19.3"}, }, ] From b4d77cf2e0fc473490699b7caa73a40f62dd171f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 9 Apr 2024 12:28:39 +0530 Subject: [PATCH 12/23] chore: use similar color for all number cards --- .../invoice_cancelled_but_not_e_invoice.json | 3 ++- .../pending_e_invoices.json | 4 ++-- .../pending_e_waybill/pending_e_waybill.json | 4 ++-- .../public/js/custom_number_card.js | 19 +++++++++++++++++++ .../public/js/india_compliance.bundle.js | 1 + 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 india_compliance/public/js/custom_number_card.js diff --git a/india_compliance/gst_india/number_card/invoice_cancelled_but_not_e_invoice/invoice_cancelled_but_not_e_invoice.json b/india_compliance/gst_india/number_card/invoice_cancelled_but_not_e_invoice/invoice_cancelled_but_not_e_invoice.json index be44b5b929..073056b8b3 100644 --- a/india_compliance/gst_india/number_card/invoice_cancelled_but_not_e_invoice/invoice_cancelled_but_not_e_invoice.json +++ b/india_compliance/gst_india/number_card/invoice_cancelled_but_not_e_invoice/invoice_cancelled_but_not_e_invoice.json @@ -1,5 +1,6 @@ { "aggregate_function_based_on": "", + "color": "#EC864B", "creation": "2023-07-25 18:33:55.452360", "docstatus": 0, "doctype": "Number Card", @@ -12,7 +13,7 @@ "is_standard": 1, "label": "Active e-Invoice, Cancelled Invoice", "method": "", - "modified": "2024-02-23 11:37:11.634469", + "modified": "2024-04-09 11:46:37.126752", "modified_by": "Administrator", "module": "GST India", "name": "Invoice Cancelled But Not e-Invoice", diff --git a/india_compliance/gst_india/number_card/pending_e_invoices/pending_e_invoices.json b/india_compliance/gst_india/number_card/pending_e_invoices/pending_e_invoices.json index 3604e87897..3363e0f91e 100644 --- a/india_compliance/gst_india/number_card/pending_e_invoices/pending_e_invoices.json +++ b/india_compliance/gst_india/number_card/pending_e_invoices/pending_e_invoices.json @@ -1,6 +1,6 @@ { "aggregate_function_based_on": "", - "color": "#ECAD4B", + "color": "#EC864B", "creation": "2023-07-25 15:10:39.976867", "docstatus": 0, "doctype": "Number Card", @@ -14,7 +14,7 @@ "is_standard": 1, "label": "Pending e-Invoices", "method": "", - "modified": "2024-02-23 11:37:43.073988", + "modified": "2024-04-09 11:45:32.550901", "modified_by": "Administrator", "module": "GST India", "name": "Pending e-Invoices", diff --git a/india_compliance/gst_india/number_card/pending_e_waybill/pending_e_waybill.json b/india_compliance/gst_india/number_card/pending_e_waybill/pending_e_waybill.json index 84428bc064..f806e3f67f 100644 --- a/india_compliance/gst_india/number_card/pending_e_waybill/pending_e_waybill.json +++ b/india_compliance/gst_india/number_card/pending_e_waybill/pending_e_waybill.json @@ -1,6 +1,6 @@ { "aggregate_function_based_on": "", - "color": "#e02f2f", + "color": "#EC864B", "creation": "2023-08-04 11:30:55.485885", "docstatus": 0, "doctype": "Number Card", @@ -12,7 +12,7 @@ "is_public": 1, "is_standard": 1, "label": "Pending e-Waybills", - "modified": "2023-10-12 17:16:39.286029", + "modified": "2024-04-09 11:46:27.074636", "modified_by": "Administrator", "module": "GST India", "name": "Pending e-Waybill", diff --git a/india_compliance/public/js/custom_number_card.js b/india_compliance/public/js/custom_number_card.js new file mode 100644 index 0000000000..ee444d1d72 --- /dev/null +++ b/india_compliance/public/js/custom_number_card.js @@ -0,0 +1,19 @@ +let FrappeNumberCard = frappe.widget.widget_factory.number_card; + +class CustomNumberCard extends FrappeNumberCard { + render_number() { + if ( + [ + "Pending e-Waybill", + "Pending e-Invoices", + "Invoice Cancelled But Not e-Invoice", + ].includes(this.card_doc.name) && + !this.formatted_number + ) + this.card_doc.color = null; + + super.render_number(); + } +} + +frappe.widget.widget_factory.number_card = CustomNumberCard; diff --git a/india_compliance/public/js/india_compliance.bundle.js b/india_compliance/public/js/india_compliance.bundle.js index e384acdca2..8ea391d433 100644 --- a/india_compliance/public/js/india_compliance.bundle.js +++ b/india_compliance/public/js/india_compliance.bundle.js @@ -4,3 +4,4 @@ import "./transaction"; import "./audit_trail_notification"; import "./item_tax_template_notification"; import "./quick_info_popover"; +import "./custom_number_card"; From 139895a6fb287f81014dbb734d55a76639863ce2 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 9 Apr 2024 14:13:11 +0530 Subject: [PATCH 13/23] fix: add overlaping invoice count summary to overview --- .../gst_sales_register_beta.py | 2 +- .../gst_india/utils/gstr/gstr_1.py | 46 +++++++++++-------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py index 416be606b5..423cd02ff3 100644 --- a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py +++ b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py @@ -64,7 +64,7 @@ def get_columns(filters): "label": _("No. of records"), "fieldname": "no_of_records", "width": "120", - "fieldtype": "HTML", + "fieldtype": "Int", }, { "label": _("Taxable Value"), diff --git a/india_compliance/gst_india/utils/gstr/gstr_1.py b/india_compliance/gst_india/utils/gstr/gstr_1.py index 40854c6e3c..e6dece6b9d 100644 --- a/india_compliance/gst_india/utils/gstr/gstr_1.py +++ b/india_compliance/gst_india/utils/gstr/gstr_1.py @@ -504,9 +504,6 @@ def get_overview(self): **amount_fields, } - nne_invoices = {"Nil-Rated": set(), "Non-GST": set(), "Exempted": set()} - overlap_invoices = {"Nil-Rated": set(), "Non-GST": set(), "Exempted": set()} - for row in invoices: category_key = summary[ row.get("invoice_sub_category", row["invoice_category"]) @@ -517,22 +514,35 @@ def get_overview(self): category_key["unique_records"].add(row.invoice_no) - if category_key["description"] in ["Nil-Rated", "Non-GST", "Exempted"]: - nne_invoices[category_key["description"]].add(row.invoice_no) + for category_key in summary.values(): + category_key["no_of_records"] = len(category_key["unique_records"]) - for row in summary.values(): - for category in ["Nil-Rated", "Non-GST", "Exempted"]: - if row["description"] != category: - overlap_invoices[category].update( - nne_invoices[category].intersection(row["unique_records"]) - ) + self.update_overlaping_invoice_summary(summary) - for row in summary.values(): - row["no_of_records"] = len(row["unique_records"]) + return list(summary.values()) - if row["description"] in ["Nil-Rated", "Non-GST", "Exempted"]: - row["no_of_records"] = ( - f"{row['no_of_records']} ({len(overlap_invoices[row['description']])})" - ) + def update_overlaping_invoice_summary(self, summary): + nil_exempt_non_gst = ("Nil-Rated", "Exempted", "Non-GST") - return list(summary.values()) + # Get Unique Taxable Invoices + unique_invoices = set() + for category, row in summary.items(): + if category in nil_exempt_non_gst: + continue + + unique_invoices.update(row["unique_records"]) + + # Get Overlaping Invoices + overlaping_invoices = set() + for category in nil_exempt_non_gst: + category_invoices = summary[category]["unique_records"] + + overlaping_invoices.update(category_invoices.intersection(unique_invoices)) + unique_invoices.update(category_invoices) + + # Update Summary + if overlaping_invoices: + summary["Overlaping Invoices"] = { + "description": "Overlaping Invoices in Nil-Rated/Exempt/Non-GST", + "no_of_records": -len(overlaping_invoices), + } From bc06faaf804252a7104aeb51f49317112ecb47fd Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Wed, 10 Apr 2024 10:41:15 +0530 Subject: [PATCH 14/23] fix: group overview in GST sales register beta by category (#2002) * fix: group overview in GST sales register beta by category * fix: better way to override datatable totals --- .../gst_sales_register_beta.js | 73 +++++-- .../gst_india/utils/gstr/gstr_1.py | 180 ++++++++++++------ 2 files changed, 181 insertions(+), 72 deletions(-) diff --git a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js index c61fe49a58..d788b3c0ed 100644 --- a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js +++ b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js @@ -1,17 +1,24 @@ // Copyright (c) 2024, Resilient Tech and contributors // For license information, please see license.txt const INVOICE_TYPE = { - "B2B,SEZ,DE": ["B2B Regular", "B2B Reverse Charge", "SEZWP", "SEZWOP", "Deemed Exports"], + "B2B, SEZ, DE": [ + "B2B Regular", + "B2B Reverse Charge", + "SEZWP", + "SEZWOP", + "Deemed Exports", + ], "B2C (Large)": ["B2C (Large)"], - "Exports": ["EXPWP", "EXPWOP"], + Exports: ["EXPWP", "EXPWOP"], "B2C (Others)": ["B2C (Others)"], - "Nil-Rated,Exempted,Non-GST": ["Nil-Rated", "Exempted", "Non-GST"], + "Nil-Rated, Exempted, Non-GST": ["Nil-Rated", "Exempted", "Non-GST"], "Credit/Debit Notes (Registered)": ["CDNR"], "Credit/Debit Notes (Unregistered)": ["CDNUR"], -} +}; frappe.query_reports["GST Sales Register Beta"] = { onload: set_sub_category_options, + filters: [ { fieldname: "company", @@ -46,42 +53,80 @@ frappe.query_reports["GST Sales Register Beta"] = { fieldname: "date_range", label: __("Date Range"), fieldtype: "DateRange", - default: [india_compliance.last_month_start(), india_compliance.last_month_end()], - width: "80" + default: [ + india_compliance.last_month_start(), + india_compliance.last_month_end(), + ], + width: "80", }, { fieldtype: "Select", fieldname: "summary_by", label: __("Summary By"), options: "Overview\nSummary by HSN\nSummary by Item", - default: "Summary by Item" + default: "Summary by Item", }, { fieldtype: "Autocomplete", fieldname: "invoice_category", label: __("Invoice Category"), - options: "B2B,SEZ,DE\nB2C (Large)\nExports\nB2C (Others)\nNil-Rated,Exempted,Non-GST\nCredit/Debit Notes (Registered)\nCredit/Debit Notes (Unregistered)", + options: + "B2B, SEZ, DE\nB2C (Large)\nExports\nB2C (Others)\nNil-Rated, Exempted, Non-GST\nCredit/Debit Notes (Registered)\nCredit/Debit Notes (Unregistered)", on_change(report) { - report.set_filter_value('invoice_sub_category', ""); + report.set_filter_value("invoice_sub_category", ""); set_sub_category_options(report); }, - depends_on: 'eval:doc.summary_by=="Summary by HSN" || doc.summary_by=="Summary by Item"' + depends_on: + 'eval:doc.summary_by=="Summary by HSN" || doc.summary_by=="Summary by Item"', }, { fieldtype: "Autocomplete", fieldname: "invoice_sub_category", label: __("Invoice Sub Category"), - depends_on: 'eval:doc.summary_by=="Summary by HSN" || doc.summary_by=="Summary by Item"' + depends_on: + 'eval:doc.summary_by=="Summary by HSN" || doc.summary_by=="Summary by Item"', + }, + ], + + formatter: (value, row, column, data, default_formatter) => { + value = default_formatter(value, row, column, data); + if (data && data.indent === 0) { + let $value = $(`${value}`).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); } - ] + + return value; + }, }; function set_sub_category_options(report) { const invoice_category = frappe.query_report.get_filter_value("invoice_category"); - report.get_filter('invoice_sub_category').set_data(INVOICE_TYPE[invoice_category] || []); + report + .get_filter("invoice_sub_category") + .set_data(INVOICE_TYPE[invoice_category] || []); if (invoice_category && INVOICE_TYPE[invoice_category].length === 1) { - report.set_filter_value("invoice_sub_category", INVOICE_TYPE[invoice_category][0]) + report.set_filter_value( + "invoice_sub_category", + INVOICE_TYPE[invoice_category][0] + ); } } +frappe_report_column_total = frappe.utils.report_column_total; + +// Override datatable hook for column total calculation +frappe.utils.report_column_total = function (...args) { + const summary_by = frappe.query_report.get_filter_value("summary_by"); + if (summary_by !== "Overview") return frappe_report_column_total.apply(this, args); + + const column_field = args[1].column.fieldname; + if (column_field === "description") return; + + const total = this.datamanager.data.reduce((acc, row) => { + if (row.indent !== 1) acc += row[column_field] || 0; + return acc; + }, 0); + + return total; +}; diff --git a/india_compliance/gst_india/utils/gstr/gstr_1.py b/india_compliance/gst_india/utils/gstr/gstr_1.py index e6dece6b9d..9ee9c6293a 100644 --- a/india_compliance/gst_india/utils/gstr/gstr_1.py +++ b/india_compliance/gst_india/utils/gstr/gstr_1.py @@ -11,6 +11,8 @@ B2C_LIMIT = 2_50_000 +# TODO: Enum for Invoice Type + class GSTR1_Categories(Enum): """ @@ -18,7 +20,7 @@ class GSTR1_Categories(Enum): """ # Invoice Items Bifurcation - B2B = "B2B,SEZ,DE" + B2B = "B2B, SEZ, DE" B2CL = "B2C (Large)" EXP = "Exports" B2CS = "B2C (Others)" @@ -59,29 +61,61 @@ class GSTR1_SubCategories(Enum): # DOC_ISSUE = "Document Issued" +CATEGORY_SUB_CATEGORY_MAPPING = { + GSTR1_Categories.B2B: ( + GSTR1_SubCategories.B2B_REGULAR, + GSTR1_SubCategories.B2B_REVERSE_CHARGE, + GSTR1_SubCategories.SEZWP, + GSTR1_SubCategories.SEZWOP, + GSTR1_SubCategories.DE, + ), + GSTR1_Categories.B2CL: (GSTR1_SubCategories.B2CL,), + GSTR1_Categories.EXP: (GSTR1_SubCategories.EXPWP, GSTR1_SubCategories.EXPWOP), + GSTR1_Categories.B2CS: (GSTR1_SubCategories.B2CS,), + GSTR1_Categories.NIL_EXEMPT: ( + GSTR1_SubCategories.NIL_RATED, + GSTR1_SubCategories.EXEMPTED, + GSTR1_SubCategories.NON_GST, + ), + GSTR1_Categories.CDNR: (GSTR1_SubCategories.CDNR,), + GSTR1_Categories.CDNUR: (GSTR1_SubCategories.CDNUR,), +} + SUB_CATEGORIES_DESCRIPTION = { - "SEZWP": "SEZ with payment", - "SEZWOP": "SEZ without payment", - "EXPWP": "Exports with payment", - "EXPWOP": "Exports without payment", - "CDNR": "Credit/Debit Notes (Registered)", - "CDNUR": "Credit/Debit Notes (Unregistered)", + GSTR1_SubCategories.SEZWP: "SEZ with payment", + GSTR1_SubCategories.SEZWOP: "SEZ without payment", + GSTR1_SubCategories.EXPWP: "Exports with payment", + GSTR1_SubCategories.EXPWOP: "Exports without payment", + GSTR1_SubCategories.CDNR: "Credit/Debit Notes (Registered)", + GSTR1_SubCategories.CDNUR: "Credit/Debit Notes (Unregistered)", } CATEGORY_CONDITIONS = { - "B2B,SEZ,DE": {"category": "is_b2b_invoice", "sub_category": "set_for_b2b"}, - "B2C (Large)": {"category": "is_b2cl_invoice", "sub_category": "set_for_b2cl"}, - "Exports": {"category": "is_export_invoice", "sub_category": "set_for_exports"}, - "B2C (Others)": {"category": "is_b2cs_invoice", "sub_category": "set_for_b2cs"}, - "Nil-Rated,Exempted,Non-GST": { + GSTR1_Categories.B2B.value: { + "category": "is_b2b_invoice", + "sub_category": "set_for_b2b", + }, + GSTR1_Categories.B2CL.value: { + "category": "is_b2cl_invoice", + "sub_category": "set_for_b2cl", + }, + GSTR1_Categories.EXP.value: { + "category": "is_export_invoice", + "sub_category": "set_for_exports", + }, + GSTR1_Categories.B2CS.value: { + "category": "is_b2cs_invoice", + "sub_category": "set_for_b2cs", + }, + GSTR1_Categories.NIL_EXEMPT.value: { "category": "is_nil_rated_exempted_non_gst_invoice", "sub_category": "set_for_nil_exp_non_gst", }, - "Credit/Debit Notes (Registered)": { + GSTR1_Categories.CDNR.value: { "category": "is_cdnr_invoice", "sub_category": "set_for_cdnr", }, - "Credit/Debit Notes (Unregistered)": { + GSTR1_Categories.CDNUR.value: { "category": "is_cdnur_invoice", "sub_category": "set_for_cdnur", }, @@ -325,20 +359,20 @@ def set_for_b2b(self, invoice): def set_for_b2cl(self, invoice): # NO INVOICE VALUE - invoice.invoice_sub_category = "B2C (Large)" + invoice.invoice_sub_category = GSTR1_SubCategories.B2CL.value def set_for_exports(self, invoice): if invoice.is_export_with_gst: - invoice.invoice_sub_category = "EXPWP" + invoice.invoice_sub_category = GSTR1_SubCategories.EXPWP.value invoice.invoice_type = "WPAY" else: - invoice.invoice_sub_category = "EXPWOP" + invoice.invoice_sub_category = GSTR1_SubCategories.EXPWOP.value invoice.invoice_type = "WOPAY" def set_for_b2cs(self, invoice): # NO INVOICE VALUE - invoice.invoice_sub_category = "B2C (Others)" + invoice.invoice_sub_category = GSTR1_SubCategories.B2CS.value def set_for_nil_exp_non_gst(self, invoice): # INVOICE TYPE @@ -352,20 +386,20 @@ def set_for_nil_exp_non_gst(self, invoice): # INVOICE SUB CATEGORY if self.is_nil_rated(invoice): - invoice.invoice_sub_category = "Nil-Rated" + invoice.invoice_sub_category = GSTR1_SubCategories.NIL_RATED.value elif self.is_exempted(invoice): - invoice.invoice_sub_category = "Exempted" + invoice.invoice_sub_category = GSTR1_SubCategories.EXEMPTED.value elif self.is_non_gst(invoice): - invoice.invoice_sub_category = "Non-GST" + invoice.invoice_sub_category = GSTR1_SubCategories.NON_GST.value def set_for_cdnr(self, invoice): self._set_invoice_type_for_b2b_and_cdnr(invoice) - invoice.invoice_sub_category = "CDNR" + invoice.invoice_sub_category = GSTR1_SubCategories.CDNR.value def set_for_cdnur(self, invoice): - invoice.invoice_sub_category = "CDNUR" + invoice.invoice_sub_category = GSTR1_SubCategories.CDNUR.value if self.is_export(invoice): if invoice.is_export_with_gst: invoice.invoice_type = "EXPWP" @@ -380,27 +414,35 @@ def set_for_cdnur(self, invoice): def _set_invoice_type_for_b2b_and_cdnr(self, invoice): if invoice.gst_category == "Deemed Export": invoice.invoice_type = "Deemed Exp" - invoice.invoice_sub_category = "Deemed Export" + invoice.invoice_sub_category = GSTR1_SubCategories.DE.value elif invoice.gst_category == "SEZ": if invoice.is_export_with_gst: invoice.invoice_type = "SEZ supplies with payment" - invoice.invoice_sub_category = "SEZWP" + invoice.invoice_sub_category = GSTR1_SubCategories.SEZWP.value else: invoice.invoice_type = "SEZ supplies without payment" - invoice.invoice_sub_category = "SEZWOP" + invoice.invoice_sub_category = GSTR1_SubCategories.SEZWOP.value elif invoice.is_reverese_charge: invoice.invoice_type = "Regular B2B" - invoice.invoice_sub_category = "B2B Reverse Charge" + invoice.invoice_sub_category = GSTR1_SubCategories.B2B_REVERSE_CHARGE.value else: invoice.invoice_type = "Regular B2B" - invoice.invoice_sub_category = "B2B Regular" + invoice.invoice_sub_category = GSTR1_SubCategories.B2B_REGULAR.value class GSTR1Invoices(GSTR1Query, GSTR1Subcategory): + AMOUNT_FIELDS = { + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + } + def __init__(self, filters=None): super().__init__(filters) @@ -482,51 +524,71 @@ def get_filtered_invoices( return filtered_invoices def get_overview(self): + final_summary = [] + sub_category_summary = self.get_sub_category_summary() + + for category, sub_categories in CATEGORY_SUB_CATEGORY_MAPPING.items(): + category_summary = { + "description": category.value, + "no_of_records": 0, + "indent": 0, + **self.AMOUNT_FIELDS, + } + final_summary.append(category_summary) + + for sub_category in sub_categories: + sub_category_row = sub_category_summary[sub_category.value] + category_summary["no_of_records"] += sub_category_row["no_of_records"] + + for key in self.AMOUNT_FIELDS: + category_summary[key] += sub_category_row[key] + + final_summary.append(sub_category_row) + + self.update_overlaping_invoice_summary(sub_category_summary, final_summary) + + return final_summary + + def get_sub_category_summary(self): invoices = self.get_invoices_for_item_wise_summary() self.process_invoices(invoices) - amount_fields = { - "taxable_value": 0, - "igst_amount": 0, - "cgst_amount": 0, - "sgst_amount": 0, - "total_cess_amount": 0, - } - summary = {} - subcategories = [category.value for category in GSTR1_SubCategories] - for category in subcategories: - summary[category] = { - "description": SUB_CATEGORIES_DESCRIPTION.get(category, category), + for category in GSTR1_SubCategories: + summary[category.value] = { + "description": SUB_CATEGORIES_DESCRIPTION.get(category, category.value), "no_of_records": 0, + "indent": 1, "unique_records": set(), - **amount_fields, + **self.AMOUNT_FIELDS, } for row in invoices: - category_key = summary[ + summary_row = summary[ row.get("invoice_sub_category", row["invoice_category"]) ] - for key in amount_fields: - category_key[key] += row[key] - - category_key["unique_records"].add(row.invoice_no) + for key in self.AMOUNT_FIELDS: + summary_row[key] += row[key] - for category_key in summary.values(): - category_key["no_of_records"] = len(category_key["unique_records"]) + summary_row["unique_records"].add(row.invoice_no) - self.update_overlaping_invoice_summary(summary) + for summary_row in summary.values(): + summary_row["no_of_records"] = len(summary_row["unique_records"]) - return list(summary.values()) + return summary - def update_overlaping_invoice_summary(self, summary): - nil_exempt_non_gst = ("Nil-Rated", "Exempted", "Non-GST") + def update_overlaping_invoice_summary(self, sub_category_summary, final_summary): + nil_exempt_non_gst = ( + GSTR1_SubCategories.NIL_RATED.value, + GSTR1_SubCategories.EXEMPTED.value, + GSTR1_SubCategories.NON_GST.value, + ) # Get Unique Taxable Invoices unique_invoices = set() - for category, row in summary.items(): + for category, row in sub_category_summary.items(): if category in nil_exempt_non_gst: continue @@ -535,14 +597,16 @@ def update_overlaping_invoice_summary(self, summary): # Get Overlaping Invoices overlaping_invoices = set() for category in nil_exempt_non_gst: - category_invoices = summary[category]["unique_records"] + category_invoices = sub_category_summary[category]["unique_records"] overlaping_invoices.update(category_invoices.intersection(unique_invoices)) unique_invoices.update(category_invoices) # Update Summary if overlaping_invoices: - summary["Overlaping Invoices"] = { - "description": "Overlaping Invoices in Nil-Rated/Exempt/Non-GST", - "no_of_records": -len(overlaping_invoices), - } + final_summary.append( + { + "description": "Overlaping Invoices in Nil-Rated/Exempt/Non-GST", + "no_of_records": -len(overlaping_invoices), + } + ) From e0b7be6a0e5f72d7bbabe1096e68a0d10710db3e Mon Sep 17 00:00:00 2001 From: Sanket322 <113279972+Sanket322@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:36:44 +0530 Subject: [PATCH 15/23] fix: validate for cess non-advol (#2009) * fix: validate for cess non-advol * fix: remove duplicate code * fix: changes as per view * chore: fix type, less nesting --------- Co-authored-by: ljain112 --- .../doctype/bill_of_entry/bill_of_entry.py | 58 +++++++++++-------- .../gst_india/overrides/transaction.py | 26 +++++++-- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py index a4b7ebe07e..9241025e09 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py +++ b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py @@ -254,9 +254,11 @@ def validate_purchase_invoice(self): def validate_taxes(self): input_accounts = get_gst_accounts_by_type(self.company, "Input", throw=True) taxable_value_map = {} + item_qty_map = {} for row in self.get("items"): taxable_value_map[row.name] = row.taxable_value + item_qty_map[row.name] = row.qty for tax in self.taxes: if not tax.tax_amount: @@ -278,33 +280,43 @@ def validate_taxes(self): [input_accounts.cess_non_advol_account], tax ) - if tax.charge_type == "Actual": + if tax.charge_type != "Actual": + continue - item_wise_tax_rates = json.loads(tax.item_wise_tax_rates) - if not item_wise_tax_rates: - frappe.throw( - _( - "Tax Row #{0}: Charge Type is set to Actual. However, this would" - " not compute item taxes, and your further reporting will be affected." - ).format(tax.idx), - title=_("Invalid Charge Type"), - ) + item_wise_tax_rates = json.loads(tax.item_wise_tax_rates) + if not item_wise_tax_rates: + frappe.throw( + _( + "Tax Row #{0}: Charge Type is set to Actual. However, this would" + " not compute item taxes, and your further reporting will be affected." + ).format(tax.idx), + title=_("Invalid Charge Type"), + ) + + # validating total tax + total_tax = 0 + is_non_cess_advol = ( + tax.account_head == input_accounts.cess_non_advol_account + ) - # validating total tax - total_tax = 0 - for item, rate in item_wise_tax_rates.items(): - item_taxable_value = taxable_value_map.get(item, 0) - total_tax += item_taxable_value * rate / 100 + for item, rate in item_wise_tax_rates.items(): + multiplier = ( + item_qty_map.get(item, 0) + if is_non_cess_advol + else taxable_value_map.get(item, 0) / 100 + ) + total_tax += multiplier * rate - tax_difference = abs(total_tax - tax.tax_amount) + tax_difference = abs(total_tax - tax.tax_amount) - if tax_difference > 1: - frappe.throw( - _( - "Tax Row #{0}: Charge Type is set to Actual. However, Tax Amount {1}" - " is incorrect. Try setting the Charge Type to On Net Total." - ).format(row.idx, tax.tax_amount) - ) + if tax_difference > 1: + column = "On Item Quantity" if is_non_cess_advol else "On Net Total" + frappe.throw( + _( + "Tax Row #{0}: Charge Type is set to Actual. However, Tax Amount {1}" + " is incorrect. Try setting the Charge Type to {2}." + ).format(row.idx, tax.tax_amount, column) + ) def get_gl_entries(self): # company_currency is required by get_gl_dict diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index 9cbd974257..7d440e7694 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -124,10 +124,14 @@ def validate_item_wise_tax_detail(doc, gst_accounts): return item_taxable_values = defaultdict(float) + item_qty_map = defaultdict(float) + + cess_non_advol_account = get_gst_accounts_by_tax_type(doc.company, "cess_non_advol") for row in doc.items: item_key = row.item_code or row.item_name item_taxable_values[item_key] += row.taxable_value + item_qty_map[item_key] += row.qty for row in doc.taxes: if row.account_head not in gst_accounts: @@ -150,15 +154,25 @@ def validate_item_wise_tax_detail(doc, gst_accounts): # Sales Invoice is created with manual tax amount. So, when a sales return is created, # the tax amount is not recalculated, causing the issue. - item_taxable_value = item_taxable_values.get(item_name, 0) - tax_difference = abs(item_taxable_value * tax_rate / 100 - tax_amount) + + is_cess_non_advol = row.account_head in cess_non_advol_account + multiplier = ( + item_qty_map.get(item_name, 0) + if is_cess_non_advol + else item_taxable_values.get(item_name, 0) / 100 + ) + tax_difference = abs(multiplier * tax_rate - tax_amount) if tax_difference > 1: + correct_charge_type = ( + "On Item Quantity" if is_cess_non_advol else "On Net Total" + ) + frappe.throw( _( "Tax Row #{0}: Charge Type is set to Actual. However, Tax Amount {1} as computed for Item {2}" - " is incorrect. Try setting the Charge Type to On Net Total." - ).format(row.idx, tax_amount, bold(item_name)) + " is incorrect. Try setting the Charge Type to {3}" + ).format(row.idx, tax_amount, bold(item_name), correct_charge_type) ) @@ -452,12 +466,12 @@ def validate_charge_type_for_cess_non_advol_accounts(cess_non_advol_accounts, ta ) if ( - tax_row.charge_type != "On Item Quantity" + tax_row.charge_type not in ["On Item Quantity", "Actual"] and tax_row.account_head in cess_non_advol_accounts ): frappe.throw( _( - "Row #{0}: Charge Type must be On Item Quantity" + "Row #{0}: Charge Type must be On Item Quantity / Actual" " as it is a Cess Non Advol Account" ).format(tax_row.idx), title=_("Invalid Charge Type"), From 8bc74113953c9d1f5548c4973230e4f2188c9c15 Mon Sep 17 00:00:00 2001 From: Priyansh Shah <108476017+priyanshshah2442@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:39:12 +0530 Subject: [PATCH 16/23] test: test cases for gst sales register beta report (#1965) * test: test cases for gst sales register beta report * fix: linters and item_code * fix: remove gst_treatement and posting date while creating invoices * fix: remove gst_treatement and posting date while creating invoices * fix: modified test cases * test: super setup to cleanup data post tests * refactor: test cases for sales register and modified required orderby in sales register beta report * fix: test cases and utility function for appending items in sales invoice * test: update similar treatment for original test cases * test: modified test output for overview --------- --- .../bill_of_entry/test_bill_of_entry.py | 1 + .../overrides/test_ineligible_itc.py | 1 + .../gst_sales_register_beta.js | 2 +- .../test_sales_register_beta.py | 697 ++++++++++++++++++ .../gst_india/utils/gstr/gstr_1.py | 10 +- india_compliance/gst_india/utils/tests.py | 4 +- 6 files changed, 712 insertions(+), 3 deletions(-) create mode 100644 india_compliance/gst_india/report/gst_sales_register_beta/test_sales_register_beta.py diff --git a/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py b/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py index f0f5beb8d7..6dad39e4be 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py +++ b/india_compliance/gst_india/doctype/bill_of_entry/test_bill_of_entry.py @@ -28,6 +28,7 @@ def test_create_bill_of_entry(self): pi = create_purchase_invoice(supplier="_Test Foreign Supplier", update_stock=1) # Create BOE + boe = make_bill_of_entry(pi.name) boe.items[0].customs_duty = 100 boe.bill_of_entry_no = "123" diff --git a/india_compliance/gst_india/overrides/test_ineligible_itc.py b/india_compliance/gst_india/overrides/test_ineligible_itc.py index 9874336327..d9df895811 100644 --- a/india_compliance/gst_india/overrides/test_ineligible_itc.py +++ b/india_compliance/gst_india/overrides/test_ineligible_itc.py @@ -35,6 +35,7 @@ }, {"item_code": "Test Service Item", "qty": 3, "rate": 500}, {"item_code": "Test Ineligible Service Item", "qty": 2, "rate": 499}, + {"item_code": "_Test Trading Goods 1", "qty": 1, "rate": 100}, ] # Item Total # 20 * 5 + 19 * 3 + 1000 * 1 + 999 * 1 + 500 * 3 + 499 * 2 + 100 * 1 (Default) = 4754 diff --git a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js index d788b3c0ed..97fac25279 100644 --- a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js +++ b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js @@ -9,7 +9,7 @@ const INVOICE_TYPE = { "Deemed Exports", ], "B2C (Large)": ["B2C (Large)"], - Exports: ["EXPWP", "EXPWOP"], + "Exports": ["EXPWP", "EXPWOP"], "B2C (Others)": ["B2C (Others)"], "Nil-Rated, Exempted, Non-GST": ["Nil-Rated", "Exempted", "Non-GST"], "Credit/Debit Notes (Registered)": ["CDNR"], diff --git a/india_compliance/gst_india/report/gst_sales_register_beta/test_sales_register_beta.py b/india_compliance/gst_india/report/gst_sales_register_beta/test_sales_register_beta.py new file mode 100644 index 0000000000..10c2567f94 --- /dev/null +++ b/india_compliance/gst_india/report/gst_sales_register_beta/test_sales_register_beta.py @@ -0,0 +1,697 @@ +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import getdate + +from india_compliance.gst_india.report.gst_sales_register_beta.gst_sales_register_beta import ( + execute, +) +from india_compliance.gst_india.utils.tests import create_sales_invoice + +today = getdate() + +FILTERS = { + "company": "_Test Indian Registered Company", + "date_range": [today, today], + "from_date": today, + "to_date": today, +} + + +EXPECTED_SUMMARY_BY_HSN = [ + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "invoice_total": -305000.0, + "returned_invoice_total": 0.0, + "gst_category": "Overseas", + "gst_treatment": "Zero-Rated", + "gst_rate": 0.0, + "taxable_value": -300000.0, + "total_amount": -300000.0, + "total_tax_amount": 0.0, + "invoice_category": "Credit/Debit Notes (Unregistered)", + "invoice_sub_category": "CDNUR", + "invoice_type": "EXPWOP", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "invoice_total": -305000.0, + "returned_invoice_total": 0.0, + "gst_category": "Overseas", + "gst_treatment": "Zero-Rated", + "gst_rate": 0.0, + "taxable_value": -5000.0, + "total_amount": -5000.0, + "total_tax_amount": 0.0, + "invoice_category": "Credit/Debit Notes (Unregistered)", + "invoice_sub_category": "CDNUR", + "invoice_type": "EXPWOP", + }, + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "invoice_total": 545000.0, + "returned_invoice_total": 0.0, + "gst_category": "Overseas", + "gst_treatment": "Zero-Rated", + "gst_rate": 0.0, + "taxable_value": 500000.0, + "total_amount": 500000.0, + "total_tax_amount": 0.0, + "invoice_category": "Exports", + "invoice_sub_category": "EXPWP", + "invoice_type": "WPAY", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "invoice_total": 545000.0, + "returned_invoice_total": 0.0, + "gst_category": "Overseas", + "gst_treatment": "Zero-Rated", + "gst_rate": 0.0, + "taxable_value": 45000.0, + "total_amount": 45000.0, + "total_tax_amount": 0.0, + "invoice_category": "Exports", + "invoice_sub_category": "EXPWP", + "invoice_type": "WPAY", + }, + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "invoice_total": 145000.0, + "returned_invoice_total": 0.0, + "gst_category": "Overseas", + "gst_treatment": "Zero-Rated", + "gst_rate": 0.0, + "taxable_value": 140000.0, + "total_amount": 140000.0, + "total_tax_amount": 0.0, + "invoice_category": "Exports", + "invoice_sub_category": "EXPWOP", + "invoice_type": "WOPAY", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "invoice_total": 145000.0, + "returned_invoice_total": 0.0, + "gst_category": "Overseas", + "gst_treatment": "Zero-Rated", + "gst_rate": 0.0, + "taxable_value": 5000.0, + "total_amount": 5000.0, + "total_tax_amount": 0.0, + "invoice_category": "Exports", + "invoice_sub_category": "EXPWOP", + "invoice_type": "WOPAY", + }, + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": "29AABCR1718E1ZL", + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Registered Composition Customer", + "place_of_supply": "29-Karnataka", + "invoice_total": -295500.0, + "returned_invoice_total": 0.0, + "gst_category": "Registered Composition", + "gst_treatment": "Taxable", + "gst_rate": 18.0, + "taxable_value": -225000.0, + "total_amount": -265500.0, + "total_tax_amount": -40500.0, + "invoice_category": "Credit/Debit Notes (Registered)", + "invoice_type": "Regular B2B", + "invoice_sub_category": "CDNR", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": "29AABCR1718E1ZL", + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Registered Composition Customer", + "place_of_supply": "29-Karnataka", + "invoice_total": -295500.0, + "returned_invoice_total": 0.0, + "gst_category": "Registered Composition", + "gst_treatment": "Nil-Rated", + "gst_rate": 0.0, + "taxable_value": -30000.0, + "total_amount": -30000.0, + "total_tax_amount": 0.0, + "invoice_category": "Nil-Rated, Exempted, Non-GST", + "invoice_type": "Inter-State to registered persons", + "invoice_sub_category": "Nil-Rated", + }, + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": "29AABCR1718E1ZL", + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Registered Composition Customer", + "place_of_supply": "29-Karnataka", + "invoice_total": 532000.0, + "returned_invoice_total": 0.0, + "gst_category": "Registered Composition", + "gst_treatment": "Taxable", + "gst_rate": 18.0, + "taxable_value": 400000.0, + "total_amount": 472000.0, + "total_tax_amount": 72000.0, + "invoice_category": "B2B, SEZ, DE", + "invoice_type": "Regular B2B", + "invoice_sub_category": "B2B Regular", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": "29AABCR1718E1ZL", + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Registered Composition Customer", + "place_of_supply": "29-Karnataka", + "invoice_total": 532000.0, + "returned_invoice_total": 0.0, + "gst_category": "Registered Composition", + "gst_treatment": "Nil-Rated", + "gst_rate": 0.0, + "taxable_value": 60000.0, + "total_amount": 60000.0, + "total_tax_amount": 0.0, + "invoice_category": "Nil-Rated, Exempted, Non-GST", + "invoice_type": "Inter-State to registered persons", + "invoice_sub_category": "Nil-Rated", + }, + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Unregistered Customer", + "place_of_supply": "24-Gujarat", + "invoice_total": 111200.0, + "returned_invoice_total": 0.0, + "gst_category": "Unregistered", + "gst_treatment": "Taxable", + "gst_rate": 18.0, + "taxable_value": 90000.0, + "total_amount": 106200.0, + "total_tax_amount": 16200.0, + "invoice_category": "B2C (Others)", + "invoice_sub_category": "B2C (Others)", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Unregistered Customer", + "place_of_supply": "24-Gujarat", + "invoice_total": 111200.0, + "returned_invoice_total": 0.0, + "gst_category": "Unregistered", + "gst_treatment": "Nil-Rated", + "gst_rate": 0.0, + "taxable_value": 5000.0, + "total_amount": 5000.0, + "total_tax_amount": 0.0, + "invoice_category": "Nil-Rated, Exempted, Non-GST", + "invoice_type": "Intra-State to unregistered persons", + "invoice_sub_category": "Nil-Rated", + }, + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Unregistered Customer", + "place_of_supply": "24-Gujarat", + "invoice_total": 29780.0, + "returned_invoice_total": 0.0, + "gst_category": "Unregistered", + "gst_treatment": "Taxable", + "gst_rate": 18.0, + "taxable_value": 21000.0, + "total_amount": 24780.0, + "total_tax_amount": 3780.0, + "invoice_category": "B2C (Others)", + "invoice_sub_category": "B2C (Others)", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Unregistered Customer", + "place_of_supply": "24-Gujarat", + "invoice_total": 29780.0, + "returned_invoice_total": 0.0, + "gst_category": "Unregistered", + "gst_treatment": "Nil-Rated", + "gst_rate": 0.0, + "taxable_value": 5000.0, + "total_amount": 5000.0, + "total_tax_amount": 0.0, + "invoice_category": "Nil-Rated, Exempted, Non-GST", + "invoice_type": "Intra-State to unregistered persons", + "invoice_sub_category": "Nil-Rated", + }, + { + "item_code": "_Test Service Item", + "gst_hsn_code": "999900", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Unregistered Customer", + "place_of_supply": "29-Karnataka", + "invoice_total": 16800.0, + "returned_invoice_total": 0.0, + "gst_category": "Unregistered", + "gst_treatment": "Taxable", + "gst_rate": 18.0, + "taxable_value": 10000.0, + "total_amount": 11800.0, + "total_tax_amount": 1800.0, + "invoice_category": "B2C (Others)", + "invoice_sub_category": "B2C (Others)", + }, + { + "item_code": "_Test Nil Rated Item", + "gst_hsn_code": "61149090", + "billing_address_gstin": None, + "company_gstin": "24AAQCA8719H1ZC", + "customer_name": "_Test Unregistered Customer", + "place_of_supply": "29-Karnataka", + "invoice_total": 16800.0, + "returned_invoice_total": 0.0, + "gst_category": "Unregistered", + "gst_treatment": "Nil-Rated", + "gst_rate": 0.0, + "taxable_value": 5000.0, + "total_amount": 5000.0, + "total_tax_amount": 0.0, + "invoice_category": "Nil-Rated, Exempted, Non-GST", + "invoice_type": "Inter-State to unregistered persons", + "invoice_sub_category": "Nil-Rated", + }, +] + +EXPECTED_OVERVIEW = [ + { + "description": "B2B, SEZ, DE", + "indent": 0, + "taxable_value": 400000.0, + "igst_amount": 72000.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "B2B Regular", + "indent": 1, + "taxable_value": 400000.0, + "igst_amount": 72000.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "B2B Reverse Charge", + "indent": 1, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "SEZ with payment", + "indent": 1, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "SEZ without payment", + "indent": 1, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "Deemed Exports", + "indent": 1, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "B2C (Large)", + "indent": 0, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "B2C (Large)", + "indent": 1, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "Exports", + "indent": 0, + "taxable_value": 690000.0, + "igst_amount": 0.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Exports with payment", + "indent": 1, + "taxable_value": 545000.0, + "igst_amount": 0.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Exports without payment", + "indent": 1, + "taxable_value": 145000.0, + "igst_amount": 0.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "B2C (Others)", + "indent": 0, + "taxable_value": 121000.0, + "igst_amount": 1800.0, + "cgst_amount": 9990.0, + "sgst_amount": 9990.0, + "total_cess_amount": 0.0, + }, + { + "description": "B2C (Others)", + "indent": 1, + "taxable_value": 121000.0, + "igst_amount": 1800.0, + "cgst_amount": 9990.0, + "sgst_amount": 9990.0, + "total_cess_amount": 0.0, + }, + { + "description": "Nil-Rated, Exempted, Non-GST", + "indent": 0, + "taxable_value": 45000.0, + "igst_amount": 0.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Nil-Rated", + "indent": 1, + "taxable_value": 45000.0, + "igst_amount": 0.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Exempted", + "indent": 1, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "Non-GST", + "indent": 1, + "taxable_value": 0, + "igst_amount": 0, + "cgst_amount": 0, + "sgst_amount": 0, + "total_cess_amount": 0, + }, + { + "description": "Credit/Debit Notes (Registered)", + "indent": 0, + "taxable_value": -225000.0, + "igst_amount": -40500.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Credit/Debit Notes (Registered)", + "indent": 1, + "taxable_value": -225000.0, + "igst_amount": -40500.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Credit/Debit Notes (Unregistered)", + "indent": 0, + "taxable_value": -305000.0, + "igst_amount": 0.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Credit/Debit Notes (Unregistered)", + "indent": 1, + "taxable_value": -305000.0, + "igst_amount": 0.0, + "cgst_amount": 0.0, + "sgst_amount": 0.0, + "total_cess_amount": 0.0, + }, + { + "description": "Overlaping Invoices in Nil-Rated/Exempt/Non-GST", + "no_of_records": -5, + }, +] +INVOICES = [ + { + "customer": "_Test Unregistered Customer", + "place_of_supply": "29-Karnataka", + "is_out_state": 1, + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 100, + "qty": 50, + }, + { + "item_code": "_Test Service Item", + "rate": 500, + "qty": 20, + }, + ], + }, + { + "customer": "_Test Unregistered Customer", + "place_of_supply": "24-Gujarat", + "is_in_state": 1, + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 100, + "qty": 50, + }, + { + "item_code": "_Test Service Item", + "rate": 700, + "qty": 30, + }, + ], + }, + { + "company_gstin": "24AAQCA8719H1ZC", + "customer": "_Test Unregistered Customer", + "place_of_supply": "24-Gujarat", + "is_in_state": 1, + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 100, + "qty": 50, + }, + { + "item_code": "_Test Service Item", + "rate": 900, + "qty": 100, + }, + ], + }, + { + "company_gstin": "24AAQCA8719H1ZC", + "customer": "_Test Registered Composition Customer", + "place_of_supply": "29-Karnataka", + "is_out_state": 1, + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 1000, + "qty": 60, + }, + { + "item_code": "_Test Service Item", + "rate": 2000, + "qty": 200, + }, + ], + }, + { + "company_gstin": "24AAQCA8719H1ZC", + "customer": "_Test Registered Composition Customer", + "place_of_supply": "29-Karnataka", + "is_return": 1, + "is_out_state": 1, + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 500, + "qty": -60, + }, + { + "item_code": "_Test Service Item", + "rate": 1500, + "qty": -150, + }, + ], + }, + { + "company_gstin": "24AAQCA8719H1ZC", + "customer": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 100, + "qty": 50, + }, + { + "item_code": "_Test Service Item", + "rate": 1400, + "qty": 100, + }, + ], + }, + { + "company_gstin": "24AAQCA8719H1ZC", + "customer": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "is_export_with_gst": 1, + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 500, + "qty": 90, + }, + { + "item_code": "_Test Service Item", + "rate": 2500, + "qty": 200, + }, + ], + }, + { + "company_gstin": "24AAQCA8719H1ZC", + "customer": "_Test Foreign Customer", + "place_of_supply": "96-Other Countries", + "is_return": 1, + "items": [ + { + "item_code": "_Test Nil Rated Item", + "rate": 100, + "qty": -50, + }, + { + "item_code": "_Test Service Item", + "rate": 2000, + "qty": -150, + }, + ], + }, +] + + +class TestSalesRegisterBeta(FrappeTestCase): + @classmethod + @change_settings("GST Settings", {"enable_overseas_transactions": 1}) + def setUpClass(cls): + super().setUpClass() + TestSalesRegisterBeta().create_sales_invoices() + + def create_sales_invoices(self): + for invoice in INVOICES: + create_sales_invoice(**invoice) + + def test_summary_by_hsn(self): + FILTERS["summary_by"] = "Summary by HSN" + report_data = execute(FILTERS) + + for index, invoice in enumerate(report_data[1]): + self.assertPartialDict(EXPECTED_SUMMARY_BY_HSN[index], invoice) + + def test_overview(self): + FILTERS["summary_by"] = "Overview" + report_data = execute(FILTERS) + + for index, invoice in enumerate(report_data[1]): + self.assertPartialDict(EXPECTED_OVERVIEW[index], invoice) + + def assertPartialDict(self, d1, d2): + self.assertIsInstance(d1, dict, "First argument is not a dictionary") + self.assertIsInstance(d2, dict, "Second argument is not a dictionary") + + if d1 != d2: + for key in d1: + if d1[key] != d2[key]: + standardMsg = f"{key}: {d1[key]} != {d2[key]}" + self.fail(standardMsg) diff --git a/india_compliance/gst_india/utils/gstr/gstr_1.py b/india_compliance/gst_india/utils/gstr/gstr_1.py index 9ee9c6293a..447202a4e8 100644 --- a/india_compliance/gst_india/utils/gstr/gstr_1.py +++ b/india_compliance/gst_india/utils/gstr/gstr_1.py @@ -198,6 +198,12 @@ def get_base_query(self): .where(self.si.docstatus == 1) .where(self.si.is_opening != "Yes") .where(IfNull(self.si.billing_address_gstin, "") != self.si.company_gstin) + .orderby( + self.si.posting_date, + self.si.name, + self.si_item.item_code, + order=Order.desc, + ) ) if self.additional_si_columns: @@ -494,7 +500,9 @@ def get_invoices_for_hsn_wise_summary(self): query.gst_treatment, query.uom, ) - .orderby(query.posting_date, query.invoice_no, order=Order.desc) + .orderby( + query.posting_date, query.invoice_no, query.item_code, order=Order.desc + ) ) return query.run(as_dict=True) diff --git a/india_compliance/gst_india/utils/tests.py b/india_compliance/gst_india/utils/tests.py index de450214b7..b302af186e 100644 --- a/india_compliance/gst_india/utils/tests.py +++ b/india_compliance/gst_india/utils/tests.py @@ -67,7 +67,9 @@ def create_transaction(**data): ) company_abbr = frappe.get_cached_value("Company", data.company, "abbr") or "_TIRC" - append_item(transaction, data, company_abbr) + + if not data.get("items"): + append_item(transaction, data, company_abbr) # Append taxes if data.is_in_state or data.is_in_state_rcm: From 35c48a293abc97f186f431ac31495e8529bfb189 Mon Sep 17 00:00:00 2001 From: Sanket322 <113279972+Sanket322@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:17:51 +0530 Subject: [PATCH 17/23] test: test-cases for regional_overrides (#2006) * fix: test-cases for regional_overrides * refactor: test case placement and only required test cases --------- Co-authored-by: Smit Vora --- .../overrides/test_advance_payment_entry.py | 54 ++++++++++ .../gst_india/overrides/test_transaction.py | 101 +++++++++++++++++- 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/overrides/test_advance_payment_entry.py b/india_compliance/gst_india/overrides/test_advance_payment_entry.py index 94429a6f77..f332d25932 100644 --- a/india_compliance/gst_india/overrides/test_advance_payment_entry.py +++ b/india_compliance/gst_india/overrides/test_advance_payment_entry.py @@ -7,9 +7,15 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import ( get_outstanding_reference_documents, ) +from erpnext.accounts.doctype.payment_reconciliation.payment_reconciliation import ( + adjust_allocations_for_taxes, +) from erpnext.accounts.doctype.unreconcile_payment.unreconcile_payment import ( create_unreconcile_doc_for_selection, ) +from erpnext.controllers.accounts_controller import ( + get_advance_payment_entries_for_regional, +) from erpnext.controllers.stock_controller import show_accounting_ledger_preview from india_compliance.gst_india.utils.tests import create_transaction @@ -453,6 +459,54 @@ def assertPLEntries(self, payment_doc, expected_pl_entries): self.assertEqual(out_str, expected_out_str) +class TestRegionalOverrides(TestAdvancePaymentEntry): + def test_get_advance_payment_entries_for_regional(self): + payment_doc = self._create_payment_entry() + invoice_doc = self._create_sales_invoice(payment_doc) + + conditions = frappe._dict({"company": invoice_doc.get("company")}) + + payment_entry = get_advance_payment_entries_for_regional( + party_type="Customer", + party=invoice_doc.customer, + party_account=[invoice_doc.debit_to], + order_list=[], + order_doctype="Sales Order", + include_unallocated=True, + condition=conditions, + ) + + payment_entry_amount = payment_entry[0].get("amount") + self.assertNotEqual(400, payment_entry_amount) + + def test_adjust_allocations_for_taxes(self): + payment_doc = self._create_payment_entry() + invoice_doc = self._create_sales_invoice() + + pr = frappe.get_doc("Payment Reconciliation") + pr.company = "_Test Indian Registered Company" + pr.party_type = "Customer" + pr.party = invoice_doc.customer + pr.receivable_payable_account = invoice_doc.debit_to + + pr.get_unreconciled_entries() + invoices = [ + row.as_dict() + for row in pr.invoices + if row.invoice_number == invoice_doc.name + ] + payments = [ + row.as_dict() + for row in pr.payments + if row.reference_name == payment_doc.name + ] + pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments})) + pr.allocation[0].allocated_amount = 50 + + adjust_allocations_for_taxes(pr) + self.assertEqual(pr.allocation[0].allocated_amount, 42.37) # 50 / 1.18 + + def make_payment_reconciliation(payment_doc, invoice_doc, amount): pr = frappe.get_doc("Payment Reconciliation") pr.company = "_Test Indian Registered Company" diff --git a/india_compliance/gst_india/overrides/test_transaction.py b/india_compliance/gst_india/overrides/test_transaction.py index 64b26d2633..03ef20d0f3 100644 --- a/india_compliance/gst_india/overrides/test_transaction.py +++ b/india_compliance/gst_india/overrides/test_transaction.py @@ -6,10 +6,20 @@ import frappe from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import today +from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import ( + make_regional_gl_entries, +) from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return -from erpnext.accounts.party import _get_party_details -from erpnext.controllers.accounts_controller import update_child_qty_rate +from erpnext.accounts.party import _get_party_details, get_regional_address_details +from erpnext.controllers.accounts_controller import ( + update_child_qty_rate, + update_gl_dict_with_regional_fields, +) +from erpnext.controllers.taxes_and_totals import get_regional_round_off_accounts from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice +from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + update_regional_gl_entries, +) from india_compliance.gst_india.constants import SALES_DOCTYPES from india_compliance.gst_india.overrides.transaction import ( @@ -19,6 +29,7 @@ from india_compliance.gst_india.utils.tests import ( _append_taxes, append_item, + create_purchase_invoice, create_transaction, ) @@ -935,6 +946,92 @@ def create_tax_accounts(account_name): ).insert(ignore_if_duplicate=True) +class TestRegionalOverrides(FrappeTestCase): + @change_settings( + "GST Settings", + {"round_off_gst_values": 1}, + ) + def test_get_regional_round_off_accounts(self): + + data = get_regional_round_off_accounts("_Test Indian Registered Company", []) + self.assertListEqual( + data, + [ + "Input Tax CGST - _TIRC", + "Input Tax SGST - _TIRC", + "Input Tax IGST - _TIRC", + "Output Tax CGST - _TIRC", + "Output Tax SGST - _TIRC", + "Output Tax IGST - _TIRC", + "Input Tax CGST RCM - _TIRC", + "Input Tax SGST RCM - _TIRC", + "Input Tax IGST RCM - _TIRC", + ], + ) + + @change_settings( + "GST Settings", + {"round_off_gst_values": 0}, + ) + def test_get_regional_round_off_accounts_with_round_off_unchecked(self): + + data = get_regional_round_off_accounts("_Test Indian Registered Company", []) + self.assertListEqual(data, []) + + def test_update_gl_dict_with_regional_fields(self): + + doc = frappe.get_doc( + {"doctype": "Sales Invoice", "company_gstin": "29AAHCM7727Q1ZI"} + ) + gl_entry = {} + update_gl_dict_with_regional_fields(doc, gl_entry) + + self.assertEqual(gl_entry.get("company_gstin", ""), "29AAHCM7727Q1ZI") + + def test_make_regional_gl_entries(self): + pi = create_purchase_invoice() + pi._has_ineligible_itc_items = True + + gl_entries = {"company_gstin": "29AAHCM7727Q1ZI"} + frappe.flags.through_repost_accounting_ledger = True + + make_regional_gl_entries(gl_entries, pi) + + frappe.flags.through_repost_accounting_ledger = False + self.assertEqual(pi._has_ineligible_itc_items, False) + + def test_update_regional_gl_entries(self): + gl_entry = {"company_gstin": "29AAHCM7727Q1ZI"} + doc = frappe.get_doc( + { + "doctype": "Sales Invoice", + "is_opening": "Yes", + "company_gstin": "29AAHCM7727Q1ZI", + } + ) + return_entry = update_regional_gl_entries(gl_entry, doc) + self.assertDictEqual(return_entry, gl_entry) + + def test_get_regional_address_details(self): + doctype = "Sales Order" + company = "_Test Indian Registered Company" + party_details = { + "customer": "_Test Registered Customer", + "customer_address": "_Test Registered Customer-Billing", + "billing_address_gstin": "24AANFA2641L1ZF", + "gst_category": "Registered Regular", + "company_gstin": "24AAQCA8719H1ZC", + } + + get_regional_address_details(party_details, doctype, company) + + self.assertEqual( + party_details.get("taxes_and_charges"), "Output GST In-state - _TIRC" + ) + self.assertEqual(party_details.get("place_of_supply"), "24-Gujarat") + self.assertTrue(party_details.get("taxes")) + + class TestItemUpdate(FrappeTestCase): DATA = { "customer": "_Test Unregistered Customer", From 19fbb834e675efcb771b2dfd649d36a194192ed7 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 11 Apr 2024 16:31:58 +0530 Subject: [PATCH 18/23] fix(minor): better way to override totals in gst sales beta report (#2022) --- .../gst_sales_register_beta.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js index 97fac25279..7ea7c6c1b1 100644 --- a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js +++ b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.js @@ -97,6 +97,15 @@ frappe.query_reports["GST Sales Register Beta"] = { return value; }, + + // Override datatable hook for column total calculation + get_datatable_options(datatable_options) { + datatable_options.hooks = { + columnTotal: custom_report_column_total, + }; + + return datatable_options; + }, }; function set_sub_category_options(report) { @@ -113,12 +122,10 @@ function set_sub_category_options(report) { } } -frappe_report_column_total = frappe.utils.report_column_total; - -// Override datatable hook for column total calculation -frappe.utils.report_column_total = function (...args) { +custom_report_column_total = function (...args) { const summary_by = frappe.query_report.get_filter_value("summary_by"); - if (summary_by !== "Overview") return frappe_report_column_total.apply(this, args); + if (summary_by !== "Overview") + return frappe.utils.report_column_total.apply(this, args); const column_field = args[1].column.fieldname; if (column_field === "description") return; From 75352214d512a77f9c298e74f2907b4bfeb46e3e Mon Sep 17 00:00:00 2001 From: Priyansh Shah <108476017+priyanshshah2442@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:35:08 +0530 Subject: [PATCH 19/23] fix: uom as per gst (#2019) * fix: uom as per gst * chore: refactor and wrap original function --------- --- india_compliance/gst_india/utils/__init__.py | 5 +++++ india_compliance/gst_india/utils/gstr/gstr_1.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 2c61bd17c0..87b9ba040f 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -677,6 +677,11 @@ def can_enable_api(settings): return settings.api_secret or frappe.conf.ic_api_secret +def get_full_gst_uom(uom, settings=None): + uom = get_gst_uom(uom, settings=settings) + return f"{uom}-{UOM_MAP.get(uom)}" + + def get_gst_uom(uom, settings=None): """Returns the GST UOM from ERPNext UOM""" settings = settings or frappe.get_cached_doc("GST Settings") diff --git a/india_compliance/gst_india/utils/gstr/gstr_1.py b/india_compliance/gst_india/utils/gstr/gstr_1.py index 447202a4e8..e4998fe457 100644 --- a/india_compliance/gst_india/utils/gstr/gstr_1.py +++ b/india_compliance/gst_india/utils/gstr/gstr_1.py @@ -9,6 +9,8 @@ from frappe.query_builder.functions import Date, IfNull, Sum from frappe.utils import getdate +from india_compliance.gst_india.utils import get_full_gst_uom + B2C_LIMIT = 2_50_000 # TODO: Enum for Invoice Type @@ -453,9 +455,12 @@ def __init__(self, filters=None): super().__init__(filters) def process_invoices(self, invoices): + settings = frappe.get_cached_doc("GST Settings") + for invoice in invoices: self.invoice_conditions = {} self.assign_categories(invoice) + invoice["uom"] = get_full_gst_uom(invoice.get("uom"), settings) def assign_categories(self, invoice): From c243e7ac92c271ef92f357017ee2632aa2978810 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 11 Apr 2024 16:40:26 +0530 Subject: [PATCH 20/23] refactor: seperate gstr_1 utilities --- .../report/gst_sales_register_beta/gst_sales_register_beta.py | 2 +- .../gst_india/utils/{gstr/gstr_1.py => gstr_1/gstr_1_data.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename india_compliance/gst_india/utils/{gstr/gstr_1.py => gstr_1/gstr_1_data.py} (100%) diff --git a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py index 423cd02ff3..3a0466b14d 100644 --- a/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py +++ b/india_compliance/gst_india/report/gst_sales_register_beta/gst_sales_register_beta.py @@ -5,7 +5,7 @@ from frappe import _ from frappe.utils import getdate -from india_compliance.gst_india.utils.gstr.gstr_1 import GSTR1Invoices +from india_compliance.gst_india.utils.gstr_1.gstr_1_data import GSTR1Invoices def execute(filters=None): diff --git a/india_compliance/gst_india/utils/gstr/gstr_1.py b/india_compliance/gst_india/utils/gstr_1/gstr_1_data.py similarity index 100% rename from india_compliance/gst_india/utils/gstr/gstr_1.py rename to india_compliance/gst_india/utils/gstr_1/gstr_1_data.py From e1af3d261caa5f6d4af56ddc8e32364c0e18468d Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Thu, 11 Apr 2024 17:16:50 +0530 Subject: [PATCH 21/23] refactor: commanify gstr_utils and make gstr_2 independent --- .../gstr_import_log/gstr_import_log.py | 2 +- .../purchase_reconciliation_tool/__init__.py | 3 +- .../purchase_reconciliation_tool.py | 4 +- .../utils/{gstr => gstr_2}/__init__.py | 77 +-------- .../gst_india/utils/{gstr => gstr_2}/gstr.py | 86 ---------- .../utils/{gstr => gstr_2}/gstr_2a.py | 2 +- .../utils/{gstr => gstr_2}/gstr_2b.py | 2 +- .../utils/{gstr => gstr_2}/test_gstr_2a.py | 6 +- .../utils/{gstr => gstr_2}/test_gstr_2b.py | 4 +- .../gst_india/utils/gstr_utils.py | 162 ++++++++++++++++++ india_compliance/hooks.py | 2 +- india_compliance/public/js/utils.js | 8 +- 12 files changed, 181 insertions(+), 177 deletions(-) rename india_compliance/gst_india/utils/{gstr => gstr_2}/__init__.py (83%) rename india_compliance/gst_india/utils/{gstr => gstr_2}/gstr.py (61%) rename india_compliance/gst_india/utils/{gstr => gstr_2}/gstr_2a.py (99%) rename india_compliance/gst_india/utils/{gstr => gstr_2}/gstr_2b.py (98%) rename india_compliance/gst_india/utils/{gstr => gstr_2}/test_gstr_2a.py (98%) rename india_compliance/gst_india/utils/{gstr => gstr_2}/test_gstr_2b.py (98%) create mode 100644 india_compliance/gst_india/utils/gstr_utils.py diff --git a/india_compliance/gst_india/doctype/gstr_import_log/gstr_import_log.py b/india_compliance/gst_india/doctype/gstr_import_log/gstr_import_log.py index 98a33cb65f..41b4346cdf 100644 --- a/india_compliance/gst_india/doctype/gstr_import_log/gstr_import_log.py +++ b/india_compliance/gst_india/doctype/gstr_import_log/gstr_import_log.py @@ -73,7 +73,7 @@ def toggle_scheduled_jobs(stopped): scheduled_job = frappe.db.get_value( "Scheduled Job Type", { - "method": "india_compliance.gst_india.utils.gstr.download_queued_request", + "method": "india_compliance.gst_india.utils.gstr_utils.download_queued_request", }, ) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py index 8e35860dd0..7867fb4adf 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py @@ -16,10 +16,9 @@ from india_compliance.gst_india.utils import ( get_escaped_name, get_gst_accounts_by_type, - get_gstin_list, get_party_for_gstin, ) -from india_compliance.gst_india.utils.gstr import IMPORT_CATEGORY, ReturnType +from india_compliance.gst_india.utils.gstr_2 import IMPORT_CATEGORY, ReturnType class Fields(Enum): diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py index 74abe18896..bcff6c5b52 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py @@ -10,6 +10,7 @@ from frappe.utils import add_to_date, cint, now_datetime from frappe.utils.response import json_handler +from india_compliance.gst_india.api_classes.returns import ReturnsAPI from india_compliance.gst_india.constants import ORIGINAL_VS_AMENDED from india_compliance.gst_india.doctype.purchase_reconciliation_tool import ( BaseUtil, @@ -24,11 +25,10 @@ is_api_enabled, ) from india_compliance.gst_india.utils.exporter import ExcelExporter -from india_compliance.gst_india.utils.gstr import ( +from india_compliance.gst_india.utils.gstr_2 import ( ACTIONS, IMPORT_CATEGORY, GSTRCategory, - ReturnsAPI, ReturnType, download_gstr_2a, download_gstr_2b, diff --git a/india_compliance/gst_india/utils/gstr/__init__.py b/india_compliance/gst_india/utils/gstr_2/__init__.py similarity index 83% rename from india_compliance/gst_india/utils/gstr/__init__.py rename to india_compliance/gst_india/utils/gstr_2/__init__.py index e8834556d6..6ce2186972 100644 --- a/india_compliance/gst_india/utils/gstr/__init__.py +++ b/india_compliance/gst_india/utils/gstr_2/__init__.py @@ -5,22 +5,13 @@ from frappe.query_builder.terms import Criterion from frappe.utils import cint -from india_compliance.gst_india.api_classes.returns import ( - GSTR2aAPI, - GSTR2bAPI, - ReturnsAPI, -) +from india_compliance.gst_india.api_classes.returns import GSTR2aAPI, GSTR2bAPI from india_compliance.gst_india.doctype.gstr_import_log.gstr_import_log import ( create_import_log, - toggle_scheduled_jobs, ) from india_compliance.gst_india.utils import get_party_for_gstin -from india_compliance.gst_india.utils.gstr import gstr_2a, gstr_2b - - -class ReturnType(Enum): - GSTR2A = "GSTR2a" - GSTR2B = "GSTR2b" +from india_compliance.gst_india.utils.gstr_2 import gstr_2a, gstr_2b +from india_compliance.gst_india.utils.gstr_utils import ReturnType class GSTRCategory(Enum): @@ -317,68 +308,6 @@ def _download_gstr_2a(gstin, return_period, json_data): save_gstr_2a(gstin, return_period, json_data) -GSTR_FUNCTIONS = { - ReturnType.GSTR2A.value: _download_gstr_2a, - ReturnType.GSTR2B.value: save_gstr_2b, -} - - -def download_queued_request(): - queued_requests = frappe.get_all( - "GSTR Import Log", - filters={"request_id": ["is", "set"]}, - fields=[ - "name", - "gstin", - "return_type", - "classification", - "return_period", - "request_id", - "request_time", - ], - ) - - if not queued_requests: - return toggle_scheduled_jobs(stopped=True) - - for doc in queued_requests: - frappe.enqueue(_download_queued_request, queue="long", doc=doc) - - -def _download_queued_request(doc): - try: - api = ReturnsAPI(doc.gstin) - response = api.download_files( - doc.return_period, - doc.request_id, - ) - - except Exception as e: - frappe.db.delete("GSTR Import Log", doc.name) - raise e - - if response.error_type in ["otp_requested", "invalid_otp"]: - return toggle_scheduled_jobs(stopped=True) - - if response.error_type == "no_docs_found": - return create_import_log( - doc.gstin, - doc.return_type, - doc.return_period, - doc.classification, - data_not_found=True, - ) - - if response.error_type == "queued": - return - - if response.error_type: - return frappe.db.delete("GSTR Import Log", {"name": doc.name}) - - frappe.db.set_value("GSTR Import Log", doc.name, "request_id", None) - GSTR_FUNCTIONS[doc.return_type](doc.gstin, doc.return_period, response) - - def show_queued_message(): frappe.msgprint( _( diff --git a/india_compliance/gst_india/utils/gstr/gstr.py b/india_compliance/gst_india/utils/gstr_2/gstr.py similarity index 61% rename from india_compliance/gst_india/utils/gstr/gstr.py rename to india_compliance/gst_india/utils/gstr_2/gstr.py index 6b7d16eb23..afa480ac4d 100644 --- a/india_compliance/gst_india/utils/gstr/gstr.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr.py @@ -1,13 +1,9 @@ import frappe -from frappe import _ -from frappe.utils import add_to_date, now_datetime from india_compliance.gst_india.constants import STATE_NUMBERS from india_compliance.gst_india.doctype.gst_inward_supply.gst_inward_supply import ( create_inward_supply, ) -from india_compliance.gst_india.utils import get_gstin_list -from india_compliance.gst_india.utils.gstr import ReturnsAPI def get_mapped_value(value, mapping): @@ -126,85 +122,3 @@ def set_key(self, key, value): def update_gstins(self): pass - - -@frappe.whitelist() -def validate_company_gstins(company=None, company_gstin=None): - """ - Checks the validity of the company's GSTIN authentication. - - Args: - company_gstin (str): The GSTIN of the company to validate. - - Returns: - dict: A dictionary where the keys are the GSTINs and the values are booleans indicating whether the authentication is valid. - """ - frappe.has_permission("GST Settings", throw=True) - - credentials = get_company_gstin_credentials(company, company_gstin) - - if company_gstin and not credentials: - frappe.throw( - _("Missing GSTIN credentials for GSTIN: {gstin}.").format( - gstin=company_gstin - ) - ) - - if not credentials: - frappe.throw(_("Missing credentials in GST Settings")) - - if company and not company_gstin: - missing_credentials = set(get_gstin_list(company)) - set( - credential.gstin for credential in credentials - ) - - if missing_credentials: - frappe.throw( - _("Missing GSTIN credentials for GSTIN(s): {gstins}.").format( - gstins=", ".join(missing_credentials), - ) - ) - - gstin_authentication_status = { - credential.gstin: ( - credential.session_expiry - and credential.auth_token - and credential.session_expiry > add_to_date(now_datetime(), minutes=30) - ) - for credential in credentials - } - - return gstin_authentication_status - - -def get_company_gstin_credentials(company=None, company_gstin=None): - filters = {"service": "Returns"} - - if company: - filters["company"] = company - - if company_gstin: - filters["gstin"] = company_gstin - - return frappe.get_all( - "GST Credential", - filters=filters, - fields=["gstin", "session_expiry", "auth_token"], - ) - - -@frappe.whitelist() -def request_otp(company_gstin): - frappe.has_permission("GST Settings", throw=True) - - return ReturnsAPI(company_gstin).request_otp() - - -@frappe.whitelist() -def authenticate_otp(company_gstin, otp): - frappe.has_permission("GST Settings", throw=True) - - api = ReturnsAPI(company_gstin) - response = api.autheticate_with_otp(otp) - - return api.process_response(response) diff --git a/india_compliance/gst_india/utils/gstr/gstr_2a.py b/india_compliance/gst_india/utils/gstr_2/gstr_2a.py similarity index 99% rename from india_compliance/gst_india/utils/gstr/gstr_2a.py rename to india_compliance/gst_india/utils/gstr_2/gstr_2a.py index ca9767d450..ad8089cf41 100644 --- a/india_compliance/gst_india/utils/gstr/gstr_2a.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr_2a.py @@ -3,7 +3,7 @@ import frappe from india_compliance.gst_india.utils import get_datetime, parse_datetime -from india_compliance.gst_india.utils.gstr.gstr import GSTR, get_mapped_value +from india_compliance.gst_india.utils.gstr_2.gstr import GSTR, get_mapped_value def map_date_format(date_str, source_format, target_format): diff --git a/india_compliance/gst_india/utils/gstr/gstr_2b.py b/india_compliance/gst_india/utils/gstr_2/gstr_2b.py similarity index 98% rename from india_compliance/gst_india/utils/gstr/gstr_2b.py rename to india_compliance/gst_india/utils/gstr_2/gstr_2b.py index a1165623ec..1e9b348b1d 100644 --- a/india_compliance/gst_india/utils/gstr/gstr_2b.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr_2b.py @@ -1,7 +1,7 @@ import frappe from india_compliance.gst_india.utils import parse_datetime -from india_compliance.gst_india.utils.gstr.gstr import GSTR, get_mapped_value +from india_compliance.gst_india.utils.gstr_2.gstr import GSTR, get_mapped_value class GSTR2b(GSTR): diff --git a/india_compliance/gst_india/utils/gstr/test_gstr_2a.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py similarity index 98% rename from india_compliance/gst_india/utils/gstr/test_gstr_2a.py rename to india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py index 6139d0e9da..30a6387019 100644 --- a/india_compliance/gst_india/utils/gstr/test_gstr_2a.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py @@ -7,7 +7,7 @@ from frappe.utils import get_datetime from india_compliance.gst_india.utils import get_data_file_path -from india_compliance.gst_india.utils.gstr import ( +from india_compliance.gst_india.utils.gstr_2 import ( GSTRCategory, ReturnType, download_gstr_2a, @@ -65,8 +65,8 @@ def tearDownClass(cls): frappe.db.delete(cls.doctype, {"company_gstin": cls.gstin}) frappe.db.delete(cls.log_doctype, {"gstin": cls.gstin}) - @patch("india_compliance.gst_india.utils.gstr.save_gstr") - @patch("india_compliance.gst_india.utils.gstr.GSTR2aAPI") + @patch("india_compliance.gst_india.utils.gstr_2.save_gstr") + @patch("india_compliance.gst_india.utils.gstr_2.GSTR2aAPI") def test_download_gstr_2a(self, mock_gstr_2a_api, mock_save_gstr): def mock_get_data(action, return_period, otp): if action in ["B2B", "B2BA", "CDN", "CDNA"]: diff --git a/india_compliance/gst_india/utils/gstr/test_gstr_2b.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b.py similarity index 98% rename from india_compliance/gst_india/utils/gstr/test_gstr_2b.py rename to india_compliance/gst_india/utils/gstr_2/test_gstr_2b.py index 3d7002b6ba..ec940318ad 100644 --- a/india_compliance/gst_india/utils/gstr/test_gstr_2b.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b.py @@ -5,8 +5,8 @@ from frappe.tests.utils import FrappeTestCase from india_compliance.gst_india.utils import get_data_file_path -from india_compliance.gst_india.utils.gstr import GSTRCategory, save_gstr_2b -from india_compliance.gst_india.utils.gstr.test_gstr_2a import TestGSTRMixin +from india_compliance.gst_india.utils.gstr_2 import GSTRCategory, save_gstr_2b +from india_compliance.gst_india.utils.gstr_2.test_gstr_2a import TestGSTRMixin class TestGSTR2b(FrappeTestCase, TestGSTRMixin): diff --git a/india_compliance/gst_india/utils/gstr_utils.py b/india_compliance/gst_india/utils/gstr_utils.py new file mode 100644 index 0000000000..05f9b6e7ff --- /dev/null +++ b/india_compliance/gst_india/utils/gstr_utils.py @@ -0,0 +1,162 @@ +from enum import Enum + +import frappe +from frappe import _ +from frappe.utils import add_to_date, now_datetime + +from india_compliance.gst_india.api_classes.returns import ReturnsAPI +from india_compliance.gst_india.doctype.gstr_import_log.gstr_import_log import ( + create_import_log, + toggle_scheduled_jobs, +) +from india_compliance.gst_india.utils import get_gstin_list + + +class ReturnType(Enum): + GSTR2A = "GSTR2a" + GSTR2B = "GSTR2b" + + +@frappe.whitelist() +def validate_company_gstins(company=None, company_gstin=None): + """ + Checks the validity of the company's GSTIN authentication. + + Args: + company_gstin (str): The GSTIN of the company to validate. + + Returns: + dict: A dictionary where the keys are the GSTINs and the values are booleans indicating whether the authentication is valid. + """ + frappe.has_permission("GST Settings", throw=True) + + credentials = get_company_gstin_credentials(company, company_gstin) + + if company_gstin and not credentials: + frappe.throw( + _("Missing GSTIN credentials for GSTIN: {gstin}.").format( + gstin=company_gstin + ) + ) + + if not credentials: + frappe.throw(_("Missing credentials in GST Settings")) + + if company and not company_gstin: + missing_credentials = set(get_gstin_list(company)) - set( + credential.gstin for credential in credentials + ) + + if missing_credentials: + frappe.throw( + _("Missing GSTIN credentials for GSTIN(s): {gstins}.").format( + gstins=", ".join(missing_credentials), + ) + ) + + gstin_authentication_status = { + credential.gstin: ( + credential.session_expiry + and credential.auth_token + and credential.session_expiry > add_to_date(now_datetime(), minutes=30) + ) + for credential in credentials + } + + return gstin_authentication_status + + +def get_company_gstin_credentials(company=None, company_gstin=None): + filters = {"service": "Returns"} + + if company: + filters["company"] = company + + if company_gstin: + filters["gstin"] = company_gstin + + return frappe.get_all( + "GST Credential", + filters=filters, + fields=["gstin", "session_expiry", "auth_token"], + ) + + +@frappe.whitelist() +def request_otp(company_gstin): + frappe.has_permission("GST Settings", throw=True) + + return ReturnsAPI(company_gstin).request_otp() + + +@frappe.whitelist() +def authenticate_otp(company_gstin, otp): + frappe.has_permission("GST Settings", throw=True) + + api = ReturnsAPI(company_gstin) + response = api.autheticate_with_otp(otp) + + return api.process_response(response) + + +def download_queued_request(): + queued_requests = frappe.get_all( + "GSTR Import Log", + filters={"request_id": ["is", "set"]}, + fields=[ + "name", + "gstin", + "return_type", + "classification", + "return_period", + "request_id", + "request_time", + ], + ) + + if not queued_requests: + return toggle_scheduled_jobs(stopped=True) + + for doc in queued_requests: + frappe.enqueue(_download_queued_request, queue="long", doc=doc) + + +def _download_queued_request(doc): + from india_compliance.gst_india.utils.gstr_2 import _download_gstr_2a, save_gstr_2b + + GSTR_FUNCTIONS = { + ReturnType.GSTR2A.value: _download_gstr_2a, + ReturnType.GSTR2B.value: save_gstr_2b, + } + + try: + api = ReturnsAPI(doc.gstin) + response = api.download_files( + doc.return_period, + doc.request_id, + ) + + except Exception as e: + frappe.db.delete("GSTR Import Log", doc.name) + raise e + + if response.error_type in ["otp_requested", "invalid_otp"]: + return toggle_scheduled_jobs(stopped=True) + + if response.error_type == "no_docs_found": + return create_import_log( + doc.gstin, + doc.return_type, + doc.return_period, + doc.classification, + data_not_found=True, + ) + + if response.error_type == "queued": + return + + if response.error_type: + return frappe.db.delete("GSTR Import Log", {"name": doc.name}) + + frappe.db.set_value("GSTR Import Log", doc.name, "request_id", None) + GSTR_FUNCTIONS[doc.return_type](doc.gstin, doc.return_period, response) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 87d0e450cf..7e69063fbe 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -375,7 +375,7 @@ "cron": { "*/5 * * * *": [ "india_compliance.gst_india.utils.e_invoice.retry_e_invoice_e_waybill_generation", - "india_compliance.gst_india.utils.gstr.download_queued_request", + "india_compliance.gst_india.utils.gstr_utils.download_queued_request", "india_compliance.gst_india.doctype.purchase_reconciliation_tool.purchase_reconciliation_tool.auto_refresh_authtoken", ], "0 2 * * *": [ diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 5c6391a9c9..0297378aa7 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -186,7 +186,7 @@ Object.assign(india_compliance, { secondary_action_label: __("Resend OTP"), secondary_action() { frappe.call({ - method: "india_compliance.gst_india.utils.gstr.gstr.request_otp", + method: "india_compliance.gst_india.utils.gstr_utils.request_otp", args: { company_gstin }, callback: function () { frappe.show_alert({ @@ -328,7 +328,7 @@ Object.assign(india_compliance, { async authenticate_company_gstins(company, company_gstin) { const { message: gstin_authentication_status } = await frappe.call({ - method: "india_compliance.gst_india.utils.gstr.gstr.validate_company_gstins", + method: "india_compliance.gst_india.utils.gstr_utils.validate_company_gstins", args: { company: company, company_gstin: company_gstin }, }); @@ -343,7 +343,7 @@ Object.assign(india_compliance, { async authenticate_otp(gstin) { await frappe.call({ - method: "india_compliance.gst_india.utils.gstr.gstr.request_otp", + method: "india_compliance.gst_india.utils.gstr_utils.request_otp", args: { company_gstin: gstin }, }); @@ -354,7 +354,7 @@ Object.assign(india_compliance, { const otp = await this.get_gstin_otp(error_type, gstin); const { message } = await frappe.call({ - method: "india_compliance.gst_india.utils.gstr.gstr.authenticate_otp", + method: "india_compliance.gst_india.utils.gstr_utils.authenticate_otp", args: { company_gstin: gstin, otp: otp }, }); From d9f8f4652fba4ee1d3b6d461240f4dc26313b5a4 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 12 Apr 2024 15:58:15 +0530 Subject: [PATCH 22/23] fix(excel): remove default bg colors from excel export --- .../purchase_reconciliation_tool.py | 6 ++++++ india_compliance/gst_india/utils/exporter.py | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py index bcff6c5b52..37bbf6e9ff 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py @@ -772,6 +772,8 @@ def export_data(self): filters=self.filters, headers=self.match_summary_header, data=self.get_match_summary_data(), + default_data_format={"bg_color": self.COLOR_PALLATE.light_gray}, + default_header_format={"bg_color": self.COLOR_PALLATE.dark_gray}, ) if not self.is_supplier_specific: @@ -780,6 +782,8 @@ def export_data(self): filters=self.filters, headers=self.supplier_header, data=self.get_supplier_data(), + default_data_format={"bg_color": self.COLOR_PALLATE.light_gray}, + default_header_format={"bg_color": self.COLOR_PALLATE.dark_gray}, ) excel.create_sheet( @@ -788,6 +792,8 @@ def export_data(self): merged_headers=self.get_merge_headers(), headers=self.invoice_header, data=self.get_invoice_data(), + default_data_format={"bg_color": self.COLOR_PALLATE.light_gray}, + default_header_format={"bg_color": self.COLOR_PALLATE.dark_gray}, ) excel.remove_sheet("Sheet") diff --git a/india_compliance/gst_india/utils/exporter.py b/india_compliance/gst_india/utils/exporter.py index ea7f8402f6..7610c93125 100644 --- a/india_compliance/gst_india/utils/exporter.py +++ b/india_compliance/gst_india/utils/exporter.py @@ -66,7 +66,6 @@ class Worksheet: "height": 20, "vertical": "center", "wrap_text": False, - "bg_color": "f2f2f2", } ) filter_format = data_format.copy().update({"bg_color": None, "bold": True}) @@ -81,7 +80,6 @@ class Worksheet: "height": 30, "vertical": "center", "wrap_text": True, - "bg_color": "d9d9d9", } ) default_styles = frappe._dict( @@ -106,10 +104,18 @@ def create( filters=None, merged_headers=None, add_totals=True, + default_data_format=None, + default_header_format=None, ): """Create worksheet""" self.headers = headers + if default_data_format: + self.data_format.update(default_data_format) + + if default_header_format: + self.header_format.update(default_header_format) + self.ws = workbook.create_sheet(sheet_name) self.add_data(filters, is_filter=True) self.add_merged_header(merged_headers) From 2a34abf8bb2ad5d4d782f4193cef12876a3e97ae Mon Sep 17 00:00:00 2001 From: Daizy Modi Date: Sat, 13 Apr 2024 11:21:25 +0530 Subject: [PATCH 23/23] fix: Document Type as `CHL` in case of return Invoice with Nil-rated (#2028) **Issue** Document type was mapped incorrect in case of Sales and Purchase Return. **Before** https://github.com/resilient-tech/india-compliance/assets/54097382/7778eba7-0692-4b1d-867c-bbd9375162b1 **After** https://github.com/resilient-tech/india-compliance/assets/54097382/d2f65194-4447-4235-a85a-5918e7b8c000 --- india_compliance/gst_india/utils/e_waybill.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/india_compliance/gst_india/utils/e_waybill.py b/india_compliance/gst_india/utils/e_waybill.py index 59ede6ee52..3a5004a00a 100644 --- a/india_compliance/gst_india/utils/e_waybill.py +++ b/india_compliance/gst_india/utils/e_waybill.py @@ -1470,9 +1470,13 @@ def update_transaction_details(self): if not doc.is_export_with_gst: self.transaction_details.update(document_type="BIL") - if doc.doctype in ("Sales Invoice", "Purchase Invoice") and all( - item.gst_treatment in ("Nil-Rated", "Exempted", "Non-GST") - for item in doc.items + if ( + doc.doctype in ("Sales Invoice", "Purchase Invoice") + and not doc.is_return + and all( + item.gst_treatment in ("Nil-Rated", "Exempted", "Non-GST") + for item in doc.items + ) ): self.transaction_details.update(document_type="BIL")