diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js index 92fd0e000..657e8da00 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js @@ -31,6 +31,17 @@ const ReturnType = { GSTR2B: "GSTR2b", }; +const filter_fields = [ + "company", + "company_gstin", + "purchase_from_date", + "purchase_to_date", + "inward_supply_from_date", + "inward_supply_to_date", + "include_ignored", + "gst_return", +]; + function remove_gstr2b_alert(alert) { if (alert.length === 0) return; $(alert).remove(); @@ -62,6 +73,19 @@ async function add_gstr2b_alert(frm) { }); } +function setup_filter_onchange_events(frm) { + const events = {}; + + filter_fields.forEach(field => { + events[field] = () => { + frm.doc.__reconciliation_data = null; + frm.refresh(); + }; + }); + + frappe.ui.form.on("Purchase Reconciliation Tool", events); +} + frappe.ui.form.on("Purchase Reconciliation Tool", { async setup(frm) { patch_set_active_tab(frm); @@ -69,13 +93,36 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { await frappe.require("purchase_reconciliation_tool.bundle.js"); frm.trigger("company"); - frm.purchase_reconciliation_tool = new PurchaseReconciliationTool(frm); - }, - - onload(frm) { - if (frm.doc.is_modified) frm.doc.reconciliation_data = null; + frm.doc.__reconciliation_data = null; add_gstr2b_alert(frm); set_date_range_description(frm); + setup_filter_onchange_events(frm); + frm.purchase_reconciliation_tool = new PurchaseReconciliationTool(frm); + + frappe.realtime.on("report_generated", message => { + frm.call("publish_reconciliation_data", { + name: message.name, + }); + }); + + frappe.realtime.on("data_reconciliation", message => { + const { data, filters, creation, report_name } = message; + + frm.report_name = report_name; + frm.is_latest_data = true; + + const is_filter_same = filter_fields.every( + field => frm.doc[field] === filters[field] + ); + if (!is_filter_same) return; + + frappe.after_ajax(() => { + frm.doc.__reconciliation_data = + data && data.result ? JSON.stringify(data.result) : null; + frm.doc.data_reconciled_on = creation; + frm.trigger("load_reconciliation_data"); + }); + }); }, async company(frm) { @@ -88,7 +135,46 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { refresh(frm) { // Primary Action frm.disable_save(); - frm.page.set_primary_action(__("Reconcile"), () => frm.save()); + const button_label = frm.doc.__reconciliation_data + ? __("Rebuild") + : __("Reconcile"); + const force_update = frm.doc.__reconciliation_data != null; + + frm.page.set_primary_action(button_label, async () => { + if ( + !frm.doc.company || + !frm.doc.company_gstin || + !frm.doc.purchase_period || + !frm.doc.inward_supply_period || + !frm.doc.gst_return + ) { + frappe.msgprint({ + message: __("Please Enter All Details"), + title: "Message", + indicator: "blue", + }); + return; + } + const filetrs_to_pass = {}; + filter_fields.forEach(field => { + filetrs_to_pass[field] = frm.doc[field]; + }); + + const { message } = await frappe.call({ + method: "frappe.core.doctype.prepared_report.prepared_report.get_reports_in_queued_state", + args: { + filters: filetrs_to_pass, + report_name: "ICUtility", + }, + }); + if (message.length) { + frappe.msgprint(__("A report is alerady in queued")); + return; + } + frm.call("get_reconciliation_data", { + force_update: force_update, + }); + }); // add custom buttons api_enabled @@ -132,11 +218,6 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { } }, - before_save(frm) { - frm.doc.__unsaved = true; - frm.doc.reconciliation_data = null; - }, - async purchase_period(frm) { await fetch_date_range(frm, "purchase"); set_date_range_description(frm, "purchase"); @@ -161,9 +242,13 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { add_gstr2b_alert(frm); }, - after_save(frm) { + load_reconciliation_data(frm) { + frm.refresh(); frm.purchase_reconciliation_tool.refresh( - frm.doc.reconciliation_data ? JSON.parse(frm.doc.reconciliation_data) : [] + frm.doc.__reconciliation_data + ? JSON.parse(frm.doc.__reconciliation_data) + : [], + frm.doc.data_reconciled_on ? frm.doc.data_reconciled_on : "" ); }, @@ -208,7 +293,9 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { setTimeout(() => { frm.dashboard.clear_headline(); }, 2000); - frm.save(); + frm.call("get_reconciliation_data", { + force_update: true, + }); }, 1000); } }); @@ -225,15 +312,15 @@ class PurchaseReconciliationTool { init(frm) { this.frm = frm; - this.data = frm.doc.reconciliation_data - ? JSON.parse(frm.doc.reconciliation_data) + this.data = frm.doc.__reconciliation_data + ? JSON.parse(frm.doc.__reconciliation_data) : []; this.filtered_data = this.data; this.$wrapper = this.frm.get_field("reconciliation_html").$wrapper; this._tabs = ["invoice", "supplier", "summary"]; } - refresh(data) { + refresh(data, data_reconciled_on) { if (data) { this.data = data; this.refresh_filter_fields(); @@ -241,14 +328,11 @@ class PurchaseReconciliationTool { this.apply_filters(!!data); - // data unchanged! - if (this.rendered_data == this.filtered_data) return; - this._tabs.forEach(tab => { this.tabs[`${tab}_tab`].refresh(this[`get_${tab}_data`]()); }); - this.rendered_data = this.filtered_data; + this.setup_footer(data_reconciled_on); } render_tab_group() { @@ -299,6 +383,23 @@ class PurchaseReconciliationTool { ); } + setup_footer(data_reconciled_on) { + if (!data_reconciled_on) return; + + const creation_time_string = `Created ${frappe.datetime.prettyDate( + data_reconciled_on + )} `; + + const wrapper = this.frm.get_field("reconciliation_html").$wrapper; + + if ($(wrapper).find(".creation-time").length) + $(wrapper).find(".creation-time").remove(); + + wrapper.append( + `
${creation_time_string}
` + ); + } + setup_filter_button() { this.filter_group = new india_compliance.FilterGroup({ doctype: "Purchase Reconciliation Tool", @@ -988,6 +1089,7 @@ class DetailViewDialog { () => { this._apply_custom_action(action); this.dialog.hide(); + this.update_report_status(); }, `mr-2 ${this._get_button_css(action)}` ); @@ -1021,6 +1123,24 @@ class DetailViewDialog { } } + async update_report_status() { + if (this.frm.is_latest_data == false) return; + + const filters = {}; + filter_fields.forEach(field => { + filters[field] = this.frm.doc[field]; + }); + filters["is_latest_data"] = 1; + + await frappe.db.set_value( + "Prepared Report", + this.frm.report_name, + "filters", + JSON.stringify(filters) + ); + this.frm.is_latest_data = false; + } + _get_button_css(action) { if (action == "Unlink") return "btn-danger not-grey"; if (action == "Pending") return "btn-secondary"; diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json index 4acc2f59e..c111f0da7 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.json @@ -25,7 +25,7 @@ "no_reconciliation_data", "is_modified", "section_break_cmfa", - "reconciliation_data" + "__reconciliation_data" ], "fields": [ { @@ -96,12 +96,12 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval: doc.reconciliation_data?.length", + "depends_on": "eval: doc.__reconciliation_data?.length", "fieldname": "reconciliation_html", "fieldtype": "HTML" }, { - "depends_on": "eval: doc.reconciliation_data && !doc.reconciliation_data.length", + "depends_on": "eval: doc.__reconciliation_data && !doc.__reconciliation_data.length", "fieldname": "no_reconciliation_data", "fieldtype": "HTML", "options": "\"No\n\t

{{ __(\"No data available for selected filters\") }}

" @@ -117,7 +117,7 @@ "options": "GSTR 2B\nBoth GSTR 2A & 2B" }, { - "depends_on": "eval: !doc.reconciliation_data", + "depends_on": "eval: !doc.__reconciliation_data", "fieldname": "not_reconciled", "fieldtype": "HTML", "options": "\"No\n\t

{{ __(\"Reconcile to view the data\") }}

" @@ -128,9 +128,8 @@ "hidden": 1 }, { - "fieldname": "reconciliation_data", - "fieldtype": "JSON", - "label": "Reconciliation Data" + "fieldname": "__reconciliation_data", + "fieldtype": "JSON" }, { "default": "0", @@ -150,7 +149,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-06-20 13:19:57.316043", + "modified": "2024-08-10 16:20:48.790753", "modified_by": "Administrator", "module": "GST India", "name": "Purchase Reconciliation Tool", 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 fa9291611..57eae1984 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 @@ -1,6 +1,6 @@ # Copyright (c) 2022, Resilient Tech and contributors # For license information, please see license.txt -import json +import gzip import re from collections import defaultdict from typing import List @@ -9,16 +9,13 @@ from frappe.model.document import Document from frappe.query_builder.functions import IfNull from frappe.utils import add_to_date, cint, now_datetime -from frappe.utils.response import json_handler from india_compliance.gst_india.api_classes.taxpayer_base import TaxpayerBaseAPI -from india_compliance.gst_india.constants import ORIGINAL_VS_AMENDED from india_compliance.gst_india.doctype.purchase_reconciliation_tool import ( BaseUtil, BillOfEntry, PurchaseInvoice, ReconciledData, - Reconciler, ) from india_compliance.gst_india.utils import ( get_gstin_list, @@ -43,6 +40,10 @@ "Pending": "Unreconciled", "Ignore": "Ignored", } +from frappe.core.doctype.prepared_report.prepared_report import ( + get_completed_prepared_report, + make_prepared_report, +) class PurchaseReconciliationTool(Document): @@ -63,34 +64,51 @@ def get_reco_doc(self): ) return {field: self.get(field) for field in fields} - def onload(self): - date_range = [ - self.inward_supply_from_date, - self.inward_supply_to_date, - ] - - self.set_onload( - "has_missing_2b_documents", - has_missing_2b_documents( - date_range, ReturnType.GSTR2B, self.company_gstin, self.company - ), - ) - - def validate(self): - # reconcile purchases and inward supplies + @frappe.whitelist() + def get_reconciliation_data(self, force_update=False): if frappe.flags.in_install or frappe.flags.in_migrate: return - _Reconciler = Reconciler(**self.get_reco_doc()) - for row in ORIGINAL_VS_AMENDED: - _Reconciler.reconcile(row["original"], row["amended"]) + doc_name = get_completed_prepared_report( + self.get_reco_doc(), frappe.session.user, "ICUtility" + ) + if doc_name and not force_update: + self.publish_reconciliation_data(doc_name) + else: + frappe.enqueue(make_prepared_report("ICUtility", self.get_reco_doc())) - self.ReconciledData = ReconciledData(**self.get_reco_doc()) - self.reconciliation_data = json.dumps( - self.ReconciledData.get(), default=json_handler + @frappe.whitelist() + def publish_reconciliation_data(self, name): + attachment = frappe.get_doc( + "File", + { + "attached_to_name": name, + "attached_to_doctype": "Prepared Report", + }, ) - self.db_set("is_modified", 0) + self.reconciliation_data = None + + if attachment: + content = attachment.get_content() + if isinstance(content, str): + content = content.encode("utf-8") + + self.reconciliation_data = frappe.parse_json( + frappe.safe_decode(gzip.decompress(content)) + ) + + frappe.publish_realtime( + "data_reconciliation", + message={ + "data": self.reconciliation_data, + "filters": self.get_reco_doc(), + "creation": attachment.creation if attachment else None, + "report_name": name, + }, + user=frappe.session.user, + doctype=self.doctype, + ) @frappe.whitelist() def upload_gstr(self, return_type, period, file_path): diff --git a/india_compliance/gst_india/report/icutility/__init__.py b/india_compliance/gst_india/report/icutility/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/india_compliance/gst_india/report/icutility/icutility.js b/india_compliance/gst_india/report/icutility/icutility.js new file mode 100644 index 000000000..db3cdfd47 --- /dev/null +++ b/india_compliance/gst_india/report/icutility/icutility.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Resilient Tech and contributors +// For license information, please see license.txt + +frappe.query_reports["ICUtility"] = { + "filters": [ + + ] +}; diff --git a/india_compliance/gst_india/report/icutility/icutility.json b/india_compliance/gst_india/report/icutility/icutility.json new file mode 100644 index 000000000..46c9dd5ad --- /dev/null +++ b/india_compliance/gst_india/report/icutility/icutility.json @@ -0,0 +1,33 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2024-08-07 15:29:53.228497", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letter_head": "", + "letterhead": null, + "modified": "2024-08-07 15:31:18.427400", + "modified_by": "Administrator", + "module": "GST India", + "name": "ICUtility", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Purchase Reconciliation Tool", + "report_name": "ICUtility", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/india_compliance/gst_india/report/icutility/icutility.py b/india_compliance/gst_india/report/icutility/icutility.py new file mode 100644 index 000000000..337559472 --- /dev/null +++ b/india_compliance/gst_india/report/icutility/icutility.py @@ -0,0 +1,35 @@ +# Copyright (c) 2024, Resilient Tech and contributors +# For license information, please see license.txt + + +from india_compliance.gst_india.constants import ORIGINAL_VS_AMENDED +from india_compliance.gst_india.doctype.purchase_reconciliation_tool.__init__ import ( + ReconciledData, + Reconciler, +) + + +def execute(filters=None): + # reconcile purchases and inward supplies + _Reconciler = Reconciler(**filters) + for row in ORIGINAL_VS_AMENDED: + _Reconciler.reconcile(row["original"], row["amended"]) + + obj = ReconciledData(**filters) + reconciliation_data = obj.get() + + return get_columns(reconciliation_data), reconciliation_data + + +def get_columns(data): + columns = [] + if not data: + return + for key in data[0].keys(): + columns.append( + { + "fieldname": key, + "fieldtype": "Data", + } + ) + return columns diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index d2d9e823c..a05f24391 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -59,4 +59,5 @@ india_compliance.patches.v14.unset_inward_supply_link_for_cancelled_purchase india_compliance.patches.v14.delete_not_generated_gstr_import_log india_compliance.patches.v14.enable_sales_through_ecommerce_operator execute:from india_compliance.gst_india.setup import set_default_print_settings; set_default_print_settings() +execute:import frappe; frappe.db.delete("Singles", {"doctype":"Purchase Reconciliation Tool"}) india_compliance.patches.v15.migrate_gstr1_log_to_returns_log