Browse Source

[Enhancement] Event timeline (#6133)

* Events in timeline wip

* Events in timeline

* Permission issue + split buttons between Frappe and ERPNext

* Add string type check

* Codacy corrections

* Permission issue

* Test and feedback corrections

* Codacy corrections

* Tests and codacy corrections

* Codacy correction

* Py3 correction

* Travis correction

* Py3 corrections"

* Travis and codacy corrections

* Removed space

* Add check for the removal of new lines

* Codacy rollup watch missing semicolon
version-14
Charles-Henri Decultot 6 years ago
committed by Rushabh Mehta
parent
commit
98abda32b4
28 changed files with 5628 additions and 4963 deletions
  1. +883
    -882
      frappe/contacts/doctype/contact/contact.json
  2. +23
    -0
      frappe/contacts/doctype/contact/test_contact.js
  3. +1615
    -1615
      frappe/core/doctype/communication/communication.json
  4. +2
    -16
      frappe/core/doctype/communication/email.py
  5. +2
    -2
      frappe/core/doctype/data_import/test_data_import.py
  6. +1948
    -1916
      frappe/core/doctype/doctype/doctype.json
  7. +1
    -1
      frappe/custom/doctype/customize_form/test_customize_form.py
  8. +61
    -6
      frappe/desk/doctype/event/event.js
  9. +63
    -129
      frappe/desk/doctype/event/event.json
  10. +57
    -1
      frappe/desk/doctype/event/event.py
  11. +0
    -0
      frappe/desk/doctype/event_participants/__init__.py
  12. +108
    -0
      frappe/desk/doctype/event_participants/event_participants.json
  13. +8
    -0
      frappe/desk/doctype/event_participants/event_participants.py
  14. +321
    -296
      frappe/desk/doctype/note/note.json
  15. +19
    -0
      frappe/desk/doctype/note/test_note.js
  16. +1
    -0
      frappe/patches.txt
  17. +18
    -0
      frappe/patches/v11_0/multiple_references_in_events.py
  18. +1
    -0
      frappe/public/build.json
  19. +6
    -1
      frappe/public/js/frappe/form/footer/timeline.html
  20. +98
    -53
      frappe/public/js/frappe/form/footer/timeline.js
  21. +3
    -3
      frappe/public/js/frappe/form/footer/timeline_item.html
  22. +6
    -1
      frappe/public/js/frappe/form/link_selector.js
  23. +9
    -36
      frappe/public/js/frappe/views/communication.js
  24. +351
    -0
      frappe/public/js/frappe/views/interaction.js
  25. +4
    -0
      frappe/public/less/form.less
  26. +1
    -2
      frappe/tests/test_document.py
  27. +18
    -2
      frappe/utils/file_manager.py
  28. +1
    -1
      rollup/watch.js

+ 883
- 882
frappe/contacts/doctype/contact/contact.json
File diff suppressed because it is too large
View File


+ 23
- 0
frappe/contacts/doctype/contact/test_contact.js View File

@@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line

QUnit.test("test: Contact", function (assert) {
let done = assert.async();

// number of asserts
assert.expect(1);

frappe.run_serially([
// insert a new Contact
() => frappe.tests.make('Contact', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);

});

+ 1615
- 1615
frappe/core/doctype/communication/communication.json
File diff suppressed because it is too large
View File


+ 2
- 16
frappe/core/doctype/communication/email.py View File

@@ -10,7 +10,7 @@ from email.utils import formataddr
from frappe.core.utils import get_parent_doc
from frappe.utils import (get_url, get_formatted_email, cint,
validate_email_add, split_emails, time_diff_in_seconds, parse_addr, get_datetime)
from frappe.utils.file_manager import get_file
from frappe.utils.file_manager import get_file, add_attachments
from frappe.email.queue import check_email_limit
from frappe.utils.scheduler import log
from frappe.email.email_body import get_message_id
@@ -83,7 +83,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =

# if not committed, delayed task doesn't find the communication
if attachments:
add_attachments(comm.name, attachments)
add_attachments("Communication", comm.name, attachments)

frappe.db.commit()

@@ -395,20 +395,6 @@ def get_bcc(doc, recipients=None, fetched_from_email_account=False):

return bcc

def add_attachments(name, attachments):
'''Add attachments to the given Communiction'''
from frappe.utils.file_manager import save_url

# loop through attachments
for a in attachments:
if isinstance(a, string_types):
attach = frappe.db.get_value("File", {"name":a},
["file_name", "file_url", "is_private"], as_dict=1)

# save attachments to new doc
save_url(attach.file_url, attach.file_name, "Communication", name,
"Home/Attachments", attach.is_private)

def filter_email_list(doc, email_list, exclude, is_cc=False, is_bcc=False):
# temp variables
filtered = []


+ 2
- 2
frappe/core/doctype/data_import/test_data_import.py View File

@@ -95,6 +95,6 @@ class TestDataImport(unittest.TestCase):
exporter.export_data("Event", all_doctypes=True, template=True, file_type="Excel")
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
content = read_xlsx_file_from_attached_file(fcontent=frappe.response.filecontent)
content.append(["", "_test", "Private", "05-11-2017 13:51:48", "0", "0", "", "1", "blue"])
content.append(["", "_test", "Private", "05-11-2017 13:51:48", "Event", "0", "0", "", "1", "", "", 0, 0, 0, 0, 0, 0, 0, "blue"])
importer.upload(content)
self.assertTrue(frappe.db.get_value("Event", {"subject": "_test"}, "name"))
self.assertTrue(frappe.db.get_value("Event", {"subject": "_test"}, "name"))

+ 1948
- 1916
frappe/core/doctype/doctype/doctype.json
File diff suppressed because it is too large
View File


+ 1
- 1
frappe/custom/doctype/customize_form/test_customize_form.py View File

@@ -46,7 +46,7 @@ class TestCustomizeForm(unittest.TestCase):

d = self.get_customize_form("Event")
self.assertEqual(d.doc_type, "Event")
self.assertEqual(len(d.get("fields")), 29)
self.assertEqual(len(d.get("fields")), 27)

d = self.get_customize_form("User")
self.assertEqual(d.doc_type, "User")


+ 61
- 6
frappe/desk/doctype/event/event.js View File

@@ -1,22 +1,31 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.provide("frappe.desk");

frappe.ui.form.on("Event", {
onload: function(frm) {
frm.set_query("ref_type", function(txt) {
frm.set_query('reference_doctype', "event_participants", function() {
return {
"filters": {
"issingle": 0,
}
};
});
})
},
refresh: function(frm) {
if(frm.doc.ref_type && frm.doc.ref_name) {
frm.add_custom_button(__(frm.doc.ref_name), function() {
frappe.set_route("Form", frm.doc.ref_type, frm.doc.ref_name);
});
if(frm.doc.event_participants) {
frm.doc.event_participants.forEach(value => {
frm.add_custom_button(__(value.reference_docname), function() {
frappe.set_route("Form", value.reference_doctype, value.reference_docname);
}, __("Participants"));
})
}

frm.page.set_inner_btn_group_as_primary(__("Add Participants"));

frm.add_custom_button(__('Add Contacts'), function() {
new frappe.desk.eventParticipants(frm, "Contact");
}, __("Add Participants"));
},
repeat_on: function(frm) {
if(frm.doc.repeat_on==="Every Day") {
@@ -28,3 +37,49 @@ frappe.ui.form.on("Event", {
}
});

frappe.ui.form.on("Event Participants", {
event_participants_remove: function(frm, cdt, cdn) {
if (cdt&&!cdn.includes("New Event Participants")){
frappe.call({
type: "POST",
method: "frappe.desk.doctype.event.event.delete_communication",
args: {
"event": frm.doc,
"reference_doctype": cdt,
"reference_docname": cdn
},
freeze: true,
callback: function(r) {
if(r.exc) {
frappe.show_alert({
message: __("{0}", [r.exc]),
indicator: 'orange'
});
}
}
});
}
}
});

frappe.desk.eventParticipants = class eventParticipants {
constructor(frm, doctype) {
this.frm = frm;
this.doctype = doctype;
this.make();
}

make() {
let me = this;

let table = me.frm.get_field("event_participants").grid;
new frappe.ui.form.LinkSelector({
doctype: me.doctype,
dynamic_link_field: "reference_doctype",
dynamic_link_reference: me.doctype,
fieldname: "reference_docname",
target: table,
txt: ""
});
}
};

+ 63
- 129
frappe/desk/doctype/event/event.json View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
@@ -83,54 +84,21 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "event_type",
"fieldname": "event_category",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Event Type",
"length": 0,
"no_copy": 0,
"oldfieldname": "event_type",
"oldfieldtype": "Select",
"options": "Private\nPublic\nCancel",
"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": 1,
"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,
"default": "1",
"fieldname": "send_reminder",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send an email reminder in the morning",
"label": "Event Category",
"length": 0,
"no_copy": 0,
"options": "Event\nMeeting\nCall\nSent/Received Email\nOther",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -149,26 +117,29 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "repeat_this_event",
"fieldtype": "Check",
"fieldname": "event_type",
"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": "Repeat this Event",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Event Type",
"length": 0,
"no_copy": 0,
"oldfieldname": "event_type",
"oldfieldtype": "Select",
"options": "Private\nPublic\nCancelled",
"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,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
@@ -180,8 +151,9 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"default": "1",
"fieldname": "send_reminder",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -189,6 +161,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send an email reminder in the morning",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -210,39 +183,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "starts_on",
"fieldtype": "Datetime",
"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": "Starts on",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ends_on",
"fieldtype": "Datetime",
"fieldname": "repeat_this_event",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -250,7 +192,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ends on",
"label": "Repeat this Event",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -272,8 +214,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "all_day",
"fieldtype": "Check",
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -281,7 +223,6 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "All Day",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -303,8 +244,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"fieldname": "starts_on",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -312,6 +253,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Starts on",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@@ -320,7 +262,7 @@
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
@@ -333,9 +275,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "color",
"fieldtype": "Color",
"fieldname": "ends_on",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -343,12 +284,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Color",
"label": "Ends on",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -367,8 +306,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"fieldname": "all_day",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -376,10 +315,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "All Day",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
@@ -749,7 +688,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -779,24 +718,24 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Text Editor",
"default": "",
"fieldname": "color",
"fieldtype": "Color",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Description",
"label": "Color",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Text",
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "300px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
@@ -804,8 +743,7 @@
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "300px"
"unique": 0
},
{
"allow_bulk_edit": 0,
@@ -814,7 +752,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "participants",
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@@ -823,10 +761,8 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Participants",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Section Break",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@@ -846,23 +782,24 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "groups",
"fieldtype": "Column Break",
"fieldname": "description",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Groups",
"label": "Description",
"length": 0,
"no_copy": 0,
"oldfieldtype": "Column Break",
"oldfieldname": "description",
"oldfieldtype": "Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "50%",
"print_width": "300px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
@@ -871,7 +808,7 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "50%"
"width": "300px"
},
{
"allow_bulk_edit": 0,
@@ -880,8 +817,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ref_type",
"fieldtype": "Link",
"fieldname": "participants",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -889,12 +826,10 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ref Type",
"label": "Participants",
"length": 0,
"no_copy": 0,
"oldfieldname": "ref_type",
"oldfieldtype": "Data",
"options": "DocType",
"oldfieldtype": "Section Break",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@@ -914,8 +849,8 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "ref_name",
"fieldtype": "Dynamic Link",
"fieldname": "event_participants",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -923,16 +858,15 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Ref Name",
"label": "Event Participants",
"length": 0,
"no_copy": 0,
"oldfieldname": "ref_name",
"oldfieldtype": "Data",
"options": "ref_type",
"options": "Event Participants",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@@ -953,7 +887,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-04 13:37:05.433906",
"modified": "2018-09-25 12:40:33.958600",
"modified_by": "Administrator",
"module": "Desk",
"name": "Event",


+ 57
- 1
frappe/desk/doctype/event/event.py View File

@@ -1,4 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
@@ -14,6 +14,7 @@ from frappe.utils.user import get_enabled_system_users
from frappe.desk.reportview import get_filters_cond

weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
communication_mapping = {"": "Event", "Event": "Event", "Meeting": "Meeting", "Call": "Phone", "Sent/Received Email": "Email", "Other": "Other"}

class Event(Document):
def validate(self):
@@ -30,6 +31,60 @@ class Event(Document):
if getdate(self.starts_on) != getdate(self.ends_on) and self.repeat_on == "Every Day":
frappe.msgprint(frappe._("Every day events should finish on the same day."), raise_exception=True)

def on_update(self):
self.sync_communication()

def on_trash(self):
communications = frappe.get_all("Communication", dict(reference_doctype=self.doctype, reference_name=self.name))
if communications:
for communication in communications:
frappe.get_doc("Communication", communication.name).delete()

def sync_communication(self):
if self.event_participants:
for participant in self.event_participants:
communication_name = frappe.db.get_value("Communication", dict(reference_doctype=self.doctype, reference_name=self.name, timeline_doctype=participant.reference_doctype, timeline_name=participant.reference_docname), "name")
if communication_name:
communication = frappe.get_doc("Communication", communication_name)
self.update_communication(participant, communication)
else:
meta = frappe.get_meta(participant.reference_doctype)
if hasattr(meta, "allow_events_in_timeline") and meta.allow_events_in_timeline==1:
self.create_communication(participant)

def create_communication(self, participant):
communication = frappe.new_doc("Communication")
self.update_communication(participant, communication)
self.communication = communication.name

def update_communication(self, participant, communication):
communication.communication_medium = "Event"
communication.subject = self.subject
communication.content = self.description if self.description else self.subject
communication.communication_date = self.starts_on
communication.timeline_doctype = participant.reference_doctype
communication.timeline_name = participant.reference_docname
communication.reference_doctype = self.doctype
communication.reference_name = self.name
communication.communication_medium = communication_mapping[self.event_category] if self.event_category else ""
communication.status = "Linked"
communication.save(ignore_permissions=True)

@frappe.whitelist()
def delete_communication(event, reference_doctype, reference_docname):
deleted_participant = frappe.get_doc(reference_doctype, reference_docname)
if isinstance(event, string_types):
event = json.loads(event)

communication_name = frappe.db.get_value("Communication", dict(reference_doctype=event["doctype"], reference_name=event["name"], timeline_doctype=deleted_participant.reference_doctype, timeline_name=deleted_participant.reference_docname), "name")
if communication_name:
deletion = frappe.get_doc("Communication", communication_name).delete()

return deletion

return {}


def get_permission_query_conditions(user):
if not user: user = frappe.session.user
return """(tabEvent.event_type='Public' or tabEvent.owner='%(user)s')""" % {
@@ -38,6 +93,7 @@ def get_permission_query_conditions(user):
}

def has_permission(doc, user):
frappe.log_error(doc.owner)
if doc.event_type=="Public" or doc.owner==user:
return True



+ 0
- 0
frappe/desk/doctype/event_participants/__init__.py View File


+ 108
- 0
frappe/desk/doctype/event_participants/event_participants.json View File

@@ -0,0 +1,108 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-09-21 15:44:58.836156",
"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": "reference_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reference Doctype",
"length": 0,
"no_copy": 0,
"options": "DocType",
"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,
"fieldname": "reference_docname",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reference Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"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
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-09-21 15:59:42.954829",
"modified_by": "Administrator",
"module": "Desk",
"name": "Event Participants",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"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
}

+ 8
- 0
frappe/desk/doctype/event_participants/event_participants.py View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document

class EventParticipants(Document):
pass

+ 321
- 296
frappe/desk/doctype/note/note.json View File

@@ -1,297 +1,322 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 1,
"beta": 0,
"creation": "2013-05-24 13:41:00",
"custom": 0,
"description": "",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"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": 1,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "public",
"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": "Public",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"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": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "public",
"fieldname": "notify_on_login",
"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": "Notify users with a popup when they log in",
"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,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "notify_on_login",
"description": "If enabled, users will be notified every time they login. If not enabled, users will only be notified once.",
"fieldname": "notify_on_every_login",
"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": "Notify Users On Every Login",
"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,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.notify_on_login && doc.public",
"fieldname": "expire_notification_on",
"fieldtype": "Date",
"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": "Expire Notification On",
"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": 1,
"set_only_once": 0,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"description": "Help: To link to another record in the system, use \"#Form/Note/[Note Name]\" as the Link URL. (don't use \"http://\")",
"fieldname": "content",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Content",
"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": 1,
"columns": 0,
"fieldname": "seen_by_section",
"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": "Seen By",
"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,
"unique": 0
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "seen_by",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Seen By Table",
"length": 0,
"no_copy": 0,
"options": "Note Seen By",
"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-file-text",
"idx": 1,
"image_view": 0,
"in_create": 0,

"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-02-17 17:02:57.095556",
"modified_by": "Administrator",
"module": "Desk",
"name": "Note",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 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": "All",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 1,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0
}
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"beta": 0,
"creation": "2013-05-24 13:41:00",
"custom": 0,
"description": "",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"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": 1,
"collapsible": 0,
"columns": 0,
"description": "",
"fieldname": "public",
"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": "Public",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"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": 1,
"collapsible": 0,
"columns": 0,
"depends_on": "public",
"fieldname": "notify_on_login",
"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": "Notify users with a popup when they log in",
"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": 1,
"collapsible": 0,
"columns": 0,
"default": "0",
"depends_on": "notify_on_login",
"description": "If enabled, users will be notified every time they login. If not enabled, users will only be notified once.",
"fieldname": "notify_on_every_login",
"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": "Notify Users On Every Login",
"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,
"depends_on": "eval:doc.notify_on_login && doc.public",
"fieldname": "expire_notification_on",
"fieldtype": "Date",
"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": "Expire Notification On",
"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": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 1,
"collapsible": 0,
"columns": 0,
"description": "Help: To link to another record in the system, use \"#Form/Note/[Note Name]\" as the Link URL. (don't use \"http://\")",
"fieldname": "content",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Content",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "seen_by_section",
"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": "Seen By",
"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": "seen_by",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Seen By Table",
"length": 0,
"no_copy": 0,
"options": "Note Seen By",
"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,
"icon": "fa fa-file-text",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-09-21 15:15:44.909636",
"modified_by": "Administrator",
"module": "Desk",
"name": "Note",
"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": "All",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 1,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

+ 19
- 0
frappe/desk/doctype/note/test_note.js View File

@@ -0,0 +1,19 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Note", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Note
() => frappe.tests.make('Note', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

+ 1
- 0
frappe/patches.txt View File

@@ -225,3 +225,4 @@ frappe.patches.v11_0.get_docs_apps_if_not_present
frappe.patches.v10_0.set_default_locking_time
frappe.patches.v11_0.rename_google_maps_doctype
frappe.patches.v10_0.modify_smallest_currency_fraction
frappe.patches.v11_0.multiple_references_in_events

+ 18
- 0
frappe/patches/v11_0/multiple_references_in_events.py View File

@@ -0,0 +1,18 @@
import frappe

def execute():
frappe.reload_doctype('Event')
# Rename "Cancel" to "Cancelled"
frappe.db.sql("""UPDATE tabEvent set event_type='Cancelled' where event_type='Cancel'""")
# Move references to Participants table
events = frappe.db.sql("""SELECT name, ref_type, ref_name FROM tabEvent WHERE ref_type!=''""", as_dict=True)
for event in events:
if event.ref_type and event.ref_name:
try:
e = frappe.get_doc('Event', event.name)
e.append('participants', {"reference_doctype": event.ref_type, "reference_docname": event.ref_name})
e.flags.ignore_mandatory = True
e.flags.ignore_permissions = True
e.save()
except Exception:
frappe.log_error(frappe.get_traceback())

+ 1
- 0
frappe/public/build.json View File

@@ -325,6 +325,7 @@
"public/js/frappe/list/header_select_all_like_filter.html",
"public/js/frappe/list/item_assigned_to_comment_count.html",
"public/js/frappe/views/treeview.js",
"public/js/frappe/views/interaction.js",

"public/js/frappe/views/image/image_view_item_row.html",
"public/js/frappe/views/image/photoswipe_dom.html",


+ 6
- 1
frappe/public/js/frappe/form/footer/timeline.html View File

@@ -9,7 +9,12 @@
{% } else { %}
<button class="btn btn-default btn-new-email btn-xs">
{%= __("New Email") %}
</button>
</button>
{% if(allow_events_in_timeline===1) { %}
<button class="btn btn-default btn-new-interaction btn-xs">
{%= __("New Event") %}
</button>
{% } %}
{% } %}
</div>
<div class="timeline-items">


+ 98
- 53
frappe/public/js/frappe/form/footer/timeline.js View File

@@ -3,15 +3,16 @@

frappe.provide('frappe.timeline');

frappe.ui.form.Timeline = Class.extend({
init: function(opts) {
frappe.ui.form.Timeline = class Timeline {
constructor(opts) {
$.extend(this, opts);
this.make();
},
make: function() {
}

make() {
var me = this;
this.wrapper = $(frappe.render_template("timeline",
{doctype: this.frm.doctype})).appendTo(this.parent);
{doctype: me.frm.doctype, allow_events_in_timeline: me.frm.meta.allow_events_in_timeline})).appendTo(me.parent);

this.list = this.wrapper.find(".timeline-items");

@@ -31,6 +32,7 @@ frappe.ui.form.Timeline = Class.extend({
});

this.setup_email_button();
this.setup_interaction_button();

this.list.on("click", ".toggle-blockquote", function() {
$(this).parent().siblings("blockquote").toggleClass("hidden");
@@ -69,11 +71,11 @@ frappe.ui.form.Timeline = Class.extend({
});
});

},
}

setup_email_button: function() {
setup_email_button() {
var me = this;
var selector = this.frm.doctype === "Communication"? ".btn-reply-email": ".btn-new-email"
var selector = this.frm.doctype === "Communication"? ".btn-reply-email": ".btn-new-email";
this.email_button = this.wrapper.find(selector)
.on("click", function() {
var args = {
@@ -96,11 +98,39 @@ frappe.ui.form.Timeline = Class.extend({
}
new frappe.views.CommunicationComposer(args)
});
},
}

refresh: function(scroll_to_end) {
setup_interaction_button() {
var me = this;
var selector = ".btn-new-interaction";
this.activity_button = this.wrapper.find(selector)
.on("click", function() {
var args = {
doc: me.frm.doc,
frm: me.frm,
recipients: me.get_recipient()
}
$.extend(args, {
txt: frappe.markdown(me.comment_area.val())
});
new frappe.views.InteractionComposer(args);
});
}

setup_editing_area() {
this.$editing_area = $('<div class="timeline-editing-area">');

this.editing_area = new frappe.ui.CommentArea({
parent: this.$editing_area,
mentions: this.get_names_for_mentions(),
no_wrapper: true
});

this.editing_area.destroy();
}

refresh(scroll_to_end) {
var me = this;
this.last_type = "Comment";

if(this.frm.doc.__islocal) {
@@ -110,18 +140,20 @@ frappe.ui.form.Timeline = Class.extend({
this.wrapper.toggle(true);
this.list.empty();
this.comment_area.set_value('');
var communications = this.get_communications(true);
let communications = this.get_communications(true);
var views = this.get_view_logs();

var timeline = communications.concat(views);
timeline
.sort((a, b) => a.creation > b.creation ? -1 : 1)
.filter(c => c.content)
.forEach(c => {
c.frm = me.frm;
me.render_timeline_item(c);
.filter(a => a.content)
.sort((b, c) => me.compare_dates(b, c))
.forEach(d => {
d.frm = me.frm;
me.render_timeline_item(d);
});


// more btn
if (this.more===undefined && timeline.length===20) {
this.more = true;
@@ -149,7 +181,14 @@ frappe.ui.form.Timeline = Class.extend({
this.frm.sidebar.refresh_comments();

this.frm.trigger('timeline_refresh');
},
}

compare_dates(b, c) {
let b_date = b.communication_date ? b.communication_date : b.creation;
let c_date = c.communication_date ? c.communication_date : c.creation;
let comparison = new Date(b_date) > new Date(c_date) ? -1 : 1;
return comparison;
}

make_editing_area(container) {
return frappe.ui.form.make_control({
@@ -166,7 +205,7 @@ frappe.ui.form.Timeline = Class.extend({
});
},

render_timeline_item: function(c) {
render_timeline_item(c) {
var me = this;
this.prepare_timeline_item(c);
var $timeline_item = $(frappe.render_template("timeline_item", {data:c, frm:this.frm}))
@@ -224,9 +263,9 @@ frappe.ui.form.Timeline = Class.extend({
this.add_reply_btn_event($timeline_item, c);
}

},
}

add_reply_btn_event: function($timeline_item, c) {
add_reply_btn_event($timeline_item, c) {
var me = this;
$timeline_item.find(".reply-link").on("click", function() {
var name = $(this).attr("data-name");
@@ -249,9 +288,9 @@ frappe.ui.form.Timeline = Class.extend({
last_email: last_email
});
});
},
}

prepare_timeline_item: function(c) {
prepare_timeline_item(c) {
if(!c.sender) c.sender = c.owner;

if(c.sender && c.sender.indexOf("<")!==-1) {
@@ -271,8 +310,9 @@ frappe.ui.form.Timeline = Class.extend({
c["edit"] = '<a class="edit-comment text-muted" title="Edit" href="#">Edit</a>';
}
}
c.comment_on_small = comment_when(c.creation, true);
c.comment_on = comment_when(c.creation);
c.comment_on_small = comment_when(c.communication_date, true);
c.comment_on = comment_when(c.communication_date);
c.futur_date = c.communication_date > frappe.datetime.now_datetime() ? true : false;
if(!c.fullname) {
c.fullname = c.sender_full_name || frappe.user.full_name(c.sender);
}
@@ -356,15 +396,15 @@ frappe.ui.form.Timeline = Class.extend({
c.show_subject = true;
}
}
},
}

is_communication_or_comment: function(c) {
is_communication_or_comment(c) {
return c.communication_type==="Communication"
|| c.communication_type==="Feedback"
|| (c.communication_type==="Comment" && (c.comment_type==="Comment"||c.comment_type==="Relinked"));
},
}

set_icon_and_color: function(c) {
set_icon_and_color(c) {
if(c.communication_type == "Feedback"){
c.icon = "octicon octicon-comment-discussion"
c.rating_icons = frappe.render_template("rating_icons", {rating: c.rating, show_label: true})
@@ -375,6 +415,9 @@ frappe.ui.form.Timeline = Class.extend({
"Chat": "octicon octicon-comment-discussion",
"Phone": "octicon octicon-device-mobile",
"SMS": "octicon octicon-comment",
"Event": "fa fa-calendar",
"Meeting": "octicon octicon-briefcase",
"ToDo": "fa fa-check",
"Created": "octicon octicon-plus",
"Submitted": "octicon octicon-lock",
"Cancelled": "octicon octicon-x",
@@ -419,8 +462,9 @@ frappe.ui.form.Timeline = Class.extend({
}
if(!c.icon_fg)
c.icon_fg = "#fff";
},
get_communications: function(with_versions) {
}

get_communications(with_versions) {
var docinfo = this.frm.get_docinfo(),
me = this,
out = [].concat(docinfo.communications);
@@ -429,8 +473,9 @@ frappe.ui.form.Timeline = Class.extend({
}

return out;
},
get_view_logs: function(){
}

get_view_logs(){
var docinfo = this.frm.get_docinfo(),
me = this,
out = [];
@@ -440,9 +485,9 @@ frappe.ui.form.Timeline = Class.extend({
out.push(c);
};
return out;
},
}

build_version_comments: function(docinfo, out) {
build_version_comments(docinfo, out) {
var me = this;
docinfo.versions.forEach(function(version) {
if(!version.data) return;
@@ -543,8 +588,9 @@ frappe.ui.form.Timeline = Class.extend({
}
});
});
},
get_version_comment: function(version, text, comment_type) {
}

get_version_comment(version, text, comment_type) {
if(!comment_type) {
text = '<a href="#Form/Version/'+version.name+'">' + text + '</a>';
}
@@ -557,9 +603,9 @@ frappe.ui.form.Timeline = Class.extend({
comment_by: version.owner,
content: text
};
},
}

insert_comment: function(comment_type, comment, btn) {
insert_comment(comment_type, comment, btn) {
var me = this;
return frappe.call({
method: "frappe.desk.form.utils.add_comment",
@@ -599,9 +645,9 @@ frappe.ui.form.Timeline = Class.extend({
}
});

},
}

delete_comment: function(name) {
delete_comment(name) {
var me = this;

frappe.confirm(__('Delete comment?'), function() {
@@ -627,7 +673,7 @@ frappe.ui.form.Timeline = Class.extend({
}
});
});
},
}

/**
* Update comment
@@ -637,8 +683,7 @@ frappe.ui.form.Timeline = Class.extend({
*
* @returns {boolean}
*/
update_comment: function(name, content)
{
update_comment(name, content){
return frappe.call({
method: 'frappe.desk.form.utils.update_comment',
args: { name, content },
@@ -648,17 +693,17 @@ frappe.ui.form.Timeline = Class.extend({
}
}
});
},
}

get_recipient: function() {
get_recipient() {
if(this.frm.email_field) {
return this.frm.doc[this.frm.email_field];
} else {
return this.frm.doc.email_id || this.frm.doc.email || "";
}
},
}

get_last_email: function(from_recipient) {
get_last_email(from_recipient) {
var last_email = null,
communications = this.frm.get_docinfo().communications,
email = this.get_recipient();
@@ -678,21 +723,21 @@ frappe.ui.form.Timeline = Class.extend({
});

return last_email;
},
}

get_names_for_mentions: function() {
get_names_for_mentions() {
var valid_users = Object.keys(frappe.boot.user_info)
.filter(user => !["Administrator", "Guest"].includes(user));

return valid_users.map(user => frappe.boot.user_info[user].name);
},
}

setup_comment_like: function() {
setup_comment_like() {
this.wrapper.on("click", ".comment-likes .octicon-heart", frappe.ui.click_toggle_like);

frappe.ui.setup_like_popover(this.wrapper, ".comment-likes");
}
});
};

$.extend(frappe.timeline, {
new_communication: function(communication) {
@@ -760,4 +805,4 @@ $.extend(frappe.timeline, {

return index;
}
})
})

+ 3
- 3
frappe/public/js/frappe/form/footer/timeline_item.html View File

@@ -96,9 +96,9 @@
data-name="{%= data.name %}" title="{%= __("Reply") %}">{%= __("Reply") %}</a>
{% } %}
{% } %}
<span class="text-muted commented-on hidden-xs">
<span class="text-muted commented-on hidden-xs {% if (data.futur_date) { %}timeline-futur{% } %}">
&ndash; {%= data.comment_on %}</span>
<span class="text-muted commented-on-small">
<span class="text-muted commented-on-small {% if (data.futur_date) { %}timeline-futur{% } %}">
&ndash; {%= data.comment_on_small %}</span>
<span class="comment-likes hidden-xs"
data-liked-by=\'{{ JSON.stringify(data._liked_by) }}\'>
@@ -117,7 +117,7 @@
<div class="timeline-item-content">
{% if data.show_subject %}
<p class="text-muted small">
<b>{{ __("Subject") }}:</b> {{ data.subject }}</p>
<b>{{ __("Title") }}:</b> {{ data.subject }}</p>
<hr>
{% endif %}



+ 6
- 1
frappe/public/js/frappe/form/link_selector.js View File

@@ -1,4 +1,4 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.ui.form.LinkSelector = Class.extend({
@@ -140,6 +140,11 @@ frappe.ui.form.LinkSelector = Class.extend({
]);
}
}, __("Set Quantity"), __("Set"));
} else if (me.dynamic_link_field) {
var d = me.target.add_new_row();
frappe.model.set_value(d.doctype, d.name, me.dynamic_link_field, me.dynamic_link_reference);
frappe.model.set_value(d.doctype, d.name, me.fieldname, value);
frappe.show_alert(__("{0} {1} added", [me.dynamic_link_reference, value]));
} else {
var d = me.target.add_new_row();
frappe.model.set_value(d.doctype, d.name, me.fieldname, value);


+ 9
- 36
frappe/public/js/frappe/views/communication.js View File

@@ -1,4 +1,4 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.last_edited_communication = {};
@@ -82,18 +82,10 @@ frappe.views.CommunicationComposer = Class.extend({
fieldname:"content"},
{fieldtype: "Section Break"},
{fieldtype: "Column Break"},
{label:__("Send As Email"), fieldtype:"Check",
fieldname:"send_email"},
{label:__("Send me a copy"), fieldtype:"Check",
fieldname:"send_me_a_copy", 'default': frappe.boot.user.send_me_a_copy},
{label:__("Send Read Receipt"), fieldtype:"Check",
fieldname:"send_read_receipt"},
{label:__("Communication Medium"), fieldtype:"Select",
options: ["Phone", "Chat", "Email", "SMS", "Visit", "Other"],
fieldname:"communication_medium"},
{label:__("Sent or Received"), fieldtype:"Select",
options: ["Received", "Sent"],
fieldname:"sent_or_received"},
{label:__("Attach Document Print"), fieldtype:"Check",
fieldname:"attach_document_print"},
{label:__("Select Print Format"), fieldtype:"Select",
@@ -436,8 +428,6 @@ frappe.views.CommunicationComposer = Class.extend({
$(fields.select_print_format.wrapper).toggle(true);
}

$(fields.send_email.input).prop("checked", true);

$(fields.send_me_a_copy.input).on('click', () => {
// update send me a copy (make it sticky)
let val = fields.send_me_a_copy.get_value();
@@ -445,18 +435,6 @@ frappe.views.CommunicationComposer = Class.extend({
frappe.boot.user.send_me_a_copy = val;
});

// toggle print format
$(fields.send_email.input).click(function() {
$(fields.communication_medium.wrapper).toggle(!!!$(this).prop("checked"));
$(fields.sent_or_received.wrapper).toggle(!!!$(this).prop("checked"));
$(fields.send_read_receipt.wrapper).toggle($(this).prop("checked"));
me.dialog.get_primary_btn().html($(this).prop("checked") ? "Send" : "Add Communication");
});

// select print format
$(fields.communication_medium.wrapper).toggle(false);
$(fields.sent_or_received.wrapper).toggle(false);

},

send_action: function() {
@@ -514,7 +492,7 @@ frappe.views.CommunicationComposer = Class.extend({
var me = this;
me.dialog.hide();

if((form_values.send_email || form_values.communication_medium === "Email") && !form_values.recipients) {
if(!form_values.recipients) {
frappe.msgprint(__("Enter Email Recipient(s)"));
return;
}
@@ -524,16 +502,13 @@ frappe.views.CommunicationComposer = Class.extend({
print_format = null;
}

if(form_values.send_email) {
if(cur_frm && !frappe.model.can_email(me.doc.doctype, cur_frm)) {
frappe.msgprint(__("You are not allowed to send emails related to this document"));
return;
}

form_values.communication_medium = "Email";
form_values.sent_or_received = "Sent";
if(cur_frm && !frappe.model.can_email(me.doc.doctype, cur_frm)) {
frappe.msgprint(__("You are not allowed to send emails related to this document"));
return;
}


return frappe.call({
method:"frappe.core.doctype.communication.email.make",
args: {
@@ -544,12 +519,10 @@ frappe.views.CommunicationComposer = Class.extend({
content: form_values.content,
doctype: me.doc.doctype,
name: me.doc.name,
send_email: form_values.send_email,
send_email: 1,
print_html: print_html,
send_me_a_copy: form_values.send_me_a_copy,
print_format: print_format,
communication_medium: form_values.communication_medium,
sent_or_received: form_values.sent_or_received,
sender: form_values.sender,
sender_full_name: form_values.sender?frappe.user.full_name():undefined,
attachments: selected_attachments,
@@ -562,7 +535,7 @@ frappe.views.CommunicationComposer = Class.extend({
if(!r.exc) {
frappe.utils.play_sound("email");

if(form_values.send_email && r.message["emails_not_sent_to"]) {
if(r.message["emails_not_sent_to"]) {
frappe.msgprint(__("Email not sent to {0} (unsubscribed / disabled)",
[ frappe.utils.escape_html(r.message["emails_not_sent_to"]) ]) );
}
@@ -657,4 +630,4 @@ frappe.views.CommunicationComposer = Class.extend({
}
fields.content.set_value(content);
}
});
});

+ 351
- 0
frappe/public/js/frappe/views/interaction.js View File

@@ -0,0 +1,351 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.provide('frappe.views');
frappe.provide("frappe.interaction_settings");

frappe.views.InteractionComposer = class InteractionComposer {
constructor(opts) {
$.extend(this, opts);
this.make();
}

make() {
let me = this;
me.dialog = new frappe.ui.Dialog({
title: (me.title || me.subject || __("New Activity")),
no_submit_on_enter: true,
fields: me.get_fields(),
primary_action_label: __("Create"),
primary_action: function() {
me.create_action();
}
});

$(document).on("upload_complete", function(event, attachment) {
if(me.dialog.display) {
let wrapper = $(me.dialog.fields_dict.select_attachments.wrapper);

// find already checked items
let checked_items = wrapper.find('[data-file-name]:checked').map(function() {
return $(this).attr("data-file-name");
});

// reset attachment list
me.render_attach();

// check latest added
checked_items.push(attachment.name);

$.each(checked_items, function(i, filename) {
wrapper.find('[data-file-name="'+ filename +'"]').prop("checked", true);
});
}
});
me.prepare();
me.dialog.show();
}

get_fields() {
let me = this;
let interaction_docs = Object.keys(get_doc_mappings());

let fields= [
{label:__("Reference"), fieldtype:"Select",
fieldname:"interaction_type", options: interaction_docs,
reqd: 1,
onchange: () => {
let values = me.get_values();
me.get_fields().forEach(field => {
if (field.fieldname != "interaction_type") {
me.dialog.set_df_property(field.fieldname, "reqd", 0);
me.dialog.set_df_property(field.fieldname, "hidden", 0);
}
});
me.set_reqd_hidden_fields(values);
me.get_event_categories();
}
},
{label:__("Category"), fieldtype:"Select",
fieldname:"category", options: "", hidden: 1},
{label:__("Public"), fieldtype:"Check",
fieldname:"public", default: "1"},
{fieldtype: "Column Break"},
{label:__("Date"), fieldtype:"Datetime",
fieldname:"due_date"},
{label:__("Assigned To"), fieldtype:"Link",
fieldname:"assigned_to", options:"User"},
{fieldtype: "Section Break"},
{label:__("Summary"), fieldtype:"Data",
fieldname:"summary"},
{fieldtype: "Section Break"},
{fieldtype:"Text Editor", fieldname:"description"},
{fieldtype: "Section Break"},
{label:__("Select Attachments"), fieldtype:"HTML",
fieldname:"select_attachments"}
];

return fields;
}

get_event_categories() {
let me = this;
frappe.model.with_doctype('Event', () => {
let categories = frappe.meta.get_docfield("Event", "event_category").options.split("\n");
me.dialog.get_input("category").empty().add_options(categories);
});
}

prepare() {
this.setup_attach();
}

set_reqd_hidden_fields(values) {
let me = this;
if (values&&"interaction_type" in values) {
let doc_mapping = get_doc_mappings();
doc_mapping[values.interaction_type]["reqd_fields"].forEach(value => {
me.dialog.set_df_property(value, 'reqd', 1);
});

doc_mapping[values.interaction_type]["hidden_fields"].forEach(value => {
me.dialog.set_df_property(value, 'hidden', 1);
});
}
}

setup_attach() {
let fields = this.dialog.fields_dict;
let attach = $(fields.select_attachments.wrapper);

let me = this;
if (!me.attachments){
me.attachments = [];
}

let args = {
args: {
from_form: 1,
folder:"Home/Attachments"
},
callback: function(attachment){
me.attachments.push(attachment);
},
max_width: null,
max_height: null
};

if(me.frm) {
args = {
args: (me.frm.attachments.get_args
? me.frm.attachments.get_args()
: { from_form: 1,folder:"Home/Attachments" }),
callback: function(attachment, r){
me.frm.attachments.attachment_uploaded(attachment, r);
},
max_width: me.frm.cscript ? me.frm.cscript.attachment_max_width : null,
max_height: me.frm.cscript ? me.frm.cscript.attachment_max_height : null
};

}

$("<h6 class='text-muted add-attachment' style='margin-top: 12px; cursor:pointer;'>"
+__("Select Attachments")+"</h6><div class='attach-list'></div>\
<p class='add-more-attachments'>\
<a class='text-muted small'><i class='octicon octicon-plus' style='font-size: 12px'></i> "
+__("Add Attachment")+"</a></p>").appendTo(attach.empty());
attach.find(".add-more-attachments a").on('click',this,function() {
me.upload = frappe.ui.get_upload_dialog(args);
});
me.render_attach();

}

render_attach(){
let fields = this.dialog.fields_dict;
let attach = $(fields.select_attachments.wrapper).find(".attach-list").empty();

let files = [];
if (this.attachments && this.attachments.length) {
files = files.concat(this.attachments);
}
if (cur_frm) {
files = files.concat(cur_frm.get_files());
}

if(files.length) {
$.each(files, function(i, f) {
if (!f.file_name) return;
f.file_url = frappe.urllib.get_full_url(f.file_url);

$(repl('<p class="checkbox">'
+ '<label><span><input type="checkbox" data-file-name="%(name)s"></input></span>'
+ '<span class="small">%(file_name)s</span>'
+ ' <a href="%(file_url)s" target="_blank" class="text-muted small">'
+ '<i class="fa fa-share" style="vertical-align: middle; margin-left: 3px;"></i>'
+ '</label></p>', f))
.appendTo(attach);
});
}
}

create_action() {
let me = this;
let btn = me.dialog.get_primary_btn();

let form_values = this.get_values();
if(!form_values) return;

let selected_attachments =
$.map($(me.dialog.wrapper).find("[data-file-name]:checked"), function(element){
return $(element).attr("data-file-name");
});

me.create_interaction(btn, form_values, selected_attachments);
}

get_values() {
let me = this;
let values = this.dialog.get_values(true);
if (values) {
values["reference_doctype"] = me.frm.doc.doctype;
values["reference_document"] = me.frm.doc.name;
}

return values;
}

create_interaction(btn, form_values, selected_attachments) {
let me = this;
me.dialog.hide();

let field_map = get_doc_mappings();
let interaction_values = {};
Object.keys(form_values).forEach(value => {
interaction_values[field_map[form_values.interaction_type]["field_map"][value]] = form_values[value];
});
if ("event_type" in interaction_values){
interaction_values["event_type"] = (form_values.public == 1) ? "Public" : "Private";
}
if (interaction_values["doctype"] == "Event") {
interaction_values["event_participants"] = [{"reference_doctype": form_values.reference_doctype,
"reference_docname": form_values.reference_document}];
}
if (!("owner" in interaction_values)){
interaction_values["owner"] = frappe.session.user;
}
return frappe.call({
method:"frappe.client.insert",
args: { doc: interaction_values},
btn: btn,
callback: function(r) {
if(!r.exc) {
frappe.show_alert({
message: __("{0} created successfully", [form_values.interaction_type]),
indicator: 'green'
});
if ("assigned_to" in form_values) {
me.assign_document(r.message, form_values["assigned_to"]);
}

if (selected_attachments) {
me.add_attachments(r.message, selected_attachments);
}
if (cur_frm) {
// clear input
cur_frm.timeline.input && cur_frm.timeline.input.val("");
cur_frm.reload_doc();
}
} else {
frappe.msgprint(__("There were errors while creating the document. Please try again."));
}
}
});

}

assign_document(doc, assignee) {
if (doc.doctype != "ToDo") {
frappe.call({
method:'frappe.desk.form.assign_to.add',
args: {
doctype: doc.doctype,
name: doc.name,
assign_to: assignee,
notify: 1
},
callback:function(r) {
if(!r.exc) {
frappe.show_alert({
message: __("The document has been assigned to {0}", [assignee]),
indicator: 'green'
});
return;
} else {
frappe.show_alert({
message: __("The document could not be correctly assigned"),
indicator: 'orange'
});
return;
}
}
});
}
}

add_attachments(doc, attachments) {
frappe.call({
method:'frappe.utils.file_manager.add_attachments',
args: {
doctype: doc.doctype,
name: doc.name,
attachments: JSON.stringify(attachments)
},
callback:function(r) {
if(!r.exc) {
return;
} else {
frappe.show_alert({
message: __("The attachments could not be correctly linked to the new document"),
indicator: 'orange'
});
return;
}
}
});

}
};

function get_doc_mappings() {
const doc_map = {
"Event": {
"field_map": {
"interaction_type": "doctype",
"summary": "subject",
"description": "description",
"category": "event_category",
"due_date": "starts_on",
"public": "event_type"
},
"reqd_fields": ["summary", "due_date"],
"hidden_fields": []
} ,
"ToDo": {
"field_map": {
"interaction_type": "doctype",
"description": "description",
"due_date": "date",
"reference_doctype": "reference_type",
"reference_document": "reference_name",
"assigned_to": "owner"
},
"reqd_fields": ["description"],
"hidden_fields": ["public", "category"]
}
};

return doc_map;
}

+ 4
- 0
frappe/public/less/form.less View File

@@ -612,6 +612,10 @@ h6.uppercase, .h6.uppercase {
}
}

.timeline-futur span{
color: @orange !important;
}

.signature-field {
min-height: 300px;
background: #fff;


+ 1
- 2
frappe/tests/test_document.py View File

@@ -196,8 +196,7 @@ class TestDocument(unittest.TestCase):
doctype, name = 'User', 'test@example.com'

d = self.test_insert()
d.ref_type = doctype
d.ref_name = name
d.append('event_participants', {"reference_doctype": doctype, "reference_docname": name})

d.save()



+ 18
- 2
frappe/utils/file_manager.py View File

@@ -3,7 +3,7 @@

from __future__ import unicode_literals
import frappe
import os, base64, re
import os, base64, re, json
import hashlib
import mimetypes
import io
@@ -12,7 +12,7 @@ from frappe import _
from frappe import conf
from copy import copy
from six.moves.urllib.parse import unquote
from six import text_type, PY2
from six import text_type, PY2, string_types


class MaxFileSizeReachedError(frappe.ValidationError):
@@ -436,3 +436,19 @@ def validate_filename(filename):
timestamp = now_datetime().strftime(" %Y-%m-%d %H:%M:%S")
fname = get_file_name(filename, timestamp)
return fname

@frappe.whitelist()
def add_attachments(doctype, name, attachments):
'''Add attachments to the given DocType'''
if isinstance(attachments, string_types):
attachments = json.loads(attachments)
# loop through attachments
files =[]
for a in attachments:
if isinstance(a, string_types):
attach = frappe.db.get_value("File", {"name":a}, ["file_name", "file_url", "is_private"], as_dict=1)
# save attachments to new doc
f = save_url(attach.file_url, attach.file_name, doctype, name, "Home/Attachments", attach.is_private)
files.append(f)

return files

+ 1
- 1
rollup/watch.js View File

@@ -92,7 +92,7 @@ ${error.toString()}

${error.frame ? error.frame : ''}
`
}
};

subscriber.publish('events', JSON.stringify(payload));
}

Loading…
Cancel
Save