fix(minor): merge develop
This commit is contained in:
commit
c93368b242
34 changed files with 488 additions and 178 deletions
|
|
@ -31,12 +31,12 @@ matrix:
|
|||
- name: "Python 3.7 MariaDB"
|
||||
python: 3.7
|
||||
env: DB=mariadb TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
|
||||
- name: "Python 3.7 PostgreSQL"
|
||||
python: 3.7
|
||||
env: DB=postgres TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
|
||||
- name: "Cypress"
|
||||
python: 3.7
|
||||
|
|
|
|||
|
|
@ -327,7 +327,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False,
|
|||
:param is_minimizable: [optional] Allow users to minimize the modal
|
||||
:param wide: [optional] Show wide modal
|
||||
"""
|
||||
from frappe.utils import encode
|
||||
from frappe.utils import strip_html_tags
|
||||
|
||||
msg = safe_decode(msg)
|
||||
out = _dict(message=msg)
|
||||
|
|
@ -354,7 +354,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False,
|
|||
out.as_list = 1
|
||||
|
||||
if flags.print_messages and out.message:
|
||||
print(f"Message: {repr(out.message).encode('utf-8')}")
|
||||
print(f"Message: {strip_html_tags(out.message)}")
|
||||
|
||||
if title:
|
||||
out.title = title
|
||||
|
|
|
|||
|
|
@ -100,10 +100,7 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
|
||||
frappe.auto_repeat.render_schedule = function(frm) {
|
||||
if (!frm.is_dirty() && frm.doc.status !== 'Disabled') {
|
||||
frappe.call({
|
||||
method: "get_auto_repeat_schedule",
|
||||
doc: frm.doc
|
||||
}).done((r) => {
|
||||
frm.call("get_auto_repeat_schedule").then(r => {
|
||||
frm.dashboard.wrapper.empty();
|
||||
frm.dashboard.add_section(
|
||||
frappe.render_template("auto_repeat_schedule", {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
"repeat_on_last_day",
|
||||
"column_break_12",
|
||||
"next_schedule_date",
|
||||
"section_break_12",
|
||||
"repeat_on_days",
|
||||
"notification",
|
||||
"notify_by_email",
|
||||
"recipients",
|
||||
|
|
@ -189,6 +191,18 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Repeat on Last Day of the Month"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.frequency==='Weekly';",
|
||||
"fieldname": "repeat_on_days",
|
||||
"fieldtype": "Table",
|
||||
"label": "Repeat on Days",
|
||||
"options": "Auto Repeat Day"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.frequency==='Weekly';",
|
||||
"fieldname": "section_break_12",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "submit_on_creation",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from datetime import timedelta
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
|
@ -13,9 +14,10 @@ from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_
|
|||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
from frappe.automation.doctype.assignment_rule.assignment_rule import get_repeated
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
|
||||
week_map = {'Monday': 0, 'Tuesday': 1, 'Wednesday': 2, 'Thursday': 3, 'Friday': 4, 'Saturday': 5, 'Sunday': 6}
|
||||
|
||||
class AutoRepeat(Document):
|
||||
def validate(self):
|
||||
|
|
@ -24,6 +26,7 @@ class AutoRepeat(Document):
|
|||
self.validate_submit_on_creation()
|
||||
self.validate_dates()
|
||||
self.validate_email_id()
|
||||
self.validate_auto_repeat_days()
|
||||
self.set_dates()
|
||||
self.update_auto_repeat_id()
|
||||
self.unlink_if_applicable()
|
||||
|
|
@ -49,7 +52,7 @@ class AutoRepeat(Document):
|
|||
if self.disabled:
|
||||
self.next_schedule_date = None
|
||||
else:
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, self.end_date)
|
||||
self.next_schedule_date = self.get_next_schedule_date(schedule_date=self.start_date)
|
||||
|
||||
def unlink_if_applicable(self):
|
||||
if self.status == 'Completed' or self.disabled:
|
||||
|
|
@ -88,6 +91,12 @@ class AutoRepeat(Document):
|
|||
else:
|
||||
frappe.throw(_("'Recipients' not specified"))
|
||||
|
||||
def validate_auto_repeat_days(self):
|
||||
auto_repeat_days = self.get_auto_repeat_days()
|
||||
if not len(set(auto_repeat_days)) == len(auto_repeat_days):
|
||||
repeated_days = get_repeated(auto_repeat_days)
|
||||
frappe.throw(_('Auto Repeat Day {0} has been repeated.').format(frappe.bold(repeated_days)))
|
||||
|
||||
def update_auto_repeat_id(self):
|
||||
#check if document is already on auto repeat
|
||||
auto_repeat = frappe.db.get_value(self.reference_doctype, self.reference_document, "auto_repeat")
|
||||
|
|
@ -113,7 +122,7 @@ class AutoRepeat(Document):
|
|||
end_date = getdate(self.end_date)
|
||||
|
||||
if not self.end_date:
|
||||
next_date = get_next_schedule_date(start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day)
|
||||
next_date = self.get_next_schedule_date(schedule_date=start_date)
|
||||
row = {
|
||||
"reference_document": self.reference_document,
|
||||
"frequency": self.frequency,
|
||||
|
|
@ -122,8 +131,7 @@ class AutoRepeat(Document):
|
|||
schedule_details.append(row)
|
||||
|
||||
if self.end_date:
|
||||
next_date = get_next_schedule_date(
|
||||
start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, for_full_schedule=True)
|
||||
next_date = self.get_next_schedule_date(schedule_date=start_date, for_full_schedule=True)
|
||||
|
||||
while (getdate(next_date) < getdate(end_date)):
|
||||
row = {
|
||||
|
|
@ -132,8 +140,7 @@ class AutoRepeat(Document):
|
|||
"next_scheduled_date" : next_date
|
||||
}
|
||||
schedule_details.append(row)
|
||||
next_date = get_next_schedule_date(
|
||||
next_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, end_date, for_full_schedule=True)
|
||||
next_date = self.get_next_schedule_date(schedule_date=next_date, for_full_schedule=True)
|
||||
|
||||
return schedule_details
|
||||
|
||||
|
|
@ -211,6 +218,75 @@ class AutoRepeat(Document):
|
|||
new_doc.set('from_date', from_date)
|
||||
new_doc.set('to_date', to_date)
|
||||
|
||||
def get_next_schedule_date(self, schedule_date, for_full_schedule=False):
|
||||
"""
|
||||
Returns the next schedule date for auto repeat after a recurring document has been created.
|
||||
Adds required offset to the schedule_date param and returns the next schedule date.
|
||||
|
||||
:param schedule_date: The date when the last recurring document was created.
|
||||
:param for_full_schedule: If True, returns the immediate next schedule date, else the full schedule.
|
||||
"""
|
||||
if month_map.get(self.frequency):
|
||||
month_count = month_map.get(self.frequency) + month_diff(schedule_date, self.start_date) - 1
|
||||
else:
|
||||
month_count = 0
|
||||
|
||||
day_count = 0
|
||||
if month_count and self.repeat_on_last_day:
|
||||
day_count = 31
|
||||
next_date = get_next_date(self.start_date, month_count, day_count)
|
||||
elif month_count and self.repeat_on_day:
|
||||
day_count = self.repeat_on_day
|
||||
next_date = get_next_date(self.start_date, month_count, day_count)
|
||||
elif month_count:
|
||||
next_date = get_next_date(self.start_date, month_count)
|
||||
else:
|
||||
days = self.get_days(schedule_date)
|
||||
next_date = add_days(schedule_date, days)
|
||||
|
||||
# next schedule date should be after or on current date
|
||||
if not for_full_schedule:
|
||||
while getdate(next_date) < getdate(today()):
|
||||
if month_count:
|
||||
month_count += month_map.get(self.frequency, 0)
|
||||
next_date = get_next_date(self.start_date, month_count, day_count)
|
||||
else:
|
||||
days = self.get_days(next_date)
|
||||
next_date = add_days(next_date, days)
|
||||
|
||||
return next_date
|
||||
|
||||
def get_days(self, schedule_date):
|
||||
if self.frequency == "Weekly":
|
||||
days = self.get_offset_for_weekly_frequency(schedule_date)
|
||||
else:
|
||||
# daily frequency
|
||||
days = 1
|
||||
|
||||
return days
|
||||
|
||||
def get_offset_for_weekly_frequency(self, schedule_date):
|
||||
# if weekdays are not set, offset is 7 from current schedule date
|
||||
if not self.repeat_on_days:
|
||||
return 7
|
||||
|
||||
repeat_on_days = self.get_auto_repeat_days()
|
||||
current_schedule_day = getdate(schedule_date).weekday()
|
||||
weekdays = list(week_map.keys())
|
||||
|
||||
# if repeats on more than 1 day or
|
||||
# start date's weekday is not in repeat days, then get next weekday
|
||||
# else offset is 7
|
||||
if len(repeat_on_days) > 1 or weekdays[current_schedule_day] not in repeat_on_days:
|
||||
weekday = get_next_weekday(current_schedule_day, repeat_on_days)
|
||||
next_weekday_number = week_map.get(weekday, 0)
|
||||
# offset for upcoming weekday
|
||||
return timedelta((7 + next_weekday_number - current_schedule_day) % 7).days
|
||||
return 7
|
||||
|
||||
def get_auto_repeat_days(self):
|
||||
return [d.day for d in self.get('repeat_on_days', [])]
|
||||
|
||||
def send_notification(self, new_doc):
|
||||
"""Notify concerned people about recurring document generation"""
|
||||
subject = self.subject or ''
|
||||
|
|
@ -291,42 +367,24 @@ class AutoRepeat(Document):
|
|||
)
|
||||
|
||||
|
||||
def get_next_schedule_date(schedule_date, frequency, start_date, repeat_on_day=None, repeat_on_last_day=False, end_date=None, for_full_schedule=False):
|
||||
if month_map.get(frequency):
|
||||
month_count = month_map.get(frequency) + month_diff(schedule_date, start_date) - 1
|
||||
else:
|
||||
month_count = 0
|
||||
|
||||
day_count = 0
|
||||
if month_count and repeat_on_last_day:
|
||||
day_count = 31
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif month_count and repeat_on_day:
|
||||
day_count = repeat_on_day
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif month_count:
|
||||
next_date = get_next_date(start_date, month_count)
|
||||
else:
|
||||
days = 7 if frequency == 'Weekly' else 1
|
||||
next_date = add_days(schedule_date, days)
|
||||
|
||||
# next schedule date should be after or on current date
|
||||
if not for_full_schedule:
|
||||
while getdate(next_date) < getdate(today()):
|
||||
if month_count:
|
||||
month_count += month_map.get(frequency)
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif days:
|
||||
next_date = add_days(next_date, days)
|
||||
|
||||
return next_date
|
||||
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
return dt
|
||||
|
||||
|
||||
def get_next_weekday(current_schedule_day, weekdays):
|
||||
days = list(week_map.keys())
|
||||
if current_schedule_day > 0:
|
||||
days = days[(current_schedule_day + 1):] + days[:current_schedule_day]
|
||||
else:
|
||||
days = days[(current_schedule_day + 1):]
|
||||
|
||||
for entry in days:
|
||||
if entry in weekdays:
|
||||
return entry
|
||||
|
||||
|
||||
#called through hooks
|
||||
def make_auto_repeat_entry():
|
||||
enqueued_method = 'frappe.automation.doctype.auto_repeat.auto_repeat.create_repeated_entries'
|
||||
|
|
@ -337,6 +395,7 @@ def make_auto_repeat_entry():
|
|||
data = get_auto_repeat_entries(date)
|
||||
frappe.enqueue(enqueued_method, data=data)
|
||||
|
||||
|
||||
def create_repeated_entries(data):
|
||||
for d in data:
|
||||
doc = frappe.get_doc('Auto Repeat', d.name)
|
||||
|
|
@ -346,10 +405,11 @@ def create_repeated_entries(data):
|
|||
|
||||
if schedule_date == current_date and not doc.disabled:
|
||||
doc.create_documents()
|
||||
schedule_date = get_next_schedule_date(schedule_date, doc.frequency, doc.start_date, doc.repeat_on_day, doc.repeat_on_last_day, doc.end_date)
|
||||
schedule_date = doc.get_next_schedule_date(schedule_date=schedule_date)
|
||||
if schedule_date and not doc.disabled:
|
||||
frappe.db.set_value('Auto Repeat', doc.name, 'next_schedule_date', schedule_date)
|
||||
|
||||
|
||||
def get_auto_repeat_entries(date=None):
|
||||
if not date:
|
||||
date = getdate(today())
|
||||
|
|
@ -358,6 +418,7 @@ def get_auto_repeat_entries(date=None):
|
|||
['status', '=', 'Active']
|
||||
])
|
||||
|
||||
|
||||
#called through hooks
|
||||
def set_auto_repeat_as_completed():
|
||||
auto_repeat = frappe.get_all("Auto Repeat", filters = {'status': ['!=', 'Disabled']})
|
||||
|
|
@ -367,6 +428,7 @@ def set_auto_repeat_as_completed():
|
|||
doc.status = 'Completed'
|
||||
doc.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = None, end_date = None):
|
||||
if not start_date:
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import unittest
|
|||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.automation.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries
|
||||
from frappe.automation.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries, week_map
|
||||
from frappe.utils import today, add_days, getdate, add_months
|
||||
|
||||
|
||||
def add_custom_fields():
|
||||
df = dict(
|
||||
fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', insert_after='sender',
|
||||
|
|
@ -42,6 +41,52 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
|
||||
self.assertEqual(todo.get('description'), new_todo.get('description'))
|
||||
|
||||
def test_weekly_auto_repeat(self):
|
||||
todo = frappe.get_doc(
|
||||
dict(doctype='ToDo', description='test weekly todo', assigned_by='Administrator')).insert()
|
||||
|
||||
doc = make_auto_repeat(reference_doctype='ToDo',
|
||||
frequency='Weekly', reference_document=todo.name, start_date=add_days(today(), -7))
|
||||
|
||||
self.assertEqual(doc.next_schedule_date, today())
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
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_weekly_auto_repeat_with_weekdays(self):
|
||||
todo = frappe.get_doc(
|
||||
dict(doctype='ToDo', description='test auto repeat with weekdays', assigned_by='Administrator')).insert()
|
||||
|
||||
weekdays = list(week_map.keys())
|
||||
current_weekday = getdate().weekday()
|
||||
days = [
|
||||
{'day': weekdays[current_weekday]},
|
||||
{'day': weekdays[(current_weekday + 2) % 7]}
|
||||
]
|
||||
doc = make_auto_repeat(reference_doctype='ToDo',
|
||||
frequency='Weekly', reference_document=todo.name, start_date=add_days(today(), -7), days=days)
|
||||
|
||||
self.assertEqual(doc.next_schedule_date, today())
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
frappe.db.commit()
|
||||
|
||||
todo = frappe.get_doc(doc.reference_doctype, doc.reference_document)
|
||||
self.assertEqual(todo.auto_repeat, doc.name)
|
||||
|
||||
doc.reload()
|
||||
self.assertEqual(doc.next_schedule_date, add_days(getdate(), 2))
|
||||
|
||||
def test_monthly_auto_repeat(self):
|
||||
start_date = today()
|
||||
end_date = add_months(start_date, 12)
|
||||
|
|
@ -144,7 +189,8 @@ def make_auto_repeat(**args):
|
|||
'notify_by_email': args.notify or 0,
|
||||
'recipients': args.recipients or "",
|
||||
'subject': args.subject or "",
|
||||
'message': args.message or ""
|
||||
'message': args.message or "",
|
||||
'repeat_on_days': args.days or []
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
return doc
|
||||
|
|
|
|||
0
frappe/automation/doctype/auto_repeat_day/__init__.py
Normal file
0
frappe/automation/doctype/auto_repeat_day/__init__.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-10 22:30:53.690228",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"day"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "day",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Day",
|
||||
"options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-10 22:30:53.690228",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Auto Repeat Day",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py
Normal file
10
frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AutoRepeatDay(Document):
|
||||
pass
|
||||
|
|
@ -289,9 +289,15 @@ class DocType(Document):
|
|||
|
||||
self.update_fields_to_fetch()
|
||||
|
||||
from frappe import conf
|
||||
allow_doctype_export = frappe.flags.allow_doctype_export or (not frappe.flags.in_test and conf.get('developer_mode'))
|
||||
if not self.custom and not frappe.flags.in_import and allow_doctype_export:
|
||||
allow_doctype_export = (
|
||||
not self.custom
|
||||
and not frappe.flags.in_import
|
||||
and (
|
||||
frappe.conf.developer_mode
|
||||
or frappe.flags.allow_doctype_export
|
||||
)
|
||||
)
|
||||
if allow_doctype_export:
|
||||
self.export_doc()
|
||||
self.make_controller_template()
|
||||
|
||||
|
|
@ -369,13 +375,10 @@ class DocType(Document):
|
|||
if merge:
|
||||
frappe.throw(_("DocType can not be merged"))
|
||||
|
||||
# Do not rename and move files and folders for custom doctype
|
||||
if not self.custom and not frappe.flags.in_test and not frappe.flags.in_patch:
|
||||
self.rename_files_and_folders(old, new)
|
||||
|
||||
def after_rename(self, old, new, merge=False):
|
||||
"""Change table name using `RENAME TABLE` if table exists. Or update
|
||||
`doctype` property for Single type."""
|
||||
|
||||
if self.issingle:
|
||||
frappe.db.sql("""update tabSingles set doctype=%s where doctype=%s""", (new, old))
|
||||
frappe.db.sql("""update tabSingles set value=%s
|
||||
|
|
@ -385,6 +388,20 @@ class DocType(Document):
|
|||
"mariadb": f"RENAME TABLE `tab{old}` TO `tab{new}`",
|
||||
"postgres": f"ALTER TABLE `tab{old}` RENAME TO `tab{new}`"
|
||||
})
|
||||
frappe.db.commit()
|
||||
|
||||
# Do not rename and move files and folders for custom doctype
|
||||
if not self.custom:
|
||||
if not frappe.flags.in_patch:
|
||||
self.rename_files_and_folders(old, new)
|
||||
|
||||
for site in frappe.utils.get_sites():
|
||||
frappe.cache().delete(f"{site}:doctype_classes", old)
|
||||
|
||||
def after_delete(self):
|
||||
if not self.custom:
|
||||
for site in frappe.utils.get_sites():
|
||||
frappe.cache().delete(f"{site}:doctype_classes", self.name)
|
||||
|
||||
def rename_files_and_folders(self, old, new):
|
||||
# move files
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class ModuleDef(Document):
|
|||
def on_trash(self):
|
||||
"""Delete module name from modules.txt"""
|
||||
|
||||
if frappe.flags.in_uninstall or self.custom:
|
||||
if not frappe.conf.get('developer_mode') or frappe.flags.in_uninstall or self.custom:
|
||||
return
|
||||
|
||||
modules = None
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Options"
|
||||
},
|
||||
{
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-17 16:15:46.937267",
|
||||
"modified": "2020-12-05 19:20:00.503097",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Report Filter",
|
||||
|
|
|
|||
|
|
@ -98,15 +98,16 @@ class User(Document):
|
|||
self.share_with_self()
|
||||
clear_notifications(user=self.name)
|
||||
frappe.clear_cache(user=self.name)
|
||||
now=frappe.flags.in_test or frappe.flags.in_install
|
||||
self.send_password_notification(self.__new_password)
|
||||
frappe.enqueue(
|
||||
'frappe.core.doctype.user.user.create_contact',
|
||||
user=self,
|
||||
ignore_mandatory=True,
|
||||
now=frappe.flags.in_test or frappe.flags.in_install
|
||||
now=now
|
||||
)
|
||||
if self.name not in ('Administrator', 'Guest') and not self.user_image:
|
||||
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name)
|
||||
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name, now=now)
|
||||
|
||||
# Set user selected timezone
|
||||
if self.time_zone:
|
||||
|
|
|
|||
|
|
@ -76,7 +76,12 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa
|
|||
|
||||
delete_from_table(doctype, name, ignore_doctypes, None)
|
||||
|
||||
if not (for_reload or frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_uninstall or frappe.flags.in_test):
|
||||
if frappe.conf.developer_mode and not doc.custom and not (
|
||||
for_reload
|
||||
or frappe.flags.in_migrate
|
||||
or frappe.flags.in_install
|
||||
or frappe.flags.in_uninstall
|
||||
):
|
||||
try:
|
||||
delete_controllers(name, doc.module)
|
||||
except (FileNotFoundError, OSError, KeyError):
|
||||
|
|
|
|||
|
|
@ -49,9 +49,7 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F
|
|||
old_doc = frappe.get_doc(doctype, old)
|
||||
out = old_doc.run_method("before_rename", old, new, merge) or {}
|
||||
new = (out.get("new") or new) if isinstance(out, dict) else (out or new)
|
||||
|
||||
if doctype != "DocType":
|
||||
new = validate_rename(doctype, new, meta, merge, force, ignore_permissions)
|
||||
new = validate_rename(doctype, new, meta, merge, force, ignore_permissions)
|
||||
|
||||
if not merge:
|
||||
rename_parent_and_child(doctype, old, new, meta)
|
||||
|
|
@ -250,6 +248,7 @@ def update_link_field_values(link_fields, old, new, doctype):
|
|||
pass
|
||||
else:
|
||||
parent = field['parent']
|
||||
docfield = field["fieldname"]
|
||||
|
||||
# Handles the case where one of the link fields belongs to
|
||||
# the DocType being renamed.
|
||||
|
|
@ -261,11 +260,8 @@ def update_link_field_values(link_fields, old, new, doctype):
|
|||
if parent == new and doctype == "DocType":
|
||||
parent = old
|
||||
|
||||
frappe.db.sql("""
|
||||
update `tab{table_name}` set `{fieldname}`=%s
|
||||
where `{fieldname}`=%s""".format(
|
||||
table_name=parent,
|
||||
fieldname=field['fieldname']), (new, old))
|
||||
frappe.db.set_value(parent, {docfield: old}, docfield, new)
|
||||
|
||||
# update cached link_fields as per new
|
||||
if doctype=='DocType' and field['parent'] == old:
|
||||
field['parent'] = new
|
||||
|
|
|
|||
|
|
@ -53,14 +53,17 @@ def get_transitions(doc, workflow = None, raise_exception=False):
|
|||
return transitions
|
||||
|
||||
def get_workflow_safe_globals():
|
||||
# access to frappe.db.get_value and frappe.db.get_list
|
||||
# access to frappe.db.get_value, frappe.db.get_list, and date time utils.
|
||||
return dict(
|
||||
frappe=frappe._dict(
|
||||
db=frappe._dict(
|
||||
get_value=frappe.db.get_value,
|
||||
get_list=frappe.db.get_list
|
||||
db=frappe._dict(get_value=frappe.db.get_value, get_list=frappe.db.get_list),
|
||||
session=frappe.session,
|
||||
utils=frappe._dict(
|
||||
now_datetime=frappe.utils.now_datetime,
|
||||
add_to_date=frappe.utils.add_to_date,
|
||||
get_datetime=frappe.utils.get_datetime,
|
||||
now=frappe.utils.now,
|
||||
),
|
||||
session=frappe.session
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ frappe.ui.form.on("Print Format", {
|
|||
hide_absolute_value_field: function (frm) {
|
||||
// TODO: make it work with frm.doc.doc_type
|
||||
// Problem: frm isn't updated in some random cases
|
||||
const doctype = locals[frm.doc.doctype][frm.doc.name];
|
||||
const doctype = locals[frm.doc.doctype][frm.doc.name].doc_type;
|
||||
if (doctype) {
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
const meta = frappe.get_meta(doctype);
|
||||
|
|
|
|||
|
|
@ -201,17 +201,17 @@
|
|||
{
|
||||
"default": "0",
|
||||
"depends_on": "doc_type",
|
||||
"description": "If checked, negative numberic values of Currency, Quantity or Count would be shown as positive",
|
||||
"description": "If checked, negative numeric values of Currency, Quantity or Count would be shown as positive",
|
||||
"fieldname": "absolute_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show absolute values"
|
||||
"label": "Show Absolute Values"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-print",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-10 18:58:55.598269",
|
||||
"modified": "2020-12-14 11:38:49.132061",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Printing",
|
||||
"name": "Print Format",
|
||||
|
|
|
|||
|
|
@ -50,7 +50,15 @@ frappe.form.formatters = {
|
|||
return frappe.form.formatters._right(value==null ? "" : cint(value), options)
|
||||
},
|
||||
Percent: function(value, docfield, options) {
|
||||
return frappe.form.formatters._right(flt(value, 2) + "%", options)
|
||||
const precision = (
|
||||
docfield.precision
|
||||
|| cint(
|
||||
frappe.boot.sysdefaults
|
||||
&& frappe.boot.sysdefaults.float_precision
|
||||
)
|
||||
|| 2
|
||||
);
|
||||
return frappe.form.formatters._right(flt(value, precision) + "%", options);
|
||||
},
|
||||
Rating: function(value) {
|
||||
return `<span class="rating">
|
||||
|
|
|
|||
|
|
@ -36,9 +36,14 @@ frappe.ui.form.QuickEntryForm = Class.extend({
|
|||
this.render_dialog();
|
||||
resolve(this);
|
||||
} else {
|
||||
// no quick entry, open full form
|
||||
frappe.quick_entry = null;
|
||||
frappe.set_route('Form', this.doctype, this.doc.name)
|
||||
.then(() => resolve(this));
|
||||
// call init_callback for consistency
|
||||
if (this.init_callback) {
|
||||
this.init_callback(this.doc);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -89,11 +89,19 @@ frappe.render_template = function(name, data) {
|
|||
}
|
||||
frappe.render_grid = function(opts) {
|
||||
// build context
|
||||
if(opts.grid) {
|
||||
if (opts.grid) {
|
||||
opts.columns = opts.grid.getColumns();
|
||||
opts.data = opts.grid.getData().getItems();
|
||||
}
|
||||
|
||||
if (
|
||||
opts.print_settings &&
|
||||
opts.print_settings.orientation &&
|
||||
opts.print_settings.orientation.toLowerCase() === "landscape"
|
||||
) {
|
||||
opts.landscape = true;
|
||||
}
|
||||
|
||||
// show landscape view if columns more than 10
|
||||
if (opts.landscape == null) {
|
||||
if(opts.columns && opts.columns.length > 10) {
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
|
|||
var f = this.fields_dict[key];
|
||||
if (f.get_value) {
|
||||
var v = f.get_value();
|
||||
if (f.df.reqd && is_null(v))
|
||||
if (f.df.reqd && is_null(strip_html(v)))
|
||||
errors.push(__(f.df.label));
|
||||
|
||||
if (f.df.reqd
|
||||
|
|
|
|||
|
|
@ -1132,7 +1132,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
get_filter_values(raise) {
|
||||
const mandatory = this.filters.filter(f => f.df.reqd);
|
||||
|
||||
// check for mandatory property for filters added via UI
|
||||
const mandatory = this.filters.filter(f => (f.df.reqd || f.df.mandatory));
|
||||
const missing_mandatory = mandatory.filter(f => !f.get_value());
|
||||
if (raise && missing_mandatory.length > 0) {
|
||||
let message = __('Please set filters');
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
|
||||
save() {
|
||||
let is_new = this.is_new;
|
||||
if (this.validate && !this.validate()) {
|
||||
frappe.throw(__("Couldn't save, please check the data you have entered"), __("Validation Error"));
|
||||
}
|
||||
|
|
@ -139,6 +140,18 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
this.handle_success(response.message);
|
||||
frappe.web_form.events.trigger('after_save');
|
||||
this.after_save && this.after_save();
|
||||
// args doctype and docname added to link doctype in file manager
|
||||
if (is_new) {
|
||||
frappe.call({
|
||||
type: 'POST',
|
||||
method: "frappe.handler.upload_file",
|
||||
args: {
|
||||
file_url: response.message.attachment,
|
||||
doctype: response.message.doctype,
|
||||
docname: response.message.name
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
always: function() {
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ export default class NumberCardWidget extends Widget {
|
|||
get_number_for_custom_card(res) {
|
||||
if (typeof res === 'object') {
|
||||
this.number = res.value;
|
||||
this.get_formatted_number(res);
|
||||
this.set_formatted_number(res);
|
||||
} else {
|
||||
this.formatted_number = res;
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ export default class NumberCardWidget extends Widget {
|
|||
return frappe.model.with_doctype(this.card_doc.document_type, () => {
|
||||
const based_on_df =
|
||||
frappe.meta.get_docfield(this.card_doc.document_type, this.card_doc.aggregate_function_based_on);
|
||||
this.get_formatted_number(based_on_df);
|
||||
this.set_formatted_number(based_on_df);
|
||||
});
|
||||
} else {
|
||||
this.formatted_number = res;
|
||||
|
|
@ -200,10 +200,10 @@ export default class NumberCardWidget extends Widget {
|
|||
}, []);
|
||||
const col = res.columns.find(col => col.fieldname == field);
|
||||
this.number = frappe.report_utils.get_result_of_fn(this.card_doc.report_function, vals);
|
||||
this.get_formatted_number(col);
|
||||
this.set_formatted_number(col);
|
||||
}
|
||||
|
||||
get_formatted_number(df) {
|
||||
set_formatted_number(df) {
|
||||
const default_country = frappe.sys_defaults.country;
|
||||
const shortened_number = frappe.utils.shorten_number(this.number, default_country, 5);
|
||||
let number_parts = shortened_number.split(' ');
|
||||
|
|
@ -257,11 +257,17 @@ export default class NumberCardWidget extends Widget {
|
|||
};
|
||||
const stats_qualifier = stats_qualifier_map[this.card_doc.stats_time_interval];
|
||||
|
||||
let get_stat = () => {
|
||||
const parts = this.percentage_stat.split(' ');
|
||||
const symbol = parts[1] || '';
|
||||
return Math.abs(parts[0]) + ' ' + symbol;
|
||||
};
|
||||
|
||||
$(this.body).find('.widget-content').append(`<div class="card-stats ${color_class}">
|
||||
<span class="percentage-stat-area">
|
||||
${caret_html}
|
||||
<span class="percentage-stat">
|
||||
${Math.abs(this.percentage_stat)} %
|
||||
${get_stat()} %
|
||||
</span>
|
||||
</span>
|
||||
<span class="stat-period text-muted">
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -150,6 +150,8 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
|
|||
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
|
||||
{% elif df.fieldtype=="HTML" %}
|
||||
{{ frappe.render_template(df.options, {"doc":doc}) }}
|
||||
{% elif df.fieldtype=="Currency" %}
|
||||
{{ doc.get_formatted(df.fieldname, parent_doc or doc, translated=df.translatable) }}
|
||||
{% else %}
|
||||
{%- set parent = parent_doc or doc -%}
|
||||
{{ doc.get_formatted(df.fieldname, parent, translated=df.translatable, absolute_value=parent.absolute_value) }}
|
||||
|
|
|
|||
|
|
@ -249,82 +249,6 @@ class TestDocument(unittest.TestCase):
|
|||
|
||||
self.assertEqual(cint(old_current) - 1, new_current)
|
||||
|
||||
def test_rename_doc(self):
|
||||
from random import choice, sample
|
||||
|
||||
available_documents = []
|
||||
doctype = "ToDo"
|
||||
|
||||
# data generation: 4 todo documents
|
||||
for num in range(1, 5):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": doctype,
|
||||
"date": add_to_date(now(), days=num),
|
||||
"description": "this is todo #{}".format(num)
|
||||
}).insert()
|
||||
available_documents.append(doc.name)
|
||||
|
||||
# test 1: document renaming
|
||||
old_name = choice(available_documents)
|
||||
new_name = old_name + '.new'
|
||||
self.assertEqual(new_name, frappe.rename_doc(doctype, old_name, new_name, force=True))
|
||||
available_documents.remove(old_name)
|
||||
available_documents.append(new_name)
|
||||
|
||||
# test 2: merge documents
|
||||
first_todo, second_todo = sample(available_documents, 2)
|
||||
|
||||
second_todo_doc = frappe.get_doc(doctype, second_todo)
|
||||
second_todo_doc.priority = "High"
|
||||
second_todo_doc.save()
|
||||
|
||||
merged_todo = frappe.rename_doc(doctype, first_todo, second_todo, merge=True, force=True)
|
||||
merged_todo_doc = frappe.get_doc(doctype, merged_todo)
|
||||
available_documents.remove(first_todo)
|
||||
|
||||
with self.assertRaises(DoesNotExistError):
|
||||
frappe.get_doc(doctype, first_todo)
|
||||
|
||||
self.assertEqual(merged_todo_doc.priority, second_todo_doc.priority)
|
||||
|
||||
for docname in available_documents:
|
||||
frappe.delete_doc(doctype, docname)
|
||||
|
||||
def test_rename_doctype(self):
|
||||
from frappe.core.doctype.doctype.test_doctype import new_doctype
|
||||
|
||||
fields =[{
|
||||
"label": "Linked To",
|
||||
"fieldname": "linked_to_doctype",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"unique": 0
|
||||
}]
|
||||
if not frappe.db.exists("DocType", "Rename This"):
|
||||
new_doctype("Rename This", unique=0, fields=fields).insert()
|
||||
|
||||
to_rename_record = frappe.get_doc({
|
||||
"doctype": "Rename This",
|
||||
"linked_to_doctype": "Rename This"
|
||||
})
|
||||
to_rename_record.insert()
|
||||
|
||||
# Rename doctype
|
||||
self.assertEqual("Renamed Doc", frappe.rename_doc("DocType", "Rename This", "Renamed Doc", force=True))
|
||||
|
||||
# Test if Doctype value has changed in Link field
|
||||
renamed_doctype_record = frappe.get_doc("Renamed Doc", to_rename_record.name)
|
||||
self.assertEqual(renamed_doctype_record.linked_to_doctype, "Renamed Doc")
|
||||
|
||||
# Test if there are conflicts between a record and a DocType
|
||||
# having the same name
|
||||
old_name = to_rename_record.name
|
||||
new_name = "ToDo"
|
||||
self.assertEqual(new_name, frappe.rename_doc("Renamed Doc", old_name, new_name, force=True))
|
||||
|
||||
frappe.delete_doc_if_exists("Renamed Doc", "ToDo")
|
||||
frappe.delete_doc_if_exists("DocType", "Renamed Doc")
|
||||
|
||||
def test_non_negative_check(self):
|
||||
frappe.delete_doc_if_exists("Currency", "Frappe Coin", 1)
|
||||
|
||||
|
|
|
|||
159
frappe/tests/test_rename_doc.py
Normal file
159
frappe/tests/test_rename_doc.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import os
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_to_date, now
|
||||
from frappe.exceptions import DoesNotExistError
|
||||
|
||||
from random import choice, sample
|
||||
from frappe.model.base_document import get_controller
|
||||
from frappe.modules.utils import get_doc_path
|
||||
|
||||
|
||||
class TestRenameDoc(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
"""Setting Up data for the tests defined under TestRenameDoc"""
|
||||
# set developer_mode to rename doc controllers
|
||||
self._original_developer_flag = frappe.conf.developer_mode
|
||||
frappe.conf.developer_mode = 1
|
||||
|
||||
# data generation: for base and merge tests
|
||||
self.available_documents = []
|
||||
self.test_doctype = "ToDo"
|
||||
|
||||
for num in range(1, 5):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": self.test_doctype,
|
||||
"date": add_to_date(now(), days=num),
|
||||
"description": "this is todo #{}".format(num),
|
||||
}).insert()
|
||||
self.available_documents.append(doc.name)
|
||||
|
||||
# data generation: for controllers tests
|
||||
self.doctype = frappe._dict({
|
||||
"old": "Test Rename Document Old",
|
||||
"new": "Test Rename Document New",
|
||||
})
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"module": "Custom",
|
||||
"name": self.doctype.old,
|
||||
"custom": 0,
|
||||
"fields": [
|
||||
{"label": "Some Field", "fieldname": "some_fieldname", "fieldtype": "Data"}
|
||||
],
|
||||
"permissions": [{"role": "System Manager", "read": 1}],
|
||||
}).insert()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
"""Deleting data generated for the tests defined under TestRenameDoc"""
|
||||
# delete the documents created
|
||||
for docname in self.available_documents:
|
||||
frappe.delete_doc(self.test_doctype, docname)
|
||||
|
||||
for dt in self.doctype.values():
|
||||
if frappe.db.exists("DocType", dt):
|
||||
frappe.delete_doc("DocType", dt)
|
||||
frappe.db.sql_ddl(f"DROP TABLE IF EXISTS `tab{dt}`")
|
||||
|
||||
frappe.delete_doc_if_exists("Renamed Doc", "ToDo")
|
||||
|
||||
# reset original value of developer_mode conf
|
||||
frappe.conf.developer_mode = self._original_developer_flag
|
||||
|
||||
def setUp(self):
|
||||
frappe.flags.link_fields = {}
|
||||
super().setUp()
|
||||
|
||||
def test_rename_doc(self):
|
||||
"""Rename an existing document via frappe.rename_doc"""
|
||||
old_name = choice(self.available_documents)
|
||||
new_name = old_name + ".new"
|
||||
self.assertEqual(new_name, frappe.rename_doc(self.test_doctype, old_name, new_name, force=True))
|
||||
self.available_documents.remove(old_name)
|
||||
self.available_documents.append(new_name)
|
||||
|
||||
def test_merging_docs(self):
|
||||
"""Merge two documents via frappe.rename_doc"""
|
||||
first_todo, second_todo = sample(self.available_documents, 2)
|
||||
|
||||
second_todo_doc = frappe.get_doc(self.test_doctype, second_todo)
|
||||
second_todo_doc.priority = "High"
|
||||
second_todo_doc.save()
|
||||
|
||||
merged_todo = frappe.rename_doc(
|
||||
self.test_doctype, first_todo, second_todo, merge=True, force=True
|
||||
)
|
||||
merged_todo_doc = frappe.get_doc(self.test_doctype, merged_todo)
|
||||
self.available_documents.remove(first_todo)
|
||||
|
||||
with self.assertRaises(DoesNotExistError):
|
||||
frappe.get_doc(self.test_doctype, first_todo)
|
||||
|
||||
self.assertEqual(merged_todo_doc.priority, second_todo_doc.priority)
|
||||
|
||||
def test_rename_controllers(self):
|
||||
"""Rename doctypes with controller code paths"""
|
||||
# check if module exists exists;
|
||||
# if custom, get_controller will return Document class
|
||||
# if not custom, a different class will be returned
|
||||
self.assertNotEqual(get_controller(self.doctype.old), frappe.model.document.Document)
|
||||
|
||||
old_doctype_path = get_doc_path("Custom", "DocType", self.doctype.old)
|
||||
|
||||
# rename doc via wrapper API accessible via /desk
|
||||
frappe.rename_doc("DocType", self.doctype.old, self.doctype.new)
|
||||
|
||||
# check if database and controllers are updated
|
||||
self.assertTrue(frappe.db.exists("DocType", self.doctype.new))
|
||||
self.assertFalse(frappe.db.exists("DocType", self.doctype.old))
|
||||
self.assertFalse(os.path.exists(old_doctype_path))
|
||||
|
||||
def test_rename_doctype(self):
|
||||
"""Rename DocType via frappe.rename_doc"""
|
||||
from frappe.core.doctype.doctype.test_doctype import new_doctype
|
||||
|
||||
if not frappe.db.exists("DocType", "Rename This"):
|
||||
new_doctype(
|
||||
"Rename This",
|
||||
fields=[
|
||||
{
|
||||
"label": "Linked To",
|
||||
"fieldname": "linked_to_doctype",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"unique": 0,
|
||||
}
|
||||
],
|
||||
).insert()
|
||||
|
||||
to_rename_record = frappe.get_doc(
|
||||
{"doctype": "Rename This", "linked_to_doctype": "Rename This"}
|
||||
).insert()
|
||||
|
||||
# Rename doctype
|
||||
self.assertEqual(
|
||||
"Renamed Doc", frappe.rename_doc("DocType", "Rename This", "Renamed Doc", force=True)
|
||||
)
|
||||
|
||||
# Test if Doctype value has changed in Link field
|
||||
linked_to_doctype = frappe.db.get_value(
|
||||
"Renamed Doc", to_rename_record.name, "linked_to_doctype"
|
||||
)
|
||||
self.assertEqual(linked_to_doctype, "Renamed Doc")
|
||||
|
||||
# Test if there are conflicts between a record and a DocType
|
||||
# having the same name
|
||||
old_name = to_rename_record.name
|
||||
new_name = "ToDo"
|
||||
self.assertEqual(
|
||||
new_name, frappe.rename_doc("Renamed Doc", old_name, new_name, force=True)
|
||||
)
|
||||
|
||||
# delete_doc doesnt drop tables
|
||||
# this is done to bypass inconsistencies in the db
|
||||
frappe.delete_doc_if_exists("DocType", "Renamed Doc")
|
||||
frappe.db.sql_ddl("drop table if exists `tabRenamed Doc`")
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const hljs = require('highlight.js/lib/highlight');
|
||||
const hljs = require('highlight.js/lib/core');
|
||||
|
||||
hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'));
|
||||
hljs.registerLanguage('python', require('highlight.js/lib/languages/python'));
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@
|
|||
"icon": "fa fa-random",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-16 04:29:20.898040",
|
||||
"modified": "2020-12-17 20:35:16.898040",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Workflow",
|
||||
"name": "Workflow",
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@
|
|||
"label": "Example",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "<pre><code>doc.grand_total > 0</code></pre>\n\n<p>Conditions should be written in simple Python. Please use properties available in the form only.</p>",
|
||||
"options": "<pre><code>doc.grand_total > 0</code></pre>\n\n<p>Conditions should be written in simple Python. Please use properties available in the form only.</p>\n<p>Allowed functions: \n</p><ul>\n<li>frappe.db.get_value</li>\n<li>frappe.db.get_list</li>\n<li>frappe.session</li>\n<li>frappe.utils.now_datetime</li>\n<li>frappe.utils.get_datetime</li>\n<li>frappe.utils.add_to_date</li>\n<li>frappe.utils.now</li>\n</ul>\n<p>Example: </p><pre><code>doc.creation > frappe.utils.add_to_date(frappe.utils.now_datetime(), days=-5, as_string=True, as_datetime=True) </code></pre><p></p>",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
|
|
@ -320,7 +320,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-10-09 10:28:53.294908",
|
||||
"modified": "2020-11-08 12:11:00.294908",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Workflow",
|
||||
"name": "Workflow Transition",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
"frappe-datatable": "^1.15.3",
|
||||
"frappe-gantt": "^0.5.0",
|
||||
"fuse.js": "^3.4.6",
|
||||
"highlight.js": "^9.18.2",
|
||||
"highlight.js": "^10.4.1",
|
||||
"js-sha256": "^0.9.0",
|
||||
"jsbarcode": "^3.9.0",
|
||||
"moment": "^2.20.1",
|
||||
|
|
|
|||
14
yarn.lock
14
yarn.lock
|
|
@ -2931,10 +2931,10 @@ hex-color-regex@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
|
||||
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
|
||||
|
||||
highlight.js@^9.18.2:
|
||||
version "9.18.5"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825"
|
||||
integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==
|
||||
highlight.js@^10.4.1:
|
||||
version "10.4.1"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0"
|
||||
integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==
|
||||
|
||||
homedir-polyfill@^1.0.1:
|
||||
version "1.0.3"
|
||||
|
|
@ -3147,9 +3147,9 @@ inherits@2.0.3:
|
|||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
version "1.3.8"
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
|
||||
inquirer@^7.3.3:
|
||||
version "7.3.3"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue