Merge branch 'develop' into check_tab

This commit is contained in:
Suraj Shetty 2020-07-19 16:57:37 +05:30 committed by GitHub
commit 4d25520a62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 849 additions and 586 deletions

View file

@ -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] = {

View file

@ -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
]

View file

@ -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,
}
};
});

View file

@ -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({

View file

@ -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);
}
}
}
});

View file

@ -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",

View file

@ -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()

View file

@ -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:

View file

@ -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

View file

@ -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'""")

View file

@ -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]);
}
});

View file

@ -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;
}
};

View file

@ -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 = [];

View file

@ -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);
},
};

View file

@ -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) {

View file

@ -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) {

View file

@ -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 };

View file

@ -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"},

View file

@ -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')

View file

@ -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
}

View file

@ -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:

View file

@ -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