diff --git a/frappe/desk/doctype/auto_repeat/__init__.py b/frappe/desk/doctype/auto_repeat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat.js b/frappe/desk/doctype/auto_repeat/auto_repeat.js new file mode 100644 index 0000000000..9781f56ef1 --- /dev/null +++ b/frappe/desk/doctype/auto_repeat/auto_repeat.js @@ -0,0 +1,75 @@ +// Copyright (c) 2018, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Auto Repeat', { + setup: function(frm) { + frm.fields_dict['reference_doctype'].get_query = function(doc) { + 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(__("Resume"), + function() { + frm.events.stop_resume_auto_repeat(frm, "Resumed"); + } + ); + } + } + }, + + stop_resume_auto_repeat: function(frm, status) { + frappe.call({ + method: "frappe.desk.doctype.auto_repeat.auto_repeat.stop_resume_auto_repeat", + args: { + subscription: frm.doc.name, + status: status + }, + callback: function(r) { + if(r.message) { + frm.set_value("status", r.message); + frm.reload_doc(); + } + } + }); + } +}); \ No newline at end of file diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat.json b/frappe/desk/doctype/auto_repeat/auto_repeat.json new file mode 100644 index 0000000000..8939e98a26 --- /dev/null +++ b/frappe/desk/doctype/auto_repeat/auto_repeat.json @@ -0,0 +1,888 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "naming_series:", + "beta": 0, + "creation": "2018-03-09 11:22:31.192349", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_1", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "naming_series", + "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": "Series", + "length": 0, + "no_copy": 0, + "options": "SUB-", + "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_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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_document", + "fieldtype": "Dynamic 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": "Reference Document", + "length": 0, + "no_copy": 1, + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "fieldtype": "Column 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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "start_date", + "fieldtype": "Date", + "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": "Start Date", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "end_date", + "fieldtype": "Date", + "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": "End Date", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "submit_on_creation", + "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": "Submit on Creation", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "disabled", + "fieldtype": "Check", + "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": "Disabled", + "length": 0, + "no_copy": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_10", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frequency", + "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": 0, + "label": "Frequency", + "length": 0, + "no_copy": 0, + "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_13", + "fieldtype": "Column 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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval: in_list([\"Monthly\", \"Quarterly\", \"Yearly\"], doc.frequency)", + "fieldname": "repeat_on_day", + "fieldtype": "Int", + "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 on Day", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "next_schedule_date", + "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": "Next Schedule Date", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "notification", + "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": "Notification", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "notify_by_email", + "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 by Email", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval: doc.notify_by_email", + "description": "To add dynamic subject, use jinja tags like\n\n
New {{ doc.doctype }} #{{ doc.name }}
", + "fieldname": "subject", + "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": "Subject", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_17", + "fieldtype": "Column 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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "notify_by_email", + "fieldname": "recipients", + "fieldtype": "Small Text", + "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": "Recipients", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "notify_by_email", + "fieldname": "print_format", + "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": "Print Format", + "length": 0, + "no_copy": 0, + "options": "Print Format", + "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_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "eval:doc.notify_by_email", + "fieldname": "section_break_20", + "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": "Message", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Please find attached {{ doc.doctype }} #{{ doc.name }}", + "fieldname": "message", + "fieldtype": "Text", + "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": "Message", + "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_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "eval: !doc.__islocal", + "fieldname": "section_break_16", + "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 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "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": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "\nDraft\nStopped\nSubmitted\nCancelled\nCompleted", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "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": "Amended From ", + "length": 0, + "no_copy": 1, + "options": "Auto Repeat", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "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": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-03-10 14:45:49.645884", + "modified_by": "Administrator", + "module": "Desk", + "name": "Auto Repeat", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "reference_document", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "reference_document", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat.py b/frappe/desk/doctype/auto_repeat/auto_repeat.py new file mode 100644 index 0000000000..db02ed7266 --- /dev/null +++ b/frappe/desk/doctype/auto_repeat/auto_repeat.py @@ -0,0 +1,332 @@ +# -*- 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 + +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() + + validate_template(self.subject or "") + validate_template(self.message or "") + + def before_submit(self): + if not self.next_schedule_date: + self.next_schedule_date = get_next_schedule_date( + self.start_date, self.frequency, self.repeat_on_day) + + def on_submit(self): + self.update_auto_repeat_id() + + 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_add + for email in email_list: + if not validate_email_add(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 + + +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): + date = date or today() + for data in get_auto_repeat_entries(date): + schedule_date = getdate(data.next_schedule_date) + while schedule_date <= getdate(today()): + 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) + + +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 ifnull(disabled, 0) = 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()) + 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)] + + frappe.sendmail(auto_repeat_doc.recipients, + subject=subject, message=message, attachments=attachments) + + +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 + })) + + assign_task_to_owner(name, "Recurring Documents Failed", recipients) + + +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 + }) diff --git a/frappe/desk/doctype/auto_repeat/auto_repeat_list.js b/frappe/desk/doctype/auto_repeat/auto_repeat_list.js new file mode 100644 index 0000000000..295d9391b0 --- /dev/null +++ b/frappe/desk/doctype/auto_repeat/auto_repeat_list.js @@ -0,0 +1,16 @@ +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"]; + } + } +}; \ No newline at end of file diff --git a/frappe/desk/doctype/auto_repeat/test_auto_repeat.py b/frappe/desk/doctype/auto_repeat/test_auto_repeat.py new file mode 100644 index 0000000000..c800509d4c --- /dev/null +++ b/frappe/desk/doctype/auto_repeat/test_auto_repeat.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +import frappe +from erpnext.accounts.report.financial_statements import get_months +from erpnext.accounts.utils import get_fiscal_year +from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.desk.doctype.auto_repeat.auto_repeat import make_auto_repeat_entry, disable_auto_repeat +from frappe.utils import today, add_days, getdate + + +def add_custom_fields(): + df = dict( + fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', insert_after='sender', + options='Auto Repeat') + 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"'): + add_custom_fields() + + def test_daily_auto_repeat(self): + todo = frappe.get_doc( + dict(doctype='ToDo', description='test recurring todo', assigned_by='Administrator')).insert() + + doc = make_auto_repeat(reference_document=todo.name) + self.assertEqual(doc.next_schedule_date, today()) + make_auto_repeat_entry() + frappe.db.commit() + + todo = frappe.get_doc(doc.reference_doctype, doc.reference_document) + self.assertEqual(todo.auto_repeat, doc.name) + + new_todo = frappe.db.get_value('ToDo', + {'auto_repeat': doc.name, 'name': ('!=', todo.name)}, 'name') + + new_todo = frappe.get_doc('ToDo', new_todo) + + self.assertEqual(todo.get('description'), new_todo.get('description')) + + def test_monthly_auto_repeat(self): + current_fiscal_year = get_fiscal_year(today(), as_dict=True) + start_date = current_fiscal_year.year_start_date + end_date = current_fiscal_year.year_end_date + + todo = frappe.get_doc( + dict(doctype='ToDo', description='test recurring todo', assigned_by='Administrator')).insert() + + self.monthly_auto_repeat('ToDo', todo.name, start_date, end_date) + + def monthly_auto_repeat(self, doctype, docname, start_date, end_date): + doc = make_auto_repeat( + reference_doctype=doctype, frequency='Monthly', reference_document=docname, start_date=start_date, + end_date=end_date) + + disable_auto_repeat(doc) + # doc.disabled = 1 + # doc.save() + # frappe.db.commit() + + make_auto_repeat_entry() + docnames = frappe.get_all(doc.reference_doctype, {'auto_repeat': doc.name}) + self.assertEqual(len(docnames), 1) + + doc = frappe.get_doc('Auto Repeat', doc.name) + doc.db_set('disabled', 0) + # doc.save() + + months = get_months(getdate(start_date), getdate(today())) + make_auto_repeat_entry() + + docnames = frappe.get_all(doc.reference_doctype, {'auto_repeat': doc.name}) + self.assertEqual(len(docnames), months) + + +def make_auto_repeat(**args): + args = frappe._dict(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'), + 'frequency': args.frequency or 'Daily', + 'start_date': args.start_date or add_days(today(), -1), + 'end_date': args.end_date or add_days(today(), 1), + 'submit_on_creation': args.submit_on_creation or 0 + }).insert(ignore_permissions=True) + + if not args.do_not_submit: + doc.submit() + + return doc diff --git a/frappe/hooks.py b/frappe/hooks.py index 1d02b64ac2..a24a7b7e66 100755 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -141,7 +141,8 @@ scheduler_events = { 'frappe.model.utils.user_settings.sync_user_settings', "frappe.utils.error.collect_error_snapshots", "frappe.desk.page.backups.backups.delete_downloadable_backups", - "frappe.limits.update_space_usage" + "frappe.limits.update_space_usage", + "frappe.desk.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry", ], "daily": [ "frappe.email.queue.clear_outbox", diff --git a/frappe/templates/emails/recurring_document_failed.html b/frappe/templates/emails/recurring_document_failed.html new file mode 100644 index 0000000000..ee4e024f96 --- /dev/null +++ b/frappe/templates/emails/recurring_document_failed.html @@ -0,0 +1,11 @@ +

{{_("Recurring")}} {{ type }} {{ _("Failed")}}

+ +

{{_("An error occured while creating recurring")}} {{ type }} {{ name }} {{_("for")}} {{ party }}.

+

{{_("This could be because of some invalid Email Addresses in the")}} {{ type }}.

+

{{_("To stop sending repetitive error notifications from the system, we have checked Disabled field in the Auto Repeat document")}} {{ auto_repeat}} {{_("for the")}} {{ type }} {{ name }}.

+

{{_("Please correct the")}} {{ type }} {{_('and uncheck Disabled in the Auto Repeat document')}} {{ auto_repeat }} {{_("for automatic creating of the recurring document to continue.")}}

+
+

{{_("It is necessary to take this action today itself for the above mentioned recurring")}} {{ type }} +{{_('to be generated. If delayed, you will have to manually change the "Repeat on Day of Month" field +of this')}} {{ type }} {{_("for generating the recurring")}} {{ type }} {{_("in the Auto Repeat document")}} {{ auto_repeat }}.

+

[{{_("This email is autogenerated")}}]