Merge branch 'develop' into add_pdf_backend_hook

This commit is contained in:
Maharshi Patel 2025-03-06 11:47:32 +05:30
commit 8ff33c1ea4
63 changed files with 10273 additions and 9440 deletions

View file

@ -70,7 +70,7 @@ jobs:
- name: Clone
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@v4.1.9
- name: Upload coverage data
uses: codecov/codecov-action@v5
with:

View file

@ -57,7 +57,7 @@ jobs:
- name: Clone
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4.1.8
uses: actions/download-artifact@v4.1.9
- name: Upload python coverage data
uses: codecov/codecov-action@v5
with:

View file

@ -41,6 +41,8 @@ describe(
before(() => {
cy.login();
cy.visit(`/app/note/new`);
// close the sidebar cause default is expanded
cy.get(".body-sidebar .collapse-sidebar-link").click();
});
test_button_names.forEach((button_name) => {

View file

@ -15,6 +15,7 @@ from frappe.desk.doctype.form_tour.form_tour import get_onboarding_ui_tours
from frappe.desk.doctype.route_history.route_history import frequently_visited_links
from frappe.desk.form.load import get_meta_bundle
from frappe.email.inbox import get_email_accounts
from frappe.integrations.frappe_providers.frappecloud_billing import is_fc_site
from frappe.model.base_document import get_controller
from frappe.permissions import has_permission
from frappe.query_builder import DocType
@ -111,6 +112,7 @@ def get_bootinfo():
bootinfo.translated_doctypes = get_translated_doctypes()
bootinfo.subscription_conf = add_subscription_conf()
bootinfo.marketplace_apps = get_marketplace_apps()
bootinfo.is_fc_site = is_fc_site()
bootinfo.changelog_feed = get_changelog_feed_items()
bootinfo.enable_address_autocompletion = frappe.db.get_single_value(
"Geolocation Settings", "enable_address_autocompletion"
@ -139,7 +141,7 @@ def load_conf_settings(bootinfo):
from frappe.core.api.file import get_max_file_size
bootinfo.max_file_size = get_max_file_size()
for key in ("developer_mode", "socketio_port", "file_watcher_port", "fc_communication_secret"):
for key in ("developer_mode", "socketio_port", "file_watcher_port"):
if key in frappe.conf:
bootinfo[key] = frappe.conf.get(key)

View file

@ -494,7 +494,7 @@ def delete_doc(doctype, name):
for row in parent.get(parentfield):
if row.name == name:
parent.remove(row)
parent.save(ignore_permissions=True)
parent.save()
break
else:
frappe.delete_doc(doctype, name, ignore_missing=False)

View file

@ -36,7 +36,6 @@ frappe.ui.form.on("DocType", {
if (doc.custom && frappe.session.user != "Administrator") {
return {
query: "frappe.core.doctype.role.role.role_query",
filters: [["Role", "name", "!=", "All"]],
};
}
});

View file

@ -171,12 +171,14 @@ class Report(Document):
prepared_report_watcher.start()
# The JOB
if self.is_standard == "Yes":
res = self.execute_module(filters)
else:
res = self.execute_script(filters)
try:
if self.is_standard == "Yes":
res = self.execute_module(filters)
else:
res = self.execute_script(filters)
finally:
prepared_report_watcher and prepared_report_watcher.cancel()
prepared_report_watcher and prepared_report_watcher.cancel()
execution_time = (datetime.datetime.now() - start_time).total_seconds()
frappe.cache.hset("report_execution_time", self.name, execution_time)

View file

@ -121,10 +121,14 @@ def get_users(role):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def role_query(doctype, txt, searchfield, start, page_len, filters):
report_filters = [["Role", "name", "like", f"%{txt}%"], ["Role", "is_custom", "=", 0]]
if filters and isinstance(filters, list):
report_filters.extend(filters)
return frappe.get_all(
"Role", limit_start=start, limit_page_length=page_len, filters=report_filters, as_list=1
"Role",
limit_start=start,
limit_page_length=page_len,
filters=[
["Role", "name", "like", f"%{txt}%"],
["Role", "is_custom", "=", 0],
["Role", "name", "!=", "All"],
],
as_list=True,
)

View file

@ -891,6 +891,8 @@ def update_password(
user_doc, redirect_url = reset_user_data(user)
user_doc.validate_reset_password()
# get redirect url from cache
redirect_to = frappe.cache.hget("redirect_after_login", user)
if redirect_to:

View file

@ -0,0 +1,24 @@
// Copyright (c) 2024, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.query_reports["Prepared Report Analytics"] = {
filters: [
{
fieldname: "report",
label: __("Report"),
fieldtype: "Data",
},
{
fieldname: "top_10",
label: __("Top 10"),
fieldtype: "Check",
default: 0,
},
{
fieldname: "in_minutes",
label: __("In Minutes"),
fieldtype: "Check",
default: 0,
},
],
};

View file

@ -0,0 +1,27 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2024-12-18 11:58:00.693755",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"letterhead": null,
"modified": "2025-02-07 17:27:53.441631",
"modified_by": "Administrator",
"module": "Core",
"name": "Prepared Report Analytics",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Prepared Report",
"report_name": "Prepared Report Analytics",
"report_type": "Script Report",
"roles": [
{
"role": "System Manager"
}
],
"timeout": 0
}

View file

@ -0,0 +1,106 @@
# Copyright (c) 2024, Frappe Technologies and contributors
# For license information, please see license.txt
from pypika import Order
import frappe
from frappe import _, qb
from frappe.query_builder import Criterion
from frappe.utils import add_months, nowdate
def execute(filters: dict | None = None):
"""Return columns and data for the report.
This is the main entry point for the report. It accepts the filters as a
dictionary and should return columns and data. It is called by the framework
every time the report is refreshed or a filter is updated.
"""
columns = get_columns(filters)
data = get_data(filters=filters)
return columns, data
def get_columns(filters) -> list[dict]:
"""Return columns for the report.
One field definition per column, just like a DocType field definition.
"""
return [
{
"label": _("Prepared Report"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Prepared Report",
"width": 250,
},
{
"label": _("Report Name"),
"fieldname": "report_name",
"fieldtype": "Data",
"width": 250,
},
{
"label": _("Start"),
"fieldname": "creation",
"fieldtype": "DateTime",
"width": 250,
},
{
"label": _("End"),
"fieldname": "report_end_time",
"fieldtype": "DateTime",
"width": 250,
},
{
"label": _("Runtime in Minutes") if filters.in_minutes else _("Runtime in Seconds"),
"fieldname": "runtime",
"fieldtype": "float",
"width": 250,
},
{
"label": _("Memory Usage in MB"),
"fieldname": "peak_memory_usage",
"fieldtype": "float",
"width": 250,
},
]
def get_data(filters) -> list[list]:
"""Return data for the report.
The report data is a list of rows, with each row being a list of cell values.
"""
pr = qb.DocType("Prepared Report")
conditions = [pr.status.eq("Completed"), pr.creation.gte(add_months(nowdate(), -2))]
if filters.report:
conditions.append(pr.report_name.like(f"%{filters.report}%"))
divisor = 1
if filters.in_minutes:
divisor = 60
query = (
qb.from_(pr)
.select(
pr.name,
pr.report_name,
pr.creation,
pr.report_end_time,
(pr.peak_memory_usage / 1024).as_("peak_memory_usage"),
)
.select(((pr.report_end_time - pr.creation) / divisor).as_("runtime"))
.where(Criterion.all(conditions))
.orderby(qb.Field("runtime"), order=Order.desc)
)
if filters.top_10:
query = query.limit(10)
res = query.run(as_dict=True)
return res

View file

@ -67,8 +67,8 @@ frappe.ui.form.on("System Health Report", {
const style = document.createElement("style");
style.innerText = `.health-check-failed {
font-weight: bold;
color: var(--text-colour);
background-color: var(--bg-red);
color: var(--text-colour) !important;
background-color: var(--bg-red) !important;
}`;
document.head.appendChild(style);

View file

@ -29,8 +29,8 @@ def savedocs(doc, action):
# action
doc.docstatus = {
"Save": DocStatus.DRAFT,
"Submit": DocStatus.SUMBITTED,
"Update": DocStatus.SUMBITTED,
"Submit": DocStatus.SUBMITTED,
"Update": DocStatus.SUBMITTED,
"Cancel": DocStatus.CANCELLED,
}[action]

View file

@ -351,6 +351,7 @@ def get_doc_count(doctype, filters):
limit=100,
distinct=True,
ignore_ifnull=True,
order_by=None,
)
)

View file

@ -111,6 +111,10 @@ def generate_report_result(
if cint(report.add_total_row) and result and not skip_total_row:
result = add_total_row(result, columns, is_tree=is_tree, parent_field=parent_field)
if isinstance(filters, dict) and filters.get("translate_data"):
total_row = cint(report.add_total_row) and result and not skip_total_row
result = translate_report_data(result, total_row)
return {
"result": result,
"columns": columns,
@ -809,3 +813,11 @@ def validate_filters_permissions(report_name, filters=None, user=None):
linked_doctype, filters[field.fieldname]
)
)
def translate_report_data(data, total_row):
for d in data[:-1] if total_row else data:
for field, value in d.items():
if isinstance(value, str):
d[field] = _(value)
return data

View file

@ -426,7 +426,7 @@ def export_query():
_(value) if translatable_fields[idx] else value for idx, value in enumerate(row)
]
processed_data.append(processed_row)
data.extend(processed_data)
data.extend(processed_data)
data = handle_duration_fieldtype_values(doctype, data, db_query.fields)

View file

@ -430,7 +430,7 @@ class EmailAccount(Document):
if _raise_error:
frappe.throw(
_("Please setup default Email Account from Settings > Email Account"),
_("Please setup default outgoing Email Account from Tools > Email Account"),
frappe.OutgoingEmailError,
)

View file

@ -263,6 +263,10 @@ class SessionBootFailed(ValidationError):
http_status_code = 500
class QueueOverloaded(ValidationError):
http_status_code = 503
class PrintFormatError(ValidationError):
pass

View file

@ -13,6 +13,7 @@
"authorize_google_calendar_access",
"sb_01",
"pull_from_google_calendar",
"sync_as_public",
"cb_01",
"push_to_google_calendar",
"section_break_3",
@ -98,6 +99,12 @@
"fieldtype": "Check",
"label": "Pull from Google Calendar"
},
{
"default": "0",
"fieldname": "sync_as_public",
"fieldtype": "Check",
"label": "Sync events from Google as public"
},
{
"fieldname": "cb_01",
"fieldtype": "Column Break"
@ -110,7 +117,7 @@
}
],
"links": [],
"modified": "2024-03-23 16:03:26.682486",
"modified": "2025-01-27 13:06:00.000000",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Google Calendar",

View file

@ -79,6 +79,7 @@ class GoogleCalendar(Document):
google_calendar_id: DF.Data | None
next_sync_token: DF.Password | None
pull_from_google_calendar: DF.Check
sync_as_public: DF.Check
push_to_google_calendar: DF.Check
refresh_token: DF.Password | None
user: DF.Link
@ -375,6 +376,8 @@ def insert_event_to_calendar(account, event, recurrence=None):
"google_calendar_event_id": event.get("id"),
"google_meet_link": event.get("hangoutLink"),
"pulled_from_google_calendar": 1,
"owner": account.owner,
"event_type": "Public" if account.sync_as_public else "Private",
}
calendar_event.update(
google_calendar_to_repeat_on(recurrence=recurrence, start=event.get("start"), end=event.get("end"))

View file

@ -6,11 +6,15 @@ from frappe import _
def get_base_url():
url = "https://frappecloud.com"
if frappe.conf.developer_mode and frappe.conf.get("saas_billing_base_url"):
url = frappe.conf.get("saas_billing_base_url")
if frappe.conf.developer_mode and frappe.conf.get("fc_base_url"):
url = frappe.conf.get("fc_base_url")
return url
def get_site_login_url():
return f"{get_base_url()}/dashboard/site-login"
def get_site_name():
site_name = frappe.local.site
if frappe.conf.developer_mode and frappe.conf.get("saas_billing_site_name"):
@ -29,15 +33,28 @@ def get_headers():
return {
"X-Site-Token": frappe.conf.get("fc_communication_secret"),
"X-Site-User": frappe.session.user,
"X-Site": get_site_name(),
}
@frappe.whitelist()
def current_site_info():
from frappe.utils import cint
request = requests.post(f"{get_base_url()}/api/method/press.saas.api.site.info", headers=get_headers())
if request.status_code == 200:
return request.json().get("message")
res = request.json().get("message")
if not res:
return None
return {
**res,
"site_name": get_site_name(),
"base_url": get_base_url(),
"setup_complete": cint(frappe.get_system_settings("setup_complete")),
}
else:
frappe.throw(_("Failed to get site info"))
@ -58,19 +75,19 @@ def api(method, data=None):
@frappe.whitelist()
def is_fc_site():
def is_fc_site() -> bool:
is_system_manager = frappe.get_roles(frappe.session.user).count("System Manager")
setup_completed = frappe.get_system_settings("setup_complete")
return is_system_manager and setup_completed and frappe.conf.get("fc_communication_secret")
return bool(is_system_manager and setup_completed and frappe.conf.get("fc_communication_secret"))
# login to frappe cloud dashboard
@frappe.whitelist()
def send_verification_code(route: str):
def send_verification_code():
request = requests.post(
f"{get_base_url()}/api/method/press.api.developer.saas.send_verification_code",
headers=get_headers(),
json={"domain": get_site_name(), "route": route},
json={"domain": get_site_name()},
)
if request.status_code == 200:
return request.json().get("message")

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Framework VERSION\n"
"Report-Msgid-Bugs-To: developers@frappe.io\n"
"POT-Creation-Date: 2025-02-23 09:33+0000\n"
"PO-Revision-Date: 2025-02-23 09:33+0000\n"
"POT-Creation-Date: 2025-03-02 09:33+0000\n"
"PO-Revision-Date: 2025-03-02 09:33+0000\n"
"Last-Translator: developers@frappe.io\n"
"Language-Team: developers@frappe.io\n"
"MIME-Version: 1.0\n"
@ -135,7 +135,7 @@ msgstr ""
msgid "1 Day"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:359
#: frappe/integrations/doctype/google_calendar/google_calendar.py:360
msgid "1 Google Calendar Event synced."
msgstr ""
@ -885,7 +885,7 @@ msgstr ""
msgid "Action Complete"
msgstr ""
#: frappe/model/document.py:1860
#: frappe/model/document.py:1859
msgid "Action Failed"
msgstr ""
@ -1296,7 +1296,7 @@ msgstr ""
msgid "Added default log doctypes: {}"
msgstr ""
#: frappe/core/doctype/file/file.py:736
#: frappe/core/doctype/file/file.py:738
msgid "Added {0}"
msgstr ""
@ -1414,11 +1414,11 @@ msgstr ""
msgid "Administrator"
msgstr ""
#: frappe/core/doctype/user/user.py:1214
#: frappe/core/doctype/user/user.py:1216
msgid "Administrator Logged In"
msgstr ""
#: frappe/core/doctype/user/user.py:1208
#: frappe/core/doctype/user/user.py:1210
msgid "Administrator accessed {0} on {1} via IP Address {2}."
msgstr ""
@ -1662,8 +1662,8 @@ msgstr ""
msgid "Allow Dropbox Access"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:101
#: frappe/integrations/doctype/google_calendar/google_calendar.py:115
#: frappe/integrations/doctype/google_calendar/google_calendar.py:102
#: frappe/integrations/doctype/google_calendar/google_calendar.py:116
msgid "Allow Google Calendar Access"
msgstr ""
@ -1926,7 +1926,7 @@ msgstr ""
msgid "Allowing DocType, DocType. Be careful!"
msgstr ""
#: frappe/core/doctype/user/user.py:1024
#: frappe/core/doctype/user/user.py:1026
msgid "Already Registered"
msgstr ""
@ -2017,7 +2017,7 @@ msgstr ""
msgid "Amendment Naming Override"
msgstr ""
#: frappe/model/document.py:522
#: frappe/model/document.py:544
msgid "Amendment Not Allowed"
msgstr ""
@ -2025,7 +2025,7 @@ msgstr ""
msgid "Amendment naming rules updated."
msgstr ""
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:322
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:323
msgid "An error occurred while setting Session Defaults"
msgstr ""
@ -2985,7 +2985,7 @@ msgstr ""
#. Option for the 'Function' (Select) field in DocType 'Number Card'
#: frappe/desk/doctype/dashboard_chart/dashboard_chart.json
#: frappe/desk/doctype/number_card/number_card.json
#: frappe/public/js/frappe/form/controls/password.js:89
#: frappe/public/js/frappe/form/controls/password.js:88
#: frappe/public/js/frappe/ui/group_by/group_by.js:21
msgid "Average"
msgstr ""
@ -3132,7 +3132,7 @@ msgstr ""
#. 'System Health Report'
#: frappe/core/workspace/build/build.json
#: frappe/desk/doctype/system_health_report/system_health_report.json
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:178
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:179
msgid "Background Jobs"
msgstr ""
@ -4061,11 +4061,11 @@ msgstr ""
msgid "Cannot Remove"
msgstr ""
#: frappe/model/base_document.py:1100
#: frappe/model/base_document.py:1143
msgid "Cannot Update After Submit"
msgstr ""
#: frappe/core/doctype/file/file.py:591
#: frappe/core/doctype/file/file.py:589
msgid "Cannot access file path {0}"
msgstr ""
@ -4081,11 +4081,11 @@ msgstr ""
msgid "Cannot cancel {0}."
msgstr ""
#: frappe/model/document.py:986
#: frappe/model/document.py:1006
msgid "Cannot change docstatus from 0 (Draft) to 2 (Cancelled)"
msgstr ""
#: frappe/model/document.py:1000
#: frappe/model/document.py:1020
msgid "Cannot change docstatus from 1 (Submitted) to 0 (Draft)"
msgstr ""
@ -4168,7 +4168,7 @@ msgstr ""
msgid "Cannot edit a standard report. Please duplicate and create a new report"
msgstr ""
#: frappe/model/document.py:1006
#: frappe/model/document.py:1026
msgid "Cannot edit cancelled document"
msgstr ""
@ -4193,7 +4193,7 @@ msgstr ""
msgid "Cannot find file {} on disk"
msgstr ""
#: frappe/core/doctype/file/file.py:531
#: frappe/core/doctype/file/file.py:529
msgid "Cannot get file contents of a Folder"
msgstr ""
@ -4201,7 +4201,7 @@ msgstr ""
msgid "Cannot have multiple printers mapped to a single print format."
msgstr ""
#: frappe/model/document.py:1074
#: frappe/model/document.py:1094
msgid "Cannot link cancelled document: {0}"
msgstr ""
@ -4675,7 +4675,7 @@ msgstr ""
msgid "Click on the link below to verify your request"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:102
#: frappe/integrations/doctype/google_calendar/google_calendar.py:103
#: frappe/integrations/doctype/google_contacts/google_contacts.py:41
#: frappe/integrations/doctype/google_drive/google_drive.py:53
#: frappe/website/doctype/website_settings/website_settings.py:161
@ -5594,7 +5594,7 @@ msgstr ""
msgid "Could not connect to outgoing email server"
msgstr ""
#: frappe/model/document.py:1070
#: frappe/model/document.py:1090
msgid "Could not find {0}"
msgstr ""
@ -7745,7 +7745,7 @@ msgstr ""
msgid "Document Naming Settings"
msgstr ""
#: frappe/model/document.py:1718
#: frappe/model/document.py:469
msgid "Document Queued"
msgstr ""
@ -7902,7 +7902,7 @@ msgid "Document Types and Permissions"
msgstr ""
#: frappe/core/doctype/submission_queue/submission_queue.py:163
#: frappe/model/document.py:1924
#: frappe/model/document.py:1923
msgid "Document Unlocked"
msgstr ""
@ -8060,7 +8060,7 @@ msgstr ""
msgid "Double click to edit label"
msgstr ""
#: frappe/core/doctype/file/file.js:10
#: frappe/core/doctype/file/file.js:15
#: frappe/email/doctype/auto_email_report/auto_email_report.js:8
#: frappe/public/js/frappe/form/grid.js:66
msgid "Download"
@ -8208,7 +8208,7 @@ msgstr ""
msgid "Duplicate Filter Name"
msgstr ""
#: frappe/model/base_document.py:611 frappe/model/rename_doc.py:111
#: frappe/model/base_document.py:654 frappe/model/rename_doc.py:111
msgid "Duplicate Name"
msgstr ""
@ -8874,7 +8874,7 @@ msgstr ""
msgid "Enable Email Notifications"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:90
#: frappe/integrations/doctype/google_calendar/google_calendar.py:91
#: frappe/integrations/doctype/google_contacts/google_contacts.py:36
#: frappe/website/doctype/website_settings/website_settings.py:129
msgid "Enable Google API in Google Settings."
@ -9182,7 +9182,7 @@ msgstr ""
msgid "Ensure the user and group search paths are correct."
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:93
#: frappe/integrations/doctype/google_calendar/google_calendar.py:94
msgid "Enter Client Id and Client Secret in Google Settings."
msgstr ""
@ -9339,15 +9339,15 @@ msgstr ""
msgid "Error while evaluating Notification {0}. Please fix your template."
msgstr ""
#: frappe/model/base_document.py:751
#: frappe/model/base_document.py:794
msgid "Error: Data missing in table {0}"
msgstr ""
#: frappe/model/base_document.py:761
#: frappe/model/base_document.py:804
msgid "Error: Value missing for {0}: {1}"
msgstr ""
#: frappe/model/base_document.py:755
#: frappe/model/base_document.py:798
msgid "Error: {0} Row #{1}: Value missing for: {2}"
msgstr ""
@ -9388,8 +9388,8 @@ msgstr ""
msgid "Event Reminders"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:452
#: frappe/integrations/doctype/google_calendar/google_calendar.py:536
#: frappe/integrations/doctype/google_calendar/google_calendar.py:455
#: frappe/integrations/doctype/google_calendar/google_calendar.py:539
msgid "Event Synced with Google Calendar."
msgstr ""
@ -9463,7 +9463,7 @@ msgstr ""
msgid "Excel"
msgstr ""
#: frappe/public/js/frappe/form/controls/password.js:91
#: frappe/public/js/frappe/form/controls/password.js:90
msgid "Excellent"
msgstr ""
@ -10024,7 +10024,7 @@ msgstr ""
msgid "Field {0} does not exist on {1}"
msgstr ""
#: frappe/desk/form/meta.py:205
#: frappe/desk/form/meta.py:208
msgid "Field {0} is referring to non-existing doctype {1}."
msgstr ""
@ -10123,7 +10123,7 @@ msgstr ""
msgid "Fields Multicheck"
msgstr ""
#: frappe/core/doctype/file/file.py:410
#: frappe/core/doctype/file/file.py:408
msgid "Fields `file_name` or `file_url` must be set for File"
msgstr ""
@ -10228,7 +10228,7 @@ msgstr ""
msgid "File backup is ready"
msgstr ""
#: frappe/core/doctype/file/file.py:594
#: frappe/core/doctype/file/file.py:592
msgid "File name cannot have {0}"
msgstr ""
@ -10236,7 +10236,7 @@ msgstr ""
msgid "File not attached"
msgstr ""
#: frappe/core/doctype/file/file.py:700 frappe/public/js/frappe/request.js:199
#: frappe/core/doctype/file/file.py:702 frappe/public/js/frappe/request.js:199
#: frappe/utils/file_manager.py:221
msgid "File size exceeded the maximum allowed size of {0} MB"
msgstr ""
@ -10249,7 +10249,7 @@ msgstr ""
msgid "File type of {0} is not allowed"
msgstr ""
#: frappe/core/doctype/file/file.py:361 frappe/core/doctype/file/file.py:426
#: frappe/core/doctype/file/file.py:361 frappe/core/doctype/file/file.py:424
msgid "File {0} does not exist"
msgstr ""
@ -10504,7 +10504,7 @@ msgstr ""
msgid "Folder name should not include '/' (slash)"
msgstr ""
#: frappe/core/doctype/file/file.py:472
#: frappe/core/doctype/file/file.py:470
msgid "Folder {0} is not empty"
msgstr ""
@ -11131,7 +11131,7 @@ msgstr ""
msgid "Generate Random Password"
msgstr ""
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:172
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:173
#: frappe/public/js/frappe/utils/utils.js:1790
msgid "Generate Tracking URL"
msgstr ""
@ -11345,19 +11345,19 @@ msgstr ""
msgid "Google Calendar"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:776
#: frappe/integrations/doctype/google_calendar/google_calendar.py:779
msgid "Google Calendar - Contact / email not found. Did not add attendee for -<br>{0}"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:252
#: frappe/integrations/doctype/google_calendar/google_calendar.py:253
msgid "Google Calendar - Could not create Calendar for {0}, error code {1}."
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:572
#: frappe/integrations/doctype/google_calendar/google_calendar.py:575
msgid "Google Calendar - Could not delete Event {0} from Google Calendar, error code {1}."
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:289
#: frappe/integrations/doctype/google_calendar/google_calendar.py:290
msgid "Google Calendar - Could not fetch event from Google Calendar, error code {0}."
msgstr ""
@ -11365,11 +11365,11 @@ msgstr ""
msgid "Google Calendar - Could not insert contact in Google Contacts {0}, error code {1}."
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:455
#: frappe/integrations/doctype/google_calendar/google_calendar.py:458
msgid "Google Calendar - Could not insert event in Google Calendar {0}, error code {1}."
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:539
#: frappe/integrations/doctype/google_calendar/google_calendar.py:542
msgid "Google Calendar - Could not update Event {0} in Google Calendar, error code {1}."
msgstr ""
@ -11385,7 +11385,7 @@ msgstr ""
msgid "Google Calendar ID"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:167
#: frappe/integrations/doctype/google_calendar/google_calendar.py:168
msgid "Google Calendar has been configured."
msgstr ""
@ -12552,7 +12552,7 @@ msgstr ""
msgid "Image link '{0}' is not valid"
msgstr ""
#: frappe/core/doctype/file/file.js:105
#: frappe/core/doctype/file/file.js:107
msgid "Image optimized"
msgstr ""
@ -12832,7 +12832,7 @@ msgstr ""
msgid "Include indentation"
msgstr ""
#: frappe/public/js/frappe/form/controls/password.js:107
#: frappe/public/js/frappe/form/controls/password.js:106
msgid "Include symbols, numbers and capital letters in the password"
msgstr ""
@ -12895,11 +12895,11 @@ msgstr ""
msgid "Incorrect Verification code"
msgstr ""
#: frappe/model/document.py:1515
#: frappe/model/document.py:1535
msgid "Incorrect value in row {0}:"
msgstr ""
#: frappe/model/document.py:1517
#: frappe/model/document.py:1537
msgid "Incorrect value:"
msgstr ""
@ -12920,7 +12920,7 @@ msgstr ""
msgid "Index Web Pages for Search"
msgstr ""
#: frappe/core/doctype/recorder/recorder.py:140
#: frappe/core/doctype/recorder/recorder.py:132
msgid "Index created successfully on column {0} of doctype {1}"
msgstr ""
@ -13279,7 +13279,7 @@ msgstr ""
msgid "Invalid Output Format"
msgstr ""
#: frappe/model/base_document.py:102
#: frappe/model/base_document.py:115
msgid "Invalid Override"
msgstr ""
@ -13287,7 +13287,7 @@ msgstr ""
msgid "Invalid Parameters."
msgstr ""
#: frappe/core/doctype/user/user.py:1229 frappe/www/update-password.html:123
#: frappe/core/doctype/user/user.py:1231 frappe/www/update-password.html:123
#: frappe/www/update-password.html:144 frappe/www/update-password.html:146
#: frappe/www/update-password.html:247
msgid "Invalid Password"
@ -13341,7 +13341,7 @@ msgstr ""
msgid "Invalid column"
msgstr ""
#: frappe/model/document.py:989 frappe/model/document.py:1003
#: frappe/model/document.py:1009 frappe/model/document.py:1023
msgid "Invalid docstatus"
msgstr ""
@ -15757,7 +15757,7 @@ msgstr ""
msgid "Miss"
msgstr ""
#: frappe/desk/form/meta.py:215
#: frappe/desk/form/meta.py:218
msgid "Missing DocType"
msgstr ""
@ -16169,7 +16169,7 @@ msgstr ""
msgid "My Device"
msgstr ""
#: frappe/public/js/frappe/ui/apps_switcher.js:62
#: frappe/public/js/frappe/ui/apps_switcher.js:57
msgid "My Workspaces"
msgstr ""
@ -16326,7 +16326,7 @@ msgstr ""
msgid "Need Workspace Manager role to edit private workspace of other users"
msgstr ""
#: frappe/model/document.py:764
#: frappe/model/document.py:787
msgid "Negative Value"
msgstr ""
@ -16753,7 +16753,7 @@ msgstr ""
msgid "No Filters Set"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:357
#: frappe/integrations/doctype/google_calendar/google_calendar.py:358
msgid "No Google Calendar Event to sync."
msgstr ""
@ -16844,7 +16844,7 @@ msgstr ""
msgid "No Select Field Found"
msgstr ""
#: frappe/core/doctype/recorder/recorder.py:187
#: frappe/core/doctype/recorder/recorder.py:179
msgid "No Suggestions"
msgstr ""
@ -16868,7 +16868,7 @@ msgstr ""
msgid "No alerts for today"
msgstr ""
#: frappe/core/doctype/recorder/recorder.py:186
#: frappe/core/doctype/recorder/recorder.py:178
msgid "No automatic optimization suggestions available."
msgstr ""
@ -17104,7 +17104,7 @@ msgstr ""
msgid "Normalized Query"
msgstr ""
#: frappe/core/doctype/user/user.py:1019
#: frappe/core/doctype/user/user.py:1021
#: frappe/templates/includes/login/login.js:257 frappe/utils/oauth.py:270
msgid "Not Allowed"
msgstr ""
@ -17856,7 +17856,7 @@ msgstr ""
msgid "Only change this if you want to use other S3 compatible object storage backends."
msgstr ""
#: frappe/model/document.py:1208
#: frappe/model/document.py:1228
msgid "Only draft documents can be discarded"
msgstr ""
@ -18031,13 +18031,13 @@ msgstr ""
msgid "Operator must be one of {0}"
msgstr ""
#: frappe/core/doctype/file/file.js:29
#: frappe/core/doctype/file/file.js:34
#: frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js:8
#: frappe/public/js/frappe/file_uploader/FilePreview.vue:27
msgid "Optimize"
msgstr ""
#: frappe/core/doctype/file/file.js:103
#: frappe/core/doctype/file/file.js:105
msgid "Optimizing image..."
msgstr ""
@ -18110,7 +18110,7 @@ msgstr ""
msgid "Options is required for field {0} of type {1}"
msgstr ""
#: frappe/model/base_document.py:810
#: frappe/model/base_document.py:853
msgid "Options not set for link field {0}"
msgstr ""
@ -18574,7 +18574,7 @@ msgstr ""
msgid "Password"
msgstr ""
#: frappe/core/doctype/user/user.py:1082
#: frappe/core/doctype/user/user.py:1084
msgid "Password Email Sent"
msgstr ""
@ -18612,7 +18612,7 @@ msgstr ""
msgid "Password not found for {0} {1} {2}"
msgstr ""
#: frappe/core/doctype/user/user.py:1081
#: frappe/core/doctype/user/user.py:1083
msgid "Password reset instructions have been sent to {}'s email"
msgstr ""
@ -19013,7 +19013,7 @@ msgstr ""
msgid "Please add a valid comment."
msgstr ""
#: frappe/core/doctype/user/user.py:1064
#: frappe/core/doctype/user/user.py:1066
msgid "Please ask your administrator to verify your sign-up"
msgstr ""
@ -19041,11 +19041,11 @@ msgstr ""
msgid "Please check the filter values set for Dashboard Chart: {}"
msgstr ""
#: frappe/model/base_document.py:890
#: frappe/model/base_document.py:933
msgid "Please check the value of \"Fetch From\" set for field {0}"
msgstr ""
#: frappe/core/doctype/user/user.py:1062
#: frappe/core/doctype/user/user.py:1064
msgid "Please check your email for verification"
msgstr ""
@ -19093,7 +19093,7 @@ msgstr ""
msgid "Please create chart first"
msgstr ""
#: frappe/desk/form/meta.py:211
#: frappe/desk/form/meta.py:214
msgid "Please delete the field from {0} or add the required doctype."
msgstr ""
@ -19200,7 +19200,7 @@ msgstr ""
msgid "Please make sure the Reference Communication Docs are not circularly linked."
msgstr ""
#: frappe/model/document.py:958
#: frappe/model/document.py:981
msgid "Please refresh to get the latest document."
msgstr ""
@ -20239,7 +20239,7 @@ msgstr ""
msgid "Query Report"
msgstr ""
#: frappe/core/doctype/recorder/recorder.py:196
#: frappe/core/doctype/recorder/recorder.py:188
msgid "Query analysis complete. Check suggested indexes."
msgstr ""
@ -20979,7 +20979,7 @@ msgstr ""
msgid "Refreshing..."
msgstr ""
#: frappe/core/doctype/user/user.py:1026
#: frappe/core/doctype/user/user.py:1028
msgid "Registered but disabled"
msgstr ""
@ -22071,7 +22071,7 @@ msgstr ""
msgid "Route: Example \"/app\""
msgstr ""
#: frappe/model/base_document.py:796 frappe/model/document.py:749
#: frappe/model/base_document.py:839 frappe/model/document.py:772
msgid "Row"
msgstr ""
@ -22084,7 +22084,7 @@ msgstr ""
msgid "Row # {0}: Non administrator user can not set the role {1} to the custom doctype"
msgstr ""
#: frappe/model/base_document.py:921
#: frappe/model/base_document.py:964
msgid "Row #{0}:"
msgstr ""
@ -22363,7 +22363,7 @@ msgstr ""
#: frappe/public/js/frappe/list/list_settings.js:36
#: frappe/public/js/frappe/list/list_settings.js:247
#: frappe/public/js/frappe/list/list_sidebar_group_by.js:25
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:332
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:333
#: frappe/public/js/frappe/utils/common.js:443
#: frappe/public/js/frappe/views/kanban/kanban_settings.js:45
#: frappe/public/js/frappe/views/kanban/kanban_settings.js:189
@ -22561,7 +22561,7 @@ msgstr ""
msgid "Scheduler Status"
msgstr ""
#: frappe/utils/scheduler.py:248
#: frappe/utils/scheduler.py:256
msgid "Scheduler can not be re-enabled when maintenance mode is active."
msgstr ""
@ -23528,11 +23528,11 @@ msgstr ""
#. Label of the session_defaults (Table) field in DocType 'Session Default
#. Settings'
#: frappe/core/doctype/session_default_settings/session_default_settings.json
#: frappe/hooks.py frappe/public/js/frappe/ui/toolbar/toolbar.js:331
#: frappe/hooks.py frappe/public/js/frappe/ui/toolbar/toolbar.js:332
msgid "Session Defaults"
msgstr ""
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:316
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:317
msgid "Session Defaults Saved"
msgstr ""
@ -23768,8 +23768,8 @@ msgstr ""
#: frappe/core/doctype/doctype/doctype.json frappe/core/doctype/user/user.json
#: frappe/integrations/workspace/integrations/integrations.json
#: frappe/public/js/frappe/form/templates/print_layout.html:25
#: frappe/public/js/frappe/ui/apps_switcher.js:127
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:289
#: frappe/public/js/frappe/ui/apps_switcher.js:122
#: frappe/public/js/frappe/ui/toolbar/toolbar.js:290
#: frappe/public/js/frappe/views/workspace/workspace.js:362
#: frappe/website/doctype/web_form/web_form.json
#: frappe/website/doctype/web_page/web_page.json
@ -24226,7 +24226,7 @@ msgstr ""
msgid "Sign Up and Confirmation"
msgstr ""
#: frappe/core/doctype/user/user.py:1019
#: frappe/core/doctype/user/user.py:1021
msgid "Sign Up is disabled"
msgstr ""
@ -24499,7 +24499,7 @@ msgstr ""
msgid "Something went wrong"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:117
#: frappe/integrations/doctype/google_calendar/google_calendar.py:118
msgid "Something went wrong during the token generation. Click on {0} to generate a new one."
msgstr ""
@ -24970,7 +24970,7 @@ msgstr ""
msgid "Strip EXIF tags from uploaded images"
msgstr ""
#: frappe/public/js/frappe/form/controls/password.js:90
#: frappe/public/js/frappe/form/controls/password.js:89
msgid "Strong"
msgstr ""
@ -25366,11 +25366,16 @@ msgstr ""
msgid "Sync Contacts"
msgstr ""
#. Label of the sync_as_public (Check) field in DocType 'Google Calendar'
#: frappe/integrations/doctype/google_calendar/google_calendar.json
msgid "Sync events from Google as public"
msgstr ""
#: frappe/custom/doctype/customize_form/customize_form.js:256
msgid "Sync on Migrate"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:296
#: frappe/integrations/doctype/google_calendar/google_calendar.py:297
msgid "Sync token was invalid and has been reset, Retry syncing."
msgstr ""
@ -25719,7 +25724,7 @@ msgstr ""
msgid "Table updated"
msgstr ""
#: frappe/model/document.py:1538
#: frappe/model/document.py:1558
msgid "Table {0} cannot be empty"
msgstr ""
@ -25836,7 +25841,7 @@ msgstr ""
msgid "Templates"
msgstr ""
#: frappe/core/doctype/user/user.py:1030
#: frappe/core/doctype/user/user.py:1032
msgid "Temporarily Disabled"
msgstr ""
@ -26115,11 +26120,11 @@ msgid ""
"</a>"
msgstr ""
#: frappe/core/doctype/user/user.py:990
#: frappe/core/doctype/user/user.py:992
msgid "The reset password link has been expired"
msgstr ""
#: frappe/core/doctype/user/user.py:992
#: frappe/core/doctype/user/user.py:994
msgid "The reset password link has either been used before or is invalid"
msgstr ""
@ -26227,7 +26232,7 @@ msgstr ""
msgid "There is nothing new to show you right now."
msgstr ""
#: frappe/core/doctype/file/file.py:588 frappe/utils/file_manager.py:372
#: frappe/core/doctype/file/file.py:586 frappe/utils/file_manager.py:372
msgid "There is some problem with the file url: {0}"
msgstr ""
@ -26361,7 +26366,7 @@ msgstr ""
msgid "This document is already amended, you cannot ammend it again"
msgstr ""
#: frappe/model/document.py:1715
#: frappe/model/document.py:466
msgid "This document is currently locked and queued for execution. Please try again after some time."
msgstr ""
@ -26389,7 +26394,7 @@ msgid ""
"eval:doc.age&gt;18"
msgstr ""
#: frappe/core/doctype/file/file.js:15
#: frappe/core/doctype/file/file.js:20
msgid "This file is public. It can be accessed without authentication."
msgstr ""
@ -26548,7 +26553,7 @@ msgstr ""
msgid "This will terminate the job immediately and might be dangerous, are you sure? "
msgstr ""
#: frappe/core/doctype/user/user.py:1243
#: frappe/core/doctype/user/user.py:1245
msgid "Throttled"
msgstr ""
@ -27020,7 +27025,7 @@ msgstr ""
msgid "Too many changes to database in single action."
msgstr ""
#: frappe/core/doctype/user/user.py:1031
#: frappe/core/doctype/user/user.py:1033
msgid "Too many users signed up recently, so the registration is disabled. Please try back in an hour"
msgstr ""
@ -27508,7 +27513,7 @@ msgstr ""
#. Option for the 'Type' (Select) field in DocType 'Workspace Shortcut'
#. Label of the url (Data) field in DocType 'Workspace Shortcut'
#. Label of the url (Small Text) field in DocType 'Integration Request'
#. Label of the url (Data) field in DocType 'Webhook Request Log'
#. Label of the url (Text) field in DocType 'Webhook Request Log'
#. Label of the url (Data) field in DocType 'Top Bar Item'
#. Label of the url (Data) field in DocType 'Website Slideshow Item'
#: frappe/desk/doctype/workspace/workspace.json
@ -27597,7 +27602,7 @@ msgstr ""
msgid "Unable to update event"
msgstr ""
#: frappe/core/doctype/file/file.py:464
#: frappe/core/doctype/file/file.py:462
msgid "Unable to write file format for {0}"
msgstr ""
@ -27728,7 +27733,7 @@ msgstr ""
msgid "Untitled Column"
msgstr ""
#: frappe/core/doctype/file/file.js:33
#: frappe/core/doctype/file/file.js:38
msgid "Unzip"
msgstr ""
@ -28384,7 +28389,7 @@ msgstr ""
msgid "User {0} has requested for data deletion"
msgstr ""
#: frappe/core/doctype/user/user.py:1372
#: frappe/core/doctype/user/user.py:1374
msgid "User {0} impersonated as {1}"
msgstr ""
@ -28553,15 +28558,15 @@ msgstr ""
msgid "Value To Be Set"
msgstr ""
#: frappe/model/base_document.py:993 frappe/model/document.py:805
#: frappe/model/base_document.py:1036 frappe/model/document.py:828
msgid "Value cannot be changed for {0}"
msgstr ""
#: frappe/model/document.py:751
#: frappe/model/document.py:774
msgid "Value cannot be negative for"
msgstr ""
#: frappe/model/document.py:755
#: frappe/model/document.py:778
msgid "Value cannot be negative for {0}: {1}"
msgstr ""
@ -28573,7 +28578,7 @@ msgstr ""
msgid "Value for field {0} is too long in {1}. Length should be lesser than {2} characters"
msgstr ""
#: frappe/model/base_document.py:402
#: frappe/model/base_document.py:427
msgid "Value for {0} cannot be a list"
msgstr ""
@ -28592,7 +28597,7 @@ msgstr ""
msgid "Value to Validate"
msgstr ""
#: frappe/model/base_document.py:1063
#: frappe/model/base_document.py:1106
msgid "Value too big"
msgstr ""
@ -28704,7 +28709,7 @@ msgstr ""
msgid "View Doctype Permissions"
msgstr ""
#: frappe/core/doctype/file/file.js:3
#: frappe/core/doctype/file/file.js:4
msgid "View File"
msgstr ""
@ -28765,10 +28770,6 @@ msgstr ""
msgid "View document"
msgstr ""
#: frappe/core/doctype/file/file.js:36
msgid "View file"
msgstr ""
#: frappe/templates/emails/auto_email_report.html:60
msgid "View report in your browser"
msgstr ""
@ -28896,7 +28897,7 @@ msgstr ""
msgid "We've received your query!"
msgstr ""
#: frappe/public/js/frappe/form/controls/password.js:88
#: frappe/public/js/frappe/form/controls/password.js:87
msgid "Weak"
msgstr ""
@ -29038,7 +29039,7 @@ msgstr ""
#. Name of a Workspace
#: frappe/core/doctype/module_def/module_def.json
#: frappe/email/doctype/newsletter/newsletter.py:458
#: frappe/public/js/frappe/ui/apps_switcher.js:115
#: frappe/public/js/frappe/ui/apps_switcher.js:110
#: frappe/public/js/frappe/ui/toolbar/about.js:8
#: frappe/website/workspace/website/website.json
msgid "Website"
@ -29345,7 +29346,7 @@ msgstr ""
#. Description of the 'Run Jobs only Daily if Inactive For (Days)' (Int) field
#. in DocType 'System Settings'
#: frappe/core/doctype/system_settings/system_settings.json
msgid "Will run scheduled jobs only once a day for inactive sites. Default 4 days if set to 0."
msgid "Will run scheduled jobs only once a day for inactive sites. Set it to 0 to avoid automatically disabling the scheduler."
msgstr ""
#: frappe/public/js/frappe/form/print_utils.js:15
@ -29579,7 +29580,7 @@ msgstr ""
msgid "Write"
msgstr ""
#: frappe/model/base_document.py:893
#: frappe/model/base_document.py:936
msgid "Wrong Fetch From value"
msgstr ""
@ -29817,7 +29818,7 @@ msgstr ""
msgid "You can disable this {0} instead of deleting it."
msgstr ""
#: frappe/core/doctype/file/file.py:702
#: frappe/core/doctype/file/file.py:704
msgid "You can increase the limit from System Settings."
msgstr ""
@ -30189,11 +30190,11 @@ msgstr ""
msgid "Your assignment on {0} {1} has been removed by {2}"
msgstr ""
#: frappe/core/doctype/file/file.js:71
#: frappe/core/doctype/file/file.js:73
msgid "Your browser does not support the audio element."
msgstr ""
#: frappe/core/doctype/file/file.js:53
#: frappe/core/doctype/file/file.js:55
msgid "Your browser does not support the video element."
msgstr ""
@ -31029,7 +31030,7 @@ msgstr ""
msgid "{0} Fields"
msgstr ""
#: frappe/integrations/doctype/google_calendar/google_calendar.py:361
#: frappe/integrations/doctype/google_calendar/google_calendar.py:362
msgid "{0} Google Calendar Events synced."
msgstr ""
@ -31060,7 +31061,7 @@ msgstr ""
msgid "{0} Name"
msgstr ""
#: frappe/model/base_document.py:1093
#: frappe/model/base_document.py:1136
msgid "{0} Not allowed to change {1} after submission from {2} to {3}"
msgstr ""
@ -31175,7 +31176,7 @@ msgctxt "Form timeline"
msgid "{0} cancelled this document {1}"
msgstr ""
#: frappe/model/document.py:519
#: frappe/model/document.py:541
msgid "{0} cannot be amended because it is not cancelled. Please cancel the document before creating an amendment."
msgstr ""
@ -31325,7 +31326,7 @@ msgstr ""
msgid "{0} is a mandatory field"
msgstr ""
#: frappe/core/doctype/file/file.py:514
#: frappe/core/doctype/file/file.py:512
msgid "{0} is a not a valid zip file"
msgstr ""
@ -31427,7 +31428,7 @@ msgstr ""
msgid "{0} is not a valid report format. Report format should one of the following {1}"
msgstr ""
#: frappe/core/doctype/file/file.py:494
#: frappe/core/doctype/file/file.py:492
msgid "{0} is not a zip file"
msgstr ""
@ -31474,7 +31475,7 @@ msgstr ""
msgid "{0} items selected"
msgstr ""
#: frappe/core/doctype/user/user.py:1381
#: frappe/core/doctype/user/user.py:1383
msgid "{0} just impersonated as you. They gave this reason: {1}"
msgstr ""
@ -31507,35 +31508,35 @@ msgstr ""
msgid "{0} months ago"
msgstr ""
#: frappe/model/document.py:1780
#: frappe/model/document.py:1779
msgid "{0} must be after {1}"
msgstr ""
#: frappe/model/document.py:1524
#: frappe/model/document.py:1544
msgid "{0} must be beginning with '{1}'"
msgstr ""
#: frappe/model/document.py:1526
#: frappe/model/document.py:1546
msgid "{0} must be equal to '{1}'"
msgstr ""
#: frappe/model/document.py:1522
#: frappe/model/document.py:1542
msgid "{0} must be none of {1}"
msgstr ""
#: frappe/model/document.py:1520 frappe/utils/csvutils.py:161
#: frappe/model/document.py:1540 frappe/utils/csvutils.py:161
msgid "{0} must be one of {1}"
msgstr ""
#: frappe/model/base_document.py:814
#: frappe/model/base_document.py:857
msgid "{0} must be set first"
msgstr ""
#: frappe/model/base_document.py:677
#: frappe/model/base_document.py:720
msgid "{0} must be unique"
msgstr ""
#: frappe/model/document.py:1528
#: frappe/model/document.py:1548
msgid "{0} must be {1} {2}"
msgstr ""
@ -31625,7 +31626,7 @@ msgstr ""
msgid "{0} role does not have permission on any doctype"
msgstr ""
#: frappe/model/document.py:1773
#: frappe/model/document.py:1772
msgid "{0} row #{1}: "
msgstr ""
@ -31721,11 +31722,11 @@ msgstr ""
msgid "{0} {1} added to Dashboard {2}"
msgstr ""
#: frappe/model/base_document.py:610 frappe/model/rename_doc.py:110
#: frappe/model/base_document.py:653 frappe/model/rename_doc.py:110
msgid "{0} {1} already exists"
msgstr ""
#: frappe/model/base_document.py:926
#: frappe/model/base_document.py:969
msgid "{0} {1} cannot be \"{2}\". It should be one of \"{3}\""
msgstr ""
@ -31749,7 +31750,7 @@ msgstr ""
msgid "{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first."
msgstr ""
#: frappe/model/base_document.py:1054
#: frappe/model/base_document.py:1097
msgid "{0}, Row {1}"
msgstr ""
@ -31757,7 +31758,7 @@ msgstr ""
msgid "{0}/{1} complete | Please leave this tab open until completion."
msgstr ""
#: frappe/model/base_document.py:1059
#: frappe/model/base_document.py:1102
msgid "{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}"
msgstr ""

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@ class DocStatus(int):
return self == DocStatus.DRAFT
def is_submitted(self):
return self == DocStatus.SUMBITTED
return self == DocStatus.SUBMITTED
def is_cancelled(self):
return self == DocStatus.CANCELLED
@ -20,7 +20,7 @@ class DocStatus(int):
@staticmethod
def submitted():
return DocStatus.SUMBITTED
return DocStatus.SUBMITTED
@staticmethod
def cancelled():
@ -28,5 +28,5 @@ class DocStatus(int):
DocStatus.DRAFT = DocStatus(0)
DocStatus.SUMBITTED = DocStatus(1)
DocStatus.SUBMITTED = DocStatus(1)
DocStatus.CANCELLED = DocStatus(2)

View file

@ -1008,7 +1008,7 @@ class Document(BaseDocument, DocRef):
else:
raise frappe.ValidationError(_("Invalid docstatus"), self.docstatus)
elif to_docstatus == DocStatus.SUMBITTED:
elif to_docstatus == DocStatus.SUBMITTED:
if self.docstatus.is_submitted():
self._action = "update_after_submit"
self.check_permission("submit")
@ -1188,7 +1188,7 @@ class Document(BaseDocument, DocRef):
def _submit(self):
"""Submit the document. Sets `docstatus` = 1, then saves."""
self.docstatus = DocStatus.SUMBITTED
self.docstatus = DocStatus.SUBMITTED
return self.save()
def _cancel(self):

View file

@ -417,8 +417,7 @@ Tip: use lucide.svg in /icons for all downloaded icons
</symbol>
<symbol viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="icon-users">
<path d="M17.727 18.728H20a1 1 0 0 0 1-1v-2.537a.818.818 0 0 0-.515-.76l-3.061-1.226a.819.819 0 0 1-.515-.758v-.718a3.258 3.258 0 0 0 1.636-2.82V7.274a3.272 3.272 0 0 0-4.909-2.835m.304 10.811l-3.062-1.227a.818.818 0 0 1-.514-.758v-.369c2.675-.357 3.272-1.532 3.272-1.532S12 9.728 12 8.092a3.273 3.273 0 1 0-6.545 0c0 1.636-1.637 3.272-1.637 3.272s.597 1.175 3.273 1.532v.37a.818.818 0 0 1-.515.758l-3.061 1.228a.819.819 0 0 0-.515.757v1.72a1 1 0 0 0 1 1h9.454a1 1 0 0 0 1-1v-1.72a.818.818 0 0 0-.514-.759z"
stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M18 21a8 8 0 0 0-16 0" /> <circle cx="10" cy="8" r="5" /> <path d="M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3" />
</symbol>
<symbol viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="icon-tool">
@ -545,8 +544,7 @@ Tip: use lucide.svg in /icons for all downloaded icons
</symbol>
<symbol viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="icon-integration">
<path d="M2.5 20.503l2.218-2.218m1.35-7.717l-1.35 1.35a4.5 4.5 0 1 0 6.364 6.365l1.35-1.35-6.364-6.365zM20.5 2.5l-2.218 2.218m-1.35 7.712l1.35-1.35a4.5 4.5 0 0 0-6.365-6.365l-1.35 1.35 6.365 6.365zM9.7 10.604l-1.8 1.8m4.5.898l-1.8 1.8"
stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="m19 5 3-3" /> <path d="m2 22 3-3" /> <path d="M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z" /> <path d="M7.5 13.5 10 11" /> <path d="M10.5 16.5 13 14" /> <path d="m12 6 6 6 2.3-2.3a2.4 2.4 0 0 0 0-3.4l-2.6-2.6a2.4 2.4 0 0 0-3.4 0Z" />
</symbol>
<symbol viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="icon-hr">
@ -876,7 +874,6 @@ Tip: use lucide.svg in /icons for all downloaded icons
<symbol viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" id="icon-hammer" stroke-linejoin="round">
<path d="m15 12-8.373 8.373a1 1 0 1 1-3-3L12 9"/><path d="m18 15 4-4"/><path d="m21.5 11.5-1.914-1.914A2 2 0 0 1 19 8.172V7l-2.26-2.26a6 6 0 0 0-4.202-1.756L9 2.96l.92.82A6.18 6.18 0 0 1 12 8.4V10l2 2h1.172a2 2 0 0 1 1.414.586L18.5 14.5"/>
</symbol>
<symbol viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" id="icon-help">
<path stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M8.208 8.115v-.099a2.021 2.021 0 0 1 1.114-1.809l.057-.028a1.85 1.85 0 0 1 1.278-.142l.121.03c.623.156 1.1.659 1.223 1.289v0c.04.208.04.421 0 .63l-.029.153a1.805 1.805 0 0 1-.97 1.276l-.2.1a1.446 1.446 0 0 0-.804 1.297v0L10 11.5"/>
<path fill="#20272E" d="M10.307 13.804a.304.304 0 1 1-.607 0 .304.304 0 0 1 .607 0Z"/>

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View file

@ -1,8 +1,9 @@
const frappeCloudBaseEndpoint = "https://frappecloud.com";
let frappeCloudBaseEndpoint = "https://frappecloud.com";
let isFCUser = false;
$(document).ready(function () {
if (
frappe.boot.fc_communication_secret &&
frappe.boot.is_fc_site &&
frappe.boot.setup_complete === 1 &&
!frappe.is_mobile() &&
frappe.user.has_role("System Manager")
@ -10,147 +11,40 @@ $(document).ready(function () {
frappe.call({
method: "frappe.integrations.frappe_providers.frappecloud_billing.current_site_info",
callback: (r) => {
if (!r?.message) return;
const response = r.message;
if (response.trial_end_date) {
const trial_end_date = new Date(response.trial_end_date);
frappeCloudBaseEndpoint = response.base_url;
isFCUser = response.is_fc_user;
if (response.trial_end_date && trial_end_date > new Date()) {
$(".layout-main-section").before(
generateTrialSubscriptionBanner(response.trial_end_date)
);
addLoginToFCDropdownItem();
$(".login-to-fc").on("click", function () {
window.route = "dashboard";
initiateRequestForLoginToFrappeCloud();
});
$(".upgrade-plan-button").on("click", function () {
window.route = "site-dashboard";
initiateRequestForLoginToFrappeCloud();
});
}
addManageBillingDropdown();
$(".login-to-fc, .upgrade-plan-button").on("click", function () {
openFrappeCloudDashboard();
});
},
});
}
});
function initiateRequestForLoginToFrappeCloud() {
frappe.confirm(__("Are you sure you want to login to Frappe Cloud dashboard?"), () => {
requestLoginToFC();
});
}
function requestLoginToFC(freezing_msg = "Initiating login to Frappe Cloud...") {
frappe.call({
method: "frappe.integrations.frappe_providers.frappecloud_billing.send_verification_code",
args: {
route: window.route,
},
freeze: true,
freeze_message: __(freezing_msg),
callback: function (r) {
if (r.message.is_user_logged_in) {
window.open(`${frappeCloudBaseEndpoint}${r.message.redirect_to}`, "_blank");
return;
} else {
showFCLoginDialog(r.message.email);
setErrorMessage("");
}
},
error: function (r) {
frappe.throw(__("Failed to login to Frappe Cloud. Please try again"));
},
});
}
function setErrorMessage(message) {
$("#fc-login-error").text(message);
}
function showFCLoginDialog(email) {
if (!window.fc_login_dialog) {
var d = new frappe.ui.Dialog({
title: __("Login to Frappe Cloud"),
primary_action_label: __("Verify", null, "Submit verification code"),
primary_action: verifyCode,
});
$(d.body).html(
repl(
`<div>
<p>We have sent the verification code to your email id <strong>${email}</strong></p>
<div class="form-group mt-2">
<div class="clearfix">
<label class="control-label" style="padding-right: 0px;">Verification Code</label>
</div>
<div class="control-input-wrapper">
<div class="control-input"><input type="text" class="input-with-feedback form-control" id="fc-login-verification-code"></div>
</div>
</div>
<p class="text-danger" id="fc-login-error"></p>
</div>`,
frappe.app
)
);
d.add_custom_action("Didn't receive code? Resend", () => {
d.hide();
requestLoginToFC("Resending Verification Code...");
});
window.fc_login_dialog = d;
}
function verifyCode() {
let otp = $("#fc-login-verification-code").val();
if (!otp) {
return;
}
frappe.call({
method: "frappe.integrations.frappe_providers.frappecloud_billing.verify_verification_code",
args: {
verification_code: otp,
route: window.route,
},
freeze: true,
freeze_message: __("Verifying verification code..."),
callback: function (r) {
const message = r.message;
if (message.login_token) {
window.fc_login_dialog.hide();
window.open(
`${frappeCloudBaseEndpoint}/api/method/press.api.developer.saas.login_to_fc?token=${message.login_token}`,
"_blank"
);
frappe.msgprint({
title: __("Frappe Cloud Login Successful"),
indicator: "green",
message: `<p>${__(
"You will be redirected to Frappe Cloud soon."
)}</p><p>${__(
"If you haven't been redirected,"
)} <a href="${frappeCloudBaseEndpoint}/api/method/press.api.developer.saas.login_to_fc?token=${
message.login_token
}" target="_blank">${__("Click here to login")}</a></p>`,
});
} else {
setErrorMessage("Login failed. Please try again");
}
},
error: function (r) {
if (r.exc) {
setErrorMessage(JSON.parse(JSON.parse(r._server_messages)[0])["message"]);
}
},
});
}
window.fc_login_dialog.show();
function addManageBillingDropdown() {
$(".dropdown-navbar-user .dropdown-menu .dropdown-divider").before(
`<div class="dropdown-item login-to-fc" target="_blank">Manage Billing</div>`
);
}
function addLoginToFCDropdownItem() {
$(".dropdown-navbar-user .dropdown-menu .dropdown-item:last()").before(
`<div class="dropdown-item login-to-fc" target="_blank">Login to Frappe Cloud</div>`
);
function openFrappeCloudDashboard() {
window.open(`${frappeCloudBaseEndpoint}/dashboard/sites/${frappe.boot.sitename}`, "_blank");
}
function generateTrialSubscriptionBanner(trialEndDate) {
@ -169,14 +63,13 @@ function generateTrialSubscriptionBanner(trialEndDate) {
align-items: center;
background-color: var(--subtle-accent);
border-radius: var(--border-radius-md);
box-shadow: var(--shadow-sm);
}
.trial-banner > div {
display: flex;
gap: 8px;
}
.trial-banner .info-icon {
margin: 4px 0;
margin: auto 0;
}
.trial-banner > div > div {
display: flex;
@ -202,7 +95,7 @@ function generateTrialSubscriptionBanner(trialEndDate) {
border-color: var(--gray-400);
}
</style>
<div class="trial-banner px-3 py-2 m-2">
<div class="trial-banner px-3 py-2 m-2 mt-4">
<div>
<svg class="info-icon" width="18" height="18" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3360_13841)">
@ -219,18 +112,26 @@ function generateTrialSubscriptionBanner(trialEndDate) {
Your trial ends in ${trial_end_string}.
</span>
<span class="description">
Please upgrade for uninterrupted services
${
isFCUser
? "Please upgrade for uninterrupted services"
: "Please contact your system administrator to upgrade your plan."
}
</span>
</div>
</div>
<button type="button"
${
isFCUser
? `<button type="button"
class="upgrade-plan-button px-2 py-1"
>
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.2641 1C5.5758 1 4.97583 1.46845 4.80889 2.1362L3.57555 7.06953C3.33887 8.01625 4.05491 8.93333 5.03077 8.93333H7.50682L6.72168 14.4293C6.68838 14.6624 6.82229 14.8872 7.04319 14.9689C7.26408 15.0507 7.51204 14.9671 7.63849 14.7684L13.2161 6.00354C13.6398 5.33782 13.1616 4.46667 12.3725 4.46667H9.59038L10.3017 1.62127C10.3391 1.4719 10.3055 1.31365 10.2108 1.19229C10.116 1.07094 9.97063 1 9.81666 1H6.2641ZM5.77903 2.37873C5.83468 2.15615 6.03467 2 6.2641 2H9.17627L8.46492 4.8454C8.42758 4.99477 8.46114 5.15302 8.55589 5.27437C8.65064 5.39573 8.79602 5.46667 8.94999 5.46667H12.3725L8.0395 12.2757L8.5783 8.50404C8.5988 8.36056 8.55602 8.21523 8.46105 8.10573C8.36608 7.99623 8.22827 7.93333 8.08332 7.93333H5.03077C4.70548 7.93333 4.4668 7.62764 4.5457 7.31207L5.77903 2.37873Z" fill="currentColor"/>
</svg>
${__("Upgrade plan")}
</button>
</button>`
: ""
}
</div>
`);
}

View file

@ -120,6 +120,10 @@ frappe.data_import.ImportPreview = class ImportPreview {
if (cell == null) {
return "";
}
if (typeof cell === "string") {
cell = frappe.utils.xss_sanitise(cell);
}
return cell;
});
});

View file

@ -107,13 +107,15 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro
this.$value
.toggle(true)
.find(".attached-file-link")
.html(filename || this.value)
.html(frappe.utils.xss_sanitise(filename || this.value))
.attr("href", dataurl || this.value);
} else {
this.$wrapper.html(`
<div class="attached-file flex justify-between align-center">
<div class="ellipsis">
<a href="${dataurl || this.value}" target="_blank">${filename || this.value}</a>
<a href="${dataurl || this.value}" target="_blank">${frappe.utils.xss_sanitise(
filename || this.value
)}</a>
</div>
</div>
`);

View file

@ -428,7 +428,11 @@ frappe.ui.form.Form = class FrappeForm {
this.read_only = frappe.workflow.is_read_only(this.doctype, this.docname);
if (this.read_only) {
this.set_read_only();
frappe.show_alert(__("This form is not editable due to a Workflow."));
this.dashboard.set_headline(
__("This form is not editable due to a Workflow."),
"blue",
true
);
}
// check if doctype is already open

View file

@ -23,7 +23,9 @@ frappe.ui.form.Layout = class Layout {
this.parent = this.body;
}
this.wrapper = $('<div class="form-layout">').appendTo(this.parent);
this.message = $('<div class="form-message hidden"></div>').appendTo(this.wrapper);
this.message = $('<div class="form-message-container hidden"></div>').appendTo(
this.wrapper
);
this.page = $('<div class="form-page"></div>').appendTo(this.wrapper);
if (!this.fields) {
@ -97,28 +99,41 @@ frappe.ui.form.Layout = class Layout {
return fields;
}
/**Render a message block with its own color and close button
* @param {String} html - message or HTML to be displayed
* @param {String} color - color of the block. One of "yellow", "blue", "red", "green" or "orange". Defaults to "blue".
* @param {Boolean} permanent - if true, the block will not have a close button
*/
show_message(html, color, permanent = false) {
if (this.message_color) {
// remove previous color
this.message.removeClass(this.message_color);
}
let close_message = $(`<div class="close-message">${frappe.utils.icon("close")}</div>`);
this.message_color =
color && ["yellow", "blue", "red", "green", "orange"].includes(color) ? color : "blue";
if (html) {
if (html.substr(0, 1) !== "<") {
// wrap in a block
html = "<div>" + html + "</div>";
}
this.message.removeClass("hidden").addClass(this.message_color);
$(html).appendTo(this.message);
if (!permanent) {
close_message.appendTo(this.message);
close_message.on("click", () => this.message.empty().addClass("hidden"));
}
} else {
if (!html) {
this.message.empty().addClass("hidden");
return;
}
// Prepare Block
let $html;
if (html.substring(0, 1) !== "<") {
// wrap in a block if `html` does not contain html tags
$html = $("<div class='form-message border-bottom'></div>").text(html);
} else {
$html = $(html);
$html.addClass("form-message border-bottom");
}
// Add close button to block if not permanent
const close_message = $(`<div class="close-message">${frappe.utils.icon("close")}</div>`);
if (!permanent) {
close_message.appendTo($html);
close_message.on("click", () => $html.remove());
}
// Add block color and append to parent container `form-message-container`
const block_color =
color && ["yellow", "blue", "red", "green", "orange"].includes(color) ? color : "blue";
$html.addClass(block_color).appendTo(this.message);
// Show parent container if hidden
this.message.removeClass("hidden");
}
render(new_fields) {

View file

@ -215,7 +215,8 @@ frappe.ui.form.QuickEntryForm = class QuickEntryForm extends frappe.ui.Dialog {
},
callback: function (r) {
if (
frappe.model.is_submittable(me.doctype) &&
r?.message?.docstatus === 0 &&
frappe.model.can_submit(me.doctype) &&
!frappe.model.has_workflow(me.doctype)
) {
frappe.run_serially([

View file

@ -3,22 +3,17 @@ frappe.ui.AppsSwitcher = class AppsSwitcher {
this.drop_down_state = false;
this.sidebar_wrapper = sidebar.wrapper;
this.sidebar = sidebar;
this.app_switcher = $(sidebar.app_switcher_dropdown[0]);
this.setup_app_switcher();
this.set_hover();
}
setup_app_switcher() {
this.app_switcher_menu = $(".app-switcher-menu");
$(".app-switcher-dropdown").on("click", () => {
this.set_active();
this.app_switcher_menu.toggleClass("hidden");
});
// hover out of the sidebar move this to sidebar.js
this.sidebar_wrapper.find(".body-sidebar").on("mouseleave", () => {
this.app_switcher_menu.addClass("hidden");
// hide any expanded menus as they leave a blank space in the sidebar
this.sidebar_wrapper.find(".drop-icon[data-state='opened'").click();
});
}
create_app_data_map() {
frappe.boot.app_data_map = {};
@ -152,4 +147,23 @@ frappe.ui.AppsSwitcher = class AppsSwitcher {
// re-render the sidebar
frappe.app.sidebar.make_sidebar();
}
set_hover() {
this.app_switcher.on("mouseover", function (event) {
if ($(this).hasClass("active-sidebar")) return;
$(this).addClass("hover");
if (!this.sidebar.sidebar_expanded) {
$(this).removeClass("hover");
}
});
this.app_switcher.on("mouseleave", function () {
$(this).removeClass("hover");
});
}
set_active() {
this.app_switcher.toggleClass("active-sidebar");
if (!this.sidebar.sidebar_expanded) {
this.app_switcher.removeClass("active-sidebar");
}
}
};

View file

@ -1,6 +1,7 @@
frappe.ui.Sidebar = class Sidebar {
constructor() {
this.items = {};
this.child_items = [];
this.sidebar_expanded = false;
if (!frappe.boot.setup_complete) {
@ -79,16 +80,64 @@ frappe.ui.Sidebar = class Sidebar {
}
set_active_workspace_item() {
if (this.is_route_in_sidebar(decodeURIComponent(window.location.pathname))) {
if (!frappe.get_route()) return;
let current_route = frappe.get_route();
let current_route_str = frappe.get_route_str();
let current_item;
if (current_route[0] == "Workspaces") {
current_item = current_route[1];
} else if (frappe.breadcrumbs) {
if (Object.keys(frappe.breadcrumbs.all).length == 0) return;
if (frappe.breadcrumbs.all[current_route_str]) {
current_item =
frappe.breadcrumbs.all[current_route_str].workspace ||
frappe.breadcrumbs.all[current_route_str].module;
}
}
if (this.is_route_in_sidebar(current_item)) {
this.active_item.addClass("active-sidebar");
}
if (this.active_item) {
if (this.is_nested_item(this.active_item.parent())) {
let current_item = this.active_item.parent();
this.expand_parent_item(current_item);
}
}
}
expand_parent_item(item) {
let parent_title = item.attr("item-parent");
if (!parent_title) return;
let parent = this.get_sidebar_item(parent_title);
$($(parent).children()[1]).removeClass("hidden");
if (parent) {
if (this.is_nested_item($(parent))) {
this.expand_parent_item($(parent));
}
}
}
is_nested_item(item) {
if (item.attr("item-parent")) {
return true;
} else {
return false;
}
}
is_route_in_sidebar(route_name) {
get_sidebar_item(name) {
let sidebar_item = "";
$(".sidebar-item-container").each(function () {
if ($(this).attr("item-name") == name) {
sidebar_item = this;
}
});
return sidebar_item;
}
is_route_in_sidebar(active_module) {
let match = false;
const that = this;
$(".item-anchor").each(function () {
if ($(this).attr("href") == route_name) {
if ($(this).attr("title") == active_module) {
match = true;
if (that.active_item) that.active_item.removeClass("active-sidebar");
that.active_item = $(this).parent();
@ -126,12 +175,19 @@ frappe.ui.Sidebar = class Sidebar {
this.make_sidebar();
}
this.set_hover();
this.set_sidebar_state();
if (!this.sidebar_expanded) this.close_children_item();
}
set_sidebar_state() {
this.sidebar_expanded = true;
if (localStorage.getItem("sidebar-expanded") !== null) {
this.sidebar_expanded = JSON.parse(localStorage.getItem("sidebar-expanded"));
this.expand_sidebar();
}
if (frappe.is_mobile()) {
this.sidebar_expanded = false;
}
this.expand_sidebar();
}
make_sidebar() {
if (this.wrapper.find(".standard-sidebar-section")[0]) {
this.wrapper.find(".standard-sidebar-section").remove();
@ -173,6 +229,9 @@ frappe.ui.Sidebar = class Sidebar {
$(".list-sidebar.hidden-xs.hidden-sm").removeClass("opened");
// $(".close-sidebar").css("display", "none");
$("body").css("overflow", "auto");
if (frappe.is_mobile()) {
this.close_sidebar();
}
});
if (
@ -242,6 +301,7 @@ frappe.ui.Sidebar = class Sidebar {
let child_container = $item_container.find(".sidebar-child-item");
child_container.addClass("hidden");
this.prepare_sidebar(child_items, child_container, $item_container);
this.child_items.push(child_container);
}
$item_container.appendTo(container);
@ -394,10 +454,18 @@ frappe.ui.Sidebar = class Sidebar {
close_sidebar() {
this.sidebar_expanded = false;
this.expand_sidebar();
this.close_children_item();
}
open_sidebar() {
this.sidebar_expanded = true;
this.expand_sidebar();
this.set_active_workspace_item();
}
close_children_item() {
this.child_items.forEach((i) => {
i.addClass("hidden");
});
}
reload() {
@ -406,4 +474,9 @@ frappe.ui.Sidebar = class Sidebar {
this.setup_pages();
});
}
set_height() {
$(".body-sidebar").css("height", window.innerHeight + "px");
$(".overlay").css("height", window.innerHeight + "px");
document.body.style.overflow = "hidden";
}
};

View file

@ -324,7 +324,10 @@ frappe.search.AwesomeBar = class AwesomeBar {
var options = {};
options[search_field] = ["like", "%" + txt + "%"];
this.options.push({
label: __("Find {0} in {1}", [txt.bold(), __(route[1]).bold()]),
label: __("Find {0} in {1}", [
frappe.utils.xss_sanitise(txt).bold(),
__(route[1]).bold(),
]),
value: __("Find {0} in {1}", [txt, __(route[1])]),
route_options: options,
onclick: function () {

View file

@ -29,6 +29,7 @@ frappe.ui.toolbar.Toolbar = class {
this.bind_events();
$(document).trigger("toolbar_setup");
$(".navbar-brand .app-logo").on("click", () => {
frappe.app.sidebar.set_height();
frappe.app.sidebar.toggle_sidebar();
});
}

View file

@ -42,6 +42,7 @@ frappe.breadcrumbs = {
}
this.all[frappe.breadcrumbs.current_page()] = obj;
this.update();
frappe.app.sidebar.set_active_workspace_item();
},
current_page() {

View file

@ -173,6 +173,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
frappe.run_serially([
() => this.get_report_doc(),
() => this.get_report_settings(),
() => this.add_translate_data_checkbox(),
() => this.setup_progress_bar(),
() => this.setup_page_head(),
() => this.refresh_report(route_options),
@ -529,7 +530,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
const { filters = [] } = this.report_settings;
let filter_area = this.page.page_form;
this.filters = filters
.map((df) => {
if (df.fieldtype === "Break") return;
@ -554,7 +554,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
// filter values have not changed
return;
}
// clear previous_filters after 10 seconds, to allow refresh for new data
this.previous_filters = current_filters;
setTimeout(() => (this.previous_filters = null), 10000);
@ -1249,7 +1248,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
if (column.colIndex === index && !value) {
value = "Total";
value = __("Total");
column = { fieldtype: "Data" }; // avoid type issues for value if Date column
} else if (["Currency", "Float"].includes(column.fieldtype)) {
// proxy for currency and float
@ -2113,4 +2112,14 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
get get_values() {
return this.get_filter_values;
}
add_translate_data_checkbox() {
if (frappe.boot.lang == "en") return;
let filter_config = {
fieldname: "translate_data",
fieldtype: "Check",
label: __("Translate Data"),
};
this.report_settings.filters.push(filter_config);
}
};

View file

@ -30,7 +30,11 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
this.report_doc = doc;
this.report_doc.json = JSON.parse(this.report_doc.json);
this.filters = this.report_doc.json.filters;
this.filters = [
...this.report_doc.json.filters,
...this.parse_filters_from_route_options(),
];
this.order_by = this.report_doc.json.order_by;
this.add_totals_row = this.report_doc.json.add_totals_row;
this.page_title = __(this.report_name);

View file

@ -9,7 +9,9 @@
background-position: 50% 50%;
fill: var(--icon-fill);
stroke: var(--icon-stroke);
stroke-width: 1.25px;
stroke-width: 1.5px;
stroke-linecap: round;
stroke-linejoin: round;
}
.es-icon {

View file

@ -138,6 +138,9 @@
min-height: 100px;
scrollbar-width: thin;
border-top: 0 !important;
.dt-cell {
line-height: 1;
}
}
.dt-tree-node__toggle + a {

View file

@ -6,6 +6,7 @@
--surface-modal: rgba(255, 255, 255, 1);
--divider-color: rgba(237, 237, 237, 1);
--sidebar-width: 220px;
--left-sidebar-width: 240px;
}
[data-theme="dark"] {
--sidebar-hover-color: rgba(43, 43, 43, 1);
@ -122,7 +123,7 @@ body {
}
.sidebar-items {
width: 204px;
width: 224px;
padding-left: 1px;
padding-right: 1px;
width: 100%;
@ -172,7 +173,7 @@ body {
}
svg {
margin: -1px;
margin: -2px;
}
}
@ -248,17 +249,17 @@ body {
.body-sidebar {
// make it an overlay on hover
position: absolute;
width: var(--sidebar-width);
width: var(--left-sidebar-width);
.app-switcher-dropdown {
width: 204px;
width: 224px;
left: 0px;
padding: 2px 0px 2px 3px;
padding: 3px;
}
.body-sidebar-top {
width: 204px;
width: 224px;
overflow-y: hidden;
.app-switcher-dropdown {
width: 204px;
width: 224px;
}
}
.sidebar-item-container {
@ -279,7 +280,7 @@ body {
visibility: visible;
}
.body-sidebar-bottom {
width: var(--sidebar-width);
width: 224px;
position: static;
}
}
@ -287,7 +288,7 @@ body {
// show placeholder so that main section remains static
.body-sidebar-placeholder {
display: flex;
width: var(--sidebar-width);
width: var(--left-sidebar-width);
}
}
@ -310,7 +311,7 @@ body {
position: relative;
.body-sidebar {
padding: 8px 8px 10px 8px;
width: var(--sidebar-width);
width: var(--left-sidebar-width);
height: 100%;
position: absolute;
bottom: 0;
@ -319,10 +320,10 @@ body {
.overlay {
display: block;
position: absolute;
width: 100vw;
width: calc(100vw - 240px);
height: 100%;
z-index: 1021;
left: var(--sidebar-width);
left: var(--left-sidebar-width);
overflow: auto;
background-color: rgba(128, 128, 128, 0.5);
}
@ -347,15 +348,15 @@ body {
text-decoration: none;
width: 38px;
height: 38px;
left: -2px;
padding: 3px;
margin-left: -2px;
.standard-sidebar-item {
padding-top: 1px;
padding-bottom: 1px;
.d-flex {
width: 161px;
}
gap: 8px;
gap: 30px;
}
.sidebar-item-control {
margin: 2px;
@ -365,15 +366,13 @@ body {
.app-switcher-menu {
position: absolute;
top: 44px;
left: 7px;
width: 205px;
top: 50px;
left: 9px;
width: 220px;
padding: 6px;
border-radius: var(--border-radius-lg);
background: var(--surface-modal);
box-shadow: 0px 10px 24px -3px rgba(0, 0, 0, 0.1);
// box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.05);
// box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.2);
box-shadow: var(--shadow-xl);
z-index: 1;
}
@ -421,7 +420,7 @@ body {
.active-sidebar {
background: var(--sidebar-active-color);
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.1);
box-shadow: var(--shadow-sm);
border-radius: 8px;
}
.overlay {

View file

@ -11,11 +11,11 @@ class TestDocStatus(IntegrationTestCase):
self.assertFalse(DocStatus.DRAFT.is_submitted())
def test_submitted(self):
self.assertEqual(DocStatus(1), DocStatus.SUMBITTED)
self.assertEqual(DocStatus(1), DocStatus.SUBMITTED)
self.assertFalse(DocStatus.SUMBITTED.is_draft())
self.assertTrue(DocStatus.SUMBITTED.is_submitted())
self.assertFalse(DocStatus.SUMBITTED.is_cancelled())
self.assertFalse(DocStatus.SUBMITTED.is_draft())
self.assertTrue(DocStatus.SUBMITTED.is_submitted())
self.assertFalse(DocStatus.SUBMITTED.is_cancelled())
def test_cancelled(self):
self.assertEqual(DocStatus(2), DocStatus.CANCELLED)

View file

@ -27,7 +27,8 @@ from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fi
import frappe
import frappe.monitor
from frappe import _
from frappe.utils import CallbackManager, cint, get_bench_id
from frappe.utils import CallbackManager, cint, get_bench_id, get_sites
from frappe.utils.caching import site_cache
from frappe.utils.commands import log
from frappe.utils.data import sbool
from frappe.utils.redis_queue import RedisQueue
@ -40,6 +41,8 @@ RQ_RESULTS_TTL = 10 * 60
RQ_MAX_JOBS = 5000 # Restart NOFORK workers after every N number of jobs
RQ_MAX_JOBS_JITTER = 50 # Random difference in max jobs to avoid restarting at same time
MAX_QUEUED_JOBS = 500 # frappe.enqueue will start failing when these many jobs exist in queue.
_redis_queue_conn = None
@ -154,6 +157,8 @@ def enqueue(
raise
_check_queue_size(q)
if not timeout:
timeout = get_queues_timeout().get(queue) or 300
@ -723,6 +728,31 @@ def flush_telemetry():
ph and ph.flush()
def _check_queue_size(q: Queue):
max_jobs = cint(frappe.conf.max_queued_jobs) or MAX_QUEUED_JOBS
# Workaround for arbitrarily sized benches,
# TODO: Some concept of site-based fairness on consumption of queue
max_jobs += _site_count() * 50
if cint(q.count) >= max_jobs:
primary_action = {
"label": "Monitor System Health",
"client_action": "frappe.set_route",
"args": ["Form", "System Health Report"],
}
frappe.throw(
_("Too many queued background jobs ({0}). Please retry after some time.").format(max_jobs),
title=_("Queue Overloaded"),
exc=frappe.QueueOverloaded,
primary_action=primary_action if frappe.has_permission("System Health Report") else None,
)
@site_cache(ttl=10 * 60)
def _site_count() -> int:
return len(get_sites())
def _start_sentry():
sentry_dsn = os.getenv("FRAPPE_SENTRY_DSN")
if not sentry_dsn:

View file

@ -163,6 +163,7 @@ def get_home_page_via_hooks():
def get_boot_data():
from frappe.integrations.frappe_providers.frappecloud_billing import is_fc_site
from frappe.locale import get_date_format, get_first_day_of_the_week, get_number_format, get_time_format
return {
@ -187,6 +188,7 @@ def get_boot_data():
},
"assets_json": get_assets_json(),
"sitename": frappe.local.site,
"is_fc_site": is_fc_site(),
}

View file

@ -23,6 +23,8 @@ no_cache = True
def get_context(context):
from frappe.integrations.frappe_providers.frappecloud_billing import is_fc_site
redirect_to = frappe.local.request.args.get("redirect-to")
redirect_to = sanitize_redirect(redirect_to)
@ -37,6 +39,12 @@ def get_context(context):
frappe.local.flags.redirect_location = redirect_to
raise frappe.Redirect
if is_fc_site():
from frappe.integrations.frappe_providers.frappecloud_billing import get_site_login_url
frappe.local.flags.redirect_location = get_site_login_url()
raise frappe.Redirect
context.no_header = True
context.for_test = "login.html"
context["title"] = "Login"

View file

@ -442,19 +442,20 @@ def get_print_format(doctype: str, print_format: "PrintFormat") -> str:
module = print_format.module or frappe.db.get_value("DocType", doctype, "module")
is_custom_module = frappe.get_cached_value("Module Def", module, "custom")
if is_custom_module:
if print_format.raw_printing:
return print_format.raw_commands
if print_format.html:
return print_format.html
path = os.path.join(
get_module_path(module, "Print Format", print_format.name),
frappe.scrub(print_format.name) + ".html",
)
if os.path.exists(path):
with open(path) as pffile:
return pffile.read()
if not is_custom_module:
path = os.path.join(
get_module_path(module, "Print Format", print_format.name),
frappe.scrub(print_format.name) + ".html",
)
if os.path.exists(path):
with open(path) as pffile:
return pffile.read()
if print_format.raw_printing:
return print_format.raw_commands
if print_format.html:
return print_format.html
frappe.throw(_("No template found at path: {0}").format(path), frappe.TemplateNotFoundError)

View file

@ -52,7 +52,7 @@
"fast-deep-equal": "^2.0.1",
"fast-glob": "^3.2.5",
"frappe-charts": "2.0.0-rc22",
"frappe-datatable": "1.17.16",
"frappe-datatable": "1.18.0",
"frappe-gantt": "^0.6.0",
"highlight.js": "^10.4.1",
"html5-qrcode": "^2.3.8",

View file

@ -1490,10 +1490,10 @@ frappe-charts@2.0.0-rc22:
resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc22.tgz#9a5a747febdc381a1d4d7af96e89cf519dfba8c0"
integrity sha512-N7f/8979wJCKjusOinaUYfMxB80YnfuVLrSkjpj4LtyqS0BGS6SuJxUnb7Jl4RWUFEIs7zEhideIKnyLeFZF4Q==
frappe-datatable@1.17.16:
version "1.17.16"
resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.17.16.tgz#4e7bf3b50dad5bc048f95ccd7ca7da91e04844ab"
integrity sha512-BJgWFX8msHZcS1mw2xbuaY1YdH1dBXUIuREVmqH5z1p78GusPaDV8sbWskTS5yVBUklMrMq2VfBTUsJXjvl+wg==
frappe-datatable@1.18.0:
version "1.18.0"
resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.18.0.tgz#d50be2ed3a4a34e22d7e64e05ee3d30f1ec9b617"
integrity sha512-8tPCFB75eF1ITYXNQaNY8HZBb/HZB2ol3HjsEYeigYbnhq/v9gYDroQuVN8v9j1WbBeFdDPR02ca2wOuUIEsDA==
dependencies:
hyperlist "^1.0.0-beta"
lodash "^4.17.5"