-
Notifications
You must be signed in to change notification settings - Fork 152
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3022 from resilient-tech/mergify/bp/version-15-ho…
…tfix/pr-3011 feat: GSTIN Status report (backport #3011)
- Loading branch information
Showing
5 changed files
with
382 additions
and
25 deletions.
There are no files selected for viewing
Empty file.
150 changes: 150 additions & 0 deletions
150
india_compliance/gst_india/report/gstin_status/gstin_status.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
// Copyright (c) 2025, Resilient Tech and contributors | ||
// For license information, please see license.txt | ||
|
||
const GSTIN_FIELDNAME = [ | ||
"status", | ||
"registration_date", | ||
"last_updated_on", | ||
"cancelled_date", | ||
"is_blocked", | ||
]; | ||
|
||
frappe.query_reports["GSTIN Status"] = { | ||
html_enabled: true, | ||
|
||
filters: [ | ||
{ | ||
fieldname: "status", | ||
label: __("Status"), | ||
fieldtype: "Select", | ||
options: [ | ||
"", | ||
"Active", | ||
"Cancelled", | ||
"Inactive", | ||
"Provisional", | ||
"Suspended", | ||
], | ||
}, | ||
{ | ||
fieldname: "party_type", | ||
label: __("Party Type"), | ||
fieldtype: "Select", | ||
options: ["", "Customer", "Supplier"], | ||
}, | ||
], | ||
|
||
formatter: function (value, row, column, data, default_formatter) { | ||
if (data) { | ||
if (column.fieldname == "status") { | ||
value = get_colored_status(value); | ||
} else if (column.fieldname == "update_gstin_details_btn") { | ||
value = create_btn_with_gstin_attr(data.gstin); | ||
} else { | ||
if (column.fieldname == "last_updated_on") { | ||
value = frappe.datetime.prettyDate(value); | ||
} else { | ||
value = default_formatter(value, row, column, data); | ||
} | ||
value = `<span fieldname="${column.fieldname}">${value}</span>`; | ||
} | ||
} | ||
|
||
return value; | ||
}, | ||
|
||
add_on_click_listner(gstin) { | ||
toggle_gstin_update_btn(gstin, (disabled = true)); | ||
const affectedElements = $(`div.dt-cell__content[title='${gstin}']`); | ||
set_btn_text(gstin, "Updating"); | ||
|
||
frappe.call({ | ||
method: "india_compliance.gst_india.doctype.gstin.gstin.get_gstin_status", | ||
args: { | ||
gstin: gstin, | ||
force_update: true, | ||
}, | ||
callback: function (r) { | ||
if (r.message) { | ||
let data = r.message; | ||
affectedElements.each(function () { | ||
row = this.parentElement.attributes["data-row-index"].value; | ||
for (let fieldname of GSTIN_FIELDNAME) { | ||
update_value(row, fieldname, data[fieldname]); | ||
} | ||
}); | ||
set_btn_text(gstin, "Updated"); | ||
} else { | ||
toggle_gstin_update_btn(gstin, (disabled = false)); | ||
} | ||
}, | ||
}); | ||
}, | ||
|
||
get_datatable_options(datatable_options) { | ||
datatable_options.cellHeight = 35; | ||
|
||
return datatable_options; | ||
}, | ||
}; | ||
const STATUS_TO_COLOR_MAPPING = { | ||
Active: "green", | ||
Cancelled: "red", | ||
Inactive: "black", | ||
Provisional: "black", | ||
Suspended: "black", | ||
}; | ||
|
||
function get_colored_status(status) { | ||
if (!status) return ""; | ||
return `<div style="color: ${STATUS_TO_COLOR_MAPPING[status]}; text-align:center; width:100%;">${status}</div>`; | ||
} | ||
|
||
function set_btn_text(gstin, text) { | ||
let btn = $(`button[data-gstin='${gstin}']`); | ||
btn.text(text); | ||
} | ||
|
||
function toggle_gstin_update_btn(gstin, disabled = null) { | ||
let btn = $(`button[data-gstin='${gstin}']`); | ||
if (disabled == null) { | ||
disabled = btn.prop("disabled"); | ||
disabled = !disabled; | ||
} | ||
|
||
btn.prop("disabled", disabled); | ||
} | ||
|
||
function create_btn_with_gstin_attr(gstin) { | ||
const BUTTON_HTML = `<button | ||
data-fieldname="gstin_update_btn" | ||
class="btn btn-xs btn-primary center" | ||
data-gstin="${gstin}" | ||
onclick="frappe.query_reports['GSTIN Status'].add_on_click_listner('${gstin}')" | ||
> | ||
Update | ||
</button>`; | ||
|
||
return BUTTON_HTML; | ||
} | ||
|
||
function update_value(row, fieldname, value) { | ||
let ele = $(`.dt-row.dt-row-${row}.vrow > div > div > [fieldname='${fieldname}']`); | ||
|
||
let column = frappe.query_report.columns.find(column => { | ||
return column.fieldname == fieldname; | ||
}); | ||
fieldtype = column.fieldtype; | ||
|
||
if (fieldname == "is_blocked") { | ||
value = [undefined, null].includes(value) ? "" : value == 0 ? "No" : "Yes"; | ||
} else if (fieldname == "last_updated_on") { | ||
value = frappe.datetime.prettyDate(value); | ||
} else { | ||
const formatter = frappe.form.get_formatter(fieldtype); | ||
value = formatter(value); | ||
} | ||
|
||
ele.text(value); | ||
ele.parent().attr("title", value); | ||
} |
34 changes: 34 additions & 0 deletions
34
india_compliance/gst_india/report/gstin_status/gstin_status.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"add_total_row": 0, | ||
"columns": [], | ||
"creation": "2025-01-28 17:10:48.875369", | ||
"disabled": 0, | ||
"docstatus": 0, | ||
"doctype": "Report", | ||
"filters": [], | ||
"idx": 0, | ||
"is_standard": "Yes", | ||
"json": "{}", | ||
"letterhead": null, | ||
"modified": "2025-01-29 13:02:30.885362", | ||
"modified_by": "Administrator", | ||
"module": "GST India", | ||
"name": "GSTIN Status", | ||
"owner": "Administrator", | ||
"prepared_report": 0, | ||
"ref_doctype": "GSTIN", | ||
"report_name": "GSTIN Status", | ||
"report_type": "Script Report", | ||
"roles": [ | ||
{ | ||
"role": "System Manager" | ||
}, | ||
{ | ||
"role": "Accounts Manager" | ||
}, | ||
{ | ||
"role": "Accounts User" | ||
} | ||
], | ||
"timeout": 1500 | ||
} |
162 changes: 162 additions & 0 deletions
162
india_compliance/gst_india/report/gstin_status/gstin_status.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
# Copyright (c) 2025, Resilient Tech and contributors | ||
# For license information, please see license.txt | ||
|
||
import frappe | ||
from frappe import _ | ||
from frappe.query_builder import Case, Order | ||
from frappe.query_builder.functions import IfNull, IsNull | ||
|
||
|
||
def execute(filters: dict | None = None): | ||
"""Return columns and data for the report. | ||
This is the main entry point for the report. It accepts the filters as a | ||
dictionary and should return columns and data. It is called by the framework | ||
every time the report is refreshed or a filter is updated. | ||
""" | ||
report = GSTINDetailedReport(filters=filters) | ||
columns = report.get_columns() | ||
data = report.get_data() | ||
|
||
return columns, data | ||
|
||
|
||
class GSTINDetailedReport: | ||
|
||
def __init__(self, filters: dict | None = None): | ||
self.filters = frappe._dict(filters or {}) | ||
self.doctypes = ( | ||
[self.filters.party_type] | ||
if self.filters.party_type | ||
else ["Customer", "Supplier"] | ||
) | ||
|
||
def get_columns(self) -> list[dict]: | ||
"""Return columns for the report. | ||
One field definition per column, just like a DocType field definition. | ||
""" | ||
columns = [ | ||
{ | ||
"label": _("Party Type"), | ||
"fieldname": "party_type", | ||
"fieldtype": "Link", | ||
"options": "DocType", | ||
"width": 100, | ||
}, | ||
{ | ||
"label": _("Party Name"), | ||
"fieldname": "party_name", | ||
"fieldtype": "Dynamic Link", | ||
"options": "party_type", | ||
"width": 220, | ||
}, | ||
{ | ||
"label": _("GSTIN"), | ||
"fieldname": "gstin", | ||
"fieldtype": "Link", | ||
"options": "GSTIN", | ||
"width": 180, | ||
}, | ||
{ | ||
"label": _("Status"), | ||
"fieldname": "status", | ||
"fieldtype": "Data", | ||
"width": 120, | ||
}, | ||
{ | ||
"label": _("Registration Date"), | ||
"fieldname": "registration_date", | ||
"fieldtype": "Date", | ||
"width": 150, | ||
}, | ||
{ | ||
"label": _("Last Updated"), | ||
"fieldname": "last_updated_on", | ||
"fieldtype": "Datetime", | ||
"width": 150, | ||
}, | ||
{ | ||
"label": _("Cancelled Date"), | ||
"fieldname": "cancelled_date", | ||
"fieldtype": "Date", | ||
"width": 150, | ||
}, | ||
{ | ||
"label": _("Is Blocked"), | ||
"fieldname": "is_blocked", | ||
"fieldtype": "Data", | ||
"width": 80, | ||
}, | ||
{ | ||
"label": _("Update GSTIN Details"), | ||
"fieldname": "update_gstin_details_btn", | ||
"fieldtype": "Button", | ||
"width": 120, | ||
}, | ||
] | ||
|
||
return columns | ||
|
||
def get_data(self): | ||
gstin = frappe.qb.DocType("GSTIN") | ||
address = frappe.qb.DocType("Address") | ||
dynamic_link = frappe.qb.DocType("Dynamic Link") | ||
|
||
party_query = ( | ||
frappe.qb.from_(address) | ||
.inner_join(dynamic_link) | ||
.on(address.name == dynamic_link.parent) | ||
.select( | ||
address.gstin, | ||
dynamic_link.link_doctype.as_("party_type"), | ||
dynamic_link.link_name.as_("party_name"), | ||
) | ||
.where(dynamic_link.link_doctype.isin(self.doctypes)) | ||
.where(IfNull(address.gstin, "") != "") | ||
).as_("party") | ||
|
||
for doctype in self.doctypes: | ||
party_query.union(get_party_query(doctype)) | ||
|
||
gstin_query = ( | ||
frappe.qb.from_(party_query) | ||
.left_join(gstin) | ||
.on(gstin.gstin == party_query.gstin) | ||
.select( | ||
party_query.gstin, | ||
gstin.status, | ||
gstin.registration_date, | ||
gstin.last_updated_on, | ||
gstin.cancelled_date, | ||
Case() | ||
.when(IsNull(gstin.is_blocked), "") | ||
.when(gstin.is_blocked == 0, "No") | ||
.else_("Yes") | ||
.as_("is_blocked"), | ||
party_query.party_type, | ||
party_query.party_name, | ||
) | ||
.orderby(gstin.modified, order=Order.desc) | ||
) | ||
|
||
if self.filters.status: | ||
gstin_query = gstin_query.where(gstin.status == self.filters.status) | ||
|
||
return gstin_query.run(as_dict=True) | ||
|
||
|
||
def get_party_query(doctype): | ||
dt = frappe.qb.DocType(doctype) | ||
|
||
query = ( | ||
frappe.qb.from_(dt) | ||
.select( | ||
dt.gstin, | ||
dt.doctype.as_("party_type"), | ||
dt.name.as_("party_name"), | ||
) | ||
.where(IfNull(dt.gstin, "") != "") | ||
) | ||
|
||
return query |
Oops, something went wrong.