From 0e7377f0b8a2c6beb1473fbdf88342f3072855a6 Mon Sep 17 00:00:00 2001 From: Ninad Parikh <109862100+Ninad1306@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:14:55 +0530 Subject: [PATCH] fix: Suggest Invoices that Should be Marked as Pending (#3009) Co-authored-by: Smit Vora (cherry picked from commit 95db03d10bf2ad1f167a6ea61f4c9bf11f041d07) --- .../gst_invoice_management_system.js | 55 +++++++++++++- .../gst_invoice_management_system.json | 13 +++- .../gst_invoice_management_system.py | 43 ++++++++++- .../doctype/gst_return_log/gst_return_log.py | 31 +++++++- .../gst_india/utils/gstin_info.py | 76 +++++++++++++------ 5 files changed, 189 insertions(+), 29 deletions(-) diff --git a/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.js b/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.js index 0e8a95fb52..be6ea8b6cc 100644 --- a/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.js +++ b/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.js @@ -55,11 +55,17 @@ frappe.ui.form.on(DOCTYPE, { render_empty_state(frm); if (!frm.doc.company) return; const options = await india_compliance.set_gstin_options(frm); - frm.set_value("company_gstin", options[0]); + + set_period_options(frm); + }, + + company_gstin(frm) { + render_empty_state(frm); + set_period_options(frm); }, - company_gstin: render_empty_state, + period: render_empty_state, refresh(frm) { show_download_invoices_message(frm); @@ -70,7 +76,13 @@ frappe.ui.form.on(DOCTYPE, { }); class IMS extends reconciliation.reconciliation_tabs { + render_data(data) { + this.process_data(data); + super.render_data(data); + } + refresh(data) { + this.process_data(data); super.refresh(data); this.set_actions_summary(); } @@ -136,6 +148,7 @@ class IMS extends reconciliation.reconciliation_tabs { "Mismatch", "Manual Match", "Missing in PI", + "Suggested Mark as Pending", ], }, { @@ -562,6 +575,28 @@ class IMS extends reconciliation.reconciliation_tabs { ${frappe.datetime.str_to_user(row.bill_date) || ""} `; } + + process_data(data = this.frm.__invoice_data) { + if (!data || !this.frm?.doc?.period) return; + + const { period } = this.frm.doc; + const month = period.slice(0, 2); + const year = period.slice(2); + const reference_date = new Date(year, month, 0); + + // Change match status of invoices in which purchase is booked in next period + // but supplier has filed return in current period + for (const row of data) { + if (!row._purchase_invoice?.posting_date) continue; + + const bill_date = str_to_obj(row._inward_supply.bill_date); + const posting_date = str_to_obj(row._purchase_invoice.posting_date); + + if (posting_date > reference_date && bill_date <= reference_date) { + row.match_status = "Suggested Mark as Pending"; + } + } + } } class IMSAction { @@ -1016,3 +1051,19 @@ function show_download_invoices_message(frm) { frm.ims_actions.download_ims_data(); }); } + +async function set_period_options(frm) { + if (!(frm.doc.company && frm.doc.company_gstin)) return; + + const { message: period_options } = await frappe.call({ + method: "india_compliance.gst_india.doctype.gst_invoice_management_system.gst_invoice_management_system.get_period_options", + args: { company: frm.doc.company, company_gstin: frm.doc.company_gstin }, + }); + + frm.get_field("period").set_data(period_options); + frm.set_value("period", period_options[0]); +} + +function str_to_obj(d) { + return frappe.datetime.user_to_obj(frappe.datetime.str_to_user(d)); +} diff --git a/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.json b/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.json index 728e7ca6cf..8c3261a2aa 100644 --- a/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.json +++ b/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.json @@ -8,6 +8,8 @@ "company", "column_break_2", "company_gstin", + "column_break_mrvm", + "period", "data_section", "invoice_html", "invoice_empty_state", @@ -49,13 +51,22 @@ { "fieldname": "data_section", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_mrvm", + "fieldtype": "Column Break" + }, + { + "fieldname": "period", + "fieldtype": "Autocomplete", + "label": "Period" } ], "hide_toolbar": 1, "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-01-17 12:19:24.001794", + "modified": "2025-01-27 10:36:10.098739", "modified_by": "Administrator", "module": "GST India", "name": "GST Invoice Management System", diff --git a/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.py b/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.py index 59817300c2..f7692f9228 100644 --- a/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.py +++ b/india_compliance/gst_india/doctype/gst_invoice_management_system/gst_invoice_management_system.py @@ -4,7 +4,7 @@ import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import format_date +from frappe.utils import add_to_date, format_date from india_compliance.gst_india.api_classes.taxpayer_base import ( TaxpayerBaseAPI, @@ -37,6 +37,10 @@ unlink_documents as _unlink_documents, ) from india_compliance.gst_india.utils.exporter import ExcelExporter +from india_compliance.gst_india.utils.gstin_info import ( + get_latest_3b_filed_period, + update_gstr_returns_info, +) from india_compliance.gst_india.utils.gstr_2 import ( GSTRCategory, ReturnType, @@ -109,7 +113,7 @@ def get_invoice_data(self, inward_supply=None, purchase=None, filters=None): "is_supplier_return_filed": doc.is_supplier_return_filed, "doc_type": doc.doc_type, "posting_date": format_date( - _purchase_invoice.pop("posting_date", None) + _purchase_invoice.get("posting_date") ), "_inward_supply": doc, "_purchase_invoice": _purchase_invoice, @@ -275,6 +279,41 @@ def download_excel_report(data, doc): build_data.export_data() +@frappe.whitelist() +def get_period_options(company, company_gstin): + def format_period(period): + return period[2:] + period[:2] + + # Calculate six months ago as fallback + six_months_ago = add_to_date(None, months=-7).strftime("%m%Y") + latest_3b_filed_period = get_latest_3b_filed_period(company, company_gstin) or ( + six_months_ago, + ) + + # Fetch latest GSTR3B filing or default to six months ago + latest_3b_filed_period = format_period(latest_3b_filed_period[0]) + six_months_ago = format_period(six_months_ago) + + if latest_3b_filed_period <= six_months_ago: + update_gstr_returns_info(company, company_gstin) + + # Generate last six months of valid periods + periods = [] + date = add_to_date(None, months=-1) + + while True: + period = date.strftime("%m%Y") + formatted_period = format_period(period) + + if formatted_period <= latest_3b_filed_period or formatted_period < "201707": + break + + periods.append(period) + date = add_to_date(date, months=-1) + + return periods + + def download_and_upload_ims_invoices(company_gstin): """ 1. This function will download invoices from GST Portal, diff --git a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py index 467825d147..2c6d8e63c6 100644 --- a/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py +++ b/india_compliance/gst_india/doctype/gst_return_log/gst_return_log.py @@ -223,11 +223,16 @@ def download_file(): frappe.response["type"] = "download" -def process_gstr_1_returns_info(company, gstin, response): +def process_gstr_returns_info(company, gstin, e_filed_list): + process_gstr_1_returns_info(company, gstin, e_filed_list) + process_gstr_3b_returns_info(company, gstin, e_filed_list) + + +def process_gstr_1_returns_info(company, gstin, e_filed_list): return_info = {} # compile gstr-1 returns info - for info in response.get("EFiledlist"): + for info in e_filed_list: if info["rtntype"] == "GSTR1": return_info[f"GSTR1-{info['ret_prd']}-{gstin}"] = info @@ -287,6 +292,28 @@ def _update_gstr_1_filed_upto(filing_date): _update_gstr_1_filed_upto(filed_upto) +def process_gstr_3b_returns_info(company, gstin, e_filed_list): + for info in e_filed_list: + if info["status"] != "Filed": + continue + + if frappe.db.exists( + "GST Return Log", + f'GSTR3B-{info["ret_prd"]}-{gstin}', + ): + continue + + gstr3b_log = frappe.new_doc("GST Return Log") + gstr3b_log.return_period = info["ret_prd"] + gstr3b_log.company = company + gstr3b_log.gstin = gstin + gstr3b_log.return_type = "GSTR3B" + gstr3b_log.filing_status = "Filed" + gstr3b_log.acknowledgement_number = info["arn"] + gstr3b_log.filing_date = datetime.strptime(info["dof"], "%d-%m-%Y").date() + gstr3b_log.insert() + + def get_gst_return_log(posting_date, company_gstin): period = getdate(posting_date).strftime("%m%Y") if name := frappe.db.exists(DOCTYPE, f"GSTR1-{period}-{company_gstin}"): diff --git a/india_compliance/gst_india/utils/gstin_info.py b/india_compliance/gst_india/utils/gstin_info.py index 59171620b6..bc24fc7206 100644 --- a/india_compliance/gst_india/utils/gstin_info.py +++ b/india_compliance/gst_india/utils/gstin_info.py @@ -2,8 +2,11 @@ from datetime import timedelta from string import whitespace +from pypika import Order + import frappe from frappe import _ +from frappe.query_builder.functions import Concat, Substring from frappe.utils import getdate from india_compliance.exceptions import GSPServerError @@ -12,7 +15,7 @@ from india_compliance.gst_india.api_classes.e_waybill import EWaybillAPI from india_compliance.gst_india.api_classes.public import PublicAPI from india_compliance.gst_india.doctype.gst_return_log.gst_return_log import ( - process_gstr_1_returns_info, + process_gstr_returns_info, ) from india_compliance.gst_india.utils import parse_datetime, titlecase, validate_gstin @@ -304,38 +307,67 @@ def fetch_transporter_id_status(transporter_id, throw=True): #################################################################################################### -def get_gstr_1_return_status( - company, gstin, period, process_info=True, year_increment=0 -): - """Returns Returns info for the given period""" +def get_gstr_1_return_status(company, gstin, period, year_increment=0): + """Returns Returns-info for the given period""" fy = get_fy(period, year_increment=year_increment) + e_filed_list = update_gstr_returns_info(company, gstin, fy) - response = PublicAPI().get_returns_info(gstin, fy) - if not response: - return - - if process_info: - frappe.enqueue( - process_gstr_1_returns_info, - company=company, - gstin=gstin, - response=response, - enqueue_after_commit=True, - ) - - for info in response.get("EFiledlist"): + for info in e_filed_list: if info["rtntype"] == "GSTR1" and info["ret_prd"] == period: return info["status"] # late filing possibility (limitation: only checks for the next FY: good enough) if not year_increment and get_current_fy() != fy: - get_gstr_1_return_status( - company, gstin, period, process_info=process_info, year_increment=1 - ) + get_gstr_1_return_status(company, gstin, period, year_increment=1) return "Not Filed" +def update_gstr_returns_info(company, gstin, fy=None): + if not fy: + fy = get_current_fy() + + response = PublicAPI().get_returns_info(gstin, fy) + if not response: + return + + e_filed_list = response.get("EFiledlist") + + # If api call is made then update logs for GSTR1 AND GSTR3B + frappe.enqueue( + process_gstr_returns_info, + company=company, + gstin=gstin, + e_filed_list=e_filed_list, + enqueue_after_commit=True, + ) + + return e_filed_list + + +def get_latest_3b_filed_period(company, company_gstin): + log = frappe.qb.DocType("GST Return Log") + + return ( + frappe.qb.from_(log) + .select(log.return_period) + .where(log.company == company) + .where(log.gstin == company_gstin) + .where(log.return_type == "GSTR3B") + .where(log.filing_status == "Filed") + .orderby( + # eg: 202411 + Concat( + Substring(log.return_period, 3, 4), # year + Substring(log.return_period, 1, 2), # month + ), + order=Order.desc, + ) + .limit(1) + .run(pluck=True) + ) + + def get_fy(period, year_increment=0): month, year = period[:2], period[2:] year = str(int(year) + year_increment)