Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: GSTIN Status report #3011

Merged
merged 16 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
161 changes: 161 additions & 0 deletions india_compliance/gst_india/report/gstin_detailed/gstin_detailed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// 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 Detailed"] = {

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) {
value = default_formatter(value, row, column, 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{
value = `<span fieldname="${column.fieldname}">${value}</span>`
}

}

return value;
},

add_on_click_listner(gstin, default_formatter) {
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]);
}
})
}
else{
toggle_gstin_update_btn(gstin, disabled=false);
}
set_btn_text(gstin, "Updated")
}

})
},
};
const STATUS_TO_COLOR_MAPPING = {
"Active": "green", // Green with 70% opacity
"Cancelled": "red", // Red with 70% opacity
"Inactive": "orange", // Orange with 70% opacity
"Provisional": "yellow", // Yellow with 70% opacity
"Suspended": "grey", // Grey with 70% opacity
};

function get_colored_status(status) {
return `<span class="badge" style="
background-color: ${STATUS_TO_COLOR_MAPPING[status]};
color: white;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
padding: 6px 12px;
display: inline-block;
backdrop-filter: blur(5px);
opacity: 0.7;
">${status}</span>`;
}

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 Detailed'].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;

const formatter = frappe.form.get_formatter(fieldtype);
value = formatter(value);

if (fieldname == "is_blocked"){
value = value==0?"No":"Yes";
}

ele.text(value);
ele.parent().attr("title", value);
}
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 Detailed",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "GSTIN",
"report_name": "GSTIN Detailed",
"report_type": "Script Report",
"roles": [
{
"role": "System Manager"
},
{
"role": "Accounts Manager"
},
{
"role": "Accounts User"
}
],
"timeout": 1500
}
150 changes: 150 additions & 0 deletions india_compliance/gst_india/report/gstin_detailed/gstin_detailed.py
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

import frappe
from frappe import _
from frappe.query_builder import Case, Order
from frappe.query_builder.functions import IfNull


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",
},
{
"label": _("Party Name"),
"fieldname": "party_name",
"fieldtype": "Dynamic Link",
"options": "party_type",
},
{
"label": _("GSTIN"),
"fieldname": "gstin",
"fieldtype": "Link",
"options": "GSTIN",
},
{
"label": _("Status"),
"fieldname": "status",
"fieldtype": "Data",
},
{
"label": _("Registration Date"),
"fieldname": "registration_date",
"fieldtype": "Date",
},
{
"label": _("Last Updated On"),
"fieldname": "last_updated_on",
"fieldtype": "Datetime",
},
{
"label": _("Cancelled Date"),
"fieldname": "cancelled_date",
"fieldtype": "Date",
},
{
"label": _("Is Blocked"),
"fieldname": "is_blocked",
"fieldtype": "Data",
},
{
"label": _("Update GSTIN Details"),
"fieldname": "update_gstin_details_btn",
"fieldtype": "Button",
"width": 100,
},
]

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(
gstin.gstin,
gstin.status,
gstin.registration_date,
gstin.last_updated_on,
gstin.cancelled_date,
Case().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