Merge branch 'develop' of https://github.com/frappe/frappe into google_calender
This commit is contained in:
commit
47d738b4b3
181 changed files with 5074 additions and 7270 deletions
|
|
@ -6,7 +6,7 @@ context('List View Settings', () => {
|
|||
it('Default settings', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.get('.list-count').should('contain', "20 of");
|
||||
cy.get('.sidebar-stat').should('contain', "No Tags");
|
||||
cy.get('.sidebar-stat').should('contain', "Tags");
|
||||
});
|
||||
it('disable count and sidebar stats then verify', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
|
|
|
|||
104
frappe/automation/doctype/auto_repeat/auto_repeat.js
Normal file
104
frappe/automation/doctype/auto_repeat/auto_repeat.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) 2018, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.provide("frappe.auto_repeat");
|
||||
|
||||
frappe.ui.form.on('Auto Repeat', {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['reference_doctype'].get_query = function() {
|
||||
return {
|
||||
query: "frappe.automation.doctype.auto_repeat.auto_repeat.get_auto_repeat_doctypes"
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['reference_document'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"auto_repeat": ''
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['print_format'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"doc_type": frm.doc.reference_doctype
|
||||
}
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
// auto repeat message
|
||||
if (frm.is_new()) {
|
||||
let customize_form_link = `<a href="#Form/Customize Form">${__('Customize Form')}</a>`;
|
||||
frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link]));
|
||||
}
|
||||
|
||||
// view document button
|
||||
if (!frm.is_dirty()) {
|
||||
let label = __('View {0}', [__(frm.doc.reference_doctype)]);
|
||||
frm.add_custom_button(label, () =>
|
||||
frappe.set_route("List", frm.doc.reference_doctype, { auto_repeat: frm.doc.name })
|
||||
);
|
||||
}
|
||||
|
||||
// auto repeat schedule
|
||||
frappe.auto_repeat.render_schedule(frm);
|
||||
},
|
||||
|
||||
template: function(frm) {
|
||||
if (frm.doc.template) {
|
||||
frappe.model.with_doc("Email Template", frm.doc.template, () => {
|
||||
let email_template = frappe.get_doc("Email Template", frm.doc.template);
|
||||
frm.set_value("subject", email_template.subject);
|
||||
frm.set_value("message", email_template.response);
|
||||
frm.refresh_field("subject");
|
||||
frm.refresh_field("message");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get_contacts: function(frm) {
|
||||
frm.call('fetch_linked_contacts');
|
||||
},
|
||||
|
||||
preview_message: function(frm) {
|
||||
if (frm.doc.message) {
|
||||
frappe.call({
|
||||
method: "frappe.automation.doctype.auto_repeat.auto_repeat.generate_message_preview",
|
||||
args: {
|
||||
reference_dt: frm.doc.reference_doctype,
|
||||
reference_doc: frm.doc.reference_document,
|
||||
subject: frm.doc.subject,
|
||||
message: frm.doc.message
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frappe.msgprint(r.message.message, r.message.subject)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(__("Please setup a message first"), __("Message not setup"))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.auto_repeat.render_schedule = function(frm) {
|
||||
if (!frm.is_dirty() && frm.doc.status !== 'Disabled') {
|
||||
frappe.call({
|
||||
method: "get_auto_repeat_schedule",
|
||||
doc: frm.doc
|
||||
}).done((r) => {
|
||||
frm.dashboard.wrapper.empty();
|
||||
frm.dashboard.add_section(
|
||||
frappe.render_template("auto_repeat_schedule", {
|
||||
schedule_details : r.message || []
|
||||
})
|
||||
);
|
||||
frm.dashboard.show();
|
||||
});
|
||||
} else {
|
||||
frm.dashboard.hide();
|
||||
}
|
||||
};
|
||||
239
frappe/automation/doctype/auto_repeat/auto_repeat.json
Normal file
239
frappe/automation/doctype/auto_repeat/auto_repeat.json
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
{
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "format:AUT-AR-{#####}",
|
||||
"creation": "2018-03-09 11:22:31.192349",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_1",
|
||||
"disabled",
|
||||
"section_break_3",
|
||||
"reference_doctype",
|
||||
"reference_document",
|
||||
"column_break_5",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"section_break_10",
|
||||
"frequency",
|
||||
"repeat_on_day",
|
||||
"repeat_on_last_day",
|
||||
"column_break_12",
|
||||
"next_schedule_date",
|
||||
"notification",
|
||||
"notify_by_email",
|
||||
"recipients",
|
||||
"get_contacts",
|
||||
"template",
|
||||
"subject",
|
||||
"message",
|
||||
"preview_message",
|
||||
"print_format",
|
||||
"status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_document",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Reference Document",
|
||||
"no_copy": 1,
|
||||
"options": "reference_doctype",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "End Date"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "frequency",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Frequency",
|
||||
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: in_list([\"Monthly\", \"Quarterly\", \"Half-yearly\", \"Yearly\"], doc.frequency) && !doc.repeat_on_last_day\n",
|
||||
"fieldname": "repeat_on_day",
|
||||
"fieldtype": "Int",
|
||||
"label": "Repeat on Day"
|
||||
},
|
||||
{
|
||||
"fieldname": "next_schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Next Schedule Date",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "notification",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Notification"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "notify_by_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Notify by Email"
|
||||
},
|
||||
{
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "recipients",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Recipients"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email && doc.reference_doctype && doc.reference_document",
|
||||
"fieldname": "get_contacts",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Contacts"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"fieldname": "template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>New {{ doc.doctype }} #{{ doc.name }}</code></pre></div>",
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"label": "Subject"
|
||||
},
|
||||
{
|
||||
"default": "Please find attached {{ doc.doctype }} #{{ doc.name }}",
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Text",
|
||||
"label": "Message"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email && doc.reference_doctype && doc.reference_document",
|
||||
"fieldname": "preview_message",
|
||||
"fieldtype": "Button",
|
||||
"label": "Preview Message"
|
||||
},
|
||||
{
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "print_format",
|
||||
"fieldtype": "Link",
|
||||
"label": "Print Format",
|
||||
"options": "Print Format"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "\nActive\nDisabled\nCompleted",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.frequency === 'Monthly'",
|
||||
"fieldname": "repeat_on_last_day",
|
||||
"fieldtype": "Check",
|
||||
"label": "Repeat on Last Day of the Month"
|
||||
}
|
||||
],
|
||||
"modified": "2019-07-17 11:30:51.412317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Auto Repeat",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "reference_document",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "reference_document",
|
||||
"track_changes": 1
|
||||
}
|
||||
374
frappe/automation/doctype/auto_repeat/auto_repeat.py
Normal file
374
frappe/automation/doctype/auto_repeat/auto_repeat.py
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
|
||||
|
||||
class AutoRepeat(Document):
|
||||
def validate(self):
|
||||
self.update_status()
|
||||
self.validate_reference_doctype()
|
||||
self.validate_dates()
|
||||
self.validate_email_id()
|
||||
self.set_dates()
|
||||
self.update_auto_repeat_id()
|
||||
self.unlink_if_applicable()
|
||||
|
||||
validate_template(self.subject or "")
|
||||
validate_template(self.message or "")
|
||||
|
||||
def before_insert(self):
|
||||
if not frappe.flags.in_test:
|
||||
start_date = self.start_date
|
||||
today_date = today()
|
||||
if start_date <= today_date:
|
||||
start_date = today_date
|
||||
|
||||
def after_save(self):
|
||||
frappe.get_doc(self.reference_doctype, self.reference_document).notify_update()
|
||||
|
||||
def on_trash(self):
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, {
|
||||
'auto_repeat': self.name
|
||||
}, 'auto_repeat', '')
|
||||
|
||||
def set_dates(self):
|
||||
if self.disabled:
|
||||
self.next_schedule_date = None
|
||||
else:
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, self.end_date)
|
||||
|
||||
def unlink_if_applicable(self):
|
||||
if self.status == 'Completed' or self.disabled:
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, 'auto_repeat', '')
|
||||
|
||||
def validate_reference_doctype(self):
|
||||
if not frappe.flags.in_test:
|
||||
if not frappe.get_meta(self.reference_doctype).allow_auto_repeat:
|
||||
frappe.throw(_("Enable Allow Auto Repeat for the doctype {0} in Customize Form").format(self.reference_doctype))
|
||||
|
||||
def validate_dates(self):
|
||||
self.validate_from_to_dates('start_date', 'end_date')
|
||||
|
||||
if self.end_date == self.start_date:
|
||||
frappe.throw(_('{0} should not be same as {1}').format(frappe.bold('End Date'), frappe.bold('Start Date')))
|
||||
|
||||
def validate_email_id(self):
|
||||
if self.notify_by_email:
|
||||
if self.recipients:
|
||||
email_list = split_emails(self.recipients.replace("\n", ""))
|
||||
from frappe.utils import validate_email_address
|
||||
|
||||
for email in email_list:
|
||||
if not validate_email_address(email):
|
||||
frappe.throw(_("{0} is an invalid email address in 'Recipients'").format(email))
|
||||
else:
|
||||
frappe.throw(_("'Recipients' not specified"))
|
||||
|
||||
def update_auto_repeat_id(self):
|
||||
#check if document is already on auto repeat
|
||||
auto_repeat = frappe.db.get_value(self.reference_doctype, self.reference_document, "auto_repeat")
|
||||
if auto_repeat and auto_repeat != self.name:
|
||||
frappe.throw(_("The {0} is already on auto repeat {1}").format(self.reference_document, auto_repeat))
|
||||
else:
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, "auto_repeat", self.name)
|
||||
|
||||
def update_status(self):
|
||||
if self.disabled:
|
||||
self.status = "Disabled"
|
||||
elif self.is_completed():
|
||||
self.status = "Completed"
|
||||
else:
|
||||
self.status = "Active"
|
||||
|
||||
def is_completed(self):
|
||||
return self.end_date and getdate(self.end_date) < getdate(today())
|
||||
|
||||
def get_auto_repeat_schedule(self):
|
||||
schedule_details = []
|
||||
start_date = getdate(self.start_date)
|
||||
end_date = getdate(self.end_date)
|
||||
today = frappe.utils.datetime.date.today()
|
||||
|
||||
if start_date < today:
|
||||
start_date = today
|
||||
|
||||
if not self.end_date:
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
row = {
|
||||
"reference_document": self.reference_document,
|
||||
"frequency": self.frequency,
|
||||
"next_scheduled_date": start_date
|
||||
}
|
||||
schedule_details.append(row)
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
|
||||
if self.end_date:
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
while (getdate(start_date) < getdate(end_date)):
|
||||
row = {
|
||||
"reference_document" : self.reference_document,
|
||||
"frequency" : self.frequency,
|
||||
"next_scheduled_date" : start_date
|
||||
}
|
||||
schedule_details.append(row)
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, end_date)
|
||||
|
||||
|
||||
return schedule_details
|
||||
|
||||
def create_documents(self):
|
||||
try:
|
||||
new_doc = self.make_new_document()
|
||||
if self.notify_by_email and self.recipients:
|
||||
self.send_notification(new_doc)
|
||||
except Exception:
|
||||
error_log = frappe.log_error(frappe.get_traceback(), _("Auto Repeat Document Creation Failure"))
|
||||
|
||||
self.disable_auto_repeat()
|
||||
|
||||
if self.reference_document and not frappe.flags.in_test:
|
||||
self.notify_error_to_user(error_log)
|
||||
|
||||
def make_new_document(self):
|
||||
reference_doc = frappe.get_doc(self.reference_doctype, self.reference_document)
|
||||
new_doc = frappe.copy_doc(reference_doc, ignore_no_copy = False)
|
||||
self.update_doc(new_doc, reference_doc)
|
||||
new_doc.insert(ignore_permissions = True)
|
||||
|
||||
return new_doc
|
||||
|
||||
def update_doc(self, new_doc, reference_doc):
|
||||
new_doc.docstatus = 0
|
||||
if new_doc.meta.get_field('set_posting_time'):
|
||||
new_doc.set('set_posting_time', 1)
|
||||
|
||||
if new_doc.meta.get_field('auto_repeat'):
|
||||
new_doc.set('auto_repeat', self.name)
|
||||
|
||||
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time', 'select_print_heading', 'remarks', 'owner']:
|
||||
if new_doc.meta.get_field(fieldname):
|
||||
new_doc.set(fieldname, reference_doc.get(fieldname))
|
||||
|
||||
for data in new_doc.meta.fields:
|
||||
if data.fieldtype == 'Date' and data.reqd:
|
||||
new_doc.set(data.fieldname, self.next_schedule_date)
|
||||
|
||||
self.set_auto_repeat_period(new_doc)
|
||||
|
||||
auto_repeat_doc = frappe.get_doc('Auto Repeat', self.name)
|
||||
|
||||
#for any action that needs to take place after the recurring document creation
|
||||
#on recurring method of that doctype is triggered
|
||||
new_doc.run_method('on_recurring', reference_doc = reference_doc, auto_repeat_doc = auto_repeat_doc)
|
||||
|
||||
def set_auto_repeat_period(self, new_doc):
|
||||
mcount = month_map.get(self.frequency)
|
||||
if mcount and new_doc.meta.get_field('from_date') and new_doc.meta.get_field('to_date'):
|
||||
last_ref_doc = frappe.db.get_all(doctype = self.reference_doctype,
|
||||
fields = ['name', 'from_date', 'to_date'],
|
||||
filters = [
|
||||
['auto_repeat', '=', self.name],
|
||||
['docstatus', '<', 2],
|
||||
],
|
||||
order_by = 'creation desc',
|
||||
limit = 1)
|
||||
|
||||
if not last_ref_doc:
|
||||
return
|
||||
|
||||
from_date = get_next_date(last_ref_doc[0].from_date, mcount)
|
||||
|
||||
if (cstr(get_first_day(last_ref_doc[0].from_date)) == cstr(last_ref_doc[0].from_date)) and \
|
||||
(cstr(get_last_day(last_ref_doc[0].to_date)) == cstr(last_ref_doc[0].to_date)):
|
||||
to_date = get_last_day(get_next_date(last_ref_doc[0].to_date, mcount))
|
||||
else:
|
||||
to_date = get_next_date(last_ref_doc[0].to_date, mcount)
|
||||
|
||||
new_doc.set('from_date', from_date)
|
||||
new_doc.set('to_date', to_date)
|
||||
|
||||
def send_notification(self, new_doc):
|
||||
"""Notify concerned people about recurring document generation"""
|
||||
subject = self.subject or ''
|
||||
message = self.message or ''
|
||||
|
||||
if not self.subject:
|
||||
subject = _("New {0}: {1}").format(new_doc.doctype, new_doc.name)
|
||||
elif "{" in self.subject:
|
||||
subject = frappe.render_template(self.subject, {'doc': new_doc})
|
||||
|
||||
if not self.message:
|
||||
message = _("Please find attached {0}: {1}").format(new_doc.doctype, new_doc.name)
|
||||
elif "{" in self.message:
|
||||
message = frappe.render_template(self.message, {'doc': new_doc})
|
||||
|
||||
print_format = self.print_format or 'Standard'
|
||||
|
||||
attachments = [frappe.attach_print(new_doc.doctype, new_doc.name,
|
||||
file_name=new_doc.name, print_format=print_format)]
|
||||
|
||||
make(doctype=new_doc.doctype, name=new_doc.name, recipients=self.recipients,
|
||||
subject=subject, content=message, attachments=attachments, send_email=1)
|
||||
|
||||
def fetch_linked_contacts(self):
|
||||
if self.reference_doctype and self.reference_document:
|
||||
res = frappe.db.get_all('Contact',
|
||||
fields=['email_id'],
|
||||
filters=[
|
||||
['Dynamic Link', 'link_doctype', '=', self.reference_doctype],
|
||||
['Dynamic Link', 'link_name', '=', self.reference_document]
|
||||
])
|
||||
|
||||
email_ids = list(set([d.email_id for d in res]))
|
||||
if not email_ids:
|
||||
frappe.msgprint(_('No contacts linked to document'), alert=True)
|
||||
else:
|
||||
self.recipients = ', '.join(email_ids)
|
||||
|
||||
def disable_auto_repeat(self):
|
||||
frappe.db.set_value('Auto Repeat', self.name, 'disabled', 1)
|
||||
|
||||
def notify_error_to_user(self, error_log):
|
||||
recipients = get_system_managers(only_name=True) + self.owner
|
||||
subject = _("Auto Repeat Document Creation Failed")
|
||||
|
||||
form_link = frappe.utils.get_link_to_form(self.reference_doctype, self.reference_document)
|
||||
auto_repeat_failed_for = _('Auto Repeat failed for {0}').format(form_link)
|
||||
|
||||
error_log_link =frappe.utils.get_link_to_form(error_log.reference_doctype, error_log.reference_document)
|
||||
error_log_message = _('Check the Error Log for more information: {0}').format(error_log_link)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=recipients,
|
||||
subject=subject,
|
||||
template="auto_repeat_fail",
|
||||
args={
|
||||
'auto_repeat_failed_for': auto_repeat_failed_for,
|
||||
'error_log_message': error_log_message
|
||||
},
|
||||
header=[subject, 'red']
|
||||
)
|
||||
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day, repeat_on_last_day = False, end_date = None):
|
||||
month_count = month_map.get(frequency)
|
||||
if month_count and repeat_on_last_day:
|
||||
next_date = get_next_date(start_date, month_count, 31)
|
||||
elif month_count and repeat_on_day:
|
||||
next_date = get_next_date(start_date, month_count, repeat_on_day)
|
||||
elif month_count:
|
||||
next_date = get_next_date(start_date, month_count)
|
||||
else:
|
||||
days = 7 if frequency == 'Weekly' else 1
|
||||
next_date = add_days(start_date, days)
|
||||
|
||||
return next_date
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
return dt
|
||||
|
||||
#called through hooks
|
||||
def make_auto_repeat_entry():
|
||||
enqueued_method = 'frappe.automation.doctype.auto_repeat.auto_repeat.create_repeated_entries'
|
||||
jobs = get_jobs()
|
||||
|
||||
if not jobs or enqueued_method not in jobs[frappe.local.site]:
|
||||
date = getdate(today())
|
||||
data = get_auto_repeat_entries(date)
|
||||
frappe.enqueue(enqueued_method, data=data)
|
||||
|
||||
def create_repeated_entries(data):
|
||||
for d in data:
|
||||
doc = frappe.get_doc('Auto Repeat', d.name)
|
||||
|
||||
current_date = getdate(today())
|
||||
schedule_date = getdate(doc.next_schedule_date)
|
||||
|
||||
while schedule_date <= current_date and not doc.disabled:
|
||||
doc.create_documents()
|
||||
schedule_date = get_next_schedule_date(schedule_date, doc.frequency, doc.repeat_on_day, doc.repeat_on_last_day, doc.end_date)
|
||||
|
||||
if schedule_date and not doc.disabled:
|
||||
frappe.db.set_value('Auto Repeat', doc.name, 'next_schedule_date', schedule_date)
|
||||
|
||||
def get_auto_repeat_entries(date=None):
|
||||
if not date:
|
||||
date = getdate(today())
|
||||
return frappe.db.get_all('Auto Repeat', filters=[
|
||||
['next_schedule_date', '<=', date],
|
||||
['status', '=', 'Active']
|
||||
])
|
||||
|
||||
#called through hooks
|
||||
def set_auto_repeat_as_completed():
|
||||
auto_repeat = frappe.get_all("Auto Repeat", filters = {'status': ['!=', 'Disabled']})
|
||||
for entry in auto_repeat:
|
||||
doc = frappe.get_doc("Auto Repeat", entry.name)
|
||||
if doc.is_completed():
|
||||
doc.status = 'Completed'
|
||||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_auto_repeat(doctype, docname, frequency, start_date, end_date = None):
|
||||
doc = frappe.new_doc('Auto Repeat')
|
||||
doc.reference_doctype = doctype
|
||||
doc.reference_document = docname
|
||||
doc.frequency = frequency
|
||||
doc.start_date = start_date
|
||||
if end_date:
|
||||
doc.end_date = end_date
|
||||
doc.save()
|
||||
return doc
|
||||
|
||||
#method for reference_doctype filter
|
||||
def get_auto_repeat_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
res = frappe.db.get_all('Property Setter', {
|
||||
'property': 'allow_auto_repeat',
|
||||
'value': '1',
|
||||
}, ['doc_type'])
|
||||
docs = [r.doc_type for r in res]
|
||||
|
||||
res = frappe.db.get_all('DocType', {
|
||||
'allow_auto_repeat': 1,
|
||||
}, ['name'])
|
||||
docs += [r.name for r in res]
|
||||
docs = set(list(docs))
|
||||
|
||||
return [[d] for d in docs]
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_reference(docname, reference):
|
||||
result = ""
|
||||
try:
|
||||
frappe.db.set_value("Auto Repeat", docname, "reference_document", reference)
|
||||
result = "success"
|
||||
except Exception as e:
|
||||
result = "error"
|
||||
raise e
|
||||
return result
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_message_preview(reference_dt, reference_doc, message=None, subject=None):
|
||||
doc = frappe.get_doc(reference_dt, reference_doc)
|
||||
subject_preview = _("Please add a subject to your email")
|
||||
msg_preview = frappe.render_template(message, {'doc': doc})
|
||||
if subject:
|
||||
subject_preview = frappe.render_template(subject, {'doc': doc})
|
||||
|
||||
return {'message': msg_preview, 'subject': subject_preview}
|
||||
11
frappe/automation/doctype/auto_repeat/auto_repeat_list.js
Normal file
11
frappe/automation/doctype/auto_repeat/auto_repeat_list.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
frappe.listview_settings['Auto Repeat'] = {
|
||||
add_fields: ["next_schedule_date"],
|
||||
get_indicator: function(doc) {
|
||||
var colors = {
|
||||
"Active": "green",
|
||||
"Disabled": "red",
|
||||
"Completed": "blue",
|
||||
};
|
||||
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
|
||||
}
|
||||
};
|
||||
|
|
@ -11,10 +11,9 @@
|
|||
{% for(var i=0; i < schedule_details.length; i++) { %}
|
||||
<tr>
|
||||
<td>{{ schedule_details[i].reference_document }}</td>
|
||||
<td> {{ schedule_details[i].frequency }} </td>
|
||||
<td> {{ schedule_details[i].next_scheduled_date }} </td>
|
||||
<td> {{ __(schedule_details[i].frequency) }} </td>
|
||||
<td> {{ frappe.datetime.str_to_user(schedule_details[i].next_scheduled_date) }} </td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
@ -7,20 +7,19 @@ import unittest
|
|||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.desk.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries, disable_auto_repeat
|
||||
from frappe.automation.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries
|
||||
from frappe.utils import today, add_days, getdate, add_months
|
||||
|
||||
|
||||
def add_custom_fields():
|
||||
df = dict(
|
||||
fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', insert_after='sender',
|
||||
options='Auto Repeat')
|
||||
options='Auto Repeat', hidden=1, print_hide=1, read_only=1)
|
||||
create_custom_field('ToDo', df)
|
||||
|
||||
|
||||
class TestAutoRepeat(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not frappe.db.sql("SELECT `name` FROM `tabCustom Field` WHERE `name`='auto_repeat'"):
|
||||
if not frappe.db.sql("SELECT `fieldname` FROM `tabCustom Field` WHERE `fieldname`='auto_repeat' and `dt`=%s", "Todo"):
|
||||
add_custom_fields()
|
||||
|
||||
def test_daily_auto_repeat(self):
|
||||
|
|
@ -29,8 +28,8 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
|
||||
doc = make_auto_repeat(reference_document=todo.name)
|
||||
self.assertEqual(doc.next_schedule_date, today())
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
frappe.db.commit()
|
||||
|
||||
todo = frappe.get_doc(doc.reference_doctype, doc.reference_document)
|
||||
|
|
@ -51,8 +50,11 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
dict(doctype='ToDo', description='test recurring todo', assigned_by='Administrator')).insert()
|
||||
|
||||
self.monthly_auto_repeat('ToDo', todo.name, start_date, end_date)
|
||||
#test without end_date
|
||||
todo = frappe.get_doc(dict(doctype='ToDo', description='test recurring todo without end_date', assigned_by='Administrator')).insert()
|
||||
self.monthly_auto_repeat('ToDo', todo.name, start_date)
|
||||
|
||||
def monthly_auto_repeat(self, doctype, docname, start_date, end_date):
|
||||
def monthly_auto_repeat(self, doctype, docname, start_date, end_date = None):
|
||||
def get_months(start, end):
|
||||
diff = (12 * end.year + end.month) - (12 * start.year + start.month)
|
||||
return diff + 1
|
||||
|
|
@ -61,10 +63,10 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
reference_doctype=doctype, frequency='Monthly', reference_document=docname, start_date=start_date,
|
||||
end_date=end_date)
|
||||
|
||||
disable_auto_repeat(doc)
|
||||
doc.disable_auto_repeat()
|
||||
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
docnames = frappe.get_all(doc.reference_doctype, {'auto_repeat': doc.name})
|
||||
self.assertEqual(len(docnames), 1)
|
||||
|
||||
|
|
@ -72,8 +74,8 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
doc.db_set('disabled', 0)
|
||||
|
||||
months = get_months(getdate(start_date), getdate(today()))
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
|
||||
docnames = frappe.get_all(doc.reference_doctype, {'auto_repeat': doc.name})
|
||||
self.assertEqual(len(docnames), months)
|
||||
|
|
@ -84,8 +86,8 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
|
||||
doc = make_auto_repeat(reference_document=todo.name, notify=1, recipients="test@domain.com", subject="New ToDo",
|
||||
message="A new ToDo has just been created for you")
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
frappe.db.commit()
|
||||
|
||||
new_todo = frappe.db.get_value('ToDo',
|
||||
|
|
@ -100,18 +102,14 @@ def make_auto_repeat(**args):
|
|||
doc = frappe.get_doc({
|
||||
'doctype': 'Auto Repeat',
|
||||
'reference_doctype': args.reference_doctype or 'ToDo',
|
||||
'reference_document': args.reference_document or frappe.db.get_value('ToDo', {'docstatus': 1}, 'name'),
|
||||
'reference_document': args.reference_document or frappe.db.get_value('ToDo', 'name'),
|
||||
'frequency': args.frequency or 'Daily',
|
||||
'start_date': args.start_date or add_days(today(), -1),
|
||||
'end_date': args.end_date or add_days(today(), 2),
|
||||
'submit_on_creation': args.submit_on_creation or 0,
|
||||
'end_date': args.end_date or "",
|
||||
'notify_by_email': args.notify or 0,
|
||||
'recipients': args.recipients or "",
|
||||
'subject': args.subject or "",
|
||||
'message': args.message or ""
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
if not args.do_not_submit:
|
||||
doc.submit()
|
||||
|
||||
return doc
|
||||
|
|
@ -92,11 +92,6 @@ def get_data():
|
|||
"name": "Google Settings",
|
||||
"description": _("Google API Settings."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Google Maps Settings",
|
||||
"description": _("Google Maps integration"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "GCalendar Settings",
|
||||
|
|
|
|||
|
|
@ -169,11 +169,27 @@ def get_data():
|
|||
"name": "Workflow Action",
|
||||
"description": _("Actions for workflow (e.g. Approve, Cancel).")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Automation"),
|
||||
"icon": "fa fa-random",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Assignment Rule",
|
||||
"description": _("Set up rules for user assignments.")
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Milestone",
|
||||
"description": _("Tracks milestones on the lifecycle of a document if it undergoes multiple stages.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Auto Repeat",
|
||||
"description": _("Automatically generates recurring documents.")
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -161,3 +161,16 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
'link_name': link_name,
|
||||
'link_doctype': link_doctype
|
||||
})
|
||||
|
||||
|
||||
def get_contact_with_phone_number(number):
|
||||
if not number: return
|
||||
|
||||
contacts = frappe.get_all('Contact', or_filters={
|
||||
'phone': ['like', '%{}'.format(number)],
|
||||
'mobile_no': ['like', '%{}'.format(number)]
|
||||
}, limit=1)
|
||||
|
||||
contact = contacts[0].name if contacts else None
|
||||
|
||||
return contact
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ def update_mins_to_first_communication(parent, communication):
|
|||
if frappe.db.get_all('User', filters={'email': communication.sender,
|
||||
'user_type': 'System User', 'enabled': 1}, limit=1):
|
||||
first_responded_on = communication.creation
|
||||
if parent.meta.has_field('first_responded_on'):
|
||||
if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent":
|
||||
parent.db_set('first_responded_on', first_responded_on)
|
||||
parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,15 @@ frappe.ui.form.on('DocType', {
|
|||
}
|
||||
|
||||
if (!frm.is_new() && !frm.doc.istable) {
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
|
||||
frappe.set_route('List', frm.doc.name, 'List');
|
||||
});
|
||||
if (frm.doc.issingle) {
|
||||
frm.add_custom_button(__('Go to {0}', [frm.doc.name]), () => {
|
||||
frappe.set_route('Form', frm.doc.name);
|
||||
});
|
||||
} else {
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
|
||||
frappe.set_route('List', frm.doc.name, 'List');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!frappe.boot.developer_mode && !frm.doc.custom) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
"allow_rename",
|
||||
"allow_import",
|
||||
"allow_events_in_timeline",
|
||||
"allow_auto_repeat",
|
||||
"view_settings",
|
||||
"title_field",
|
||||
"search_fields",
|
||||
|
|
@ -81,6 +82,7 @@
|
|||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.",
|
||||
"fieldname": "is_submittable",
|
||||
|
|
@ -88,6 +90,7 @@
|
|||
"label": "Is Submittable"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Child Tables are shown as a Grid in other DocTypes",
|
||||
"fieldname": "istable",
|
||||
"fieldtype": "Check",
|
||||
|
|
@ -97,6 +100,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "Single Types have only one record no tables associated. Values are stored in tabSingles",
|
||||
"fieldname": "issingle",
|
||||
|
|
@ -135,6 +139,7 @@
|
|||
"label": "Track Changes"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "If enabled, the document is marked as seen, the first time a user opens it",
|
||||
"fieldname": "track_seen",
|
||||
|
|
@ -150,11 +155,13 @@
|
|||
"label": "Track Views"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "custom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Custom?"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "beta",
|
||||
"fieldtype": "Check",
|
||||
"label": "Beta"
|
||||
|
|
@ -236,6 +243,7 @@
|
|||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide_toolbar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Sidebar and Menu",
|
||||
|
|
@ -243,6 +251,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Copy",
|
||||
|
|
@ -250,6 +259,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_rename",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Rename",
|
||||
|
|
@ -257,15 +267,23 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_import",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Import (via Data Import Tool)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_events_in_timeline",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow events in timeline"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_auto_repeat",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Auto Repeat"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "view_settings",
|
||||
|
|
@ -329,6 +347,13 @@
|
|||
"label": "Color"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_preview_popup",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Preview Popup"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_name_in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "Make \"name\" searchable in Global Search"
|
||||
|
|
@ -354,6 +379,7 @@
|
|||
"options": "Domain"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "User Cannot Search",
|
||||
|
|
@ -361,6 +387,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_create",
|
||||
"fieldtype": "Check",
|
||||
"label": "User Cannot Create",
|
||||
|
|
@ -411,17 +438,11 @@
|
|||
"fieldtype": "Select",
|
||||
"label": "Database Engine",
|
||||
"options": "InnoDB\nMyISAM"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_preview_popup",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Preview Popup"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bolt",
|
||||
"idx": 6,
|
||||
"modified": "2019-05-16 14:58:33.405381",
|
||||
"modified": "2019-07-04 23:23:17.174960",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
@ -454,4 +475,4 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from frappe.utils import now, cint
|
|||
from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.desk.notifications import delete_notification_count_for
|
||||
from frappe.modules import make_boilerplate, get_doc_path
|
||||
from frappe.database.schema import validate_column_name, validate_column_length
|
||||
|
|
@ -47,7 +48,8 @@ class DocType(Document):
|
|||
- Validate series
|
||||
- Check fieldnames (duplication etc)
|
||||
- Clear permission table for child tables
|
||||
- Add `amended_from` and `amended_by` if Amendable"""
|
||||
- Add `amended_from` and `amended_by` if Amendable
|
||||
- Add custom field `auto_repeat` if Repeatable"""
|
||||
|
||||
self.check_developer_mode()
|
||||
|
||||
|
|
@ -76,6 +78,7 @@ class DocType(Document):
|
|||
validate_permissions(self)
|
||||
|
||||
self.make_amendable()
|
||||
self.make_repeatable()
|
||||
self.validate_website()
|
||||
|
||||
if not self.is_new():
|
||||
|
|
@ -526,6 +529,14 @@ class DocType(Document):
|
|||
"no_copy": 1
|
||||
})
|
||||
|
||||
def make_repeatable(self):
|
||||
"""If allow_auto_repeat is set, add auto_repeat custom field."""
|
||||
if self.allow_auto_repeat:
|
||||
if not frappe.db.exists('Custom Field', {'fieldname': 'auto_repeat', 'dt': self.name}):
|
||||
insert_after = self.fields[len(self.fields) - 1].fieldname
|
||||
df = dict(fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', options='Auto Repeat', insert_after=insert_after, read_only=1, no_copy=1, print_hide=1)
|
||||
create_custom_field(self.name, df)
|
||||
|
||||
def get_max_idx(self):
|
||||
"""Returns the highest `idx`"""
|
||||
max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""",
|
||||
|
|
|
|||
|
|
@ -1,173 +1,72 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:language_code",
|
||||
"beta": 0,
|
||||
"creation": "2014-08-22 16:12:17.249590",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "language_code",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Language Code",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "language_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Language 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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "flag",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Flag",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "based_on",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Language",
|
||||
"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,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-globe",
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:40:33.210645",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Language",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "language_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "language_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:language_code",
|
||||
"creation": "2014-08-22 16:12:17.249590",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"language_code",
|
||||
"language_name",
|
||||
"flag",
|
||||
"based_on"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "language_code",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Language Code",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "language_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Language Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "flag",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Flag"
|
||||
},
|
||||
{
|
||||
"fieldname": "based_on",
|
||||
"fieldtype": "Link",
|
||||
"label": "Based On",
|
||||
"options": "Language"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-globe",
|
||||
"modified": "2019-07-19 16:32:12.652550",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Language",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Guest",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "language_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "language_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ from frappe.modules.export_file import export_to_files
|
|||
from frappe.modules import make_boilerplate
|
||||
from frappe.core.doctype.page.page import delete_custom_role
|
||||
from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles
|
||||
from frappe.desk.reportview import append_totals_row
|
||||
from six import iteritems
|
||||
|
||||
|
||||
|
|
@ -76,11 +77,6 @@ class Report(Document):
|
|||
if not self.json:
|
||||
self.json = '{}'
|
||||
|
||||
if self.json:
|
||||
data = json.loads(self.json)
|
||||
data["add_total_row"] = self.add_total_row
|
||||
self.json = json.dumps(data)
|
||||
|
||||
def export_doc(self):
|
||||
if frappe.flags.in_import:
|
||||
return
|
||||
|
|
@ -178,6 +174,9 @@ class Report(Document):
|
|||
|
||||
out = out + [list(d) for d in result]
|
||||
|
||||
if params.get('add_totals_row'):
|
||||
out = append_totals_row(out)
|
||||
|
||||
if as_dict:
|
||||
data = []
|
||||
for row in out:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,7 @@
|
|||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"doc_type",
|
||||
"properties",
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
"quick_entry",
|
||||
"track_changes",
|
||||
"track_views",
|
||||
"allow_auto_repeat",
|
||||
"image_view",
|
||||
"column_break_5",
|
||||
"title_field",
|
||||
|
|
@ -59,17 +61,20 @@
|
|||
"label": "Max Attachments"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Copy"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "istable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Table",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "istable",
|
||||
"fieldname": "editable_grid",
|
||||
"fieldtype": "Check",
|
||||
|
|
@ -82,11 +87,13 @@
|
|||
"label": "Quick Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "track_changes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Track Changes"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.image_field",
|
||||
"fieldname": "image_view",
|
||||
"fieldtype": "Check",
|
||||
|
|
@ -150,16 +157,23 @@
|
|||
"options": "Customize Form Field"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "track_views",
|
||||
"fieldtype": "Check",
|
||||
"label": "Track Views"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_auto_repeat",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Auto Repeat"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"modified": "2019-05-13 18:54:40.610862",
|
||||
"modified": "2019-07-01 22:50:50.372465",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form",
|
||||
|
|
@ -177,6 +191,7 @@
|
|||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "doc_type",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ from frappe.utils import cint
|
|||
from frappe.model.document import Document
|
||||
from frappe.model import no_value_fields, core_doctypes_list
|
||||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.model.docfield import supports_translation
|
||||
|
||||
doctype_properties = {
|
||||
|
|
@ -29,6 +30,7 @@ doctype_properties = {
|
|||
'max_attachments': 'Int',
|
||||
'track_changes': 'Check',
|
||||
'track_views': 'Check',
|
||||
'allow_auto_repeat': 'Check'
|
||||
}
|
||||
|
||||
docfield_properties = {
|
||||
|
|
@ -65,6 +67,7 @@ docfield_properties = {
|
|||
'columns': 'Int',
|
||||
'remember_last_selected_value': 'Check',
|
||||
'allow_bulk_edit': 'Check',
|
||||
'auto_repeat': 'Link'
|
||||
}
|
||||
|
||||
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
|
||||
|
|
@ -108,6 +111,13 @@ class CustomizeForm(Document):
|
|||
translation = self.get_name_translation()
|
||||
self.label = translation.target_name if translation else ''
|
||||
|
||||
#If allow_auto_repeat is set, add auto_repeat custom field.
|
||||
if self.allow_auto_repeat:
|
||||
if not frappe.db.exists('Custom Field', {'fieldname': 'auto_repeat', 'dt': self.doc_type}):
|
||||
insert_after = self.fields[len(self.fields) - 1].fieldname
|
||||
df = dict(fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', options='Auto Repeat', insert_after=insert_after, read_only=1, no_copy=1, print_hide=1)
|
||||
create_custom_field(self.doc_type, df)
|
||||
|
||||
# NOTE doc is sent to clientside by run_method
|
||||
|
||||
def get_name_translation(self):
|
||||
|
|
|
|||
|
|
@ -553,6 +553,10 @@ class Database(object):
|
|||
val = val[0][0] if val else None
|
||||
|
||||
df = frappe.get_meta(doctype).get_field(fieldname)
|
||||
|
||||
if not df:
|
||||
frappe.throw(_('Invalid field name: {0}').format(frappe.bold(fieldname)), self.InvalidColumnName)
|
||||
|
||||
if df.fieldtype in frappe.model.numeric_fieldtypes:
|
||||
val = cint(val)
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@ class DbManager:
|
|||
if not host:
|
||||
host = self.get_current_host()
|
||||
|
||||
self.db.sql("GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'%s';" % (target, user, host))
|
||||
if frappe.conf.get('rds_db', 0) == 1:
|
||||
self.db.sql("GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON `%s`.* TO '%s'@'%s';" % (target, user, host))
|
||||
else:
|
||||
self.db.sql("GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'%s';" % (target, user, host))
|
||||
|
||||
def flush_privileges(self):
|
||||
self.db.sql("FLUSH PRIVILEGES")
|
||||
|
|
|
|||
|
|
@ -111,13 +111,10 @@ class PostgresDatabase(Database):
|
|||
|
||||
def format_date(self, date):
|
||||
if not date:
|
||||
return '0001-01-01::DATE'
|
||||
return '0001-01-01'
|
||||
|
||||
if isinstance(date, frappe.string_types):
|
||||
if ':' not in date:
|
||||
date = date + '::DATE'
|
||||
else:
|
||||
date = date.strftime('%Y-%m-%d') + '::DATE'
|
||||
if not isinstance(date, frappe.string_types):
|
||||
date = date.strftime('%Y-%m-%d')
|
||||
|
||||
return date
|
||||
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
// Copyright (c) 2018, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.provide("frappe.auto_repeat");
|
||||
|
||||
frappe.ui.form.on('Auto Repeat', {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['reference_doctype'].get_query = function() {
|
||||
return {
|
||||
query: "frappe.desk.doctype.auto_repeat.auto_repeat.auto_repeat_doctype_query"
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['reference_document'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"docstatus": 1,
|
||||
"auto_repeat": ''
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['print_format'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"doc_type": frm.doc.reference_doctype
|
||||
}
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
if(frm.doc.docstatus == 1) {
|
||||
|
||||
let label = __('View {0}', [__(frm.doc.reference_doctype)]);
|
||||
frm.add_custom_button(__(label),
|
||||
function() {
|
||||
frappe.route_options = {
|
||||
"auto_repeat": frm.doc.name,
|
||||
};
|
||||
frappe.set_route("List", frm.doc.reference_doctype);
|
||||
}
|
||||
);
|
||||
|
||||
if(frm.doc.status != 'Stopped') {
|
||||
frm.add_custom_button(__("Stop"),
|
||||
function() {
|
||||
frm.events.stop_resume_auto_repeat(frm, "Stopped");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(frm.doc.status == 'Stopped') {
|
||||
frm.add_custom_button(__("Restart"),
|
||||
function() {
|
||||
frm.events.stop_resume_auto_repeat(frm, "Resumed");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
frm.toggle_display('auto_repeat_schedule', !in_list(['Stopped', 'Cancelled'], frm.doc.status));
|
||||
if(frm.doc.start_date && !in_list(['Stopped', 'Cancelled'], frm.doc.status)){
|
||||
frappe.auto_repeat.render_schedule(frm);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
stop_resume_auto_repeat: function(frm, status) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.auto_repeat.auto_repeat.stop_resume_auto_repeat",
|
||||
args: {
|
||||
auto_repeat: frm.doc.name,
|
||||
status: status
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value("status", r.message);
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
template: function(frm) {
|
||||
if (frm.doc.template) {
|
||||
frappe.model.with_doc("Email Template", frm.doc.template, () => {
|
||||
let email_template = frappe.get_doc("Email Template", frm.doc.template);
|
||||
frm.set_value("subject", email_template.subject);
|
||||
frm.set_value("message", email_template.response);
|
||||
frm.refresh_field("subject");
|
||||
frm.refresh_field("message");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get_contacts: function(frm) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.auto_repeat.auto_repeat.get_contacts",
|
||||
args: {
|
||||
reference_doctype: frm.doc.reference_doctype,
|
||||
reference_name: frm.doc.reference_document
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value("recipients", r.message.join());
|
||||
frm.refresh_field("recipients");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
preview_message: function(frm) {
|
||||
if (frm.doc.message) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.auto_repeat.auto_repeat.generate_message_preview",
|
||||
args: {
|
||||
reference_dt: frm.doc.reference_doctype,
|
||||
reference_doc: frm.doc.reference_document,
|
||||
subject: frm.doc.subject,
|
||||
message: frm.doc.message
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frappe.msgprint(r.message.message, r.message.subject)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(__("Please setup a message first"), __("Message not setup"))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.auto_repeat.render_schedule = function(frm) {
|
||||
frappe.call({
|
||||
method: "get_auto_repeat_schedule",
|
||||
doc: frm.doc
|
||||
}).done((r) => {
|
||||
var wrapper = $(frm.fields_dict["auto_repeat_schedule"].wrapper);
|
||||
wrapper.html(frappe.render_template ("auto_repeat_schedule", {"schedule_details" : r.message || []} ));
|
||||
frm.refresh_fields();
|
||||
});
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,418 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import calendar
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
|
||||
|
||||
class AutoRepeat(Document):
|
||||
def validate(self):
|
||||
self.update_status()
|
||||
self.validate_reference_doctype()
|
||||
self.validate_dates()
|
||||
self.validate_next_schedule_date()
|
||||
self.validate_email_id()
|
||||
self.link_party()
|
||||
|
||||
validate_template(self.subject or "")
|
||||
validate_template(self.message or "")
|
||||
|
||||
def before_submit(self):
|
||||
start_date_copy = self.start_date
|
||||
today_copy = add_days(today(), -1)
|
||||
|
||||
if start_date_copy <= today_copy:
|
||||
start_date_copy = today_copy
|
||||
|
||||
if not self.next_schedule_date:
|
||||
self.next_schedule_date = get_next_schedule_date(
|
||||
start_date_copy, self.frequency, self.repeat_on_day)
|
||||
|
||||
def on_submit(self):
|
||||
self.update_auto_repeat_id()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_status()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
self.validate_dates()
|
||||
self.set_next_schedule_date()
|
||||
|
||||
def before_cancel(self):
|
||||
self.unlink_auto_repeat_id()
|
||||
self.next_schedule_date = None
|
||||
|
||||
def unlink_auto_repeat_id(self):
|
||||
frappe.db.sql(
|
||||
"update `tab{0}` set auto_repeat = null where auto_repeat=%s".format(self.reference_doctype), self.name)
|
||||
|
||||
def validate_reference_doctype(self):
|
||||
if not frappe.get_meta(self.reference_doctype).has_field('auto_repeat'):
|
||||
frappe.throw(_("Add custom field Auto Repeat in the doctype {0}").format(self.reference_doctype))
|
||||
|
||||
def validate_dates(self):
|
||||
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
|
||||
frappe.throw(_("End date must be greater than start date"))
|
||||
|
||||
def validate_next_schedule_date(self):
|
||||
if self.repeat_on_day and self.next_schedule_date:
|
||||
next_date = getdate(self.next_schedule_date)
|
||||
if next_date.day != self.repeat_on_day:
|
||||
# if the repeat day is the last day of the month (31)
|
||||
# and the current month does not have as many days,
|
||||
# then the last day of the current month is a valid date
|
||||
lastday = calendar.monthrange(next_date.year, next_date.month)[1]
|
||||
if self.repeat_on_day < lastday:
|
||||
# the specified day of the month is not same as the day specified
|
||||
# or the last day of the month
|
||||
frappe.throw(_("Next Date's day and Repeat on Day of Month must be equal"))
|
||||
|
||||
def validate_email_id(self):
|
||||
if self.notify_by_email:
|
||||
if self.recipients:
|
||||
email_list = split_emails(self.recipients.replace("\n", ""))
|
||||
|
||||
from frappe.utils import validate_email_address
|
||||
for email in email_list:
|
||||
if not validate_email_address(email):
|
||||
frappe.throw(_("{0} is an invalid email address in 'Recipients'").format(email))
|
||||
else:
|
||||
frappe.throw(_("'Recipients' not specified"))
|
||||
|
||||
def set_next_schedule_date(self):
|
||||
if self.repeat_on_day:
|
||||
self.next_schedule_date = get_next_date(self.next_schedule_date, 0, self.repeat_on_day)
|
||||
|
||||
def update_auto_repeat_id(self):
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, "auto_repeat", self.name)
|
||||
|
||||
def update_status(self, status=None):
|
||||
self.status = {
|
||||
'0': 'Draft',
|
||||
'1': 'Submitted',
|
||||
'2': 'Cancelled'
|
||||
}[cstr(self.docstatus or 0)]
|
||||
|
||||
if status and status != 'Resumed':
|
||||
self.status = status
|
||||
|
||||
if self.docstatus == 2:
|
||||
self.db_set("status", self.status)
|
||||
|
||||
def get_auto_repeat_schedule(self):
|
||||
schedule_details = []
|
||||
start_date_copy = getdate(self.start_date)
|
||||
end_date_copy = getdate(self.end_date)
|
||||
today_copy = frappe.utils.datetime.date.today()
|
||||
|
||||
if start_date_copy < today_copy:
|
||||
start_date_copy = today_copy
|
||||
|
||||
if not self.end_date:
|
||||
days = 60 if self.frequency in ['Daily', 'Weekly'] else 365
|
||||
end_date_copy = add_days(today_copy, days)
|
||||
|
||||
start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day)
|
||||
while (getdate(start_date_copy) < getdate(end_date_copy)):
|
||||
row = {
|
||||
"reference_document" : self.reference_document,
|
||||
"frequency" : self.frequency,
|
||||
"next_scheduled_date" : start_date_copy
|
||||
}
|
||||
schedule_details.append(row)
|
||||
start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day)
|
||||
|
||||
return schedule_details
|
||||
|
||||
def link_party(self):
|
||||
reference = frappe.get_meta(self.reference_doctype)
|
||||
for field in reference.fields:
|
||||
if field.options in ['Customer', 'Supplier', 'Employee']:
|
||||
self.reference_party_doctype = field.options
|
||||
self.reference_party = frappe.db.get_value(self.reference_doctype, self.reference_document, field.fieldname)
|
||||
break
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day):
|
||||
mcount = month_map.get(frequency)
|
||||
if mcount:
|
||||
next_date = get_next_date(start_date, mcount, repeat_on_day)
|
||||
else:
|
||||
days = 7 if frequency == 'Weekly' else 1
|
||||
next_date = add_days(start_date, days)
|
||||
return next_date
|
||||
|
||||
def make_auto_repeat_entry(date=None):
|
||||
enqueued_method = 'frappe.desk.doctype.auto_repeat.auto_repeat.create_repeated_entries'
|
||||
jobs = get_jobs()
|
||||
|
||||
if not jobs or enqueued_method not in jobs[frappe.local.site]:
|
||||
date = date or today()
|
||||
for data in get_auto_repeat_entries(date):
|
||||
frappe.enqueue(enqueued_method, data=data)
|
||||
|
||||
def create_repeated_entries(data):
|
||||
schedule_date = getdate(data.next_schedule_date)
|
||||
while schedule_date <= getdate(today()) and not frappe.db.get_value('Auto Repeat', data.name, 'disabled'):
|
||||
create_documents(data, schedule_date)
|
||||
schedule_date = get_next_schedule_date(schedule_date, data.frequency, data.repeat_on_day)
|
||||
|
||||
if schedule_date and not frappe.db.get_value('Auto Repeat', data.name, 'disabled'):
|
||||
frappe.db.set_value('Auto Repeat', data.name, 'next_schedule_date', schedule_date)
|
||||
frappe.db.commit()
|
||||
|
||||
def get_auto_repeat_entries(date):
|
||||
return frappe.db.sql(""" select * from `tabAuto Repeat`
|
||||
where docstatus = 1 and next_schedule_date <=%s
|
||||
and reference_document is not null and reference_document != ''
|
||||
and next_schedule_date <= ifnull(end_date, '2199-12-31')
|
||||
and disabled = 0 and status != 'Stopped' """, (date), as_dict=1)
|
||||
|
||||
def create_documents(data, schedule_date):
|
||||
try:
|
||||
doc = make_new_document(data, schedule_date)
|
||||
if data.notify_by_email and data.recipients:
|
||||
print_format = data.print_format or "Standard"
|
||||
send_notification(doc, data, print_format=print_format)
|
||||
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.db.begin()
|
||||
frappe.log_error(frappe.get_traceback(), _("Recurring document creation failure"))
|
||||
disable_auto_repeat(data)
|
||||
frappe.db.commit()
|
||||
if data.reference_document and not frappe.flags.in_test:
|
||||
notify_error_to_user(data)
|
||||
|
||||
def disable_auto_repeat(data):
|
||||
auto_repeat = frappe.get_doc('Auto Repeat', data.name)
|
||||
auto_repeat.db_set('disabled', 1)
|
||||
|
||||
def notify_error_to_user(data):
|
||||
party = ''
|
||||
party_type = ''
|
||||
|
||||
if data.reference_doctype in ['Sales Order', 'Sales Invoice', 'Delivery Note']:
|
||||
party_type = 'customer'
|
||||
elif data.reference_doctype in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
||||
party_type = 'supplier'
|
||||
|
||||
if party_type:
|
||||
party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type)
|
||||
|
||||
notify_errors(data.reference_document, data.reference_doctype, party, data.owner, data.name)
|
||||
|
||||
def make_new_document(args, schedule_date):
|
||||
doc = frappe.get_doc(args.reference_doctype, args.reference_document)
|
||||
new_doc = frappe.copy_doc(doc, ignore_no_copy=False)
|
||||
update_doc(new_doc, doc, args, schedule_date)
|
||||
new_doc.insert(ignore_permissions=True)
|
||||
|
||||
if args.submit_on_creation:
|
||||
new_doc.submit()
|
||||
|
||||
return new_doc
|
||||
|
||||
def update_doc(new_document, reference_doc, args, schedule_date):
|
||||
new_document.docstatus = 0
|
||||
if new_document.meta.get_field('set_posting_time'):
|
||||
new_document.set('set_posting_time', 1)
|
||||
|
||||
mcount = month_map.get(args.frequency)
|
||||
|
||||
if new_document.meta.get_field('auto_repeat'):
|
||||
new_document.set('auto_repeat', args.name)
|
||||
|
||||
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time',
|
||||
'select_print_heading', 'remarks', 'owner']:
|
||||
if new_document.meta.get_field(fieldname):
|
||||
new_document.set(fieldname, reference_doc.get(fieldname))
|
||||
|
||||
# copy item fields
|
||||
if new_document.meta.get_field('items'):
|
||||
for i, item in enumerate(new_document.items):
|
||||
for fieldname in ("page_break",):
|
||||
item.set(fieldname, reference_doc.items[i].get(fieldname))
|
||||
|
||||
for data in new_document.meta.fields:
|
||||
if data.fieldtype == 'Date' and data.reqd:
|
||||
new_document.set(data.fieldname, schedule_date)
|
||||
|
||||
set_auto_repeat_period(args, mcount, new_document)
|
||||
|
||||
new_document.run_method("on_recurring", reference_doc=reference_doc, auto_repeat_doc=args)
|
||||
|
||||
def set_auto_repeat_period(args, mcount, new_document):
|
||||
if mcount and new_document.meta.get_field('from_date') and new_document.meta.get_field('to_date'):
|
||||
last_ref_doc = frappe.db.sql("""
|
||||
select name, from_date, to_date
|
||||
from `tab{0}`
|
||||
where auto_repeat=%s and docstatus < 2
|
||||
order by creation desc
|
||||
limit 1
|
||||
""".format(args.reference_doctype), args.name, as_dict=1)
|
||||
|
||||
if not last_ref_doc:
|
||||
return
|
||||
|
||||
from_date = get_next_date(last_ref_doc[0].from_date, mcount)
|
||||
|
||||
if (cstr(get_first_day(last_ref_doc[0].from_date)) == cstr(last_ref_doc[0].from_date)) and \
|
||||
(cstr(get_last_day(last_ref_doc[0].to_date)) == cstr(last_ref_doc[0].to_date)):
|
||||
to_date = get_last_day(get_next_date(last_ref_doc[0].to_date, mcount))
|
||||
else:
|
||||
to_date = get_next_date(last_ref_doc[0].to_date, mcount)
|
||||
|
||||
new_document.set('from_date', from_date)
|
||||
new_document.set('to_date', to_date)
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
|
||||
return dt
|
||||
|
||||
def send_notification(new_rv, auto_repeat_doc, print_format='Standard'):
|
||||
"""Notify concerned persons about recurring document generation"""
|
||||
print_format = print_format
|
||||
subject = auto_repeat_doc.subject or ''
|
||||
message = auto_repeat_doc.message or ''
|
||||
|
||||
if not auto_repeat_doc.subject:
|
||||
subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
|
||||
elif "{" in auto_repeat_doc.subject:
|
||||
subject = frappe.render_template(auto_repeat_doc.subject, {'doc': new_rv})
|
||||
|
||||
if not auto_repeat_doc.message:
|
||||
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name)
|
||||
elif "{" in auto_repeat_doc.message:
|
||||
message = frappe.render_template(auto_repeat_doc.message, {'doc': new_rv})
|
||||
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name,
|
||||
file_name=new_rv.name, print_format=print_format)]
|
||||
|
||||
make(doctype=new_rv.doctype, name=new_rv.name, recipients=auto_repeat_doc.recipients,
|
||||
subject=subject, content=message, attachments=attachments, send_email=1)
|
||||
|
||||
def notify_errors(doc, doctype, party, owner, name):
|
||||
recipients = get_system_managers(only_name=True)
|
||||
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
|
||||
subject=_("[Urgent] Error while creating recurring %s for %s" % (doctype, doc)),
|
||||
message=frappe.get_template("templates/emails/recurring_document_failed.html").render({
|
||||
"type": _(doctype),
|
||||
"name": doc,
|
||||
"party": party or "",
|
||||
"auto_repeat": name
|
||||
}))
|
||||
try:
|
||||
assign_task_to_owner(name, _("Recurring Documents Failed"), recipients)
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), _("Recurring Documents Failed"))
|
||||
|
||||
def assign_task_to_owner(name, msg, users):
|
||||
for d in users:
|
||||
args = {
|
||||
'doctype': 'Auto Repeat',
|
||||
'assign_to': d,
|
||||
'name': name,
|
||||
'description': msg,
|
||||
'priority': 'High'
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_auto_repeat(doctype, docname):
|
||||
doc = frappe.new_doc('Auto Repeat')
|
||||
|
||||
reference_doc = frappe.get_doc(doctype, docname)
|
||||
doc.reference_doctype = doctype
|
||||
doc.reference_document = docname
|
||||
doc.start_date = reference_doc.get('posting_date') or reference_doc.get('transaction_date')
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def stop_resume_auto_repeat(auto_repeat, status):
|
||||
doc = frappe.get_doc('Auto Repeat', auto_repeat)
|
||||
frappe.msgprint(_("Auto Repeat has been {0}").format(status))
|
||||
if status == 'Resumed':
|
||||
doc.next_schedule_date = get_next_schedule_date(today(),
|
||||
doc.frequency, doc.repeat_on_day)
|
||||
|
||||
doc.update_status(status)
|
||||
doc.save()
|
||||
|
||||
return doc.status
|
||||
|
||||
def auto_repeat_doctype_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select parent from `tabDocField`
|
||||
where fieldname = 'auto_repeat'
|
||||
and parent like %(txt)s
|
||||
order by
|
||||
if(locate(%(_txt)s, parent), locate(%(_txt)s, parent), 99999),
|
||||
parent
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'key': searchfield,
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_contacts(reference_doctype, reference_name):
|
||||
docfields = frappe.get_meta(reference_doctype).fields
|
||||
|
||||
contact_fields = []
|
||||
for field in docfields:
|
||||
if field.fieldtype == "Link" and field.options == "Contact":
|
||||
contact_fields.append(field.fieldname)
|
||||
|
||||
if contact_fields:
|
||||
contacts = []
|
||||
for contact_field in contact_fields:
|
||||
contacts.append(frappe.db.get_value(reference_doctype, reference_name, contact_field))
|
||||
else:
|
||||
return []
|
||||
|
||||
if contacts:
|
||||
emails = []
|
||||
for contact in contacts:
|
||||
emails.append(frappe.db.get_value("Contact", contact, "email_id"))
|
||||
|
||||
return emails
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_reference(docname, reference):
|
||||
try:
|
||||
frappe.db.set_value("Auto Repeat", docname, "reference_document", reference)
|
||||
return "success"
|
||||
except Exception as e:
|
||||
raise e
|
||||
return "error"
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_message_preview(reference_dt, reference_doc, message=None, subject=None):
|
||||
doc = frappe.get_doc(reference_dt, reference_doc)
|
||||
subject_preview = _("Please add a subject to your email")
|
||||
msg_preview = frappe.render_template(message, {'doc': doc})
|
||||
if subject:
|
||||
subject_preview = frappe.render_template(subject, {'doc': doc})
|
||||
|
||||
return {'message': msg_preview, 'subject': subject_preview}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
frappe.listview_settings['Auto Repeat'] = {
|
||||
add_fields: ["next_schedule_date"],
|
||||
get_indicator: function(doc) {
|
||||
if(doc.disabled) {
|
||||
return [__("Disabled"), "red"];
|
||||
} else if(doc.next_schedule_date >= frappe.datetime.get_today() && doc.status != 'Stopped') {
|
||||
return [__("Active"), "green"];
|
||||
} else if(doc.docstatus === 0) {
|
||||
return [__("Draft"), "red", "docstatus,=,0"];
|
||||
} else if(doc.status === 'Stopped') {
|
||||
return [__("Stopped"), "red"];
|
||||
} else {
|
||||
return [__("Expired"), "darkgrey"];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -684,4 +684,4 @@
|
|||
"track_changes": 1,
|
||||
"track_seen": 1,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import json
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_list_settings(doctype):
|
||||
|
|
@ -22,31 +20,37 @@ def set_list_settings(doctype, values):
|
|||
doc = frappe.new_doc("List View Setting")
|
||||
doc.name = doctype
|
||||
frappe.clear_messages()
|
||||
doc.update(json.loads(values))
|
||||
doc.update(frappe.parse_json(values))
|
||||
doc.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_user_assignments_and_count(doctype, current_filters):
|
||||
|
||||
def get_group_by_count(doctype, current_filters, field):
|
||||
current_filters = frappe.parse_json(current_filters)
|
||||
subquery_condition = ''
|
||||
if current_filters:
|
||||
# get the subquery
|
||||
subquery = frappe.get_all(doctype,
|
||||
filters=current_filters, return_query = True)
|
||||
|
||||
subquery = frappe.get_all(doctype, filters=current_filters, return_query = True)
|
||||
if field == 'assigned_to':
|
||||
subquery_condition = ' and `tabToDo`.reference_name in ({subquery})'.format(subquery = subquery)
|
||||
return frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count
|
||||
from
|
||||
`tabToDo`, `tabUser`
|
||||
where
|
||||
`tabToDo`.status='Open' and
|
||||
`tabToDo`.owner = `tabUser`.name and
|
||||
`tabUser`.user_type = 'System User'
|
||||
{subquery_condition}
|
||||
group by
|
||||
`tabToDo`.owner
|
||||
order by
|
||||
count desc
|
||||
limit 50""".format(subquery_condition = subquery_condition), as_dict=True)
|
||||
else :
|
||||
return frappe.db.get_list(doctype,
|
||||
filters=current_filters,
|
||||
group_by=field,
|
||||
fields=['count(*) as count', field + ' as name'],
|
||||
order_by='count desc',
|
||||
limit=50,
|
||||
)
|
||||
|
||||
todo_list = frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count
|
||||
from
|
||||
`tabToDo`, `tabUser`
|
||||
where
|
||||
`tabToDo`.status='Open' and
|
||||
`tabToDo`.owner = `tabUser`.name and
|
||||
`tabUser`.user_type = 'System User'
|
||||
{subquery_condition}
|
||||
group by
|
||||
`tabToDo`.owner
|
||||
order by
|
||||
count desc
|
||||
limit 50""".format(subquery_condition = subquery_condition), as_dict=True)
|
||||
|
||||
return todo_list
|
||||
|
|
@ -287,44 +287,141 @@ def get_onboard_items(app, module):
|
|||
|
||||
return onboard_items or fallback_items
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_links_for_module(app, module):
|
||||
return [l.get('label') for l in get_links(app, module)]
|
||||
|
||||
def get_links(app, module):
|
||||
try:
|
||||
sections = get_config(app, frappe.scrub(module))
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
link_names = []
|
||||
|
||||
links = []
|
||||
for section in sections:
|
||||
for item in section["items"]:
|
||||
link_names.append(item.get("label"))
|
||||
return link_names
|
||||
for item in section['items']:
|
||||
links.append(item)
|
||||
return links
|
||||
|
||||
@frappe.whitelist()
|
||||
def hide_modules_from_desktop(modules):
|
||||
def get_desktop_settings():
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
all_modules = get_modules_from_all_apps_for_user()
|
||||
home_settings = get_home_settings()
|
||||
|
||||
modules_by_name = {}
|
||||
for m in all_modules:
|
||||
modules_by_name[m['module_name']] = m
|
||||
|
||||
module_categories = ['Modules', 'Domains', 'Places', 'Administration']
|
||||
user_modules_by_category = {}
|
||||
|
||||
user_saved_modules_by_category = home_settings.modules_by_category or {}
|
||||
user_saved_links_by_module = home_settings.links_by_module or {}
|
||||
|
||||
def apply_user_saved_links(module):
|
||||
module = frappe._dict(module)
|
||||
all_links = get_links(module.app, module.module_name)
|
||||
module_links_by_label = {}
|
||||
for link in all_links:
|
||||
module_links_by_label[link['label']] = link
|
||||
|
||||
if module.module_name in user_saved_links_by_module:
|
||||
user_links = frappe.parse_json(user_saved_links_by_module[module.module_name])
|
||||
module.links = [module_links_by_label[l] for l in user_links if l in module_links_by_label]
|
||||
|
||||
return module
|
||||
|
||||
for category in module_categories:
|
||||
if category in user_saved_modules_by_category:
|
||||
user_modules = user_saved_modules_by_category[category]
|
||||
user_modules_by_category[category] = [apply_user_saved_links(modules_by_name[m]) \
|
||||
for m in user_modules]
|
||||
else:
|
||||
user_modules_by_category[category] = [apply_user_saved_links(m) \
|
||||
for m in all_modules if m.get('category') == category]
|
||||
|
||||
# filter out hidden modules
|
||||
if home_settings.hidden_modules:
|
||||
for category in user_modules_by_category:
|
||||
hidden_modules = home_settings.hidden_modules or []
|
||||
modules = user_modules_by_category[category]
|
||||
user_modules_by_category[category] = [module for module in modules if module.module_name not in hidden_modules]
|
||||
|
||||
return user_modules_by_category
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_hidden_modules(category_map):
|
||||
category_map = frappe.parse_json(category_map)
|
||||
home_settings = get_home_settings()
|
||||
|
||||
saved_hidden_modules = home_settings.hidden_modules or []
|
||||
|
||||
for category in category_map:
|
||||
config = frappe._dict(category_map[category])
|
||||
saved_hidden_modules += config.removed or []
|
||||
saved_hidden_modules = [d for d in saved_hidden_modules if d not in (config.added or [])]
|
||||
|
||||
home_settings.hidden_modules = saved_hidden_modules
|
||||
set_home_settings(home_settings)
|
||||
|
||||
return get_desktop_settings()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_modules_order(module_category, modules):
|
||||
modules = frappe.parse_json(modules)
|
||||
home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
home_settings = frappe.parse_json(home_settings or '{}')
|
||||
|
||||
home_settings['hidden_modules'] = modules
|
||||
frappe.db.set_value('User', frappe.session.user, 'home_settings', json.dumps(home_settings))
|
||||
|
||||
return home_settings
|
||||
home_settings = get_home_settings()
|
||||
|
||||
home_settings.modules_by_category = home_settings.modules_by_category or {}
|
||||
home_settings.modules_by_category[module_category] = modules
|
||||
|
||||
set_home_settings(home_settings)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_links_for_module(module_name, links):
|
||||
home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
home_settings = frappe.parse_json(home_settings or '{}')
|
||||
links = frappe.parse_json(links)
|
||||
home_settings = get_home_settings()
|
||||
|
||||
home_settings.setdefault('links', {})
|
||||
home_settings['links'].setdefault(module_name, None)
|
||||
home_settings['links'][module_name] = links
|
||||
home_settings.setdefault('links_by_module', {})
|
||||
home_settings['links_by_module'].setdefault(module_name, None)
|
||||
home_settings['links_by_module'][module_name] = links
|
||||
|
||||
set_home_settings(home_settings)
|
||||
|
||||
return get_desktop_settings()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_options_for_show_hide_cards():
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
all_modules = get_modules_from_all_apps_for_user()
|
||||
home_settings = get_home_settings()
|
||||
|
||||
hidden_modules = home_settings.hidden_modules or []
|
||||
|
||||
options = []
|
||||
for module in all_modules:
|
||||
module = frappe._dict(module)
|
||||
options.append({
|
||||
'category': module.category,
|
||||
'label': module.label,
|
||||
'value': module.module_name,
|
||||
'checked': module.module_name not in hidden_modules
|
||||
})
|
||||
|
||||
return options
|
||||
|
||||
def set_home_settings(home_settings):
|
||||
frappe.cache().hset('home_settings', frappe.session.user, home_settings)
|
||||
frappe.db.set_value('User', frappe.session.user, 'home_settings', json.dumps(home_settings))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_home_settings():
|
||||
def get_from_db():
|
||||
settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
return frappe.parse_json(settings or '{}')
|
||||
|
||||
home_settings = frappe.cache().hget('home_settings', frappe.session.user, get_from_db)
|
||||
return home_settings
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ def export_query():
|
|||
if isinstance(data.get("file_format_type"), string_types):
|
||||
file_format_type = data["file_format_type"]
|
||||
|
||||
include_indentation = data["include_indentation"]
|
||||
if isinstance(data.get("visible_idx"), string_types):
|
||||
visible_idx = json.loads(data.get("visible_idx"))
|
||||
else:
|
||||
|
|
@ -318,7 +319,7 @@ def export_query():
|
|||
columns = get_columns_dict(data.columns)
|
||||
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
xlsx_data = build_xlsx_data(columns, data, visible_idx)
|
||||
xlsx_data = build_xlsx_data(columns, data, visible_idx, include_indentation)
|
||||
xlsx_file = make_xlsx(xlsx_data, "Query Report")
|
||||
|
||||
frappe.response['filename'] = report_name + '.xlsx'
|
||||
|
|
@ -326,7 +327,7 @@ def export_query():
|
|||
frappe.response['type'] = 'binary'
|
||||
|
||||
|
||||
def build_xlsx_data(columns, data, visible_idx):
|
||||
def build_xlsx_data(columns, data, visible_idx,include_indentation):
|
||||
result = [[]]
|
||||
|
||||
# add column headings
|
||||
|
|
@ -344,7 +345,7 @@ def build_xlsx_data(columns, data, visible_idx):
|
|||
label = columns[idx]["label"]
|
||||
fieldname = columns[idx]["fieldname"]
|
||||
cell_value = row.get(fieldname, row.get(label, ""))
|
||||
if 'indent' in row and idx == 0:
|
||||
if cint(include_indentation) and 'indent' in row and idx == 0:
|
||||
cell_value = (' ' * cint(row['indent'])) + cell_value
|
||||
row_data.append(cell_value)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -185,6 +185,10 @@ def append_totals_row(data):
|
|||
for i in range(len(row)):
|
||||
if isinstance(row[i], (float, int)):
|
||||
totals[i] = (totals[i] or 0) + row[i]
|
||||
|
||||
if not isinstance(totals[0], (int, float)):
|
||||
totals[0] = 'Total'
|
||||
|
||||
data.append(totals)
|
||||
|
||||
return data
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -161,7 +161,6 @@ scheduler_events = {
|
|||
"frappe.desk.page.backups.backups.delete_downloadable_backups",
|
||||
"frappe.limits.update_space_usage",
|
||||
"frappe.limits.update_site_usage",
|
||||
"frappe.desk.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry",
|
||||
"frappe.deferred_insert.save_to_db",
|
||||
"frappe.desk.form.document_follow.send_hourly_updates",
|
||||
],
|
||||
|
|
@ -181,6 +180,8 @@ scheduler_events = {
|
|||
"frappe.desk.form.document_follow.send_daily_updates",
|
||||
"frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points",
|
||||
"frappe.integrations.doctype.google_contacts.google_contacts.sync",
|
||||
"frappe.automation.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry",
|
||||
"frappe.automation.doctype.auto_repeat.auto_repeat.set_auto_repeat_as_completed"
|
||||
],
|
||||
"daily_long": [
|
||||
"frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily",
|
||||
|
|
@ -236,6 +237,7 @@ setup_wizard_exception = "frappe.desk.page.setup_wizard.setup_wizard.email_setup
|
|||
before_write_file = "frappe.limits.validate_space_limit"
|
||||
|
||||
before_migrate = ['frappe.patches.v11_0.sync_user_permission_doctype_before_migrate.execute']
|
||||
after_migrate = ['frappe.website.doctype.website_theme.website_theme.generate_theme_files_if_not_exist']
|
||||
|
||||
otp_methods = ['OTP App','Email','SMS']
|
||||
user_privacy_documents = [
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (c) 2017, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Google Maps Settings', {
|
||||
});
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-10-16 17:13:05.684227",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "enabled",
|
||||
"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": "Enabled",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client_key",
|
||||
"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": "Client Key",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "home_address",
|
||||
"fieldtype": "Link",
|
||||
"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": "Home Address",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Address",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 14:53:09.170463",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Google Maps Settings",
|
||||
"name_case": "",
|
||||
"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": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class GoogleMapsSettings(Document):
|
||||
def validate(self):
|
||||
if self.enabled:
|
||||
if not self.client_key:
|
||||
frappe.throw(_("Client key is required"))
|
||||
if not self.home_address:
|
||||
frappe.throw(_("Home Address is required"))
|
||||
|
||||
def get_client(self):
|
||||
if not self.enabled:
|
||||
frappe.throw(_("Google Maps integration is not enabled"))
|
||||
|
||||
import googlemaps
|
||||
|
||||
try:
|
||||
client = googlemaps.Client(key=self.client_key)
|
||||
except Exception as e:
|
||||
frappe.throw(e.message)
|
||||
|
||||
return client
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Google Maps Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Google Maps
|
||||
() => frappe.tests.make('Google Maps Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
|
||||
class TestGoogleMapsSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
"enable",
|
||||
"google_credentials",
|
||||
"client_id",
|
||||
"client_secret"
|
||||
"client_secret",
|
||||
"api_key"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -32,10 +33,15 @@
|
|||
"fieldtype": "Password",
|
||||
"in_list_view": 1,
|
||||
"label": "Client Secret"
|
||||
},
|
||||
{
|
||||
"fieldname": "api_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "API Key"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"modified": "2019-06-19 15:28:05.957380",
|
||||
"modified": "2019-06-29 13:26:33.201060",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Google Settings",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"creation": "2019-05-29 01:24:29.585060",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"ldap_group",
|
||||
"erpnext_role"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "ldap_group",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "LDAP Group",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "erpnext_role",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "ERPNext Role",
|
||||
"options": "Role",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-07-15 06:46:38.050408",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "LDAP Group Mapping",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class LDAPGroupMapping(Document):
|
||||
pass
|
||||
|
|
@ -1,594 +1,215 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-09-22 04:16:48.829658",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"ldap_server_url",
|
||||
"column_break_4",
|
||||
"base_dn",
|
||||
"password",
|
||||
"section_break_5",
|
||||
"organizational_unit",
|
||||
"default_role",
|
||||
"ldap_search_string",
|
||||
"ldap_email_field",
|
||||
"ldap_username_field",
|
||||
"column_break_11",
|
||||
"ldap_first_name_field",
|
||||
"ldap_middle_name_field",
|
||||
"ldap_last_name_field",
|
||||
"ldap_phone_field",
|
||||
"ldap_mobile_field",
|
||||
"ldap_security",
|
||||
"ssl_tls_mode",
|
||||
"require_trusted_certificate",
|
||||
"column_break_17",
|
||||
"local_private_key_file",
|
||||
"local_server_certificate_file",
|
||||
"local_ca_certs_file",
|
||||
"ldap_group_mappings_section",
|
||||
"ldap_group_field",
|
||||
"ldap_groups"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"default": "0",
|
||||
"fieldname": "enabled",
|
||||
"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": "Enabled",
|
||||
"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": "Enabled"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_server_url",
|
||||
"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": "LDAP Server Url",
|
||||
"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": 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,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "organizational_unit",
|
||||
"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": "Organizational Unit",
|
||||
"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": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "base_dn",
|
||||
"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": "Base Distinguished Name (DN)",
|
||||
"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": 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,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "password",
|
||||
"fieldtype": "Password",
|
||||
"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": "Password for Base DN",
|
||||
"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": 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,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_5",
|
||||
"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,
|
||||
"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": "LDAP User Creation and Mapping"
|
||||
},
|
||||
{
|
||||
"fieldname": "organizational_unit",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Organizational Unit for Users",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "default_role",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Role on Creation",
|
||||
"options": "Role",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_search_string",
|
||||
"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": "LDAP Search String",
|
||||
"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": 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,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_first_name_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": "LDAP First Name Field",
|
||||
"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": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_email_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": "LDAP Email Field",
|
||||
"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": 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,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_username_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": "LDAP Username Field",
|
||||
"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": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_first_name_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP First Name Field",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_middle_name_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Middle Name Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_last_name_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Last Name Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_phone_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Phone Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_mobile_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Mobile Field"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_security",
|
||||
"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": "LDAP Security",
|
||||
"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": "LDAP Security"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Off",
|
||||
"description": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ssl_tls_mode",
|
||||
"fieldtype": "Select",
|
||||
"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": "SSL/TLS Mode",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Off\nStartTLS",
|
||||
"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
|
||||
"options": "Off\nStartTLS"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "No",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "require_trusted_certificate",
|
||||
"fieldtype": "Select",
|
||||
"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": "Require Trusted Certificate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "No\nYes",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "local_private_key_file",
|
||||
"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": "Path to private Key File",
|
||||
"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": "Path to private Key File"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "local_server_certificate_file",
|
||||
"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": "Path to Server Certificate",
|
||||
"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": "Path to Server Certificate"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "local_ca_certs_file",
|
||||
"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": "Path to CA Certs File",
|
||||
"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": "Path to CA Certs File"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_group_mappings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "LDAP Group Mappings"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_group_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Group Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_groups",
|
||||
"fieldtype": "Table",
|
||||
"label": "LDAP Group Mappings",
|
||||
"options": "LDAP Group Mapping"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-04-29 10:56:42.322696",
|
||||
"modified": "2019-07-15 06:48:16.562109",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "LDAP Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"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": 1,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -15,155 +15,195 @@ class LDAPSettings(Document):
|
|||
|
||||
if not self.flags.ignore_mandatory:
|
||||
if self.ldap_search_string and self.ldap_search_string.endswith("={0}"):
|
||||
connect_to_ldap(server_url=self.ldap_server_url,
|
||||
base_dn=self.base_dn,
|
||||
password=self.get_password(raise_exception=False),
|
||||
ssl_tls_mode=self.ssl_tls_mode,
|
||||
trusted_cert=self.require_trusted_certificate,
|
||||
private_key_file=self.local_private_key_file,
|
||||
server_cert_file=self.local_server_certificate_file,
|
||||
ca_certs_file=self.local_ca_certs_file
|
||||
)
|
||||
self.connect_to_ldap(base_dn=self.base_dn, password=self.get_password(raise_exception=False))
|
||||
else:
|
||||
frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}"))
|
||||
|
||||
def connect_to_ldap(self, base_dn, password):
|
||||
try:
|
||||
import ldap3
|
||||
import ssl
|
||||
|
||||
def get_ldap_client_settings():
|
||||
#return the settings to be used on the client side.
|
||||
result = {
|
||||
"enabled": False
|
||||
}
|
||||
settings = frappe.get_doc("LDAP Settings")
|
||||
if self.require_trusted_certificate == 'Yes':
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1)
|
||||
else:
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1)
|
||||
|
||||
if settings and settings.enabled:
|
||||
result["enabled"] = True
|
||||
result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login"
|
||||
return result
|
||||
if self.local_private_key_file:
|
||||
tls_configuration.private_key_file = self.local_private_key_file
|
||||
if self.local_server_certificate_file:
|
||||
tls_configuration.certificate_file = self.local_server_certificate_file
|
||||
if self.local_ca_certs_file:
|
||||
tls_configuration.ca_certs_file = self.local_ca_certs_file
|
||||
|
||||
server = ldap3.Server(host=self.ldap_server_url, tls=tls_configuration)
|
||||
bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if self.ssl_tls_mode == "StartTLS" else True
|
||||
|
||||
def connect_to_ldap(server_url,
|
||||
base_dn,
|
||||
password,
|
||||
ssl_tls_mode,
|
||||
trusted_cert,
|
||||
private_key_file,
|
||||
server_cert_file,
|
||||
ca_certs_file):
|
||||
try:
|
||||
import ldap3
|
||||
import ssl
|
||||
conn = ldap3.Connection(
|
||||
server=server,
|
||||
user=base_dn,
|
||||
password=password,
|
||||
auto_bind=bind_type,
|
||||
read_only=True,
|
||||
raise_exceptions=True)
|
||||
|
||||
if trusted_cert == 'Yes':
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED,
|
||||
version=ssl.PROTOCOL_TLSv1)
|
||||
return conn
|
||||
|
||||
except ImportError:
|
||||
msg = _("Please Install the ldap3 library via pip to use ldap functionality.")
|
||||
frappe.throw(msg, title=_("LDAP Not Installed"))
|
||||
except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
|
||||
frappe.throw(_("Invalid username or password"))
|
||||
except Exception as ex:
|
||||
frappe.throw(_(str(ex)))
|
||||
|
||||
@staticmethod
|
||||
def get_ldap_client_settings():
|
||||
# return the settings to be used on the client side.
|
||||
result = {
|
||||
"enabled": False
|
||||
}
|
||||
ldap = frappe.get_doc("LDAP Settings")
|
||||
if ldap.enabled:
|
||||
result["enabled"] = True
|
||||
result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login"
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def update_user_fields(cls, user, user_data):
|
||||
|
||||
updatable_data = {key: value for key, value in user_data.items() if key != 'email'}
|
||||
|
||||
for key, value in updatable_data.items():
|
||||
setattr(user, key, value)
|
||||
user.save(ignore_permissions=True)
|
||||
|
||||
def sync_roles(self, user, additional_groups=None):
|
||||
|
||||
current_roles = set([d.role for d in user.get("roles")])
|
||||
|
||||
needed_roles = set()
|
||||
needed_roles.add(self.default_role)
|
||||
|
||||
lower_groups = [g.lower() for g in additional_groups or []]
|
||||
|
||||
all_mapped_roles = {r.erpnext_role for r in self.ldap_groups}
|
||||
matched_roles = {r.erpnext_role for r in self.ldap_groups if r.ldap_group.lower() in lower_groups}
|
||||
unmatched_roles = all_mapped_roles.difference(matched_roles)
|
||||
needed_roles.update(matched_roles)
|
||||
roles_to_remove = current_roles.intersection(unmatched_roles)
|
||||
|
||||
if not needed_roles.issubset(current_roles):
|
||||
missing_roles = needed_roles.difference(current_roles)
|
||||
user.add_roles(*missing_roles)
|
||||
|
||||
user.remove_roles(*roles_to_remove)
|
||||
|
||||
def create_or_update_user(self, user_data, groups=None):
|
||||
user = None
|
||||
if frappe.db.exists("User", user_data['email']):
|
||||
user = frappe.get_doc("User", user_data['email'])
|
||||
LDAPSettings.update_user_fields(user=user, user_data=user_data)
|
||||
else:
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE,
|
||||
version=ssl.PROTOCOL_TLSv1)
|
||||
doc = user_data
|
||||
doc.update({
|
||||
"doctype": "User",
|
||||
"send_welcome_email": 0,
|
||||
"language": "",
|
||||
"user_type": "System User",
|
||||
# "roles": [{
|
||||
# "role": self.default_role
|
||||
# }]
|
||||
})
|
||||
user = frappe.get_doc(doc)
|
||||
user.insert(ignore_permissions=True)
|
||||
# always add default role.
|
||||
user.add_roles(self.default_role)
|
||||
if self.ldap_group_field:
|
||||
self.sync_roles(user, groups)
|
||||
return user
|
||||
|
||||
if private_key_file:
|
||||
tls_configuration.private_key_file = private_key_file
|
||||
if server_cert_file:
|
||||
tls_configuration.certificate_file = server_cert_file
|
||||
if ca_certs_file:
|
||||
tls_configuration.ca_certs_file = ca_certs_file
|
||||
def get_ldap_attributes(self):
|
||||
ldap_attributes = [self.ldap_email_field, self.ldap_username_field, self.ldap_first_name_field]
|
||||
|
||||
server = ldap3.Server(host=server_url,
|
||||
tls=tls_configuration)
|
||||
bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if ssl_tls_mode == "StartTLS" else True
|
||||
if self.ldap_group_field:
|
||||
ldap_attributes.append(self.ldap_group_field)
|
||||
|
||||
conn = ldap3.Connection(server=server,
|
||||
user=base_dn,
|
||||
password=password,
|
||||
auto_bind=bind_type,
|
||||
read_only=True,
|
||||
raise_exceptions=True)
|
||||
if self.ldap_middle_name_field:
|
||||
ldap_attributes.append(self.ldap_middle_name_field)
|
||||
|
||||
return conn
|
||||
if self.ldap_last_name_field:
|
||||
ldap_attributes.append(self.ldap_last_name_field)
|
||||
|
||||
except ImportError:
|
||||
msg = _("Please Install the ldap3 library via pip to use ldap functionality.")
|
||||
frappe.throw(msg, title=_("LDAP Not Installed"))
|
||||
except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
|
||||
frappe.throw(_("Invalid Credentials"))
|
||||
except Exception as ex:
|
||||
frappe.throw(_(str(ex)))
|
||||
if self.ldap_phone_field:
|
||||
ldap_attributes.append(self.ldap_phone_field)
|
||||
|
||||
if self.ldap_mobile_field:
|
||||
ldap_attributes.append(self.ldap_mobile_field)
|
||||
|
||||
return ldap_attributes
|
||||
|
||||
def authenticate(self, username, password):
|
||||
|
||||
if not self.enabled:
|
||||
frappe.throw(_("LDAP is not enabled."))
|
||||
|
||||
user_filter = self.ldap_search_string.format(username)
|
||||
ldap_attributes = self.get_ldap_attributes()
|
||||
|
||||
conn = self.connect_to_ldap(self.base_dn, self.get_password(raise_exception=False))
|
||||
|
||||
conn.search(
|
||||
search_base=self.organizational_unit,
|
||||
search_filter="({0})".format(user_filter),
|
||||
attributes=ldap_attributes)
|
||||
|
||||
if len(conn.entries) == 1 and conn.entries[0]:
|
||||
user = conn.entries[0]
|
||||
# only try and connect as the user, once we have their fqdn entry.
|
||||
self.connect_to_ldap(base_dn=user.entry_dn, password=password)
|
||||
|
||||
groups = None
|
||||
if self.ldap_group_field:
|
||||
groups = getattr(user, self.ldap_group_field).values
|
||||
return self.create_or_update_user(self.convert_ldap_entry_to_dict(user), groups=groups)
|
||||
else:
|
||||
frappe.throw(_("Invalid username or password"))
|
||||
|
||||
def convert_ldap_entry_to_dict(self, user_entry):
|
||||
data = {
|
||||
'username': user_entry[self.ldap_username_field].value,
|
||||
'email': user_entry[self.ldap_email_field].value,
|
||||
'first_name': user_entry[self.ldap_first_name_field].value
|
||||
}
|
||||
|
||||
# optional fields
|
||||
|
||||
if self.ldap_middle_name_field:
|
||||
data['middle_name'] = user_entry[self.ldap_middle_name_field].value
|
||||
|
||||
if self.ldap_last_name_field:
|
||||
data['last_name'] = user_entry[self.ldap_last_name_field].value
|
||||
|
||||
if self.ldap_phone_field:
|
||||
data['phone'] = user_entry[self.ldap_phone_field].value
|
||||
|
||||
if self.ldap_mobile_field:
|
||||
data['mobile_no'] = user_entry[self.ldap_mobile_field].value
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def login():
|
||||
# LDAP LOGIN LOGIC
|
||||
args = frappe.form_dict
|
||||
user = authenticate_ldap_user(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd))
|
||||
ldap = frappe.get_doc("LDAP Settings")
|
||||
|
||||
user = ldap.authenticate(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd))
|
||||
|
||||
frappe.local.login_manager.user = user.name
|
||||
frappe.local.login_manager.post_login()
|
||||
|
||||
# because of a GET request!
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def authenticate_ldap_user(user=None,
|
||||
password=None):
|
||||
|
||||
params = {}
|
||||
settings = frappe.get_doc("LDAP Settings")
|
||||
if settings and settings.enabled:
|
||||
conn = connect_to_ldap(server_url=settings.ldap_server_url,
|
||||
base_dn=settings.base_dn,
|
||||
password=settings.get_password(raise_exception=False),
|
||||
ssl_tls_mode=settings.ssl_tls_mode,
|
||||
trusted_cert=settings.require_trusted_certificate,
|
||||
private_key_file=settings.local_private_key_file,
|
||||
server_cert_file=settings.local_server_certificate_file,
|
||||
ca_certs_file=settings.local_ca_certs_file)
|
||||
|
||||
user_filter = settings.ldap_search_string.format(user)
|
||||
conn.search(search_base=settings.organizational_unit,
|
||||
search_filter="({0})".format(user_filter),
|
||||
attributes=[settings.ldap_email_field,
|
||||
settings.ldap_username_field,
|
||||
settings.ldap_first_name_field])
|
||||
|
||||
if len(conn.entries) > 0 and conn.entries[0]:
|
||||
user = conn.entries[0]
|
||||
params["email"] = str(user[settings.ldap_email_field])
|
||||
params["username"] = str(user[settings.ldap_username_field])
|
||||
params["first_name"] = str(user[settings.ldap_first_name_field])
|
||||
connect_to_ldap(server_url=settings.ldap_server_url,
|
||||
base_dn=user.entry_dn,
|
||||
password=frappe.as_unicode(password),
|
||||
ssl_tls_mode=settings.ssl_tls_mode,
|
||||
trusted_cert=settings.require_trusted_certificate,
|
||||
private_key_file=settings.local_private_key_file,
|
||||
server_cert_file=settings.local_server_certificate_file,
|
||||
ca_certs_file=settings.local_ca_certs_file
|
||||
)
|
||||
return create_user(params)
|
||||
else:
|
||||
frappe.throw(_("Not a valid LDAP user"))
|
||||
else:
|
||||
frappe.throw(_("LDAP is not enabled."))
|
||||
|
||||
|
||||
def create_user(params):
|
||||
if frappe.db.exists("User", params["email"]):
|
||||
user = frappe.get_doc("User", params["email"])
|
||||
user.first_name = params["first_name"]
|
||||
user.username = params["username"]
|
||||
user.save(ignore_permissions=True)
|
||||
return user
|
||||
|
||||
else:
|
||||
params.update({
|
||||
"doctype": "User",
|
||||
"send_welcome_email": 0,
|
||||
"language": "",
|
||||
"user_type": "System User",
|
||||
"roles": [{
|
||||
"role": _("Customer")
|
||||
}]
|
||||
})
|
||||
|
||||
user = frappe.get_doc(params).insert(ignore_permissions=True)
|
||||
|
||||
return user
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestLDAPSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -66,15 +66,26 @@ def take_backups_if(freq):
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def take_backups_s3():
|
||||
def take_backups_s3(retry_count=0):
|
||||
try:
|
||||
backup_to_s3()
|
||||
send_email(True, "S3 Backup Settings")
|
||||
except JobTimeoutException:
|
||||
if retry_count < 2:
|
||||
args = {
|
||||
"retry_count" :retry_count + 1
|
||||
}
|
||||
enqueue("frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3",
|
||||
queue='long', timeout=1500, **args)
|
||||
else:
|
||||
notify()
|
||||
except Exception:
|
||||
error_message = frappe.get_traceback()
|
||||
frappe.errprint(error_message)
|
||||
send_email(False, "S3 Backup Settings", error_message)
|
||||
notify()
|
||||
|
||||
def notify():
|
||||
error_message = frappe.get_traceback()
|
||||
frappe.errprint(error_message)
|
||||
send_email(False, "S3 Backup Settings", error_message)
|
||||
|
||||
def send_email(success, service_name, error_status=None):
|
||||
if success:
|
||||
|
|
@ -134,6 +145,7 @@ def upload_file_to_s3(filename, folder, conn, bucket):
|
|||
conn.upload_file(filename, bucket, destpath)
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error()
|
||||
print("Error uploading: %s" % (e))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ def get_field_currency(df, doc=None):
|
|||
|
||||
if ":" in cstr(df.get("options")):
|
||||
split_opts = df.get("options").split(":")
|
||||
if len(split_opts)==3:
|
||||
if len(split_opts)==3 and doc.get(split_opts[1]):
|
||||
currency = frappe.get_cached_value(split_opts[0], doc.get(split_opts[1]), split_opts[2])
|
||||
else:
|
||||
currency = doc.get(df.get("options"))
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ frappe.patches.v12_0.set_primary_key_in_series
|
|||
execute:frappe.delete_doc("Page", "modules", ignore_missing=True)
|
||||
frappe.patches.v11_0.set_default_letter_head_source
|
||||
frappe.patches.v12_0.setup_comments_from_communications
|
||||
frappe.patches.v12_0.init_desk_settings #11-03-2019
|
||||
frappe.patches.v12_0.init_desk_settings #16-05-2019
|
||||
frappe.patches.v12_0.replace_null_values_in_tables
|
||||
frappe.patches.v12_0.reset_home_settings
|
||||
frappe.patches.v12_0.update_print_format_type
|
||||
|
|
@ -246,3 +246,4 @@ frappe.patches.v11_0.apply_customization_to_custom_doctype
|
|||
frappe.patches.v12_0.remove_feedback_rating
|
||||
frappe.patches.v12_0.move_form_attachments_to_attachments_folder
|
||||
frappe.patches.v12_0.move_timeline_links_to_dynamic_links
|
||||
frappe.patches.v12_0.delete_feedback_request_if_exists #1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql('''
|
||||
DELETE from `tabDocType`
|
||||
WHERE name = 'Feedback Request'
|
||||
''')
|
||||
|
|
@ -8,4 +8,4 @@ from frappe.desk.moduleview import get_onboard_items
|
|||
def execute():
|
||||
"""Reset the initial customizations for desk, with modules, indices and links."""
|
||||
frappe.reload_doc("core", "doctype", "user")
|
||||
frappe.db.sql("""update `tabUser` set home_settings = %s""", (''), debug=True)
|
||||
frappe.db.sql("""update tabUser set home_settings = ''""")
|
||||
|
|
|
|||
9
frappe/patches/v12_0/website_meta_tag_parent.py
Normal file
9
frappe/patches/v12_0/website_meta_tag_parent.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
# convert all /path to path
|
||||
frappe.db.sql('''
|
||||
UPDATE `tabWebsite Meta Tag`
|
||||
SET parent = SUBSTR(parent, 2)
|
||||
WHERE parent like '/%'
|
||||
''')
|
||||
|
|
@ -25,7 +25,7 @@ def migrate_style_settings():
|
|||
website_theme.no_sidebar = cint(frappe.db.get_single_value("Website Settings", "no_sidebar"))
|
||||
|
||||
website_theme.save()
|
||||
website_theme.use_theme()
|
||||
website_theme.set_as_default()
|
||||
|
||||
def map_color_fields(style_settings, website_theme):
|
||||
color_fields_map = {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"public/js/frappe/ui/messages.js",
|
||||
"public/js/frappe/translate.js",
|
||||
"public/js/frappe/utils/pretty_date.js",
|
||||
"public/js/lib/microtemplate.js",
|
||||
"public/js/frappe/microtemplate.js",
|
||||
"public/js/frappe/query_string.js",
|
||||
|
||||
"public/js/frappe/ui/dropzone.js",
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
"public/js/frappe/router_history.js",
|
||||
"public/js/frappe/defaults.js",
|
||||
"public/js/frappe/roles_editor.js",
|
||||
"public/js/lib/microtemplate.js",
|
||||
"public/js/frappe/microtemplate.js",
|
||||
|
||||
"public/js/legacy/handler.js",
|
||||
|
||||
|
|
@ -276,6 +276,7 @@
|
|||
"public/js/frappe/list/list_sidebar.js",
|
||||
"public/js/frappe/list/list_sidebar.html",
|
||||
"public/js/frappe/list/list_sidebar_stat.html",
|
||||
"public/js/frappe/list/list_sidebar_group_by.js",
|
||||
"public/js/frappe/list/list_view_permission_restrictions.html",
|
||||
|
||||
"public/js/frappe/views/gantt/gantt_view.js",
|
||||
|
|
|
|||
|
|
@ -464,6 +464,11 @@ frappe.Application = Class.extend({
|
|||
return frappe.call('frappe.client.get_hooks', { hook: 'app_logo_url' })
|
||||
.then(r => {
|
||||
frappe.app.logo_url = (r.message || []).slice(-1)[0];
|
||||
if (window.cordova) {
|
||||
let host = frappe.request.url;
|
||||
host = host.slice(0, host.length - 1);
|
||||
frappe.app.logo_url = host + frappe.app.logo_url;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -309,9 +309,8 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
|
|||
function get_filter_description(filter) {
|
||||
let doctype = filter[0];
|
||||
let fieldname = filter[1];
|
||||
let label = meta
|
||||
? frappe.meta.get_docfield(doctype, fieldname).label
|
||||
: frappe.model.unscrub(fieldname);
|
||||
let docfield = frappe.meta.get_docfield(doctype, fieldname);
|
||||
let label = docfield ? docfield.label : frappe.model.unscrub(fieldname);
|
||||
|
||||
let value = filter[3] == null || filter[3] === ''
|
||||
? __('empty')
|
||||
|
|
|
|||
|
|
@ -69,6 +69,8 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({
|
|||
|
||||
this.set_input_attributes();
|
||||
this.values = [];
|
||||
this._options = [];
|
||||
this._selected_values = [];
|
||||
this.highlighted = -1;
|
||||
},
|
||||
|
||||
|
|
@ -104,6 +106,20 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({
|
|||
this.update_status();
|
||||
},
|
||||
|
||||
set_value(value) {
|
||||
if (!value) return Promise.resolve();
|
||||
if (typeof value === 'string') {
|
||||
value = [value];
|
||||
}
|
||||
this.values = value;
|
||||
this.values.forEach(value => {
|
||||
this.update_selected_values(value);
|
||||
});
|
||||
this.parse_validate_and_set_in_model('');
|
||||
this.update_status();
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
update_selected_values(value) {
|
||||
this._selected_values = this._selected_values || [];
|
||||
let option = this._options.find(opt => opt.value === value);
|
||||
|
|
@ -122,7 +138,8 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({
|
|||
text = this.get_placeholder_text();
|
||||
} else if (this.values.length === 1) {
|
||||
let val = this.values[0];
|
||||
text = this._options.find(opt => opt.value === val).label;
|
||||
let option = this._options.find(opt => opt.value === val);
|
||||
text = option ? option.label : val;
|
||||
} else {
|
||||
text = __('{0} values selected', [this.values.length]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,14 +120,16 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
shortcut: 'shift+>',
|
||||
action: () => this.navigate_records(0),
|
||||
page: this.page,
|
||||
description: __('Go to next record')
|
||||
description: __('Go to next record'),
|
||||
condition: () => !this.is_new()
|
||||
});
|
||||
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: 'shift+<',
|
||||
action: () => this.navigate_records(1),
|
||||
page: this.page,
|
||||
description: __('Go to previous record')
|
||||
description: __('Go to previous record'),
|
||||
condition: () => !this.is_new()
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +542,9 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
|
||||
me.script_manager.trigger("after_save");
|
||||
// submit comment if entered
|
||||
me.timeline.comment_area.submit();
|
||||
if (me.timeline) {
|
||||
me.timeline.comment_area.submit();
|
||||
}
|
||||
me.refresh();
|
||||
} else {
|
||||
if(on_error) {
|
||||
|
|
|
|||
|
|
@ -606,7 +606,7 @@ export default class GridRow {
|
|||
}
|
||||
}
|
||||
|
||||
get_visible_columns(blacklist) {
|
||||
get_visible_columns(blacklist=[]) {
|
||||
var me = this;
|
||||
var visible_columns = $.map(this.docfields, function(df) {
|
||||
var visible = !df.hidden && df.in_list_view && me.grid.frm.get_perm(df.permlevel, "read")
|
||||
|
|
|
|||
|
|
@ -35,6 +35,20 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
|
|||
if(!this.date_field) {
|
||||
this.date_field = "transaction_date";
|
||||
}
|
||||
|
||||
// setters can be defined as a dict or a list of fields
|
||||
// setters define the additional filters that get applied
|
||||
// for selection
|
||||
|
||||
// CASE 1: DocType name and fieldname is the same, example "customer" and "customer"
|
||||
// setters define the filters applied in the modal
|
||||
// if the fieldnames and doctypes are consistently named,
|
||||
// pass a dict with the setter key and value, for example
|
||||
// {customer: [customer_name]}
|
||||
|
||||
// CASE 2: if the fieldname of the target is different,
|
||||
// then pass a list of fields with appropriate fieldname
|
||||
|
||||
if($.isArray(this.setters)) {
|
||||
for (let df of this.setters) {
|
||||
fields.push(df, {fieldtype: "Column Break"});
|
||||
|
|
@ -142,6 +156,7 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
|
|||
clearTimeout($this.data('timeout'));
|
||||
$this.data('timeout', setTimeout(function() {
|
||||
frappe.flags.auto_scroll = false;
|
||||
me.empty_list();
|
||||
me.get_results();
|
||||
}, 300));
|
||||
});
|
||||
|
|
@ -198,16 +213,15 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
|
|||
|
||||
render_result_list: function(results, more = 0) {
|
||||
var me = this;
|
||||
|
||||
var more_btn = me.dialog.fields_dict.more_btn.$wrapper;
|
||||
|
||||
// Make empty result set if filter is set
|
||||
if (!frappe.flags.auto_scroll) {
|
||||
this.$results.splice(1, this.$results.length);
|
||||
this.empty_list();
|
||||
}
|
||||
|
||||
if(results.length === 0) {
|
||||
this.$results.empty();
|
||||
this.empty_list();
|
||||
more_btn.hide();
|
||||
return;
|
||||
} else if(more) {
|
||||
|
|
@ -223,6 +237,10 @@ frappe.ui.form.MultiSelectDialog = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
empty_list: function() {
|
||||
this.$results.find('.list-item-container').remove();
|
||||
},
|
||||
|
||||
get_results: function() {
|
||||
let me = this;
|
||||
|
||||
|
|
|
|||
|
|
@ -484,7 +484,7 @@ frappe.ui.get_print_settings = function (pdf, callback, letter_head) {
|
|||
default: "Landscape"
|
||||
}];
|
||||
|
||||
frappe.prompt(columns, function (data) {
|
||||
return frappe.prompt(columns, function (data) {
|
||||
var data = $.extend(print_settings, data);
|
||||
if (!data.with_letter_head) {
|
||||
data.letter_head = null;
|
||||
|
|
|
|||
|
|
@ -159,17 +159,29 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
doc: me.dialog.doc
|
||||
},
|
||||
callback: function(r) {
|
||||
me.dialog.hide();
|
||||
// delete the old doc
|
||||
frappe.model.clear_doc(me.dialog.doc.doctype, me.dialog.doc.name);
|
||||
me.dialog.doc = r.message;
|
||||
if(frappe._from_link) {
|
||||
frappe.ui.form.update_calling_link(me.dialog.doc);
|
||||
|
||||
if (frappe.model.is_submittable(me.doctype)) {
|
||||
frappe.run_serially([
|
||||
() => me.dialog.working = true,
|
||||
() => {
|
||||
me.dialog.set_primary_action(__('Submit'), function() {
|
||||
me.submit(r.message);
|
||||
});
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
if(me.after_insert) {
|
||||
me.after_insert(me.dialog.doc);
|
||||
me.dialog.hide();
|
||||
// delete the old doc
|
||||
frappe.model.clear_doc(me.dialog.doc.doctype, me.dialog.doc.name);
|
||||
me.dialog.doc = r.message;
|
||||
if(frappe._from_link) {
|
||||
frappe.ui.form.update_calling_link(me.dialog.doc);
|
||||
} else {
|
||||
me.open_form_if_not_list();
|
||||
if(me.after_insert) {
|
||||
me.after_insert(me.dialog.doc);
|
||||
} else {
|
||||
me.open_form_if_not_list();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -185,6 +197,26 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
});
|
||||
},
|
||||
|
||||
submit: function(doc) {
|
||||
var me = this;
|
||||
frappe.call({
|
||||
method: "frappe.client.submit",
|
||||
args : {
|
||||
doc: doc
|
||||
},
|
||||
callback: function(r) {
|
||||
me.dialog.hide();
|
||||
// delete the old doc
|
||||
frappe.model.clear_doc(me.dialog.doc.doctype, me.dialog.doc.name);
|
||||
me.dialog.doc = r.message;
|
||||
if (frappe._from_link) {
|
||||
frappe.ui.form.update_calling_link(me.dialog.doc);
|
||||
}
|
||||
cur_frm.reload_doc();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
open_form_if_not_list: function() {
|
||||
let route = frappe.get_route();
|
||||
let doc = this.dialog.doc;
|
||||
|
|
|
|||
|
|
@ -142,13 +142,16 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
|
|||
}
|
||||
});
|
||||
|
||||
if (frm.is_new() && frm.meta.autoname === 'Prompt' && !frm.doc.__newname) {
|
||||
error_fields = [__('Name'), ...error_fields];
|
||||
}
|
||||
|
||||
if (error_fields.length) {
|
||||
if (doc.parenttype) {
|
||||
var message = __('Mandatory fields required in table {0}, Row {1}',
|
||||
[__(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label).bold(), doc.idx]);
|
||||
} else {
|
||||
var message = __('Mandatory fields required in {0}', [__(doc.doctype)]);
|
||||
|
||||
}
|
||||
message = message + '<br><br><ul><li>' + error_fields.join('</li><li>') + "</ul>";
|
||||
frappe.msgprint({
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ frappe.ui.form.ScriptManager = Class.extend({
|
|||
|
||||
function setup_add_fetch(df) {
|
||||
if((['Data', 'Read Only', 'Text', 'Small Text', 'Currency',
|
||||
'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date'].includes(df.fieldtype) || df.read_only==1)
|
||||
'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select'].includes(df.fieldtype) || df.read_only==1)
|
||||
&& df.fetch_from && df.fetch_from.indexOf(".")!=-1) {
|
||||
var parts = df.fetch_from.split(".");
|
||||
me.frm.add_fetch(parts[0], parts[1], df.fieldname);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
|
||||
this.bind_events();
|
||||
this.setup_keyboard_shortcuts();
|
||||
this.show_auto_repeat_status();
|
||||
frappe.ui.form.setup_user_image_event(this.frm);
|
||||
|
||||
this.refresh();
|
||||
|
|
@ -88,6 +89,28 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
}
|
||||
},
|
||||
|
||||
show_auto_repeat_status: function() {
|
||||
if (this.frm.meta.allow_auto_repeat && this.frm.doc.auto_repeat) {
|
||||
const me = this;
|
||||
frappe.call({
|
||||
method: "frappe.client.get_value",
|
||||
args:{
|
||||
doctype: "Auto Repeat",
|
||||
filters: {
|
||||
name: this.frm.doc.auto_repeat
|
||||
},
|
||||
fieldname: ["frequency"]
|
||||
},
|
||||
callback: function(res) {
|
||||
me.sidebar.find(".auto-repeat-status").html(__("Repeats {0}", [res.message.frequency]));
|
||||
me.sidebar.find(".auto-repeat-status").on("click", function(){
|
||||
frappe.set_route("Form", "Auto Repeat", me.frm.doc.auto_repeat);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh_comments: function() {
|
||||
$.map(this.frm.timeline.get_communications(), function(c) {
|
||||
return (c.communication_type==="Communication" || (c.communication_type=="Comment" && c.comment_type==="Comment")) ? c : null;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ frappe.ui.form.set_user_image = function(frm) {
|
|||
var image_field = frm.meta.image_field;
|
||||
var image = frm.doc[image_field];
|
||||
var title_image = frm.page.$title_area.find('.title-image');
|
||||
var image_actions = frm.sidebar.image_wrapper.find('.sidebar-image-actions');
|
||||
|
||||
|
||||
image_section.toggleClass('hide', image_field ? false : true);
|
||||
|
|
@ -32,6 +33,8 @@ frappe.ui.form.set_user_image = function(frm) {
|
|||
.css("background-image", 'url("' + image + '")')
|
||||
.html('');
|
||||
|
||||
image_actions.find('.sidebar-image-change, .sidebar-image-remove').show();
|
||||
|
||||
} else {
|
||||
image_section
|
||||
.find(".sidebar-image")
|
||||
|
|
@ -51,6 +54,8 @@ frappe.ui.form.set_user_image = function(frm) {
|
|||
.css({'background-color': frappe.get_palette(title)})
|
||||
.html(frappe.get_abbr(title));
|
||||
|
||||
image_actions.find('.sidebar-image-change').show();
|
||||
image_actions.find('.sidebar-image-remove').hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -63,12 +68,27 @@ frappe.ui.form.setup_user_image_event = function(frm) {
|
|||
});
|
||||
}
|
||||
|
||||
// bind click on image_wrapper
|
||||
frm.sidebar.image_wrapper.on('click', function() {
|
||||
var field = frm.get_field(frm.meta.image_field);
|
||||
if(!field.$input) {
|
||||
field.make_input();
|
||||
frm.sidebar.image_wrapper.on('click', ':not(.sidebar-image-actions)', (e) => {
|
||||
let $target = $(e.currentTarget);
|
||||
if ($target.is('a.dropdown-toggle, .dropdown')) {
|
||||
return;
|
||||
}
|
||||
let dropdown = frm.sidebar.image_wrapper.find('.sidebar-image-actions .dropdown');
|
||||
dropdown.toggleClass('open');
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// bind click on image_wrapper
|
||||
frm.sidebar.image_wrapper.on('click', '.sidebar-image-change, .sidebar-image-remove', function(e) {
|
||||
let $target = $(e.currentTarget);
|
||||
var field = frm.get_field(frm.meta.image_field);
|
||||
if ($target.is('.sidebar-image-change')) {
|
||||
if(!field.$input) {
|
||||
field.make_input();
|
||||
}
|
||||
field.$input.trigger('click');
|
||||
} else {
|
||||
field.set_value('').then(() => frm.save());
|
||||
}
|
||||
field.$input.trigger('click');
|
||||
});
|
||||
}
|
||||
|
|
@ -12,6 +12,15 @@
|
|||
<div class="sidebar-standard-image">
|
||||
<div class="standard-image"></div>
|
||||
</div>
|
||||
<div class="sidebar-image-actions">
|
||||
<div class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ __("Change") }}</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a class="sidebar-image-change">{{ __("Upload") }}</a></li>
|
||||
<li><a class="sidebar-image-remove">{{ __("Remove") }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% if frm.meta.beta %}
|
||||
|
|
@ -72,6 +81,9 @@
|
|||
<li class="h6 viewers-label">{%= __("Currently Viewing") %}</li>
|
||||
<li class="form-viewers"></li>
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu">
|
||||
<a><li class="auto-repeat-status"><li></a>
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu">
|
||||
<li class="liked-by-parent">
|
||||
<span class="liked-by">
|
||||
|
|
@ -102,4 +114,4 @@
|
|||
<ul class="list-unstyled visible-xs visible-sm">
|
||||
|
||||
<li class="close-sidebar">Close</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -129,7 +129,10 @@ frappe.ui.form.Toolbar = Class.extend({
|
|||
if(frappe.model.can_email(null, me.frm) && me.frm.doc.docstatus < 2) {
|
||||
this.page.add_menu_item(__("Email"), function() {
|
||||
me.frm.email_doc();
|
||||
}, true, 'Ctrl+E');
|
||||
}, true, {
|
||||
shortcut: 'Ctrl+E',
|
||||
condition: () => !this.frm.is_new()
|
||||
});
|
||||
}
|
||||
|
||||
// go to field modal
|
||||
|
|
@ -168,7 +171,10 @@ frappe.ui.form.Toolbar = Class.extend({
|
|||
&& frappe.model.can_delete(me.frm.doctype)) {
|
||||
this.page.add_menu_item(__("Delete"), function() {
|
||||
me.frm.savetrash();
|
||||
}, true, 'Shift+Ctrl+D');
|
||||
}, true, {
|
||||
shortcut: 'Shift+Ctrl+D',
|
||||
condition: () => !this.frm.is_new()
|
||||
});
|
||||
}
|
||||
|
||||
if(frappe.user_roles.includes("System Manager") && me.frm.meta.issingle === 0) {
|
||||
|
|
@ -178,7 +184,7 @@ frappe.ui.form.Toolbar = Class.extend({
|
|||
})
|
||||
}, true);
|
||||
|
||||
if (frappe.boot.developer_mode===1 && me.frm.meta.issingle) {
|
||||
if (frappe.boot.developer_mode===1) {
|
||||
// edit doctype
|
||||
this.page.add_menu_item(__("Edit DocType"), function() {
|
||||
frappe.set_route('Form', 'DocType', me.frm.doctype);
|
||||
|
|
@ -186,11 +192,21 @@ frappe.ui.form.Toolbar = Class.extend({
|
|||
}
|
||||
}
|
||||
|
||||
// Auto Repeat
|
||||
if(this.can_repeat()) {
|
||||
this.page.add_menu_item(__("Repeat"), function(){
|
||||
frappe.utils.new_auto_repeat_prompt(me.frm);
|
||||
}, true);
|
||||
}
|
||||
|
||||
// New
|
||||
if(p[CREATE] && !this.frm.meta.issingle) {
|
||||
this.page.add_menu_item(__("New {0}", [__(me.frm.doctype)]), function() {
|
||||
frappe.new_doc(me.frm.doctype, true);
|
||||
}, true, 'Ctrl+B');
|
||||
}, true, {
|
||||
shortcut: 'Ctrl+B',
|
||||
condition: () => !this.frm.is_new()
|
||||
});
|
||||
}
|
||||
|
||||
// Navigate
|
||||
|
|
@ -203,6 +219,11 @@ frappe.ui.form.Toolbar = Class.extend({
|
|||
});
|
||||
}
|
||||
},
|
||||
can_repeat: function() {
|
||||
return this.frm.meta.allow_auto_repeat
|
||||
&& !this.frm.is_new()
|
||||
&& !this.frm.doc.auto_repeat;
|
||||
},
|
||||
can_save: function() {
|
||||
return this.get_docstatus()===0;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ frappe.ui.form.States = Class.extend({
|
|||
},
|
||||
|
||||
refresh: function() {
|
||||
const me = this;
|
||||
// hide if its not yet saved
|
||||
if(this.frm.doc.__islocal) {
|
||||
this.set_default_state();
|
||||
|
|
@ -59,8 +58,6 @@ frappe.ui.form.States = Class.extend({
|
|||
// state text
|
||||
const state = this.get_state();
|
||||
|
||||
let doctype = this.frm.doctype;
|
||||
|
||||
if(state) {
|
||||
// show actions from that state
|
||||
this.show_actions(state);
|
||||
|
|
@ -71,8 +68,6 @@ frappe.ui.form.States = Class.extend({
|
|||
var added = false;
|
||||
var me = this;
|
||||
|
||||
this.frm.page.clear_actions_menu();
|
||||
|
||||
// if the loaded doc is dirty, don't show workflow buttons
|
||||
if (this.frm.doc.__unsaved===1) {
|
||||
return;
|
||||
|
|
@ -90,7 +85,8 @@ frappe.ui.form.States = Class.extend({
|
|||
}
|
||||
|
||||
frappe.workflow.get_transitions(this.frm.doc).then(transitions => {
|
||||
$.each(transitions, function(i, d) {
|
||||
this.frm.page.clear_actions_menu();
|
||||
transitions.forEach(d => {
|
||||
if(frappe.user_roles.includes(d.allowed) && has_approval_access(d)) {
|
||||
added = true;
|
||||
me.frm.page.add_action_item(__(d.action), function() {
|
||||
|
|
|
|||
|
|
@ -52,21 +52,31 @@
|
|||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="assigned-to" style="display: none">
|
||||
<a class = "dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#" onclick="return false;">
|
||||
{%= __("Assigned To") %} <span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu assigned-dropdown" style="max-height: 300px; overflow-y: auto;" role="menu">
|
||||
<li><div class="list-loading text-center assigned-loading text-muted">
|
||||
{%= (__("Loading") + "..." ) %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% if(frappe.help.has_help(doctype)) { %}
|
||||
<li><a class="help-link list-link" data-doctype="{{ doctype }}">{{ __("Help") }}</a></li>
|
||||
{% } %}
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu list-group-by">
|
||||
</ul>
|
||||
|
||||
<ul class="list-unstyled sidebar-menu sidebar-stat">
|
||||
<li class="list-sidebar-label stat-label">{{ __("Tags") }}</li>
|
||||
<li class="list-stats list-link">
|
||||
<div class="btn-group">
|
||||
<a class = "dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" href="#" onclick="return false;">
|
||||
{{ __("Tags") }}<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu list-stats-dropdown" role="menu">
|
||||
<div class="dropdown-search">
|
||||
<input type="text" placeholder="Search" class="form-control dropdown-search-input input-xs">
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-link" style="margin-top: 10px;">
|
||||
<a class="list-tag-preview hidden-xs text-muted">{{ __("Show tags") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu charts-menu hide">
|
||||
<li class="h6">{%= __("Charts") %}</li>
|
||||
<li class="list-link">
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.make();
|
||||
this.get_stats();
|
||||
this.cat_tags = [];
|
||||
}
|
||||
|
||||
|
|
@ -23,7 +22,7 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
this.sidebar = $('<div class="list-sidebar overlay-sidebar hidden-xs hidden-sm"></div>')
|
||||
.html(sidebar_content)
|
||||
.appendTo(this.page.sidebar.empty());
|
||||
|
||||
|
||||
this.setup_reports();
|
||||
this.setup_list_filter();
|
||||
this.setup_views();
|
||||
|
|
@ -31,6 +30,7 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
this.setup_calendar_view();
|
||||
this.setup_email_inbox();
|
||||
this.setup_keyboard_shortcuts();
|
||||
this.setup_list_group_by();
|
||||
|
||||
let limits = frappe.boot.limits;
|
||||
|
||||
|
|
@ -38,13 +38,14 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
this.setup_upgrade_box();
|
||||
}
|
||||
|
||||
if(this.doctype !== 'ToDo') {
|
||||
$('.assigned-to').show();
|
||||
if (this.list_view.list_view_settings && this.list_view.list_view_settings.disable_sidebar_stats) {
|
||||
this.sidebar.find('.sidebar-stat').remove();
|
||||
} else {
|
||||
this.sidebar.find('.list-stats').on('click', (e) => {
|
||||
$(e.currentTarget).find('.stat-link').remove();
|
||||
this.get_stats();
|
||||
});
|
||||
}
|
||||
$('.assigned-to').on('click', () => {
|
||||
$('.assigned').remove();
|
||||
this.setup_assigned_to();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -225,31 +226,6 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
});
|
||||
}
|
||||
|
||||
setup_assigned_to() {
|
||||
$('.assigned-loading').show();
|
||||
let dropdown = this.page.sidebar.find('.assigned-dropdown');
|
||||
let current_filters = this.list_view.get_filters_for_args();
|
||||
|
||||
frappe.call('frappe.desk.listview.get_user_assignments_and_count', {doctype: this.doctype, current_filters: current_filters}).then((data) => {
|
||||
$('.assigned-loading').hide();
|
||||
let current_user = data.message.find(user => user.name === frappe.session.user);
|
||||
if(current_user) {
|
||||
let current_user_count = current_user.count;
|
||||
this.get_html_for_assigned(frappe.session.user, current_user_count).appendTo(dropdown);
|
||||
}
|
||||
let user_list = data.message.filter(user => !['Guest', frappe.session.user, 'Administrator'].includes(user.name) && user.count!==0 );
|
||||
user_list.forEach((user) => {
|
||||
this.get_html_for_assigned(user.name, user.count).appendTo(dropdown);
|
||||
});
|
||||
$(".assigned-dropdown li a").on("click", (e) => {
|
||||
let assigned_user = $(e.currentTarget).find($('.assigned-user')).text();
|
||||
if(assigned_user === 'Me') assigned_user = frappe.session.user;
|
||||
this.list_view.filter_area.remove('_assign');
|
||||
this.list_view.filter_area.add(this.list_view.doctype, "_assign", "like", `%${assigned_user}%`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setup_keyboard_shortcuts() {
|
||||
this.sidebar.find('.list-link > a, .list-link > .btn-group > a').each((i, el) => {
|
||||
frappe.ui.keys
|
||||
|
|
@ -258,12 +234,38 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
});
|
||||
}
|
||||
|
||||
get_html_for_assigned(name, count) {
|
||||
if (name === frappe.session.user) name='Me';
|
||||
if (count > 99) count='99+';
|
||||
let html = $('<li class="assigned"><a class="badge-hover" href="#" onclick="return false;" role="assigned-item"><span class="assigned-user">'
|
||||
+ name + '</span><span class="badge pull-right" style="position:relative">' + count + '</span></a></li>');
|
||||
return html;
|
||||
setup_list_group_by() {
|
||||
this.list_group_by = new frappe.views.ListGroupBy({
|
||||
doctype: this.doctype,
|
||||
sidebar: this,
|
||||
list_view: this.list_view,
|
||||
page: this.page
|
||||
});
|
||||
}
|
||||
|
||||
setup_dropdown_search(dropdown, text_class) {
|
||||
let $dropdown_search = dropdown.find('.dropdown-search').show();
|
||||
let $search_input = $dropdown_search.find('.dropdown-search-input');
|
||||
$search_input.focus();
|
||||
$dropdown_search.on('click',(e)=>{
|
||||
e.stopPropagation();
|
||||
});
|
||||
let $elements = dropdown.find('li');
|
||||
$dropdown_search.on('keyup',()=> {
|
||||
let text_filter = $search_input.val().toLowerCase();
|
||||
let text;
|
||||
for (var i = 0; i < $elements.length; i++) {
|
||||
text = $elements.eq(i).find(text_class).text();
|
||||
if (text.toLowerCase().indexOf(text_filter) > -1) {
|
||||
$elements.eq(i).css('display','');
|
||||
} else {
|
||||
$elements.eq(i).css('display','none');
|
||||
}
|
||||
}
|
||||
});
|
||||
dropdown.parent().on('hide.bs.dropdown',()=> {
|
||||
$dropdown_search.val('');
|
||||
});
|
||||
}
|
||||
|
||||
setup_upgrade_box() {
|
||||
|
|
@ -302,9 +304,6 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
|
||||
get_stats() {
|
||||
var me = this;
|
||||
if (this.list_view.list_view_settings && this.list_view.list_view_settings.disable_sidebar_stats) {
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: 'frappe.desk.reportview.get_sidebar_stats',
|
||||
type: 'GET',
|
||||
|
|
@ -337,6 +336,8 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
//render normal stats
|
||||
me.render_stat("_user_tags", (r.message.stats || {})["_user_tags"]);
|
||||
}
|
||||
let stats_dropdown = me.sidebar.find('.list-stats-dropdown');
|
||||
me.setup_dropdown_search(stats_dropdown,'.stat-label');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -399,7 +400,7 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
me.list_view.refresh();
|
||||
});
|
||||
})
|
||||
.insertBefore(this.sidebar.find(".close-sidebar-button"));
|
||||
.appendTo(this.sidebar.find(".list-stats-dropdown"));
|
||||
}
|
||||
|
||||
set_fieldtype(df) {
|
||||
|
|
|
|||
175
frappe/public/js/frappe/list/list_sidebar_group_by.js
Normal file
175
frappe/public/js/frappe/list/list_sidebar_group_by.js
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
|
||||
frappe.provide('frappe.views');
|
||||
|
||||
frappe.views.ListGroupBy = class ListGroupBy {
|
||||
constructor(opts) {
|
||||
$.extend(this, opts);
|
||||
this.make_wrapper();
|
||||
|
||||
this.user_settings = frappe.get_user_settings(this.doctype);
|
||||
this.group_by_fields = ['assigned_to'];
|
||||
if(this.user_settings.group_by_fields) {
|
||||
this.group_by_fields = this.group_by_fields.concat(this.user_settings.group_by_fields);
|
||||
}
|
||||
this.render_group_by_items();
|
||||
this.make_group_by_fields_modal();
|
||||
this.setup_dropdown();
|
||||
this.setup_filter_by();
|
||||
}
|
||||
|
||||
make_group_by_fields_modal() {
|
||||
let d = new frappe.ui.Dialog ({
|
||||
title: __("Add Filter By"),
|
||||
fields: this.get_group_by_dropdown_fields()
|
||||
});
|
||||
d.set_primary_action("Add", ({ group_by_fields }) => {
|
||||
frappe.model.user_settings.save(this.doctype, 'group_by_fields', group_by_fields || null);
|
||||
this.group_by_fields = group_by_fields ? ['assigned_to', ...group_by_fields] : ['assigned_to'];
|
||||
this.render_group_by_items();
|
||||
d.hide();
|
||||
});
|
||||
|
||||
this.page.sidebar.find(".add-list-group-by a ").on("click", () => {
|
||||
d.show();
|
||||
});
|
||||
}
|
||||
|
||||
make_wrapper() {
|
||||
this.$wrapper = this.sidebar.sidebar.find('.list-group-by');
|
||||
let html = `
|
||||
<li class="list-sidebar-label">
|
||||
${__('Filter By')}
|
||||
</li>
|
||||
<div class="list-group-by-fields">
|
||||
</div>
|
||||
<li class="add-list-group-by list-link">
|
||||
<a class="add-group-by hidden-xs text-muted">
|
||||
${__("Add Fields")} <i class="octicon octicon-plus" style="margin-left: 2px;"></i>
|
||||
</a>
|
||||
</li>
|
||||
`;
|
||||
this.$wrapper.html(html);
|
||||
}
|
||||
|
||||
render_group_by_items() {
|
||||
let get_item_html = (fieldname) => {
|
||||
let label = fieldname === 'assigned_to'
|
||||
? __('Assigned To')
|
||||
: frappe.meta.get_label(this.doctype, fieldname);
|
||||
|
||||
return `<li class="group-by-field list-link">
|
||||
<div class="btn-group">
|
||||
<a class = "dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||
data-label="${label}" data-fieldname="${fieldname}" href="#" onclick="return false;">
|
||||
${__(label)}<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu group-by-dropdown" role="menu">
|
||||
<li><div class="list-loading text-center group-by-loading text-muted">
|
||||
${__("Loading...")}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>`;
|
||||
};
|
||||
let html = this.group_by_fields.map(get_item_html).join('');
|
||||
this.$wrapper.find('.list-group-by-fields').html(html);
|
||||
}
|
||||
|
||||
setup_dropdown() {
|
||||
this.$wrapper.on('click', '.group-by-field', (e)=> {
|
||||
let dropdown = $(e.currentTarget).find('.group-by-dropdown');
|
||||
let fieldname = $(e.currentTarget).find('a').attr('data-fieldname');
|
||||
this.get_group_by_count(fieldname).then(field_count_list => {
|
||||
if (field_count_list.length) {
|
||||
this.render_dropdown_items(field_count_list, dropdown);
|
||||
this.sidebar.setup_dropdown_search(dropdown, '.group-by-value');
|
||||
} else {
|
||||
dropdown.find('.group-by-loading').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get_group_by_dropdown_fields() {
|
||||
let group_by_fields = [];
|
||||
let fields = this.list_view.meta.fields.filter((f)=> ["Select", "Link"].includes(f.fieldtype));
|
||||
group_by_fields.push({
|
||||
label: __(this.doctype),
|
||||
fieldname: 'group_by_fields',
|
||||
fieldtype: 'MultiCheck',
|
||||
columns: 2,
|
||||
options: fields
|
||||
.map(df => ({
|
||||
label: __(df.label),
|
||||
value: df.fieldname,
|
||||
checked: this.group_by_fields.includes(df.fieldname)
|
||||
}))
|
||||
});
|
||||
return group_by_fields;
|
||||
}
|
||||
|
||||
get_group_by_count(field) {
|
||||
let args = {
|
||||
doctype: this.doctype,
|
||||
current_filters: this.list_view.get_filters_for_args(),
|
||||
field: field,
|
||||
};
|
||||
return frappe.call('frappe.desk.listview.get_group_by_count', args).then((r) => {
|
||||
let field_counts = r.message || [];
|
||||
field_counts = field_counts.filter(f => f.count !== 0);
|
||||
if (field === 'assigned_to') {
|
||||
field_counts = field_counts.filter(f => !['Guest', 'Administrator'].includes(f.name));
|
||||
}
|
||||
return field_counts;
|
||||
});
|
||||
}
|
||||
|
||||
render_dropdown_items(fields, dropdown) {
|
||||
let get_dropdown_html = (field) => {
|
||||
let label = field.name == null ? __('Not Specified') : field.name;
|
||||
if (label === frappe.session.user) {
|
||||
label = __('Me');
|
||||
}
|
||||
let value = field.name == null ? '' : encodeURIComponent(field.name);
|
||||
|
||||
return `<li class="group-by-item" data-value="${value}">
|
||||
<a class="badge-hover" href="#" onclick="return false;">
|
||||
<span class="group-by-value">${label}</span>
|
||||
<span class="badge pull-right group-by-count">${field.count}</span>
|
||||
</a>
|
||||
</li>`;
|
||||
};
|
||||
let standard_html = `
|
||||
<div class="dropdown-search">
|
||||
<input type="text" placeholder="${__('Search')}" class="form-control dropdown-search-input input-xs">
|
||||
</div>
|
||||
`;
|
||||
let dropdown_html = standard_html + fields.map(get_dropdown_html).join('');
|
||||
dropdown.html(dropdown_html);
|
||||
}
|
||||
|
||||
setup_filter_by() {
|
||||
this.$wrapper.on('click', '.group-by-item', (e) => {
|
||||
let $target = $(e.currentTarget);
|
||||
let fieldname = $target.parents('.group-by-field').find('a').data('fieldname');
|
||||
let value = decodeURIComponent($target.data('value').trim());
|
||||
fieldname = fieldname === 'assigned_to' ? '_assign': fieldname;
|
||||
|
||||
this.list_view.filter_area.remove(fieldname);
|
||||
|
||||
let operator = '=';
|
||||
if (value === '') {
|
||||
operator = 'is';
|
||||
value = 'not set';
|
||||
}
|
||||
if (fieldname === '_assign') {
|
||||
operator = 'like';
|
||||
value = `%${value}%`;
|
||||
}
|
||||
|
||||
this.list_view.filter_area.add(this.doctype, fieldname, operator, value);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -1,22 +1,16 @@
|
|||
<ul class="list-unstyled sidebar-menu sidebar-stat">
|
||||
<li class="divider"></li>
|
||||
<li class="h6 stat-label">{{ label }}</li>
|
||||
{% if(!stat.length) { %}
|
||||
<li class="stat-no-records text-muted">{{ __("No records tagged.") }}</li>
|
||||
{% } else {
|
||||
for (var i=0, l=stat.length; i < l; i++) {
|
||||
var stat_label = stat[i][0];
|
||||
var stat_count = stat[i][1];
|
||||
%}
|
||||
<li>
|
||||
<a class="stat-link badge-hover" data-label="{{ stat_label %}" data-field="{{ field %}">
|
||||
<span class="badge">{{ stat_count }}</span>
|
||||
<span>{{ __(stat_label) }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% }
|
||||
} %}
|
||||
</ul>
|
||||
<div style="margin-top: 10px;">
|
||||
<a class="list-tag-preview hidden-xs text-muted">{{ __("Show tags") }}</a>
|
||||
</div>
|
||||
|
||||
{% if(!stat.length) { %}
|
||||
<li class="stat-no-records text-muted">{{ __("No records tagged.") }}</li>
|
||||
{% } else {
|
||||
for (var i=0, l=stat.length; i < l; i++) {
|
||||
var stat_label = stat[i][0];
|
||||
var stat_count = stat[i][1];
|
||||
%}
|
||||
<li>
|
||||
<a class="stat-link badge-hover" data-label="{{ stat_label %}" data-field="{{ field %}" href="#" onclick="return false;">
|
||||
<span class="badge pull-right" style="position: relative">{{ stat_count }}</span>
|
||||
<span class="stat-label">{{ __(stat_label) }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% }
|
||||
} %}
|
||||
|
|
|
|||
|
|
@ -655,7 +655,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
get_count_str() {
|
||||
const current_count = this.data.length;
|
||||
let current_count = this.data.length;
|
||||
let count_without_children = this.data.uniqBy(d => d.name).length;
|
||||
|
||||
const filters = this.get_filters_for_args();
|
||||
const with_child_table_filter = filters.some(filter => {
|
||||
return filter[0] !== this.doctype;
|
||||
|
|
@ -676,7 +678,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
}).then(r => {
|
||||
this.total_count = r.message.values[0][0] || current_count;
|
||||
const str = __('{0} of {1}', [current_count, this.total_count]);
|
||||
let str = __('{0} of {1}', [current_count, this.total_count]);
|
||||
if (count_without_children !== current_count) {
|
||||
str = __('{0} of {1} ({2} rows with children)', [count_without_children, this.total_count, current_count]);
|
||||
}
|
||||
return str;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,11 @@ frappe.call = function(opts) {
|
|||
let url = opts.url;
|
||||
if (!url) {
|
||||
url = '/api/method/' + args.cmd;
|
||||
if (window.cordova) {
|
||||
let host = frappe.request.url;
|
||||
host = host.slice(0, host.length - 1);
|
||||
url = host + url;
|
||||
}
|
||||
delete args.cmd;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ frappe.ui.keys.bind_shortcut_group_event = () => {
|
|||
highlight_alt_shortcuts();
|
||||
}
|
||||
|
||||
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key && e.altKey) {
|
||||
let shortcut = get_shortcut_for_key(key);
|
||||
if (shortcut) {
|
||||
|
|
|
|||
|
|
@ -21,17 +21,21 @@ frappe.ui.keys.setup = function() {
|
|||
|
||||
let standard_shortcuts = [];
|
||||
frappe.ui.keys.standard_shortcuts = standard_shortcuts;
|
||||
frappe.ui.keys.add_shortcut = ({shortcut, action, description, page, target, ignore_inputs = false} = {}) => {
|
||||
frappe.ui.keys.add_shortcut = ({shortcut, action, description, page, target, condition, ignore_inputs = false} = {}) => {
|
||||
if (target instanceof jQuery) {
|
||||
let $target = target;
|
||||
action = () => {
|
||||
$target[0].click();
|
||||
}
|
||||
}
|
||||
frappe.ui.keys.on(shortcut, (e) => {
|
||||
if (!condition) {
|
||||
condition = () => true;
|
||||
}
|
||||
let handler = (e) => {
|
||||
let $focused_element = $(document.activeElement);
|
||||
let is_input_focused = $focused_element.is('input, select, textarea, [contenteditable=true]');
|
||||
if (is_input_focused && !ignore_inputs) return;
|
||||
if (!condition()) return;
|
||||
|
||||
if (!page || page.wrapper.is(':visible')) {
|
||||
let prevent_default = action(e);
|
||||
|
|
@ -41,11 +45,19 @@ frappe.ui.keys.add_shortcut = ({shortcut, action, description, page, target, ign
|
|||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// monkey patch page to handler
|
||||
handler.page = page;
|
||||
// remove handler with the same page attached to it
|
||||
frappe.ui.keys.off(shortcut, page);
|
||||
// attach new handler
|
||||
frappe.ui.keys.on(shortcut, handler);
|
||||
|
||||
// update standard shortcut list
|
||||
let existing_shortcut_index = standard_shortcuts.findIndex(
|
||||
s => s.shortcut === shortcut
|
||||
);
|
||||
let new_shortcut = { shortcut, action, description, page };
|
||||
let new_shortcut = { shortcut, action, description, page, condition };
|
||||
if (existing_shortcut_index === -1) {
|
||||
standard_shortcuts.push(new_shortcut);
|
||||
} else {
|
||||
|
|
@ -54,6 +66,8 @@ frappe.ui.keys.add_shortcut = ({shortcut, action, description, page, target, ign
|
|||
}
|
||||
|
||||
frappe.ui.keys.show_keyboard_shortcut_dialog = () => {
|
||||
if (frappe.ui.keys.is_dialog_shown) return;
|
||||
|
||||
let global_shortcuts = standard_shortcuts.filter(shortcut => !shortcut.page);
|
||||
let current_page_shortcuts = standard_shortcuts.filter(
|
||||
shortcut => shortcut.page && shortcut.page === window.cur_page.page.page);
|
||||
|
|
@ -62,19 +76,21 @@ frappe.ui.keys.show_keyboard_shortcut_dialog = () => {
|
|||
if (!shortcuts.length) {
|
||||
return '';
|
||||
}
|
||||
let html = shortcuts.map(shortcut => {
|
||||
let shortcut_label = shortcut.shortcut
|
||||
.split('+')
|
||||
.map(frappe.utils.to_title_case)
|
||||
.join('+');
|
||||
if (frappe.utils.is_mac()) {
|
||||
shortcut_label = shortcut_label.replace('Ctrl', '⌘');
|
||||
}
|
||||
return `<tr>
|
||||
<td width="40%"><kbd>${shortcut_label}</kbd></td>
|
||||
<td width="60%">${shortcut.description || ''}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
let html = shortcuts
|
||||
.filter(s => s.condition ? s.condition() : true)
|
||||
.map(shortcut => {
|
||||
let shortcut_label = shortcut.shortcut
|
||||
.split('+')
|
||||
.map(frappe.utils.to_title_case)
|
||||
.join('+');
|
||||
if (frappe.utils.is_mac()) {
|
||||
shortcut_label = shortcut_label.replace('Ctrl', '⌘');
|
||||
}
|
||||
return `<tr>
|
||||
<td width="40%"><kbd>${shortcut_label}</kbd></td>
|
||||
<td width="60%">${shortcut.description || ''}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
html = `<h5 style="margin: 0;">${heading}</h5>
|
||||
<table style="margin-top: 10px;" class="table table-bordered">
|
||||
${html}
|
||||
|
|
@ -87,6 +103,9 @@ frappe.ui.keys.show_keyboard_shortcut_dialog = () => {
|
|||
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Keyboard Shortcuts'),
|
||||
on_hide() {
|
||||
frappe.ui.keys.is_dialog_shown = false;
|
||||
}
|
||||
});
|
||||
|
||||
dialog.$body.append(global_shortcuts_html);
|
||||
|
|
@ -98,6 +117,7 @@ frappe.ui.keys.show_keyboard_shortcut_dialog = () => {
|
|||
`);
|
||||
|
||||
dialog.show();
|
||||
frappe.ui.keys.is_dialog_shown = true;
|
||||
}
|
||||
|
||||
frappe.ui.keys.get_key = function(e) {
|
||||
|
|
@ -130,6 +150,15 @@ frappe.ui.keys.on = function(key, handler) {
|
|||
frappe.ui.keys.handlers[key].push(handler);
|
||||
}
|
||||
|
||||
frappe.ui.keys.off = function(key, page) {
|
||||
let handlers = frappe.ui.keys.handlers[key];
|
||||
if (!handlers || handlers.length === 0) return;
|
||||
frappe.ui.keys.handlers[key] = handlers.filter(h => {
|
||||
if (!page) return false;
|
||||
return h.page !== page;
|
||||
});
|
||||
}
|
||||
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: 'ctrl+s',
|
||||
action: function(e) {
|
||||
|
|
@ -157,7 +186,7 @@ frappe.ui.keys.add_shortcut({
|
|||
e.preventDefault();
|
||||
$('.navbar-home img').click();
|
||||
},
|
||||
description: __('Home')
|
||||
description: __('Navigate Home')
|
||||
});
|
||||
|
||||
frappe.ui.keys.add_shortcut({
|
||||
|
|
@ -166,7 +195,7 @@ frappe.ui.keys.add_shortcut({
|
|||
e.preventDefault();
|
||||
$('.dropdown-navbar-user a').eq(0).click();
|
||||
},
|
||||
description: __('Settings')
|
||||
description: __('Open Settings')
|
||||
});
|
||||
|
||||
frappe.ui.keys.add_shortcut({
|
||||
|
|
@ -174,7 +203,7 @@ frappe.ui.keys.add_shortcut({
|
|||
action: function() {
|
||||
frappe.ui.keys.show_keyboard_shortcut_dialog();
|
||||
},
|
||||
description: __('Keyboard Shortcuts')
|
||||
description: __('Show Keyboard Shortcuts')
|
||||
});
|
||||
|
||||
frappe.ui.keys.add_shortcut({
|
||||
|
|
@ -183,7 +212,7 @@ frappe.ui.keys.add_shortcut({
|
|||
e.preventDefault();
|
||||
$('.dropdown-help a').eq(0).click();
|
||||
},
|
||||
description: __('Help')
|
||||
description: __('Open Help')
|
||||
});
|
||||
|
||||
frappe.ui.keys.on('escape', function(e) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
frappe.ui.LinkPreview = class {
|
||||
|
||||
constructor() {
|
||||
this.$links = [];
|
||||
this.popovers_list = [];
|
||||
this.LINK_CLASSES = 'a[data-doctype], input[data-fieldtype="Link"], .popover';
|
||||
this.popover_timeout = null;
|
||||
this.setup_events();
|
||||
|
|
@ -104,7 +104,7 @@ frappe.ui.LinkPreview = class {
|
|||
}
|
||||
|
||||
handle_popover_hide() {
|
||||
$(document.body).on('mouseout', this.LINK_CLASSES, () => {
|
||||
$(document).on('mouseout', this.LINK_CLASSES, () => {
|
||||
// To allow popover to be hovered on
|
||||
if (!$('.popover:hover').length) {
|
||||
this.link_hovered = false;
|
||||
|
|
@ -129,7 +129,7 @@ frappe.ui.LinkPreview = class {
|
|||
}
|
||||
|
||||
clear_all_popovers() {
|
||||
this.$links.forEach($el => $el.popover('hide'));
|
||||
this.popovers_list.forEach($el => $el.hide());
|
||||
}
|
||||
|
||||
get_preview_fields() {
|
||||
|
|
@ -190,7 +190,7 @@ frappe.ui.LinkPreview = class {
|
|||
$popover.addClass('link-preview-popover');
|
||||
$popover.toggleClass('control-field-popover', this.is_link);
|
||||
|
||||
this.$links.push(this.element);
|
||||
this.popovers_list.push(this.element.data('bs.popover'));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -313,21 +313,12 @@ frappe.ui.Page = Class.extend({
|
|||
|
||||
let $li;
|
||||
if (shortcut) {
|
||||
let shortcut_label = shortcut;
|
||||
if (frappe.utils.is_mac()) {
|
||||
shortcut_label = shortcut.replace('Ctrl', '⌘');
|
||||
}
|
||||
let shortcut_obj = this.prepare_shortcut_obj(shortcut, click, label);
|
||||
$li = $(`<li><a class="grey-link dropdown-item" href="#" onClick="return false;">
|
||||
<span class="menu-item-label">${label}</span>
|
||||
<span class="text-muted pull-right">${shortcut_label}</span>
|
||||
<span class="text-muted pull-right">${shortcut_obj.shortcut_label}</span>
|
||||
</a><li>`);
|
||||
shortcut = shortcut.toLowerCase();
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut,
|
||||
target: $li.find('a'),
|
||||
description: label,
|
||||
page: this
|
||||
});
|
||||
frappe.ui.keys.add_shortcut(shortcut_obj);
|
||||
} else {
|
||||
$li = $(`<li><a class="grey-link dropdown-item" href="#" onClick="return false;">
|
||||
<span class="menu-item-label">${label}</span></a><li>`);
|
||||
|
|
@ -354,6 +345,35 @@ frappe.ui.Page = Class.extend({
|
|||
return $link;
|
||||
},
|
||||
|
||||
prepare_shortcut_obj(shortcut, click, label) {
|
||||
let shortcut_obj;
|
||||
// convert to object, if shortcut string passed
|
||||
if (typeof shortcut === 'string') {
|
||||
shortcut_obj = { shortcut };
|
||||
} else {
|
||||
shortcut_obj = shortcut;
|
||||
}
|
||||
// label
|
||||
if (frappe.utils.is_mac()) {
|
||||
shortcut_obj.shortcut_label = shortcut_obj.shortcut.replace('Ctrl', '⌘');
|
||||
} else {
|
||||
shortcut_obj.shortcut_label = shortcut_obj.shortcut;
|
||||
}
|
||||
// actual shortcut string
|
||||
shortcut_obj.shortcut = shortcut_obj.shortcut.toLowerCase();
|
||||
// action is button click
|
||||
if (!shortcut_obj.action) {
|
||||
shortcut_obj.action = click;
|
||||
}
|
||||
// shortcut description can be button label
|
||||
if (!shortcut_obj.description) {
|
||||
shortcut_obj.description = label;
|
||||
}
|
||||
// page
|
||||
shortcut_obj.page = this;
|
||||
return shortcut_obj;
|
||||
},
|
||||
|
||||
/*
|
||||
* Check if there already exists a button with a specified label in a specified button group
|
||||
* @param {object} parent - This should be the `ul` of the button group.
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ frappe.ui.misc.about = function() {
|
|||
show_versions(r.message);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
show_versions(frappe.versions);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -258,4 +258,59 @@ frappe.utils.xss_sanitise = function (string, options) {
|
|||
}
|
||||
|
||||
return sanitised;
|
||||
}
|
||||
}
|
||||
|
||||
frappe.utils.new_auto_repeat_prompt = function(frm) {
|
||||
const fields = [
|
||||
{
|
||||
'fieldname': 'frequency',
|
||||
'fieldtype': 'Select',
|
||||
'label': __('Frequency'),
|
||||
'reqd': 1,
|
||||
'options': [
|
||||
{'label': __('Daily'), 'value': 'Daily'},
|
||||
{'label': __('Weekly'), 'value': 'Weekly'},
|
||||
{'label': __('Monthly'), 'value': 'Monthly'},
|
||||
{'label': __('Quarterly'), 'value': 'Quarterly'},
|
||||
{'label': __('Half-yearly'), 'value': 'Half-yearly'},
|
||||
{'label': __('Yearly'), 'value': 'Yearly'}
|
||||
]
|
||||
},
|
||||
{
|
||||
'fieldname': 'start_date',
|
||||
'fieldtype': 'Date',
|
||||
'label': __('Start Date'),
|
||||
'reqd': 1,
|
||||
'default': frappe.datetime.nowdate()
|
||||
},
|
||||
{
|
||||
'fieldname': 'end_date',
|
||||
'fieldtype': 'Date',
|
||||
'label': __('End Date')
|
||||
}
|
||||
];
|
||||
frappe.prompt(fields, function(values) {
|
||||
frappe.call({
|
||||
method: "frappe.automation.doctype.auto_repeat.auto_repeat.make_auto_repeat",
|
||||
args: {
|
||||
'doctype': frm.doc.doctype,
|
||||
'docname': frm.doc.name,
|
||||
'frequency': values['frequency'],
|
||||
'start_date': values['start_date'],
|
||||
'end_date': values['end_date']
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frappe.show_alert({
|
||||
'message': __("Auto Repeat created for this document"),
|
||||
'indicator': 'green'
|
||||
});
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
__('Auto Repeat'),
|
||||
__('Save')
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,16 @@ frappe.breadcrumbs = {
|
|||
"Dashboard Chart Source": "Customization",
|
||||
},
|
||||
|
||||
module_map: {
|
||||
'Core': 'Settings',
|
||||
'Email': 'Settings',
|
||||
'Custom': 'Settings',
|
||||
'Workflow': 'Settings',
|
||||
'Printing': 'Settings',
|
||||
'Automation': 'Settings',
|
||||
'Setup': 'Settings',
|
||||
},
|
||||
|
||||
set_doctype_module: function(doctype, module) {
|
||||
localStorage["preferred_breadcrumbs:" + doctype] = module;
|
||||
},
|
||||
|
|
@ -73,8 +83,8 @@ frappe.breadcrumbs = {
|
|||
}
|
||||
|
||||
if(breadcrumbs.module) {
|
||||
if(in_list(["Core", "Email", "Custom", "Workflow", "Print"], breadcrumbs.module)) {
|
||||
breadcrumbs.module = "Setup";
|
||||
if (frappe.breadcrumbs.module_map[breadcrumbs.module]) {
|
||||
breadcrumbs.module = frappe.breadcrumbs.module_map[breadcrumbs.module];
|
||||
}
|
||||
|
||||
if(frappe.get_module(breadcrumbs.module)) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
v-if="!hidden"
|
||||
class="border module-box"
|
||||
:class="{ 'hovered-box': hovered }"
|
||||
:data-module-name="module_name"
|
||||
>
|
||||
<div class="flush-top">
|
||||
<div class="module-box-content">
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
<a class="module-box-link" :href="type === 'module' ? '#modules/' + module_name : link">
|
||||
<h4 class="h4">
|
||||
<div>
|
||||
<i :class="iconClass" style="color:#8d99a6;font-size:18px;margin-right:6px;"></i>
|
||||
<i :class="icon_class" style="color:#8d99a6;font-size:18px;margin-right:6px;"></i>
|
||||
{{ label }}
|
||||
</div>
|
||||
</h4>
|
||||
|
|
@ -54,7 +55,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
iconClass() {
|
||||
icon_class() {
|
||||
if (this.icon) {
|
||||
return this.icon;
|
||||
} else {
|
||||
|
|
@ -82,7 +83,12 @@ export default {
|
|||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.module-box:hover {
|
||||
.module-box.sortable-chosen {
|
||||
background-color: @disabled-background;
|
||||
border-color: @disabled-background;
|
||||
}
|
||||
|
||||
.modules-container:not(.dragging) .module-box:hover {
|
||||
border-color: @text-muted;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
<div class="module-category h6 uppercase">{{ category }}</div>
|
||||
</div>
|
||||
|
||||
<div class="modules-container">
|
||||
<div class="modules-container" :class="{'dragging': dragging}" ref="modules-container">
|
||||
<desk-module-box
|
||||
v-for="(module, index) in modules"
|
||||
:key="module.name"
|
||||
:key="module.module_name"
|
||||
:index="index"
|
||||
v-bind="module"
|
||||
@customize="show_module_card_customize_dialog(module)"
|
||||
|
|
@ -24,7 +24,32 @@ export default {
|
|||
components: {
|
||||
DeskModuleBox
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dragging: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setup_sortable();
|
||||
},
|
||||
methods: {
|
||||
setup_sortable() {
|
||||
let modules_container =this.$refs['modules-container'];
|
||||
this.sortable = new Sortable(modules_container, {
|
||||
animation: 150,
|
||||
onStart: () => this.dragging = true,
|
||||
onEnd: () => {
|
||||
this.dragging = false;
|
||||
let modules = Array.from(modules_container.querySelectorAll('.module-box'))
|
||||
.map(node => node.dataset.moduleName);
|
||||
|
||||
this.$emit('module-order-change', {
|
||||
module_category: this.category,
|
||||
modules
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
show_module_card_customize_dialog(module) {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Customize Shortcuts'),
|
||||
|
|
@ -34,7 +59,7 @@ export default {
|
|||
fieldname: 'links',
|
||||
fieldtype: 'MultiSelectPills',
|
||||
get_data() {
|
||||
return frappe.call('frappe.desk.moduleview.get_links', {
|
||||
return frappe.call('frappe.desk.moduleview.get_links_for_module', {
|
||||
app: module.app,
|
||||
module: module.module_name,
|
||||
}).then(r => r.message);
|
||||
|
|
@ -48,7 +73,7 @@ export default {
|
|||
module_name: module.module_name,
|
||||
links
|
||||
}).then(r => {
|
||||
this.$emit('update_home_settings', r.message);
|
||||
this.$emit('update-desktop-settings', r.message);
|
||||
});
|
||||
d.hide();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@
|
|||
v-if="get_modules_for_category(category).length"
|
||||
:category="category"
|
||||
:modules="get_modules_for_category(category)"
|
||||
@update_home_settings="hs => update_modules_with_home_settings(hs)"
|
||||
@update-desktop-settings="update_desktop_settings"
|
||||
@module-order-change="update_module_order"
|
||||
>
|
||||
</desk-section>
|
||||
</div>
|
||||
|
|
@ -30,99 +31,96 @@ export default {
|
|||
DeskSection
|
||||
},
|
||||
data() {
|
||||
let modules_list = frappe.boot.allowed_modules
|
||||
.filter(d => (d.type==='module' || d.category==='Places') && !d.blocked)
|
||||
.map(d => {
|
||||
d.links = (d.links || []).map(link => {
|
||||
link.route = generate_route(link);
|
||||
return link;
|
||||
});
|
||||
return d;
|
||||
});
|
||||
|
||||
return {
|
||||
module_categories: ['Modules', 'Domains', 'Places', 'Administration'],
|
||||
modules: modules_list,
|
||||
modules: [],
|
||||
home_settings_fetched: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch_home_settings();
|
||||
this.fetch_desktop_settings();
|
||||
},
|
||||
methods: {
|
||||
fetch_home_settings() {
|
||||
return frappe.db.get_value('User', user, 'home_settings')
|
||||
fetch_desktop_settings() {
|
||||
frappe.call('frappe.desk.moduleview.get_desktop_settings')
|
||||
.then(r => {
|
||||
let home_settings = JSON.parse(r.message.home_settings || '{}');
|
||||
this.update_modules_with_home_settings(home_settings);
|
||||
this.home_settings_fetched = true;
|
||||
if (r.message) {
|
||||
this.update_desktop_settings(r.message);
|
||||
this.home_settings_fetched = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
update_modules_with_home_settings(home_settings) {
|
||||
this.modules = this.modules.map(m => {
|
||||
let hidden_modules = home_settings.hidden_modules || [];
|
||||
m.hidden = hidden_modules.includes(m.module_name);
|
||||
|
||||
let links = home_settings.links && home_settings.links[m.module_name];
|
||||
|
||||
if (links) {
|
||||
links = JSON.parse(links);
|
||||
|
||||
let default_links = m.links.map(link => link.name);
|
||||
m.links = m.links.map(link => {
|
||||
link.hidden = !links.includes(link.name);
|
||||
update_desktop_settings(desktop_settings) {
|
||||
this.modules = this.add_routes_for_module_links(desktop_settings);
|
||||
},
|
||||
add_routes_for_module_links(user_settings) {
|
||||
for (let category in user_settings) {
|
||||
user_settings[category] = user_settings[category].map(m => {
|
||||
m.links = (m.links || []).map(link => {
|
||||
link.route = generate_route(link);
|
||||
return link;
|
||||
});
|
||||
let new_links = links
|
||||
.filter(link => !default_links.includes(link))
|
||||
.filter(Boolean)
|
||||
.map(link => {
|
||||
let new_link = { name: link, label: link, type: 'doctype' };
|
||||
new_link.route = generate_route(new_link);
|
||||
return new_link;
|
||||
});
|
||||
m.links = m.links.concat(new_links);
|
||||
}
|
||||
|
||||
return m;
|
||||
});
|
||||
return m;
|
||||
});
|
||||
}
|
||||
return user_settings;
|
||||
},
|
||||
update_module_order({ module_category, modules }) {
|
||||
frappe.call('frappe.desk.moduleview.update_modules_order', { module_category, modules });
|
||||
},
|
||||
get_modules_for_category(category) {
|
||||
return this.modules.filter(m => m.category === category && !m.hidden);
|
||||
return this.modules[category] || [];
|
||||
},
|
||||
show_hide_cards_dialog() {
|
||||
let fields = this.module_categories.map(category => {
|
||||
let modules = this.modules.filter(m => m.category === category);
|
||||
let options = modules.map(
|
||||
m => ({ label: m.label, value: m.module_name, checked: !m.hidden })
|
||||
);
|
||||
return {
|
||||
label: category,
|
||||
fieldname: category,
|
||||
fieldtype: 'MultiCheck',
|
||||
options,
|
||||
columns: 2
|
||||
}
|
||||
});
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Show / Hide Cards'),
|
||||
fields: fields.filter(f => f.options.length > 0),
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: (values) => {
|
||||
let all_modules = this.modules.map(m => m.module_name);
|
||||
let modules_to_show = Object.keys(values).map(k => values[k]).flatMap(m => m);
|
||||
let modules_to_hide = all_modules.filter(m => !modules_to_show.includes(m));
|
||||
d.hide();
|
||||
frappe.call('frappe.desk.moduleview.get_options_for_show_hide_cards')
|
||||
.then(r => {
|
||||
let module_options = r.message;
|
||||
let fields = this.module_categories.map(category => {
|
||||
let options = module_options.filter(m => m.category === category);
|
||||
return {
|
||||
label: category,
|
||||
fieldname: category,
|
||||
fieldtype: 'MultiCheck',
|
||||
options,
|
||||
columns: 2
|
||||
}
|
||||
}).filter(f => f.options.length > 0);
|
||||
|
||||
frappe.call('frappe.desk.moduleview.hide_modules_from_desktop', {
|
||||
modules: modules_to_hide
|
||||
})
|
||||
.then(r => r.message)
|
||||
.then(hs => this.update_modules_with_home_settings(hs));
|
||||
}
|
||||
});
|
||||
let old_values = null;
|
||||
|
||||
d.show();
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Show / Hide Cards'),
|
||||
fields: fields,
|
||||
primary_action_label: __('Save'),
|
||||
primary_action: (values) => {
|
||||
|
||||
let category_map = {};
|
||||
for (let category of this.module_categories) {
|
||||
let old_modules = old_values[category] || [];
|
||||
let new_modules = values[category] || [];
|
||||
|
||||
let removed = old_modules.filter(module => !new_modules.includes(module));
|
||||
let added = new_modules.filter(module => !old_modules.includes(module));
|
||||
|
||||
category_map[category] = { added, removed };
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: 'frappe.desk.moduleview.update_hidden_modules',
|
||||
args: { category_map },
|
||||
btn: d.get_primary_btn()
|
||||
}).then(r => {
|
||||
this.update_desktop_settings(r.message)
|
||||
d.hide();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
|
||||
// deepcopy
|
||||
old_values = JSON.parse(JSON.stringify(d.get_values()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,15 @@
|
|||
<tr>
|
||||
{% for col in columns %}
|
||||
{% if col.name && col._id !== "_check" %}
|
||||
<th style="min-width: {{ col.minWidth }}px"
|
||||
<th
|
||||
{% if col.minWidth %}
|
||||
style="min-width: {{ col.minWidth }}px"
|
||||
{% endif %}
|
||||
{% if col.docfield && frappe.model.is_numeric_field(col.docfield) %}
|
||||
class="text-right"
|
||||
{% endif %}>{{ __(col.name) }}</th>
|
||||
{% endif %}
|
||||
>
|
||||
{{ __(col.name) }}</th>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -493,6 +493,15 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
if (!(options && options.data && options.data.labels && options.data.labels.length > 0)) return;
|
||||
|
||||
if (options.fieldtype) {
|
||||
options.tooltipOptions = {
|
||||
formatTooltipY: d => frappe.format(d, {
|
||||
fieldtype: options.fieldtype,
|
||||
options: options.options
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
@ -889,11 +898,16 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
options: ['Excel', 'CSV'],
|
||||
default: 'Excel',
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("Include indentation"),
|
||||
fieldname: "include_indentation",
|
||||
fieldtype: "Check",
|
||||
}
|
||||
], ({ file_format }) => {
|
||||
], ({ file_format, include_indentation }) => {
|
||||
if (file_format === 'CSV') {
|
||||
const column_row = this.columns.map(col => col.label);
|
||||
const data = this.get_data_for_csv();
|
||||
const data = this.get_data_for_csv(include_indentation);
|
||||
const out = [column_row].concat(data);
|
||||
|
||||
frappe.tools.downloadify(out, null, this.report_name);
|
||||
|
|
@ -914,6 +928,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
file_format_type: file_format,
|
||||
filters: filters,
|
||||
visible_idx,
|
||||
include_indentation,
|
||||
};
|
||||
|
||||
open_url_post(frappe.request.url, args);
|
||||
|
|
@ -921,7 +936,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}, __('Export Report: '+ this.report_name), __('Download'));
|
||||
}
|
||||
|
||||
get_data_for_csv() {
|
||||
get_data_for_csv(include_indentation) {
|
||||
const indices = this.datatable.bodyRenderer.visibleRowIndices;
|
||||
const rows = indices.map(i => this.datatable.datamanager.getRow(i));
|
||||
return rows.map(row => {
|
||||
|
|
@ -929,8 +944,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
return row
|
||||
.slice(standard_column_count)
|
||||
.map((cell, i) => {
|
||||
if (i === 0) {
|
||||
return ' '.repeat(row.meta.indent) + (cell.content || '');
|
||||
if (include_indentation && i===0) {
|
||||
cell.content = ' '.repeat(row.meta.indent) + (cell.content || '');
|
||||
}
|
||||
return cell.content || '';
|
||||
});
|
||||
|
|
@ -976,11 +991,12 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
{
|
||||
label: __('Print'),
|
||||
action: () => {
|
||||
frappe.ui.get_print_settings(
|
||||
let dialog = frappe.ui.get_print_settings(
|
||||
false,
|
||||
print_settings => this.print_report(print_settings),
|
||||
this.report_doc.letter_head
|
||||
);
|
||||
this.add_portrait_warning(dialog);
|
||||
},
|
||||
condition: () => frappe.model.can_print(this.report_doc.ref_doctype),
|
||||
standard: true
|
||||
|
|
@ -988,11 +1004,13 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
{
|
||||
label: __('PDF'),
|
||||
action: () => {
|
||||
frappe.ui.get_print_settings(
|
||||
let dialog = frappe.ui.get_print_settings(
|
||||
false,
|
||||
print_settings => this.pdf_report(print_settings),
|
||||
this.report_doc.letter_head
|
||||
);
|
||||
|
||||
this.add_portrait_warning(dialog);
|
||||
},
|
||||
condition: () => frappe.model.can_print(this.report_doc.ref_doctype),
|
||||
standard: true
|
||||
|
|
@ -1125,6 +1143,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
];
|
||||
}
|
||||
|
||||
add_portrait_warning(dialog) {
|
||||
if (this.columns.length > 10) {
|
||||
dialog.set_df_property('orientation', 'change', () => {
|
||||
let value = dialog.get_value('orientation');
|
||||
let description = value === 'Portrait'
|
||||
? __('Report with more than 10 columns looks better in Landscape mode.')
|
||||
: '';
|
||||
dialog.set_df_property('orientation', 'description', description);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
add_custom_column(custom_column, custom_data, link_field, column_field, insert_after) {
|
||||
const column = this.prepare_columns(custom_column);
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ frappe.views.TreeView = Class.extend({
|
|||
|
||||
this.page = this.parent.page;
|
||||
frappe.container.change_to(this.page_name);
|
||||
frappe.breadcrumbs.add(me.opts.breadcrumb || locals.DocType[me.doctype].module);
|
||||
frappe.breadcrumbs.add(me.opts.breadcrumb || locals.DocType[me.doctype].module, me.doctype);
|
||||
|
||||
this.set_title();
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
else return;
|
||||
}
|
||||
|
||||
set_default_values() {
|
||||
let values = frappe.utils.get_query_params();
|
||||
delete values.new;
|
||||
this.set_values(values);
|
||||
}
|
||||
|
||||
set_form_description(intro) {
|
||||
let intro_wrapper = document.getElementById('introduction');
|
||||
intro_wrapper.innerHTML = intro;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ frappe.ready(function() {
|
|||
|
||||
web_form.prepare(web_form_doc, r.message.doc || {});
|
||||
web_form.make();
|
||||
web_form.set_default_values();
|
||||
})
|
||||
|
||||
function get_data() {
|
||||
|
|
|
|||
|
|
@ -722,7 +722,9 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error, resolve,
|
|||
|
||||
me.script_manager.trigger("after_save");
|
||||
// submit comment if entered
|
||||
me.timeline.comment_area.submit();
|
||||
if (me.timeline) {
|
||||
me.timeline.comment_area.submit();
|
||||
}
|
||||
me.refresh();
|
||||
} else {
|
||||
if (on_error) {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@
|
|||
.form-inner-toolbar .dropdown-menu {
|
||||
right: 0px;
|
||||
left: auto;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.layout-main-section {
|
||||
|
|
|
|||
|
|
@ -126,9 +126,6 @@ body[data-route^="Module"] .main-menu {
|
|||
}
|
||||
}
|
||||
|
||||
.stat-link {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
a.close {
|
||||
position: absolute;
|
||||
|
|
@ -188,19 +185,32 @@ body[data-route^="Module"] .main-menu {
|
|||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.sidebar-image-wrapper:after {
|
||||
content: '\A';
|
||||
position: absolute;
|
||||
width: 100%; height:100%;
|
||||
top:0; left:0;
|
||||
background: #fff;
|
||||
opacity: 0;
|
||||
transition: all 0.5s;
|
||||
-webkit-transition: all 0.6s;
|
||||
.sidebar-image-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar-image-wrapper:hover:after {
|
||||
opacity: 0.5;
|
||||
.sidebar-image, .sidebar-standard-image {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.sidebar-image-wrapper:hover {
|
||||
.sidebar-image, .sidebar-standard-image {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.sidebar-image-actions {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-image-actions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -374,14 +384,25 @@ body[data-route^="Module"] .main-menu {
|
|||
}
|
||||
|
||||
|
||||
.sidebar-left .list-sidebar {
|
||||
.stat-label,
|
||||
.stat-no-records {
|
||||
.sidebar-padding;
|
||||
.list-sidebar {
|
||||
.list-sidebar-label {
|
||||
color: @text-muted;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0;
|
||||
font-size: @text-small;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
margin-bottom: -10px;
|
||||
.group-by-count {
|
||||
position:relative
|
||||
}
|
||||
|
||||
.group-by-dropdown, .list-stats-dropdown {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
max-width: 200px;
|
||||
}
|
||||
.dropdown-search {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -214,6 +214,8 @@ def revert(name, reason):
|
|||
if doc_to_revert.type != 'Auto':
|
||||
frappe.throw(_('This document cannot be reverted'))
|
||||
|
||||
if doc_to_revert.reverted: return
|
||||
|
||||
doc_to_revert.reverted = 1
|
||||
doc_to_revert.save(ignore_permissions=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,34 @@ class TestEnergyPointLog(unittest.TestCase):
|
|||
# no points for admin
|
||||
self.assertEquals(points_after_closing_todo, 0)
|
||||
|
||||
def test_revert_points_on_cancelled_doc(self):
|
||||
frappe.set_user('test@example.com')
|
||||
create_energy_point_rule_for_todo()
|
||||
created_todo = create_a_todo()
|
||||
created_todo.status = 'Closed'
|
||||
created_todo.save()
|
||||
|
||||
energy_point_logs = frappe.get_all('Energy Point Log')
|
||||
|
||||
self.assertEquals(len(energy_point_logs), 1)
|
||||
|
||||
# for submit and cancel permission
|
||||
frappe.set_user('Administrator')
|
||||
# submit
|
||||
created_todo.docstatus = 1
|
||||
created_todo.save()
|
||||
|
||||
# cancel
|
||||
created_todo.docstatus = 2
|
||||
created_todo.save()
|
||||
|
||||
energy_point_logs = frappe.get_all('Energy Point Log', fields=['reference_name', 'type', 'reverted'])
|
||||
|
||||
self.assertListEqual(energy_point_logs, [
|
||||
{'reference_name': created_todo.name, 'type': 'Revert', 'reverted': 0},
|
||||
{'reference_name': created_todo.name, 'type': 'Auto', 'reverted': 1}
|
||||
])
|
||||
|
||||
def create_energy_point_rule_for_todo(multiplier_field=None):
|
||||
name = 'ToDo Closed'
|
||||
point_rule = frappe.db.get_all(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
import frappe.cache_manager
|
||||
from frappe.model.document import Document
|
||||
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
|
||||
from frappe.social.doctype.energy_point_log.energy_point_log import create_energy_points_log
|
||||
from frappe.social.doctype.energy_point_log.energy_point_log import create_energy_points_log, revert
|
||||
|
||||
class EnergyPointRule(Document):
|
||||
def on_update(self):
|
||||
|
|
@ -51,10 +52,27 @@ def process_energy_points(doc, state):
|
|||
or not is_energy_point_enabled()):
|
||||
return
|
||||
|
||||
old_doc = doc.get_doc_before_save()
|
||||
|
||||
# check if doc has been cancelled
|
||||
if old_doc and old_doc.docstatus == 1 and doc.docstatus == 2:
|
||||
return revert_points_for_cancelled_doc(doc)
|
||||
|
||||
for d in frappe.cache_manager.get_doctype_map('Energy Point Rule', doc.doctype,
|
||||
dict(reference_doctype = doc.doctype, enabled=1)):
|
||||
frappe.get_doc('Energy Point Rule', d.get('name')).apply(doc)
|
||||
|
||||
|
||||
def revert_points_for_cancelled_doc(doc):
|
||||
energy_point_logs = frappe.get_all('Energy Point Log', {
|
||||
'reference_doctype': doc.doctype,
|
||||
'reference_name': doc.name,
|
||||
'type': 'Auto'
|
||||
})
|
||||
for log in energy_point_logs:
|
||||
revert(log.name, _('Reference document has been cancelled'))
|
||||
|
||||
|
||||
def get_energy_point_doctypes():
|
||||
return [
|
||||
d.reference_doctype for d in frappe.get_all('Energy Point Rule',
|
||||
|
|
|
|||
8
frappe/templates/emails/auto_repeat_fail.html
Normal file
8
frappe/templates/emails/auto_repeat_fail.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<p>{{ auto_repeat_failed_for }}</p>
|
||||
|
||||
<p>{{ _("The Auto Repeat for this document has been disabled.") }}</p>
|
||||
<p>{{ error_log_message }}</p>
|
||||
|
||||
<div class="more-info">
|
||||
{{_("This email is autogenerated")}}
|
||||
</div>
|
||||
8
frappe/templates/emails/data_deletion_approval.html
Normal file
8
frappe/templates/emails/data_deletion_approval.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<p>{{_("User {0} has requested for data deletion").format(user)}}.</p>
|
||||
<p>{{_("Click on the link below to approve the request")}}.</p>
|
||||
|
||||
<p style="margin: 30px 0px;">
|
||||
<a href="{{ url }}" rel="nofollow" class="btn btn-primary btn-sm primary-action" style="padding: 8px 20px;">
|
||||
{{ _("Confirm Request") }}
|
||||
</a>
|
||||
</p>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue