Skip to content

Commit

Permalink
Merge pull request #3022 from resilient-tech/mergify/bp/version-15-ho…
Browse files Browse the repository at this point in the history
…tfix/pr-3011

feat: GSTIN Status report (backport #3011)
  • Loading branch information
mergify[bot] authored Feb 3, 2025
2 parents d0d790c + cd8bf6f commit 65b6876
Show file tree
Hide file tree
Showing 5 changed files with 382 additions and 25 deletions.
Empty file.
150 changes: 150 additions & 0 deletions india_compliance/gst_india/report/gstin_status/gstin_status.js
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 india_compliance/gst_india/report/gstin_status/gstin_status.json
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 india_compliance/gst_india/report/gstin_status/gstin_status.py
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
Loading

0 comments on commit 65b6876

Please sign in to comment.