Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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.
107 changes: 107 additions & 0 deletions sf_trading/sf_trading/report/work_flow_approval/work_flow_approval.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@

let wf_action = "";

frappe.query_reports["Work Flow Approval"] = {
filters: [
{ fieldname: "user", label: "User", fieldtype: "Link", options: "User", default: frappe.session.user },
{ fieldname: "company", label: "Company", fieldtype: "Link", options: "Company" },

{
fieldname: "doctype", label: "Document Type", fieldtype: "Link", options: "DocType", reqd: 1,
get_query: function() {
return {
query: "sf_trading.sf_trading.report.work_flow_approval.work_flow_approval.get_workflow_doctypes"
};
},
on_change: function() {
wf_action = "";
$("#wf-action-select").val("");
load_actions();
frappe.query_report.refresh();
}
},
{ fieldname: "from_date", label: "From Date", fieldtype: "Date" },
{ fieldname: "to_date", label: "To Date", fieldtype: "Date" }
],

after_datatable_render: function(dt) {
$(dt.wrapper).find(".dt-cell--col-0").each(function(i) {
if (i === 0) return;
let $c = $(this).css({ "text-align": "center", "cursor": "pointer" });
if (!$c.find('input[type="checkbox"]').length)
$c.html('<input type="checkbox" class="row-check" style="width:16px;height:16px;cursor:pointer;">');
$c.off("click").on("click", function(e) {
if (!$(e.target).is("input"))
$c.find('input[type="checkbox"]').prop("checked", v => !v);
});
});
},

onload: function(report) {
setTimeout(function() {
if (report.page.page_form.length && !$("#wf-action-select").length) {
report.page.page_form.append(`
<div style="display:inline-block;margin-left:8px;vertical-align:middle;">
<select id="wf-action-select" style="height:30px;border:none;border-radius:25px;
padding:0 14px;font-size:13px;font-family:inherit;color:#333;
background-color:#f4f5f6;min-width:160px;cursor:pointer;outline:none;">
<option value="">Workflow Action</option>
</select>
</div>`);
$("#wf-action-select").on("change", function() { wf_action = $(this).val(); });
}
load_actions();
}, 800);

report.page.add_inner_button("Apply Workflow Action", function() {
let docs = [];

$(report.datatable.wrapper).find("input.row-check:checked").each(function() {
let idx = parseInt($(this).closest(".dt-row").attr("data-row-index"));
if (!isNaN(idx) && report.data[idx])
docs.push({ doctype: report.data[idx].doctype, name: report.data[idx].name });
});

if (!docs.length) {
$(report.datatable.wrapper).find(".dt-row").each(function(i) {
if ($(this).find("input.row-check").prop("checked") && report.data[i])
docs.push({ doctype: report.data[i].doctype, name: report.data[i].name });
});
}

if (!docs.length) return frappe.msgprint(__("Please select at least one document."));

let action = $("#wf-action-select").val() || wf_action;
if (!action) return frappe.msgprint(__("Please select a Workflow Action."));

frappe.confirm(
__("Apply <b>{0}</b> to {1} document(s)?", [action, docs.length]),
() => frappe.call({
method: "sf_trading.sf_trading.report.work_flow_approval.work_flow_approval.apply_bulk_workflow",
args: { docs: JSON.stringify(docs), action },
freeze: true,
freeze_message: __("Applying..."),
callback: r => {
if (!r.exc) {
frappe.msgprint({ title: __("Result"), message: r.message, indicator: "green" });
frappe.query_report.refresh();
}
}
})
);
});
}
};

function load_actions() {
let doctype = frappe.query_report.get_filter_value("doctype");
if (!doctype) return;
frappe.call({
method: "sf_trading.sf_trading.report.work_flow_approval.work_flow_approval.get_workflow_actions",
args: { doctype },
callback: r => {
let $s = $("#wf-action-select").empty().append('<option value="">Workflow Action</option>');
(r.message || []).forEach(a => $s.append(`<option value="${a}">${a}</option>`));
}
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"add_total_row": 0,
"add_translate_data": 0,
"columns": [],
"creation": "2026-03-03 11:04:33.355621",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"letter_head": null,
"modified": "2026-03-03 20:21:39.835341",
"modified_by": "Administrator",
"module": "Sf Trading",
"name": "Work Flow Approval",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Workflow",
"report_name": "Work Flow Approval",
"report_type": "Script Report",
"roles": [
{
"role": "System Manager"
}
],
"timeout": 0
}
104 changes: 104 additions & 0 deletions sf_trading/sf_trading/report/work_flow_approval/work_flow_approval.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# # Copyright (c) 2026, enfono and contributors
# # For license information, please see license.txt

import frappe
def execute(filters=None):
return get_columns(), get_data(filters)


def get_columns():
return [
{ "label": "User", "fieldname": "owner", "fieldtype": "Link", "options": "User", "width": 180 },
{ "label": "Company", "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 150 },
{ "label": "DocType", "fieldname": "doctype", "fieldtype": "Data", "width": 150 },
{ "label": "Document", "fieldname": "name", "fieldtype": "Dynamic Link", "options": "doctype", "width": 200 },
{ "label": "Status", "fieldname": "workflow_state", "fieldtype": "Data", "width": 150 },
{ "label": "Created Date", "fieldname": "creation", "fieldtype": "Datetime", "width": 180 }
]


def get_data(filters):
if not filters.get("doctype"):
return []

doctype = filters.get("doctype")

if not frappe.db.has_column(doctype, "workflow_state"):
return []

has_company = frappe.db.has_column(doctype, "company")
conditions = "workflow_state = 'Pending'"
values = {}

if filters.get("company") and has_company:
conditions += " AND company = %(company)s"
values["company"] = filters["company"]

if filters.get("from_date"):
conditions += " AND creation >= %(from_date)s"
values["from_date"] = filters["from_date"]

if filters.get("to_date"):
conditions += " AND creation <= %(to_date)s"
values["to_date"] = filters["to_date"]

fields = ("company, " if has_company else "") + "name, workflow_state, owner, creation"
data = frappe.db.sql(f"""
SELECT {fields} FROM `tab{doctype}`
WHERE {conditions} ORDER BY creation DESC
""", values, as_dict=True)

for d in data:
d["doctype"] = doctype
if not has_company:
d["company"] = ""

return data


@frappe.whitelist()
def get_workflow_actions(doctype):
wf = frappe.db.get_all("Workflow",
filters={"document_type": doctype, "is_active": 1},
fields=["name"], limit=1)

if not wf:
return []

seen, actions = set(), []
for t in frappe.get_doc("Workflow", wf[0].name).transitions:
if t.action and t.action not in seen:
seen.add(t.action)
actions.append(t.action)
return actions

@frappe.whitelist()
def apply_bulk_workflow(docs, action):
import json
from frappe.model.workflow import apply_workflow

results = []
for d in json.loads(docs):
try:
apply_workflow(frappe.get_doc(d["doctype"], d["name"]), action)
results.append(f" {d['name']} — Success")
except Exception as e:
results.append(f" {d['name']} — {str(e)}")

frappe.db.commit()
return "<br>".join(results)


@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_workflow_doctypes(doctype, txt, searchfield, start, page_len, filters):
workflows = frappe.db.get_all("Workflow",
filters={"is_active": 1},
fields=["document_type"],
limit=100
)

doctypes = [[w.document_type, w.document_type] for w in workflows
if w.document_type and txt.lower() in w.document_type.lower()]

return doctypes
Loading