Merge branch 'develop' into check_tab
This commit is contained in:
commit
4d25520a62
22 changed files with 849 additions and 586 deletions
|
|
@ -337,7 +337,7 @@ class CookieManager:
|
|||
if frappe.session.session_country:
|
||||
self.set_cookie("country", frappe.session.session_country)
|
||||
|
||||
def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Strict"):
|
||||
def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Lax"):
|
||||
if not secure:
|
||||
secure = frappe.local.request.scheme == "https"
|
||||
self.cookies[key] = {
|
||||
|
|
|
|||
|
|
@ -631,6 +631,29 @@ def stop_recording(context):
|
|||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
||||
@click.command('ngrok')
|
||||
@pass_context
|
||||
def start_ngrok(context):
|
||||
from pyngrok import ngrok
|
||||
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
|
||||
port = frappe.conf.http_port or frappe.conf.webserver_port
|
||||
public_url = ngrok.connect(port=port, options={
|
||||
'host_header': site
|
||||
})
|
||||
print(f'Public URL: {public_url}')
|
||||
print('Inspect logs at http://localhost:4040')
|
||||
|
||||
ngrok_process = ngrok.get_ngrok_process()
|
||||
try:
|
||||
# Block until CTRL-C or some other terminating event
|
||||
ngrok_process.proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
print("Shutting down server...")
|
||||
frappe.destroy()
|
||||
ngrok.kill()
|
||||
|
||||
commands = [
|
||||
add_system_manager,
|
||||
|
|
@ -656,5 +679,6 @@ commands = [
|
|||
browse,
|
||||
start_recording,
|
||||
stop_recording,
|
||||
add_to_hosts
|
||||
add_to_hosts,
|
||||
start_ngrok
|
||||
]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ frappe.ui.form.on('Dashboard', {
|
|||
return {
|
||||
filters: {
|
||||
is_public: 1,
|
||||
is_standard: 1,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
@ -22,7 +21,6 @@ frappe.ui.form.on('Dashboard', {
|
|||
return {
|
||||
filters: {
|
||||
is_public: 1,
|
||||
is_standard: 1,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -175,6 +175,9 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
|
||||
set_chart_field_options: function(frm) {
|
||||
let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null;
|
||||
if (frm.doc.dynamic_filters_json.length > 2) {
|
||||
filters = {...filters, ...JSON.parse(frm.doc.dynamic_filters_json)};
|
||||
}
|
||||
frappe.xcall(
|
||||
'frappe.desk.query_report.run',
|
||||
{
|
||||
|
|
@ -193,7 +196,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
|
||||
if (!frm.doc.is_custom) {
|
||||
if (data.result.length) {
|
||||
frm.field_options = frappe.report_utils.get_possible_chart_options(data.columns, data);
|
||||
frm.field_options = frappe.report_utils.get_field_options_from_report(data.columns, data);
|
||||
frm.set_df_property('x_field', 'options', frm.field_options.non_numeric_fields);
|
||||
if (!frm.field_options.numeric_fields.length) {
|
||||
frappe.msgprint(__(`Report has no numeric fields, please change the Report Name`));
|
||||
|
|
@ -435,49 +438,10 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
frm.trigger('set_dynamic_filters_in_table');
|
||||
|
||||
let filters = JSON.parse(frm.doc.filters_json || '[]');
|
||||
let fields = [
|
||||
{
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'description',
|
||||
options:
|
||||
`<div>
|
||||
<p>Set dynamic filter values in JavaScript for the required fields here.
|
||||
</p>
|
||||
<p>Ex:
|
||||
<code>frappe.defaults.get_user_default("Company")</code>
|
||||
</p>
|
||||
</div>`
|
||||
}
|
||||
];
|
||||
|
||||
if (is_document_type) {
|
||||
if (frm.dynamic_filters) {
|
||||
filters = [...filters, ...frm.dynamic_filters];
|
||||
}
|
||||
filters.forEach(f => {
|
||||
for (let field of fields) {
|
||||
if (field.fieldname == f[0] + ':' + f[1]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (f[2] == '=') {
|
||||
fields.push({
|
||||
label: `${f[1]} (${f[0]})`,
|
||||
fieldname: f[0] + ':' + f[1],
|
||||
fieldtype: 'Data',
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
filters = {...frm.dynamic_filters, ...filters};
|
||||
for (let key of Object.keys(filters)) {
|
||||
fields.push({
|
||||
label: key,
|
||||
fieldname: key,
|
||||
fieldtype: 'Data',
|
||||
});
|
||||
}
|
||||
}
|
||||
let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog(
|
||||
is_document_type, filters, frm.dynamic_filters
|
||||
);
|
||||
|
||||
frm.dynamic_filter_table.on('click', () => {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
|
|
|
|||
|
|
@ -9,8 +9,65 @@ frappe.ui.form.on('Number Card', {
|
|||
frm.set_df_property("filters_section", "hidden", 1);
|
||||
frm.set_df_property("dynamic_filters_section", "hidden", 1);
|
||||
frm.trigger('set_options');
|
||||
frm.trigger('render_filters_table');
|
||||
frm.trigger('render_dynamic_filters_table');
|
||||
|
||||
if (!frm.doc.type) {
|
||||
frm.set_value('type', 'Document Type');
|
||||
}
|
||||
|
||||
if (frm.doc.type == 'Report' && frm.doc.report_name) {
|
||||
frm.trigger('set_report_filters');
|
||||
}
|
||||
|
||||
if (frm.doc.type == 'Custom') {
|
||||
if (!frappe.boot.developer_mode) {
|
||||
frm.disable_form();
|
||||
}
|
||||
frm.filters = eval(frm.doc.filters_config);
|
||||
frm.trigger('set_filters_description');
|
||||
frm.trigger('set_method_description');
|
||||
frm.trigger('render_filters_table');
|
||||
}
|
||||
frm.trigger('create_add_to_dashboard_button');
|
||||
},
|
||||
|
||||
create_add_to_dashboard_button: function(frm) {
|
||||
frm.add_custom_button('Add Card to Dashboard', () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Add to Dashboard'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'dashboard',
|
||||
options: 'Dashboard',
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
values.name = frm.doc.name;
|
||||
frappe.xcall(
|
||||
'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard',
|
||||
{
|
||||
args: values
|
||||
}
|
||||
).then(()=> {
|
||||
let dashboard_route_html =
|
||||
`<a href = "#dashboard/${values.dashboard}">${values.dashboard}</a>`;
|
||||
let message =
|
||||
__(`Number Card ${values.name} add to Dashboard ` + dashboard_route_html);
|
||||
|
||||
frappe.msgprint(message);
|
||||
});
|
||||
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
|
||||
if (!frm.doc.name) {
|
||||
frappe.msgprint(__('Please create Card first'));
|
||||
} else {
|
||||
d.show();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
before_save: function(frm) {
|
||||
|
|
@ -21,16 +78,81 @@ frappe.ui.form.on('Number Card', {
|
|||
|
||||
frm.set_value('filters_json', JSON.stringify(static_filters));
|
||||
frm.trigger('render_filters_table');
|
||||
frm.trigger('render_dynamic_filters_table');
|
||||
},
|
||||
|
||||
is_standard: function(frm) {
|
||||
if (frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frm.trigger('render_dynamic_filters_table');
|
||||
} else {
|
||||
frm.set_df_property("dynamic_filters_section", "hidden", 1);
|
||||
frm.trigger('render_dynamic_filters_table');
|
||||
frm.set_df_property("dynamic_filters_section", "hidden", 1);
|
||||
},
|
||||
|
||||
set_filters_description: function(frm) {
|
||||
if (frm.doc.type == 'Custom') {
|
||||
frm.fields_dict.filters_config.set_description(`
|
||||
Set the filters here. For example:
|
||||
<pre class="small text-muted">
|
||||
<code>
|
||||
[{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname: "account",
|
||||
label: __("Account"),
|
||||
fieldtype: "Link",
|
||||
options: "Account",
|
||||
reqd: 1
|
||||
}]
|
||||
</code></pre>`);
|
||||
}
|
||||
},
|
||||
|
||||
set_method_description: function(frm) {
|
||||
if (frm.doc.type == 'Custom') {
|
||||
frm.fields_dict.method.set_description(`
|
||||
Set the path to a whitelisted function that will return the number on the card in the format:
|
||||
<pre class="small text-muted">
|
||||
<code>
|
||||
{
|
||||
"value": value,
|
||||
"fieldtype": "Currency"
|
||||
}
|
||||
</code></pre>`);
|
||||
}
|
||||
},
|
||||
|
||||
type: function(frm) {
|
||||
frm.trigger('set_filters_description');
|
||||
if (frm.doc.type == 'Report') {
|
||||
frm.set_query('report_name', () => {
|
||||
return {
|
||||
filters: {
|
||||
'report_type': ['!=', 'Report Builder']
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
report_name: function(frm) {
|
||||
frm.set_value('filters_json', '{}');
|
||||
frm.set_value('dynamic_filters_json', '{}');
|
||||
frm.set_df_property('report_field', 'options', []);
|
||||
frm.trigger('set_report_filters');
|
||||
},
|
||||
|
||||
filters_config: function(frm) {
|
||||
frm.filters = eval(frm.doc.filters_config);
|
||||
const filter_values = frappe.report_utils.get_filter_values(frm.filters);
|
||||
frm.set_value('filters_json', JSON.stringify(filter_values));
|
||||
frm.trigger('render_filters_table');
|
||||
},
|
||||
|
||||
document_type: function(frm) {
|
||||
frm.set_query('document_type', function() {
|
||||
return {
|
||||
|
|
@ -46,6 +168,10 @@ frappe.ui.form.on('Number Card', {
|
|||
},
|
||||
|
||||
set_options: function(frm) {
|
||||
if (frm.doc.type !== 'Document Type') {
|
||||
return;
|
||||
}
|
||||
|
||||
let aggregate_based_on_fields = [];
|
||||
const doctype = frm.doc.document_type;
|
||||
|
||||
|
|
@ -64,72 +190,60 @@ frappe.ui.form.on('Number Card', {
|
|||
|
||||
frm.set_df_property('aggregate_function_based_on', 'options', aggregate_based_on_fields);
|
||||
});
|
||||
frm.trigger('render_filters_table');
|
||||
frm.trigger('render_dynamic_filters_table');
|
||||
}
|
||||
},
|
||||
|
||||
set_report_filters: function(frm) {
|
||||
const report_name = frm.doc.report_name;
|
||||
if (report_name) {
|
||||
frappe.report_utils.get_report_filters(report_name).then(filters => {
|
||||
if (filters) {
|
||||
frm.filters = filters;
|
||||
const filter_values = frappe.report_utils.get_filter_values(filters);
|
||||
if (frm.doc.filters_json.length <= 2) {
|
||||
frm.set_value('filters_json', JSON.stringify(filter_values));
|
||||
}
|
||||
}
|
||||
frm.trigger('render_filters_table');
|
||||
frm.trigger('set_report_field_options');
|
||||
frm.trigger('render_dynamic_filters_table');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
set_report_field_options: function(frm) {
|
||||
let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null;
|
||||
if (frm.doc.dynamic_filters_json.length > 2) {
|
||||
filters = {...filters, ...JSON.parse(frm.doc.dynamic_filters_json)};
|
||||
}
|
||||
frappe.xcall(
|
||||
'frappe.desk.query_report.run',
|
||||
{
|
||||
report_name: frm.doc.report_name,
|
||||
filters: filters,
|
||||
ignore_prepared_report: 1
|
||||
}
|
||||
).then(data => {
|
||||
if (data.result.length) {
|
||||
frm.field_options = frappe.report_utils.get_field_options_from_report(data.columns, data);
|
||||
frm.set_df_property('report_field', 'options', frm.field_options.numeric_fields);
|
||||
if (!frm.field_options.numeric_fields.length) {
|
||||
frappe.msgprint(__(`Report has no numeric fields, please change the Report Name`));
|
||||
}
|
||||
} else {
|
||||
frappe.msgprint(__('Report has no data, please modify the filters or change the Report Name'));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render_filters_table: function(frm) {
|
||||
frm.set_df_property("filters_section", "hidden", 0);
|
||||
let is_document_type = frm.doc.type == 'Document Type';
|
||||
let is_dynamic_filter = f => ['Date', 'DateRange'].includes(f.fieldtype) && f.default;
|
||||
|
||||
let wrapper = $(frm.get_field('filters_json').wrapper).empty();
|
||||
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 33%">${__('Filter')}</th>
|
||||
<th style="width: 33%">${__('Condition')}</th>
|
||||
<th>${__('Value')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>`).appendTo(wrapper);
|
||||
|
||||
frm.filters = JSON.parse(frm.doc.filters_json || '[]');
|
||||
|
||||
set_filters_in_table(frm.filters, table);
|
||||
|
||||
table.on('click', () => {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Set Filters'),
|
||||
fields: [{
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'filter_area',
|
||||
}],
|
||||
primary_action: function() {
|
||||
let values = this.get_values();
|
||||
if (values) {
|
||||
this.hide();
|
||||
frm.filters = frm.filter_group.get_filters();
|
||||
frm.set_value('filters_json', JSON.stringify(frm.filters));
|
||||
set_filters_in_table(frm.filters, table);
|
||||
frm.trigger('render_dynamic_filters_table');
|
||||
}
|
||||
},
|
||||
primary_action_label: "Set"
|
||||
});
|
||||
|
||||
frappe.dashboards.filters_dialog = dialog;
|
||||
|
||||
frm.filter_group = new frappe.ui.FilterGroup({
|
||||
parent: dialog.get_field('filter_area').$wrapper,
|
||||
doctype: frm.doc.document_type,
|
||||
on_change: () => {},
|
||||
});
|
||||
|
||||
frm.filter_group.add_filters_to_filter_group(frm.filters);
|
||||
|
||||
dialog.show();
|
||||
dialog.set_values(frm.filters);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
render_dynamic_filters_table: function(frm) {
|
||||
if (!frappe.boot.developer_mode || !frm.doc.is_standard) {
|
||||
return;
|
||||
}
|
||||
let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty();
|
||||
|
||||
frm.set_df_property("dynamic_filters_section", "hidden", 0);
|
||||
|
||||
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -140,92 +254,211 @@ frappe.ui.form.on('Number Card', {
|
|||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>`).appendTo(wrapper);
|
||||
|
||||
frm.dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || '[]');
|
||||
|
||||
set_filters_in_table(frm.dynamic_filters, table);
|
||||
$(`<p class="text-muted small">${__("Click table to edit")}</p>`).appendTo(wrapper);
|
||||
|
||||
let filters = JSON.parse(frm.doc.filters_json || '[]');
|
||||
let fields = [
|
||||
{
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'description',
|
||||
options:
|
||||
`<div>
|
||||
<p>Set dynamic filter values in JavaScript for the required fields here.
|
||||
</p>
|
||||
<p>Ex:
|
||||
<code>frappe.defaults.get_user_default("Company")</code>
|
||||
</p>
|
||||
</div>`
|
||||
}
|
||||
];
|
||||
let filters_set = false;
|
||||
|
||||
if (frm.dynamic_filters.length) {
|
||||
filters = [...filters, ...frm.dynamic_filters];
|
||||
// Set dynamic filters for reports
|
||||
if (frm.doc.type == 'Report') {
|
||||
let set_filters = false;
|
||||
frm.filters.forEach(f => {
|
||||
if (is_dynamic_filter(f)) {
|
||||
filters[f.fieldname] = f.default;
|
||||
set_filters = true;
|
||||
}
|
||||
});
|
||||
set_filters && frm.set_value('filters_json', JSON.stringify(filters));
|
||||
}
|
||||
|
||||
filters.forEach(f => {
|
||||
for (let field of fields) {
|
||||
if (field.fieldname == f[0] + ':' + f[1]) {
|
||||
return;
|
||||
let fields;
|
||||
if (is_document_type) {
|
||||
fields = [
|
||||
{
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'filter_area',
|
||||
}
|
||||
}
|
||||
if (f[2] == '=') {
|
||||
fields.push({
|
||||
label: `${f[1]} (${f[0]})`,
|
||||
fieldname: f[0] + ':' + f[1],
|
||||
fieldtype: 'Data',
|
||||
];
|
||||
|
||||
if (filters.length) {
|
||||
filters.forEach(filter => {
|
||||
const filter_row =
|
||||
$(`<tr>
|
||||
<td>${filter[1]}</td>
|
||||
<td>${filter[2] || ""}</td>
|
||||
<td>${filter[3]}</td>
|
||||
</tr>`);
|
||||
|
||||
table.find('tbody').append(filter_row);
|
||||
});
|
||||
filters_set = true;
|
||||
}
|
||||
});
|
||||
} else if (frm.filters.length) {
|
||||
fields = frm.filters.filter(f => f.fieldname);
|
||||
fields.map(f => {
|
||||
if (filters[f.fieldname]) {
|
||||
let condition = '=';
|
||||
const filter_row =
|
||||
$(`<tr>
|
||||
<td>${f.label}</td>
|
||||
<td>${condition}</td>
|
||||
<td>${filters[f.fieldname] || ""}</td>
|
||||
</tr>`);
|
||||
table.find('tbody').append(filter_row);
|
||||
if (!filters_set) filters_set = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!filters_set) {
|
||||
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
|
||||
${__("Click to Set Filters")}</td></tr>`);
|
||||
table.find('tbody').append(filter_row);
|
||||
}
|
||||
|
||||
table.on('click', () => {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Set Filters'),
|
||||
fields: fields,
|
||||
primary_action: () => {
|
||||
let values = dialog.get_values();
|
||||
fields: fields.filter(f => !is_dynamic_filter(f)),
|
||||
primary_action: function() {
|
||||
let values = this.get_values();
|
||||
if (values) {
|
||||
dialog.hide();
|
||||
let dynamic_filters = [];
|
||||
for (let key of Object.keys(values)) {
|
||||
let [doctype, fieldname] = key.split(':');
|
||||
dynamic_filters.push([doctype, fieldname, '=', values[key]]);
|
||||
this.hide();
|
||||
if (is_document_type) {
|
||||
let filters = frm.filter_group.get_filters();
|
||||
frm.set_value('filters_json', JSON.stringify(filters));
|
||||
} else {
|
||||
frm.set_value('filters_json', JSON.stringify(values));
|
||||
}
|
||||
|
||||
frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters));
|
||||
frm.dynamic_filters = dynamic_filters;
|
||||
set_filters_in_table(frm.dynamic_filters, table);
|
||||
frm.trigger('render_filters_table');
|
||||
}
|
||||
},
|
||||
primary_action_label: "Set"
|
||||
});
|
||||
|
||||
if (is_document_type) {
|
||||
frm.filter_group = new frappe.ui.FilterGroup({
|
||||
parent: dialog.get_field('filter_area').$wrapper,
|
||||
doctype: frm.doc.document_type,
|
||||
on_change: () => {},
|
||||
});
|
||||
filters && frm.filter_group.add_filters_to_filter_group(filters);
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
|
||||
if (frm.doc.type == 'Report') {
|
||||
//Set query report object so that it can be used while fetching filter values in the report
|
||||
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
|
||||
frappe.query_reports[frm.doc.report_name]
|
||||
&& frappe.query_reports[frm.doc.report_name].onload
|
||||
&& frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
|
||||
}
|
||||
|
||||
dialog.set_values(filters);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
render_dynamic_filters_table(frm) {
|
||||
if (!frappe.boot.developer_mode || !frm.doc.is_standard || frm.doc.type == 'Custom') {
|
||||
return;
|
||||
}
|
||||
|
||||
function set_filters_in_table(filters, table) {
|
||||
if (!filters.length) {
|
||||
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
|
||||
${__("Click to Set Filters")}</td></tr>`);
|
||||
table.find('tbody').html(filter_row);
|
||||
} else {
|
||||
let filter_rows = '';
|
||||
filters.forEach(filter => {
|
||||
filter_rows +=
|
||||
`<tr>
|
||||
<td>${filter[1]}</td>
|
||||
<td>${filter[2] || ""}</td>
|
||||
<td>${filter[3]}</td>
|
||||
</tr>`;
|
||||
frm.set_df_property("dynamic_filters_section", "hidden", 0);
|
||||
|
||||
let is_document_type = frm.doc.type == 'Document Type';
|
||||
|
||||
let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty();
|
||||
|
||||
frm.dynamic_filter_table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">${__('Filter')}</th>
|
||||
<th style="width: 20%">${__('Condition')}</th>
|
||||
<th>${__('Value')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>`).appendTo(wrapper);
|
||||
|
||||
frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
|
||||
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||
: null;
|
||||
|
||||
frm.trigger('set_dynamic_filters_in_table');
|
||||
|
||||
let filters = JSON.parse(frm.doc.filters_json || '[]');
|
||||
|
||||
let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog(
|
||||
is_document_type, filters, frm.dynamic_filters
|
||||
);
|
||||
|
||||
frm.dynamic_filter_table.on('click', () => {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Set Dynamic Filters'),
|
||||
fields: fields,
|
||||
primary_action: () => {
|
||||
let values = dialog.get_values();
|
||||
dialog.hide();
|
||||
let dynamic_filters = [];
|
||||
for (let key of Object.keys(values)) {
|
||||
if (is_document_type) {
|
||||
let [doctype, fieldname] = key.split(':');
|
||||
dynamic_filters.push([doctype, fieldname, '=', values[key]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_document_type) {
|
||||
frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters));
|
||||
} else {
|
||||
frm.set_value('dynamic_filters_json', JSON.stringify(values));
|
||||
}
|
||||
frm.trigger('set_dynamic_filters_in_table');
|
||||
},
|
||||
primary_action_label: "Set"
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
dialog.set_values(frm.dynamic_filters);
|
||||
});
|
||||
table.find('tbody').html(filter_rows);
|
||||
},
|
||||
|
||||
set_dynamic_filters_in_table: function(frm) {
|
||||
frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
|
||||
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||
: null;
|
||||
|
||||
if (!frm.dynamic_filters) {
|
||||
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
|
||||
${__("Click to Set Dynamic Filters")}</td></tr>`);
|
||||
frm.dynamic_filter_table.find('tbody').html(filter_row);
|
||||
} else {
|
||||
let filter_rows = '';
|
||||
if ($.isArray(frm.dynamic_filters)) {
|
||||
frm.dynamic_filters.forEach(filter => {
|
||||
filter_rows +=
|
||||
`<tr>
|
||||
<td>${filter[1]}</td>
|
||||
<td>${filter[2] || ""}</td>
|
||||
<td>${filter[3]}</td>
|
||||
</tr>`;
|
||||
});
|
||||
} else {
|
||||
let condition = '=';
|
||||
for (let [key, val] of Object.entries(frm.dynamic_filters)) {
|
||||
filter_rows +=
|
||||
`<tr>
|
||||
<td>${key}</td>
|
||||
<td>${condition}</td>
|
||||
<td>${val || ""}</td>
|
||||
</tr>`
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
frm.dynamic_filter_table.find('tbody').html(filter_rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,11 +9,18 @@
|
|||
"is_standard",
|
||||
"module",
|
||||
"label",
|
||||
"type",
|
||||
"report_name",
|
||||
"method",
|
||||
"function",
|
||||
"aggregate_function_based_on",
|
||||
"column_break_2",
|
||||
"document_type",
|
||||
"report_field",
|
||||
"report_function",
|
||||
"is_public",
|
||||
"custom_configuration_section",
|
||||
"filters_config",
|
||||
"stats_section",
|
||||
"show_percentage_stats",
|
||||
"stats_time_interval",
|
||||
|
|
@ -26,20 +33,21 @@
|
|||
],
|
||||
"fields": [
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Document Type'",
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval: doc.type == 'Document Type'",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.document_type",
|
||||
"depends_on": "eval: doc.type == 'Document Type'",
|
||||
"fieldname": "function",
|
||||
"fieldtype": "Select",
|
||||
"label": "Function",
|
||||
"options": "Count\nSum\nAverage\nMinimum\nMaximum",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval: doc.type == 'Document Type'",
|
||||
"options": "Count\nSum\nAverage\nMinimum\nMaximum"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.function !== 'Count'",
|
||||
|
|
@ -98,6 +106,7 @@
|
|||
"options": "Daily\nWeekly\nMonthly\nYearly"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Document Type'",
|
||||
"fieldname": "stats_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Stats"
|
||||
|
|
@ -114,34 +123,74 @@
|
|||
"fieldtype": "Link",
|
||||
"label": "Module",
|
||||
"mandatory_depends_on": "eval: doc.is_standard",
|
||||
"options": "Module Def",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Module Def"
|
||||
},
|
||||
{
|
||||
"fieldname": "dynamic_filters_json",
|
||||
"fieldtype": "Code",
|
||||
"label": "Dynamic Filters JSON",
|
||||
"options": "JSON",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "dynamic_filters_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dynamic Filters Section",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Dynamic Filters Section"
|
||||
},
|
||||
{
|
||||
"fieldname": "type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Type",
|
||||
"options": "Document Type\nReport\nCustom"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Report'",
|
||||
"fieldname": "report_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Report Name",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Report'",
|
||||
"options": "Report"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Report'",
|
||||
"fieldname": "report_field",
|
||||
"fieldtype": "Select",
|
||||
"label": "Field",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Report'"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Custom'",
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
"label": "Method",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Custom'"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Custom'",
|
||||
"fieldname": "custom_configuration_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Custom Configuration"
|
||||
},
|
||||
{
|
||||
"fieldname": "filters_config",
|
||||
"fieldtype": "Code",
|
||||
"label": "Filters Configuration",
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type == 'Report'",
|
||||
"fieldname": "report_function",
|
||||
"fieldtype": "Select",
|
||||
"label": "Function",
|
||||
"mandatory_depends_on": "eval: doc.type == 'Report'",
|
||||
"options": "Sum\nAverage\nMinimum\nMaximum"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-10 17:55:35.873222",
|
||||
"modified": "2020-07-18 17:08:22.882538",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Number Card",
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ def has_permission(doc, ptype, user):
|
|||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_result(doc, to_date=None):
|
||||
def get_result(doc, filters, to_date=None):
|
||||
doc = frappe.parse_json(doc)
|
||||
fields = []
|
||||
sql_function_map = {
|
||||
|
|
@ -70,13 +70,13 @@ def get_result(doc, to_date=None):
|
|||
else:
|
||||
fields = ['{function}({based_on}) as result'.format(function=function, based_on=doc.aggregate_function_based_on)]
|
||||
|
||||
filters = frappe.parse_json(doc.filters_json)
|
||||
filters = frappe.parse_json(filters)
|
||||
|
||||
if not filters:
|
||||
filters = []
|
||||
|
||||
if to_date:
|
||||
filters.append([doc.document_type, 'creation', '<', to_date, False])
|
||||
filters.append([doc.document_type, 'creation', '<', to_date])
|
||||
|
||||
res = frappe.db.get_list(doc.document_type, fields=fields, filters=filters)
|
||||
number = res[0]['result'] if res else 0
|
||||
|
|
@ -84,7 +84,7 @@ def get_result(doc, to_date=None):
|
|||
return cint(number)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_percentage_difference(doc, result):
|
||||
def get_percentage_difference(doc, filters, result):
|
||||
doc = frappe.parse_json(doc)
|
||||
result = frappe.parse_json(result)
|
||||
|
||||
|
|
@ -93,13 +93,13 @@ def get_percentage_difference(doc, result):
|
|||
if not doc.get('show_percentage_stats'):
|
||||
return
|
||||
|
||||
previous_result = calculate_previous_result(doc)
|
||||
previous_result = calculate_previous_result(doc, filters)
|
||||
difference = (result - previous_result)/100.0
|
||||
|
||||
return difference
|
||||
|
||||
|
||||
def calculate_previous_result(doc):
|
||||
def calculate_previous_result(doc, filters):
|
||||
from frappe.utils import add_to_date
|
||||
|
||||
current_date = frappe.utils.now()
|
||||
|
|
@ -112,7 +112,7 @@ def calculate_previous_result(doc):
|
|||
else:
|
||||
previous_date = add_to_date(current_date, years=-1)
|
||||
|
||||
number = get_result(doc, previous_date)
|
||||
number = get_result(doc, filters, previous_date)
|
||||
return number
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -155,3 +155,22 @@ def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters):
|
|||
search_conditions=search_conditions,
|
||||
conditions=conditions
|
||||
), values)
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_report_number_card(args):
|
||||
card = create_number_card(args)
|
||||
args = frappe.parse_json(args)
|
||||
args.name = card.name
|
||||
if args.dashboard:
|
||||
add_card_to_dashboard(frappe.as_json(args))
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_card_to_dashboard(args):
|
||||
args = frappe.parse_json(args)
|
||||
|
||||
dashboard = frappe.get_doc('Dashboard', args.dashboard)
|
||||
dashboard_link = frappe.new_doc('Number Card Link')
|
||||
dashboard_link.card = args.name
|
||||
|
||||
dashboard.append('cards', dashboard_link)
|
||||
dashboard.save()
|
||||
|
|
@ -403,9 +403,16 @@ class Document(BaseDocument):
|
|||
|
||||
def set_new_name(self, force=False, set_name=None, set_child_names=True):
|
||||
"""Calls `frappe.naming.set_new_name` for parent and child docs."""
|
||||
|
||||
if self.flags.name_set and not force:
|
||||
return
|
||||
|
||||
# If autoname has set as Prompt (name)
|
||||
if self.get("__newname"):
|
||||
self.name = self.get("__newname")
|
||||
self.flags.name_set = True
|
||||
return
|
||||
|
||||
if set_name:
|
||||
self.name = set_name
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ execute:frappe.delete_doc_if_exists('DocType', 'GSuite Templates')
|
|||
execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account')
|
||||
execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings')
|
||||
frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats
|
||||
frappe.patches.v12_0.remove_example_email_thread_notify
|
||||
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
|
||||
frappe.patches.v12_0.set_correct_url_in_files
|
||||
frappe.patches.v13_0.website_theme_custom_scss
|
||||
|
|
@ -293,4 +294,4 @@ execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link")
|
|||
frappe.patches.v13_0.update_date_filters_in_user_settings
|
||||
frappe.patches.v13_0.update_duration_options
|
||||
frappe.patches.v13_0.replace_old_data_import # 2020-06-24
|
||||
frappe.patches.v13_0.create_custom_dashboards_cards_and_charts
|
||||
frappe.patches.v13_0.create_custom_dashboards_cards_and_charts
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
# remove all example.com email user accounts from notifications
|
||||
frappe.db.sql("""UPDATE `tabUser`
|
||||
SET thread_notify=0, send_me_a_copy=0
|
||||
WHERE email like '%@example.com'""")
|
||||
|
|
@ -240,13 +240,8 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
var me = this;
|
||||
var data = this.dialog.get_values(true);
|
||||
$.each(data, function(key, value) {
|
||||
if(key==='__newname') {
|
||||
me.dialog.doc.name = value;
|
||||
}
|
||||
else {
|
||||
if(!is_null(value)) {
|
||||
me.dialog.doc[key] = value;
|
||||
}
|
||||
if (!is_null(value)) {
|
||||
me.dialog.doc[key] = value;
|
||||
}
|
||||
});
|
||||
return this.dialog.doc;
|
||||
|
|
@ -282,7 +277,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
field.doctype = me.doc.doctype;
|
||||
field.docname = me.doc.name;
|
||||
|
||||
if(!is_null(me.doc[fieldname])) {
|
||||
if (!is_null(me.doc[fieldname])) {
|
||||
field.set_input(me.doc[fieldname]);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -119,6 +119,86 @@ frappe.dashboard_utils = {
|
|||
}
|
||||
|
||||
return static_filters;
|
||||
},
|
||||
|
||||
get_fields_for_dynamic_filter_dialog(is_document_type, filters, dynamic_filters) {
|
||||
let fields = [
|
||||
{
|
||||
fieldtype: 'HTML',
|
||||
fieldname: 'description',
|
||||
options:
|
||||
`<div>
|
||||
<p>Set dynamic filter values in JavaScript for the required fields here.
|
||||
</p>
|
||||
<p>Ex:
|
||||
<code>frappe.defaults.get_user_default("Company")</code>
|
||||
</p>
|
||||
</div>`
|
||||
}
|
||||
];
|
||||
|
||||
if (is_document_type) {
|
||||
if (dynamic_filters) {
|
||||
filters = [...filters, ...dynamic_filters];
|
||||
}
|
||||
filters.forEach(f => {
|
||||
for (let field of fields) {
|
||||
if (field.fieldname == f[0] + ':' + f[1]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (f[2] == '=') {
|
||||
fields.push({
|
||||
label: `${f[1]} (${f[0]})`,
|
||||
fieldname: f[0] + ':' + f[1],
|
||||
fieldtype: 'Data',
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
filters = {...dynamic_filters, ...filters};
|
||||
for (let key of Object.keys(filters)) {
|
||||
fields.push({
|
||||
label: key,
|
||||
fieldname: key,
|
||||
fieldtype: 'Data',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
|
||||
get_all_filters(doc) {
|
||||
let filters = JSON.parse(doc.filters_json || "null");
|
||||
let dynamic_filters = JSON.parse(doc.dynamic_filters_json || "null");
|
||||
|
||||
if (!dynamic_filters) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
if ($.isArray(dynamic_filters)) {
|
||||
dynamic_filters.forEach(f => {
|
||||
try {
|
||||
f[3] = eval(f[3]);
|
||||
} catch (e) {
|
||||
frappe.throw(__(`Invalid expression set in filter ${f[1]} (${f[0]})`));
|
||||
}
|
||||
});
|
||||
filters = [...filters, ...dynamic_filters];
|
||||
} else {
|
||||
for (let key of Object.keys(dynamic_filters)) {
|
||||
try {
|
||||
const val = eval(dynamic_filters[key]);
|
||||
dynamic_filters[key] = val;
|
||||
} catch (e) {
|
||||
frappe.throw(__(`Invalid expression set in filter ${key}`));
|
||||
}
|
||||
}
|
||||
Object.assign(filters, dynamic_filters);
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -128,10 +128,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
() => this.setup_progress_bar(),
|
||||
() => this.setup_page_head(),
|
||||
() => this.refresh_report(),
|
||||
() => this.add_chart_buttons_to_toolbar(true)
|
||||
() => this.add_chart_buttons_to_toolbar(true),
|
||||
() => this.add_card_button_to_toolbar(true),
|
||||
]);
|
||||
}
|
||||
|
||||
add_card_button_to_toolbar() {
|
||||
this.page.add_inner_button(__("Create Card"), () => {
|
||||
this.add_card_to_dashboard();
|
||||
});
|
||||
}
|
||||
|
||||
add_chart_buttons_to_toolbar(show) {
|
||||
if (show) {
|
||||
this.page.add_inner_button(__("Set Chart"), () => {
|
||||
|
|
@ -148,6 +155,62 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
}
|
||||
|
||||
add_card_to_dashboard() {
|
||||
let field_options = frappe.report_utils.get_field_options_from_report(this.columns, this.raw_data);
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __('Create Card'),
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'report_field',
|
||||
label: __('Field'),
|
||||
fieldtype: 'Select',
|
||||
options: field_options.numeric_fields,
|
||||
},
|
||||
{
|
||||
fieldname: 'cb_1',
|
||||
fieldtype: 'Column Break'
|
||||
},
|
||||
{
|
||||
fieldname: 'report_function',
|
||||
label: __('Function'),
|
||||
options: ['Sum', 'Average', 'Minimum', 'Maximum'],
|
||||
fieldtype: 'Select'
|
||||
},
|
||||
{
|
||||
fieldname: 'sb_1',
|
||||
label: __('Add to Dashboard'),
|
||||
fieldtype: 'Section Break'
|
||||
},
|
||||
{
|
||||
fieldname: 'dashboard',
|
||||
label: __('Choose Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Dashboard',
|
||||
},
|
||||
{
|
||||
fieldname: 'cb_2',
|
||||
fieldtype: 'Column Break'
|
||||
},
|
||||
{
|
||||
fieldname: 'label',
|
||||
label: __('Card Label'),
|
||||
fieldtype: 'Data',
|
||||
}
|
||||
],
|
||||
primary_action_label: __('Add'),
|
||||
primary_action: (values) => {
|
||||
if (!values.label) {
|
||||
values.label = `${values.report_function} of ${toTitle(values.report_field)}`;
|
||||
}
|
||||
this.create_number_card(values, values.dashboard, values.label);
|
||||
dialog.hide();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
|
||||
}
|
||||
|
||||
add_chart_to_dashboard() {
|
||||
if (this.chart_fields || this.chart_options) {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
|
|
@ -182,6 +245,24 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
}
|
||||
|
||||
create_number_card(values, dashboard_name, card_name) {
|
||||
let args = {
|
||||
'dashboard': dashboard_name || null,
|
||||
'type': 'Report',
|
||||
'report_name': this.report_name,
|
||||
'filters_json': JSON.stringify(this.get_filter_values()),
|
||||
};
|
||||
Object.assign(args, values);
|
||||
|
||||
this.add_to_dashboard(
|
||||
'frappe.desk.doctype.number_card.number_card.create_report_number_card',
|
||||
args,
|
||||
dashboard_name,
|
||||
card_name,
|
||||
'Number Card'
|
||||
);
|
||||
}
|
||||
|
||||
create_dashboard_chart(chart_args, dashboard_name, chart_name) {
|
||||
let args = {
|
||||
'dashboard': dashboard_name || null,
|
||||
|
|
@ -224,19 +305,29 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
);
|
||||
}
|
||||
|
||||
frappe.xcall(
|
||||
this.add_to_dashboard(
|
||||
'frappe.desk.doctype.dashboard_chart.dashboard_chart.create_report_chart',
|
||||
args,
|
||||
dashboard_name,
|
||||
chart_name,
|
||||
'Dashboard Chart'
|
||||
);
|
||||
}
|
||||
|
||||
add_to_dashboard(method, args, dashboard_name, name, doctype) {
|
||||
frappe.xcall(
|
||||
method,
|
||||
{args: args}
|
||||
).then( () => {
|
||||
).then(() => {
|
||||
let message;
|
||||
if (dashboard_name) {
|
||||
let dashboard_route_html = `<a href = "#dashboard/${dashboard_name}">${dashboard_name}</a>`;
|
||||
message = __(`New Dashboard Chart ${chart_name} added to Dashboard ` + dashboard_route_html);
|
||||
message = __(`New {0} {1} added to Dashboard ` + dashboard_route_html, [doctype, name]);
|
||||
} else {
|
||||
message = __(`New chart ${chart_name} created`);
|
||||
message = __(`New {0} {1} created`, [doctype, name]);
|
||||
}
|
||||
|
||||
frappe.msgprint(message, __('New Chart Created'));
|
||||
frappe.msgprint(message, __(`New {0} Created`, [doctype]));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -518,6 +609,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
this.render_datatable();
|
||||
this.add_chart_buttons_to_toolbar(true);
|
||||
this.add_card_button_to_toolbar();
|
||||
} else {
|
||||
this.data = [];
|
||||
this.toggle_nothing_to_show(true);
|
||||
|
|
@ -700,7 +792,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
open_create_chart_dialog() {
|
||||
const me = this;
|
||||
let field_options = frappe.report_utils.get_possible_chart_options(this.columns, this.raw_data);
|
||||
let field_options = frappe.report_utils.get_field_options_from_report(this.columns, this.raw_data);
|
||||
|
||||
function set_chart_values(values) {
|
||||
values.y_fields = [];
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ frappe.report_utils = {
|
|||
}
|
||||
},
|
||||
|
||||
get_possible_chart_options: function(columns, data) {
|
||||
get_field_options_from_report: function(columns, data) {
|
||||
const rows = data.result.filter(value => Object.keys(value).length);
|
||||
const first_row = Array.isArray(rows[0]) ? rows[0] : columns.map(col => rows[0][col.fieldname]);
|
||||
|
||||
|
|
@ -138,4 +138,14 @@ frappe.report_utils = {
|
|||
return filter_values;
|
||||
},
|
||||
|
||||
get_result_of_fn(fn, values) {
|
||||
const get_result = {
|
||||
'Minimum': values => values.reduce((min, val) => Math.min(min, val), values[0]),
|
||||
'Maximum': values => values.reduce((min, val) => Math.max(min, val), values[0]),
|
||||
'Average': values => values.reduce((a, b) => a + b, 0) / values.length,
|
||||
'Sum': values => values.reduce((a, b) => a + b, 0)
|
||||
};
|
||||
return get_result[fn](values);
|
||||
},
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -639,7 +639,7 @@ export default class ChartWidget extends Widget {
|
|||
|
||||
set_chart_filters() {
|
||||
let user_saved_filters = this.chart_settings.filters || null;
|
||||
let chart_saved_filters = this.get_all_chart_filters();
|
||||
let chart_saved_filters = frappe.dashboard_utils.get_all_filters(this.chart_doc);
|
||||
|
||||
if (this.chart_doc.chart_type == 'Report') {
|
||||
return frappe.dashboard_utils
|
||||
|
|
@ -655,38 +655,6 @@ export default class ChartWidget extends Widget {
|
|||
}
|
||||
}
|
||||
|
||||
get_all_chart_filters() {
|
||||
let filters = JSON.parse(this.chart_doc.filters_json || "null");
|
||||
let dynamic_filters = JSON.parse(this.chart_doc.dynamic_filters_json || "null");
|
||||
|
||||
if (!dynamic_filters) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
if ($.isArray(dynamic_filters)) {
|
||||
dynamic_filters.forEach(f => {
|
||||
try {
|
||||
f[3] = eval(f[3]);
|
||||
} catch (e) {
|
||||
frappe.throw(__(`Invalid expression set in filter ${f[1]} (${f[0]})`));
|
||||
}
|
||||
});
|
||||
filters = [...filters, ...dynamic_filters];
|
||||
} else {
|
||||
for (let key of Object.keys(dynamic_filters)) {
|
||||
try {
|
||||
const val = eval(dynamic_filters[key]);
|
||||
dynamic_filters[key] = val;
|
||||
} catch (e) {
|
||||
frappe.throw(__(`Invalid expression set in filter ${key}`));
|
||||
}
|
||||
}
|
||||
Object.assign(filters, dynamic_filters);
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
update_default_date_filters(report_filters, chart_filters) {
|
||||
report_filters.map(f => {
|
||||
if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Widget from "./base_widget.js";
|
||||
import { go_to_list_with_filters, shorten_number } from "./utils";
|
||||
import { generate_route, shorten_number } from "./utils";
|
||||
|
||||
export default class NumberCardWidget extends Widget {
|
||||
constructor(opts) {
|
||||
|
|
@ -59,20 +59,39 @@ export default class NumberCardWidget extends Widget {
|
|||
}
|
||||
).then(doc => {
|
||||
this.name = doc.name;
|
||||
this.card_doc.stats_time_interval = doc.stats_time_interval;
|
||||
this.card_doc.name = this.name;
|
||||
this.card_doc = doc;
|
||||
this.widget.attr('data-widget-name', this.name);
|
||||
});
|
||||
}
|
||||
|
||||
set_events() {
|
||||
$(this.body).click(() => {
|
||||
if (this.in_customize_mode) return;
|
||||
let filters = JSON.parse(this.card_doc.filters_json);
|
||||
go_to_list_with_filters(this.card_doc.document_type, filters);
|
||||
if (this.in_customize_mode || this.card_doc.type == 'Custom') return;
|
||||
this.set_route();
|
||||
});
|
||||
}
|
||||
|
||||
set_route() {
|
||||
const is_document_type = this.card_doc.type !== 'Report';
|
||||
const name = is_document_type ? this.card_doc.document_type : this.card_doc.report_name;
|
||||
const route = generate_route({
|
||||
name: name,
|
||||
type: is_document_type ? 'doctype' : 'report',
|
||||
is_query_report: !is_document_type,
|
||||
});
|
||||
|
||||
if (is_document_type) {
|
||||
const filters = JSON.parse(this.card_doc.filters_json);
|
||||
frappe.route_options = filters.reduce((acc, filter) => {
|
||||
return Object.assign(acc, {
|
||||
[`${filter[0]}.${filter[1]}`]: [filter[2], filter[3]]
|
||||
});
|
||||
}, {});
|
||||
}
|
||||
|
||||
frappe.set_route(route);
|
||||
}
|
||||
|
||||
set_doc_args() {
|
||||
this.card_doc = Object.assign({}, {
|
||||
document_type: this.document_type,
|
||||
|
|
@ -84,11 +103,53 @@ export default class NumberCardWidget extends Widget {
|
|||
});
|
||||
}
|
||||
|
||||
get_settings(type) {
|
||||
this.filters = this.get_filters();
|
||||
const settings_map = {
|
||||
'Custom': {
|
||||
method: this.card_doc.method,
|
||||
args: {
|
||||
filters: this.filters
|
||||
},
|
||||
get_number: res => this.get_number_for_custom_card(res),
|
||||
},
|
||||
'Report': {
|
||||
method: 'frappe.desk.query_report.run',
|
||||
args: {
|
||||
report_name: this.card_doc.report_name,
|
||||
filters: this.filters,
|
||||
ignore_prepared_report: 1
|
||||
},
|
||||
get_number: res => this.get_number_for_report_card(res),
|
||||
},
|
||||
'Document Type': {
|
||||
method: 'frappe.desk.doctype.number_card.number_card.get_result',
|
||||
args: {
|
||||
doc: this.card_doc,
|
||||
filters: this.filters,
|
||||
},
|
||||
get_number: res => this.get_number_for_doctype_card(res),
|
||||
}
|
||||
};
|
||||
return settings_map[type];
|
||||
}
|
||||
|
||||
get_filters() {
|
||||
const filters = frappe.dashboard_utils.get_all_filters(this.card_doc);
|
||||
return filters;
|
||||
}
|
||||
|
||||
render_card() {
|
||||
this.prepare_actions();
|
||||
this.set_title();
|
||||
this.set_loading_state();
|
||||
|
||||
if (!this.card_doc.type) {
|
||||
this.card_doc.type = 'Document Type';
|
||||
}
|
||||
|
||||
this.settings = this.get_settings(this.card_doc.type);
|
||||
|
||||
frappe.run_serially([
|
||||
() => this.render_number(),
|
||||
() => this.render_stats(),
|
||||
|
|
@ -102,43 +163,69 @@ export default class NumberCardWidget extends Widget {
|
|||
}
|
||||
|
||||
get_number() {
|
||||
return frappe.xcall('frappe.desk.doctype.number_card.number_card.get_result', {
|
||||
doc: this.card_doc
|
||||
}).then(res => {
|
||||
this.number = res;
|
||||
if (this.card_doc.function !== 'Count') {
|
||||
return frappe.model.with_doctype(this.card_doc.document_type, () => {
|
||||
this.get_formatted_number();
|
||||
});
|
||||
} else {
|
||||
this.number_html = res;
|
||||
}
|
||||
return frappe.xcall(this.settings.method, this.settings.args).then(res => {
|
||||
this.settings.get_number(res);
|
||||
});
|
||||
}
|
||||
|
||||
get_formatted_number() {
|
||||
const based_on_df =
|
||||
frappe.meta.get_docfield(this.card_doc.document_type, this.card_doc.aggregate_function_based_on);
|
||||
get_number_for_custom_card(res) {
|
||||
if (typeof res === 'object') {
|
||||
this.number = res.value;
|
||||
this.get_formatted_number(res);
|
||||
} else {
|
||||
this.formatted_number = res;
|
||||
}
|
||||
}
|
||||
|
||||
get_number_for_doctype_card(res) {
|
||||
this.number = res;
|
||||
if (this.card_doc.function !== 'Count') {
|
||||
return frappe.model.with_doctype(this.card_doc.document_type, () => {
|
||||
const based_on_df =
|
||||
frappe.meta.get_docfield(this.card_doc.document_type, this.card_doc.aggregate_function_based_on);
|
||||
this.get_formatted_number(based_on_df);
|
||||
});
|
||||
} else {
|
||||
this.formatted_number = res;
|
||||
}
|
||||
}
|
||||
|
||||
get_number_for_report_card(res) {
|
||||
const field = this.card_doc.report_field;
|
||||
const vals = res.result.reduce((acc, col) => {
|
||||
col[field] && acc.push(col[field]);
|
||||
return acc;
|
||||
}, []);
|
||||
const col = res.columns.find(col => col.fieldname == field);
|
||||
this.number = frappe.report_utils.get_result_of_fn(this.card_doc.report_function, vals);
|
||||
this.get_formatted_number(col);
|
||||
}
|
||||
|
||||
get_formatted_number(df) {
|
||||
const default_country = frappe.sys_defaults.country;
|
||||
const shortened_number = shorten_number(this.number, default_country);
|
||||
let number_parts = shortened_number.split(' ');
|
||||
|
||||
const symbol = number_parts[1] || '';
|
||||
const formatted_number = $(frappe.format(number_parts[0], based_on_df)).text();
|
||||
const formatted_number = $(frappe.format(number_parts[0], df)).text();
|
||||
|
||||
this.number_html = formatted_number + ' ' + symbol;
|
||||
this.formatted_number = formatted_number + ' ' + symbol;
|
||||
}
|
||||
|
||||
render_number() {
|
||||
return this.get_number().then(() => {
|
||||
$(this.body).html(`<div class="widget-content">
|
||||
<div class="number" style="color:${this.card_doc.color}">${this.number_html}</div>
|
||||
<div class="number" style="color:${this.card_doc.color}">${this.formatted_number}</div>
|
||||
</div>`);
|
||||
});
|
||||
}
|
||||
|
||||
render_stats() {
|
||||
let caret_html ='';
|
||||
if (this.card_doc.type !== 'Document Type') {
|
||||
return;
|
||||
}
|
||||
|
||||
let caret_html = '';
|
||||
let color_class = '';
|
||||
|
||||
return this.get_percentage_stats().then(() => {
|
||||
|
|
@ -177,6 +264,7 @@ export default class NumberCardWidget extends Widget {
|
|||
get_percentage_stats() {
|
||||
return frappe.xcall('frappe.desk.doctype.number_card.number_card.get_percentage_difference', {
|
||||
doc: this.card_doc,
|
||||
filters: this.filters,
|
||||
result: this.number
|
||||
}).then(res => {
|
||||
if (res !== undefined) {
|
||||
|
|
|
|||
|
|
@ -117,16 +117,6 @@ const build_summary_item = (summary) => {
|
|||
</div>`);
|
||||
};
|
||||
|
||||
function go_to_list_with_filters(doctype, filters) {
|
||||
const route = `List/${doctype}/List`;
|
||||
frappe.set_route(route).then(()=> {
|
||||
let list_view = frappe.views.list_view[route];
|
||||
let filter_area = list_view.filter_area;
|
||||
filter_area.clear();
|
||||
filter_area.filter_list.add_filters_to_filter_group(filters);
|
||||
});
|
||||
}
|
||||
|
||||
function shorten_number(number, country) {
|
||||
country = (country == 'India') ? country : '';
|
||||
const number_system = get_number_system(country);
|
||||
|
|
@ -167,4 +157,4 @@ function get_number_system(country) {
|
|||
return number_system_map[country];
|
||||
}
|
||||
|
||||
export { generate_route, generate_grid, build_summary_item, go_to_list_with_filters, shorten_number };
|
||||
export { generate_route, generate_grid, build_summary_item, shorten_number };
|
||||
|
|
@ -50,11 +50,13 @@ def install_basic_docs():
|
|||
install_docs = [
|
||||
{'doctype':'User', 'name':'Administrator', 'first_name':'Administrator',
|
||||
'email':'admin@example.com', 'enabled':1, "is_admin": 1,
|
||||
'roles': [{'role': 'Administrator'}]
|
||||
'roles': [{'role': 'Administrator'}],
|
||||
'thread_notify': 0, 'send_me_a_copy': 0
|
||||
},
|
||||
{'doctype':'User', 'name':'Guest', 'first_name':'Guest',
|
||||
'email':'guest@example.com', 'enabled':1, "is_guest": 1,
|
||||
'roles': [{'role': 'Guest'}]
|
||||
'roles': [{'role': 'Guest'}],
|
||||
'thread_notify': 0, 'send_me_a_copy': 0
|
||||
},
|
||||
{'doctype': "Role", "role_name": "Report Manager"},
|
||||
{'doctype': "Role", "role_name": "Translator"},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import frappe
|
|||
|
||||
from six import iteritems
|
||||
from past.builtins import cmp
|
||||
from frappe.utils import markdown
|
||||
from frappe.utils import md_to_html
|
||||
|
||||
|
||||
def delete_page_cache(path):
|
||||
|
|
@ -357,7 +357,7 @@ def get_html_content_based_on_type(doc, fieldname, content_type):
|
|||
content = doc.get(fieldname)
|
||||
|
||||
if content_type == 'Markdown':
|
||||
content = markdown(doc.get(fieldname + '_md'))
|
||||
content = md_to_html(doc.get(fieldname + '_md'))
|
||||
elif content_type == 'HTML':
|
||||
content = doc.get(fieldname + '_html')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,389 +1,123 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:workflow_name",
|
||||
"beta": 0,
|
||||
"creation": "2012-12-28 10:49:55",
|
||||
"custom": 0,
|
||||
"description": "Defines workflow states and rules for a document.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"workflow_name",
|
||||
"document_type",
|
||||
"is_active",
|
||||
"override_status",
|
||||
"send_email_alert",
|
||||
"states_head",
|
||||
"states",
|
||||
"transition_rules",
|
||||
"transitions",
|
||||
"workflow_state_field"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "workflow_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Workflow Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "DocType on which this Workflow is applicable.",
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"description": "If checked, all other workflows become inactive.",
|
||||
"fieldname": "is_active",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Is Active",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Is Active"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"description": "If Checked workflow status will not override status in list view",
|
||||
"fieldname": "override_status",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Don't Override Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Don't Override Status"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"description": "Emails will be sent with next possible workflow actions",
|
||||
"fieldname": "send_email_alert",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Send Email Alert",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Send Email Alert"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.",
|
||||
"fieldname": "states_head",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "States",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "States"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "All possible Workflow States and roles of the workflow. Docstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"",
|
||||
"fieldname": "states",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document States",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Workflow Document State",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.",
|
||||
"fieldname": "transition_rules",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Transition Rules",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Transition Rules"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Rules defining transition of state in the workflow.",
|
||||
"fieldname": "transitions",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Transitions",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Workflow Transition",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "workflow_state",
|
||||
"description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)",
|
||||
"fieldname": "workflow_state_field",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Workflow State Field",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-random",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2018-06-11 10:45:46.418470",
|
||||
"links": [],
|
||||
"modified": "2020-07-16 04:29:20.898040",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Workflow",
|
||||
"name": "Workflow",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ from frappe import _
|
|||
|
||||
from frappe.modules import get_doc_path
|
||||
from frappe.core.doctype.access_log.access_log import make_access_log
|
||||
from frappe.utils import cint, strip_html
|
||||
from frappe.utils import cint, sanitize_html, strip_html
|
||||
from six import string_types
|
||||
|
||||
no_cache = 1
|
||||
|
|
@ -20,9 +20,9 @@ def get_context(context):
|
|||
"""Build context for print"""
|
||||
if not ((frappe.form_dict.doctype and frappe.form_dict.name) or frappe.form_dict.doc):
|
||||
return {
|
||||
"body": """<h1>Error</h1>
|
||||
"body": sanitize_html("""<h1>Error</h1>
|
||||
<p>Parameters doctype and name required</p>
|
||||
<pre>%s</pre>""" % repr(frappe.form_dict)
|
||||
<pre>%s</pre>""" % repr(frappe.form_dict))
|
||||
}
|
||||
|
||||
if frappe.form_dict.doc:
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ dropbox==9.1.0
|
|||
email-reply-parser==0.5.9
|
||||
Faker==2.0.4
|
||||
future==0.18.2
|
||||
GitPython==2.1.15
|
||||
gitdb2==2.0.6;python_version<'3.4'
|
||||
GitPython==2.1.15
|
||||
google-api-python-client==1.9.3
|
||||
google-auth-httplib2==0.0.3
|
||||
google-auth-oauthlib==0.4.1
|
||||
|
|
@ -40,6 +40,7 @@ psycopg2-binary==2.8.4
|
|||
pyasn1==0.4.8
|
||||
PyJWT==1.7.1
|
||||
PyMySQL==0.9.3
|
||||
pyngrok==4.1.6
|
||||
pyOpenSSL==19.1.0
|
||||
pyotp==2.3.0
|
||||
PyPDF2==1.26.0
|
||||
|
|
@ -64,6 +65,6 @@ unittest-xml-reporting==2.5.2
|
|||
urllib3==1.25.8
|
||||
watchdog==0.8.0
|
||||
Werkzeug==0.16.1
|
||||
Whoosh==2.7.4
|
||||
xlrd==1.2.0
|
||||
zxcvbn-python==4.4.24
|
||||
Whoosh==2.7.4
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue