diff --git a/cypress/integration/web_form.js b/cypress/integration/web_form.js index 36f65a80bc..cbcd9e5bcf 100644 --- a/cypress/integration/web_form.js +++ b/cypress/integration/web_form.js @@ -27,7 +27,9 @@ context("Web Form", () => { cy.wait("@save_form"); + cy.get('.frappe-control[data-fieldname="route"]').scrollIntoView(); cy.get_field("route").should("have.value", "note"); + cy.get(".title-area .indicator-pill") .should("contain.text", "Published") .should("have.class", "green"); diff --git a/frappe/core/doctype/communication/communication.js b/frappe/core/doctype/communication/communication.js index 86ef59f994..d6103636e8 100644 --- a/frappe/core/doctype/communication/communication.js +++ b/frappe/core/doctype/communication/communication.js @@ -211,8 +211,7 @@ frappe.ui.form.on("Communication", { ], primary_action_label: __("Move"), primary_action(values) { - d.hide(); - frappe.call({ + return frappe.call({ method: "frappe.email.inbox.move_email", args: { communication: frm.doc.name, @@ -220,6 +219,7 @@ frappe.ui.form.on("Communication", { }, freeze: true, callback: function () { + d.hide(); window.history.back(); }, }); diff --git a/frappe/core/doctype/doctype/doctype_list.js b/frappe/core/doctype/doctype/doctype_list.js index 46b5e5b99d..3bb353c266 100644 --- a/frappe/core/doctype/doctype/doctype_list.js +++ b/frappe/core/doctype/doctype/doctype_list.js @@ -103,7 +103,7 @@ frappe.listview_settings["DocType"] = { primary_action_label: __("Create & Continue"), primary_action(values) { if (!values.istable) values.editable_grid = 0; - frappe.db + return frappe.db .insert({ doctype: "DocType", ...values, diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 3b0514d594..4e9e06a808 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -890,6 +890,14 @@ def has_permission(doc, ptype=None, user=None, debug=False): if user != "Guest" and doc.owner == user: return True + if ( + user != "Guest" + and ptype in ["read", "write", "share", "submit"] + and frappe.share.get_shared( + "File", filters=[["share_name", "=", doc.name]], rights=[ptype], user=user + ) + ): + return True if doc.attached_to_doctype and doc.attached_to_name: attached_to_doctype = doc.attached_to_doctype diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index 67abd82879..9d2a797ae8 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -427,6 +427,29 @@ def relink_mismatched_files(doc: "Document") -> None: for df in attach_fields: if doc.get(df.fieldname): relink_files(doc, df.fieldname, doc.__temporary_name) + + # Relink files in child table Attach fields + table_fields = doc.meta.get("fields", {"fieldtype": "Table"}) + for table_df in table_fields: + child_rows = doc.get(table_df.fieldname) or [] + if not child_rows: + continue + + child_meta = frappe.get_meta(table_df.options) + child_attach_fields = child_meta.get("fields", {"fieldtype": ["in", ["Attach", "Attach Image"]]}) + + if not child_attach_fields: + continue + + for child_row in child_rows: + for child_df in child_attach_fields: + file_url = child_row.get(child_df.fieldname) + if file_url: + frappe.db.set_value( + "File", + {"file_url": file_url, "attached_to_name": doc.__temporary_name}, + {"attached_to_name": doc.name}, + ) # delete temporary name after relinking is done doc.delete_key("__temporary_name") diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 4be1cfadec..3e9ff32d5e 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -201,18 +201,19 @@ frappe.ui.form.on("User", { }, ], primary_action: (values) => { - d.hide(); if (values.new_password !== values.confirm_password) { frappe.throw(__("Passwords do not match!")); } - frappe.call( - "frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", - { - user: frm.doc.email, - password: values.new_password, - logout: values.logout_sessions, - } - ); + return frappe + .call( + "frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", + { + user: frm.doc.email, + password: values.new_password, + logout: values.logout_sessions, + } + ) + .then(() => d.hide()); }, }); d.show(); diff --git a/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js index 5a1ae6f87a..093016705e 100644 --- a/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js +++ b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js @@ -25,7 +25,7 @@ frappe.query_reports["Database Storage Usage By Tables"] = { size: "small", primary_action_label: "Optimize", primary_action(values) { - frappe.call({ + return frappe.call({ method: "frappe.core.report.database_storage_usage_by_tables.database_storage_usage_by_tables.optimize_doctype", args: { doctype_name: values.doctype_name, @@ -38,9 +38,9 @@ frappe.query_reports["Database Storage Usage By Tables"] = { ) ); } + d.hide(); }, }); - d.hide(); }, }); d.show(); diff --git a/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py index df0a0c9470..df8dff27ce 100644 --- a/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py +++ b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py @@ -22,6 +22,7 @@ def execute(filters=None): round((data_length / 1024 / 1024), 2) as data_size, round((index_length / 1024 / 1024), 2) as index_size FROM information_schema.TABLES + WHERE table_schema = DATABASE() ORDER BY (data_length + index_length) DESC; """, "postgres": """ diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index e11d496d69..1c3f0e8f1f 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -488,7 +488,13 @@ frappe.ui.form.on("Dashboard Chart", { }); dialog.show(); - dialog.set_values(frm.dynamic_filters); + if (frm.dynamic_filters) { + let filter_values = {}; + frm.dynamic_filters.forEach((f) => { + filter_values[f[0] + ":" + f[1]] = f[3]; + }); + dialog.set_values(filter_values); + } }); }, diff --git a/frappe/desk/doctype/number_card/number_card.js b/frappe/desk/doctype/number_card/number_card.js index c69b96bf89..3ec198db4a 100644 --- a/frappe/desk/doctype/number_card/number_card.js +++ b/frappe/desk/doctype/number_card/number_card.js @@ -124,11 +124,6 @@ frappe.ui.form.on("Number Card", { frappe.model.with_doctype(doctype, () => { frappe.get_meta(doctype).fields.map((df) => { if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) { - if (df.fieldtype == "Currency") { - if (!df.options || df.options !== "Company:company:default_currency") { - return; - } - } aggregate_based_on_fields.push({ label: df.label, value: df.fieldname }); } }); diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 4c54d1be4a..ccb681804f 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -437,37 +437,19 @@ def get_linked_docs(doctype: str, name: str, linkinfo: dict | None = None) -> di is_target_doctype_table = frappe.get_meta(doctype).istable for linked_doctype, link_context in linkinfo.items(): - # Don't try to fetch linked documents if the user can't read the doctype - if not frappe.has_permission(linked_doctype): - continue - linked_doctype_meta = frappe.get_meta(linked_doctype) if linked_doctype_meta.issingle: continue + has_permission = frappe.has_permission(linked_doctype) filters = [] + or_filters = [] ret = None parent_info = None - fields = [ - d.fieldname - for d in linked_doctype_meta.get( - "fields", - { - "in_list_view": 1, - "fieldtype": ["not in", ("Image", "HTML", "Button", *frappe.model.table_fields)], - }, - ) - ] + ["name", "modified", "docstatus"] - - if add_fields := link_context.get("add_fields"): - fields += add_fields - - fields = [sf.strip() for sf in fields if sf] - if filters_ctx := link_context.get("filters"): - ret = frappe.get_list(doctype=linked_doctype, fields=fields, filters=filters_ctx, order_by=None) + filters = filters_ctx elif link_context.get("get_parent"): # check for child table @@ -478,13 +460,10 @@ def get_linked_docs(doctype: str, name: str, linkinfo: dict | None = None) -> di doctype, name, ["parenttype", "parent"], as_dict=True, order_by=None ) - if parent_info and parent_info.parenttype == linked_doctype: - ret = frappe.get_list( - doctype=linked_doctype, - fields=fields, - filters=[[linked_doctype, "name", "=", parent_info.parent]], - order_by=None, - ) + if not (parent_info and parent_info.parenttype == linked_doctype): + continue + + filters = [[linked_doctype, "name", "=", parent_info.parent]] elif child_doctype := link_context.get("child_doctype"): or_filters = [ @@ -495,15 +474,6 @@ def get_linked_docs(doctype: str, name: str, linkinfo: dict | None = None) -> di if doctype_fieldname := link_context.get("doctype_fieldname"): filters.append([child_doctype, doctype_fieldname, "=", doctype]) - ret = frappe.get_list( - doctype=linked_doctype, - fields=fields, - filters=filters, - or_filters=or_filters, - distinct=True, - order_by=None, - ) - elif link_fieldnames := link_context.get("fieldname"): if isinstance(link_fieldnames, str): link_fieldnames = [link_fieldnames] @@ -518,12 +488,51 @@ def get_linked_docs(doctype: str, name: str, linkinfo: dict | None = None) -> di or frappe.db.exists(linked_doctype, {"parenttype": doctype, "parent": name}) ): continue + + total_count = len( + frappe.get_all( + linked_doctype, + filters=filters, + or_filters=or_filters, + fields=["name"], + order_by=None, + ) + ) + + if not total_count: + continue + + if has_permission: + fields = [ + d.fieldname + for d in linked_doctype_meta.get( + "fields", + { + "in_list_view": 1, + "fieldtype": ["not in", ("Image", "HTML", "Button", *frappe.model.table_fields)], + }, + ) + ] + ["name", "modified", "docstatus"] + + if add_fields := link_context.get("add_fields"): + fields += add_fields + + fields = [sf.strip() for sf in fields if sf] + ret = frappe.get_list( - doctype=linked_doctype, fields=fields, filters=filters, or_filters=or_filters, order_by=None + doctype=linked_doctype, + fields=fields, + filters=filters, + or_filters=or_filters, + distinct=True, + order_by=None, ) - if ret: - results[linked_doctype] = ret + permitted_count = len(ret or []) + results[linked_doctype] = { + "docs": ret or [], + "hidden_count": total_count - permitted_count, + } return results diff --git a/frappe/desk/page/desktop/desktop.css b/frappe/desk/page/desktop/desktop.css index ef5e4e6062..4c294eb722 100644 --- a/frappe/desk/page/desktop/desktop.css +++ b/frappe/desk/page/desktop/desktop.css @@ -87,12 +87,17 @@ } } .modal -.modal-body .icons-container,.folder-icon .icons-container { +.modal-body .icons-container, .folder-icon .icons-container { padding:0px; margin: 0px; height: 100%; + overflow: auto; +} + +.folder-icon .icons-container { overflow: hidden; } + .icons{ gap: 16px; display: grid; diff --git a/frappe/desk/page/desktop/desktop.js b/frappe/desk/page/desktop/desktop.js index 514af59374..ba76bfb88f 100644 --- a/frappe/desk/page/desktop/desktop.js +++ b/frappe/desk/page/desktop/desktop.js @@ -548,7 +548,6 @@ class DesktopPage { frappe.router.on("change", function () { if (frappe.get_route()[0] == "desktop" || frappe.get_route()[0] == "") { me.setup_navbar(); - me.setup_edit_button(); } else { $(".navbar").show(); frappe.desktop_utils.close_desktop_modal(); diff --git a/frappe/locale/sr.po b/frappe/locale/sr.po index 97e1c2ff25..59a68f9ff8 100644 --- a/frappe/locale/sr.po +++ b/frappe/locale/sr.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" "POT-Creation-Date: 2026-02-22 09:42+0000\n" -"PO-Revision-Date: 2026-02-23 22:07\n" +"PO-Revision-Date: 2026-02-26 23:27\n" "Last-Translator: developers@frappe.io\n" "Language-Team: Serbian (Cyrillic)\n" "MIME-Version: 1.0\n" @@ -1357,7 +1357,7 @@ msgstr "Додај параметре упита" #. Label of the add_reply_to_header (Check) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Add Reply-To header" -msgstr "" +msgstr "Додај заглавље адресе за одговор" #: frappe/core/doctype/user/user.py:860 msgid "Add Roles" @@ -1523,7 +1523,7 @@ msgstr "Додај на контролну таблу" #: frappe/desk/doctype/workspace/workspace.js:49 msgid "Add to Desktop" -msgstr "" +msgstr "Додај на радну површину" #: frappe/public/js/frappe/form/sidebar/assign_to.js:110 msgid "Add to ToDo" @@ -1646,7 +1646,7 @@ msgstr "Адресе и контакти" #. Account' #: frappe/email/doctype/email_account/email_account.json msgid "Addresses added here will be used as the Reply-To header for outgoing emails sent from this account." -msgstr "" +msgstr "Адресе додате овде користиће се као адреса за одговор за излазне имејлове послате са овог налога." #. Description of a DocType #: frappe/custom/doctype/client_script/client_script.json @@ -3082,7 +3082,7 @@ msgstr "Историја измена" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/users.json msgid "Audits" -msgstr "" +msgstr "Ревизије" #. Label of the auth_url_data (Code) field in DocType 'Social Login Key' #: frappe/integrations/doctype/social_login_key/social_login_key.json @@ -3516,7 +3516,7 @@ msgstr "Слика позадине" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/system.json msgid "Background Job" -msgstr "" +msgstr "Позадински задатак" #. Label of a Link in the Build Workspace #. Label of the background_jobs_section (Section Break) field in DocType @@ -4934,7 +4934,7 @@ msgstr "Кликните да поставите филтере" #: frappe/desk/page/desktop/desktop.js:1261 msgid "Click to edit" -msgstr "" +msgstr "Кликните за уређивање" #: frappe/public/js/frappe/list/list_view.js:754 msgid "Click to sort by {0}" @@ -6537,7 +6537,7 @@ msgstr "Цијан" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "DELAY" -msgstr "" +msgstr "ОДЛАГАЊЕ" #. Option for the 'Method' (Select) field in DocType 'Recorder' #. Option for the 'Request Method' (Select) field in DocType 'Webhook' @@ -7365,7 +7365,7 @@ msgstr "Статус" #. Label of the dsn_notify_type (Select) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Delivery Status Notification Type" -msgstr "" +msgstr "Врста обавештења о статусу испоруке" #. Option for the 'Sign ups' (Select) field in DocType 'Social Login Key' #: frappe/integrations/doctype/social_login_key/social_login_key.json @@ -9430,7 +9430,7 @@ msgstr "Планер омогућен" #. Label of the enabled (Check) field in DocType 'Notification Settings' #: frappe/desk/doctype/notification_settings/notification_settings.json msgid "Enabled System Notification" -msgstr "" +msgstr "Омогућено системско обавештење" #: frappe/email/doctype/email_account/email_account.py:1101 msgid "Enabled email inbox for user {0}" @@ -10128,7 +10128,7 @@ msgstr "Додатни параметри" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "FAILURE" -msgstr "" +msgstr "НЕУСПЕХ" #. Option for the 'Social Login Provider' (Select) field in DocType 'Social #. Login Key' @@ -10272,7 +10272,7 @@ msgstr "Неуспешан покушај пријаве на Frappe Cloud" #: frappe/email/doctype/email_account/email_account.py:232 msgid "Failed to retrieve the list of IMAP folders from the server. Please ensure the mailbox is accessible and the account has permission to list folders." -msgstr "" +msgstr "Неуспешно преузимање листе IMAP директоријума са сервера. Проверите да ли је поштанско сандуче доступно и да ли налог има дозволу за приказ директоријума." #: frappe/email/doctype/email_queue/email_queue.py:311 msgid "Failed to send email with subject:" @@ -11339,7 +11339,7 @@ msgstr "Јединица фракције" #. Label of a Desktop Icon #: frappe/desktop_icon/framework.json msgid "Framework" -msgstr "" +msgstr "Framework" #. Option for the 'Social Login Provider' (Select) field in DocType 'Social #. Login Key' @@ -12235,7 +12235,7 @@ msgstr "Наслов" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/system.json msgid "Health Report" -msgstr "" +msgstr "Извештај о стању система" #. Option for the 'Type' (Select) field in DocType 'Dashboard Chart' #: frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -12654,7 +12654,7 @@ msgstr "IMAP датотека" #: frappe/email/doctype/email_account/email_account.py:235 #: frappe/email/doctype/email_account/email_account.py:263 msgid "IMAP Folder Not Found" -msgstr "" +msgstr "IMAP директоријум није пронађен" #. Label of the ip_address (Data) field in DocType 'Activity Log' #. Label of the ip_address (Data) field in DocType 'Comment' @@ -13414,7 +13414,7 @@ msgstr "Погрешан верификациони код" #: frappe/public/js/frappe/views/gantt/gantt_view.js:88 msgid "Incorrect configuration" -msgstr "" +msgstr "Неисправна конфигурација" #: frappe/model/document.py:1733 msgid "Incorrect value in row {0}:" @@ -16979,7 +16979,7 @@ msgstr "MyISAM" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "NEVER" -msgstr "" +msgstr "НИКАДА" #: frappe/workflow/doctype/workflow/workflow.js:19 msgid "NOTE: If you add states or transitions in the table, it will be reflected in the Workflow Builder but you will have to position them manually. Also Workflow Builder is currently in BETA." @@ -17750,7 +17750,7 @@ msgstr "Нема података за извоз" #: frappe/public/js/frappe/views/reports/query_report.js:1543 msgid "No data to perform this action" -msgstr "" +msgstr "Нема података за извршавање ове радње" #: frappe/contacts/doctype/address/address.py:247 msgid "No default Address Template found. Please create a new one from Setup > Printing and Branding > Address Template." @@ -17795,7 +17795,7 @@ msgstr "Нема додатних записа" #: frappe/public/js/frappe/views/reports/report_view.js:337 msgid "No matching entries in the current results" -msgstr "" +msgstr "Нема подударних записа у тренутним резултатима" #: frappe/templates/includes/search_template.html:49 msgid "No matching records. Search something new" @@ -18429,7 +18429,7 @@ msgstr "OAuth грешка" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/integrations.json msgid "OAuth Provider" -msgstr "" +msgstr "OAuth провајдер" #. Name of a DocType #. Label of a Link in the Integrations Workspace @@ -18698,7 +18698,7 @@ msgstr "Дозволи уређивање само за" #: frappe/core/doctype/module_def/module_def.py:95 msgid "Only Custom Modules can be renamed." -msgstr "" +msgstr "Искључиво прилагођени модули могу бити преименовани." #: frappe/core/doctype/doctype/doctype.py:1652 msgid "Only Options allowed for Data field are:" @@ -19969,7 +19969,7 @@ msgstr "Молимо Вас да додате валидан коментар." #: frappe/public/js/frappe/views/reports/query_report.js:1544 msgid "Please adjust filters to include some data" -msgstr "" +msgstr "Прилагодите филтере како бисте укључили неке податке" #: frappe/core/doctype/user/user.py:1122 msgid "Please ask your administrator to verify your sign-up" @@ -20029,7 +20029,7 @@ msgstr "Молимо Вас да кликнете на следећи линк #: frappe/public/js/frappe/views/gantt/gantt_view.js:89 msgid "Please configure the start field for this Doctype in the controller file." -msgstr "" +msgstr "Молимо Вас да конфигуришете почетно поље за овај DocType у датотеци контролера." #: frappe/www/confirm_workflow_action.html:4 msgid "Please confirm your action to {0} this document." @@ -21132,7 +21132,7 @@ msgstr "Љубичасто" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/integrations.json msgid "Push Notification" -msgstr "" +msgstr "Push обавештење" #. Name of a DocType #. Label of a Link in the Integrations Workspace @@ -22225,16 +22225,16 @@ msgstr "Одговори свима" #. Name of a DocType #: frappe/email/doctype/reply_to_address/reply_to_address.json msgid "Reply To Address" -msgstr "" +msgstr "Адреса за одговор" #: frappe/email/doctype/email_account/email_account.py:278 msgid "Reply To email is required" -msgstr "" +msgstr "Адреса за одговор је обавезна" #. Label of the reply_to_addresses (Table) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Reply-To Addresses" -msgstr "" +msgstr "Адреса за одговор" #. Label of the report (Check) field in DocType 'Custom DocPerm' #. Label of the report (Link) field in DocType 'Custom Role' @@ -23276,19 +23276,19 @@ msgstr "SSL/TLS режим" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "SUCCESS" -msgstr "" +msgstr "УСПЕХ" #. Option for the 'Delivery Status Notification Type' (Select) field in DocType #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "SUCCESS,FAILURE" -msgstr "" +msgstr "УСПЕХ, НЕУСПЕХ" #. Option for the 'Delivery Status Notification Type' (Select) field in DocType #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "SUCCESS,FAILURE,DELAY" -msgstr "" +msgstr "УСПЕХ, НЕУСПЕХ, ОДЛАГАЊЕ" #: frappe/public/js/frappe/color_picker/color_picker.js:20 msgid "SWATCHES" @@ -24115,7 +24115,7 @@ msgstr "Изабери две верзије за приказ разлика." #. DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Select which delivery events should trigger a delivery status notification (DSN) from the SMTP server." -msgstr "" +msgstr "Изаберите који догађаји испоруке треба да покрену обавештење о статусу испоруке (DNS) са SMTP сервера." #: frappe/public/js/frappe/form/link_selector.js:24 #: frappe/public/js/frappe/form/multi_select_dialog.js:80 @@ -27098,7 +27098,7 @@ msgstr "Коментар не може бити празан" #: frappe/email/doctype/email_account/email_account.py:290 msgid "The configured SMTP server does not support DSN (Delivery Status Notification)." -msgstr "" +msgstr "Конфигурисани SMTP сервер не подржава DNS (обавештење о статусу испоруке)." #: frappe/templates/emails/workflow_action.html:9 msgid "The contents of this email are strictly confidential. Please do not forward this email to anyone." @@ -27160,7 +27160,7 @@ msgstr "Следећа скрипта заглавља ће додати тре #: frappe/email/doctype/email_account/email_account.py:257 msgid "The following configured IMAP folder(s) were not found on the server:
Please verify the folder names exactly as they appear on the server (folder names are case-sensitive)." -msgstr "" +msgstr "Следећи конфигурисани IMAP директоријуми нису пронађени на серверу:
Молимо Вас да проверите називе директоријума тачно онако како су приказани на серверу (велика и мала слова су битна за називе датотека)." #: frappe/core/doctype/data_import/importer.py:1092 msgid "The following values are invalid: {0}. Values must be one of {1}" @@ -27252,7 +27252,7 @@ msgstr "Изабрани документ {0} није {1}." #: frappe/email/doctype/email_account/email_account.py:247 msgid "The server did not return any IMAP folders for this account." -msgstr "" +msgstr "Сервер није вратио ниједан IMAP директоријум за овај налог." #: frappe/utils/response.py:343 msgid "The system is being updated. Please refresh again after a few moments." @@ -29089,7 +29089,7 @@ msgstr "Отпреми" #: frappe/public/js/frappe/file_uploader/FileUploader.vue:663 msgid "Upload Failed" -msgstr "" +msgstr "Отпремање је неуспешно" #: frappe/public/js/print_format_builder/LetterHeadEditor.vue:93 msgid "Upload Image" @@ -30783,7 +30783,7 @@ msgstr "Ставка бочне траке радног простора" #: frappe/desk/doctype/workspace/workspace.js:58 msgid "Workspace added to desktop" -msgstr "" +msgstr "Радни простор је додат на радну површину" #: frappe/public/js/frappe/views/workspace/workspace.js:558 msgid "Workspace {0} created" @@ -31698,7 +31698,7 @@ msgstr "нпр. \"Подршка\", \"Продаја\", \"Петар Петро #: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:230 msgid "e.g. (55 + 434) / 4" -msgstr "" +msgstr "на пример (55 + 434) / 4" #. Description of the 'Incoming Server' (Data) field in DocType 'Email Account' #. Description of the 'Incoming Server' (Data) field in DocType 'Email Domain' @@ -32860,7 +32860,7 @@ msgstr "{0} од {1} ({2} редова са зависним подацима)" #: frappe/public/js/frappe/views/reports/report_view.js:456 msgid "{0} of {1} records match (filtered on visible rows only)" -msgstr "" +msgstr "{0} од {1} записа одговара критеријуму (филтрирано само по видљивим редовима)" #: frappe/utils/data.py:1571 msgctxt "Money in words" diff --git a/frappe/locale/sr_CS.po b/frappe/locale/sr_CS.po index 615d2f479f..48c4216803 100644 --- a/frappe/locale/sr_CS.po +++ b/frappe/locale/sr_CS.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: frappe\n" "Report-Msgid-Bugs-To: developers@frappe.io\n" "POT-Creation-Date: 2026-02-22 09:42+0000\n" -"PO-Revision-Date: 2026-02-23 22:07\n" +"PO-Revision-Date: 2026-02-26 23:27\n" "Last-Translator: developers@frappe.io\n" "Language-Team: Serbian (Latin)\n" "MIME-Version: 1.0\n" @@ -1358,7 +1358,7 @@ msgstr "Dodaj parametre upita" #. Label of the add_reply_to_header (Check) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Add Reply-To header" -msgstr "" +msgstr "Dodaj zaglavlje adrese za odgovor" #: frappe/core/doctype/user/user.py:860 msgid "Add Roles" @@ -1524,7 +1524,7 @@ msgstr "Dodaj na kontrolnu tablu" #: frappe/desk/doctype/workspace/workspace.js:49 msgid "Add to Desktop" -msgstr "" +msgstr "Dodaj na radnu površinu" #: frappe/public/js/frappe/form/sidebar/assign_to.js:110 msgid "Add to ToDo" @@ -1647,7 +1647,7 @@ msgstr "Adrese i kontakti" #. Account' #: frappe/email/doctype/email_account/email_account.json msgid "Addresses added here will be used as the Reply-To header for outgoing emails sent from this account." -msgstr "" +msgstr "Adrese dodate ovde koristiće se kao adresa za odgovor za izlazne imejlove poslate sa ovog naloga." #. Description of a DocType #: frappe/custom/doctype/client_script/client_script.json @@ -3083,7 +3083,7 @@ msgstr "Istorija izmena" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/users.json msgid "Audits" -msgstr "" +msgstr "Revizije" #. Label of the auth_url_data (Code) field in DocType 'Social Login Key' #: frappe/integrations/doctype/social_login_key/social_login_key.json @@ -3517,7 +3517,7 @@ msgstr "Slika pozadine" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/system.json msgid "Background Job" -msgstr "" +msgstr "Pozadinski zadatak" #. Label of a Link in the Build Workspace #. Label of the background_jobs_section (Section Break) field in DocType @@ -4935,7 +4935,7 @@ msgstr "Kliknite da postavite filtere" #: frappe/desk/page/desktop/desktop.js:1261 msgid "Click to edit" -msgstr "" +msgstr "Kliknite za uređivanje" #: frappe/public/js/frappe/list/list_view.js:754 msgid "Click to sort by {0}" @@ -6538,7 +6538,7 @@ msgstr "Cijan" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "DELAY" -msgstr "" +msgstr "ODLAGANJE" #. Option for the 'Method' (Select) field in DocType 'Recorder' #. Option for the 'Request Method' (Select) field in DocType 'Webhook' @@ -7366,7 +7366,7 @@ msgstr "Status" #. Label of the dsn_notify_type (Select) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Delivery Status Notification Type" -msgstr "" +msgstr "Vrsta obaveštenja o statusu isporuke" #. Option for the 'Sign ups' (Select) field in DocType 'Social Login Key' #: frappe/integrations/doctype/social_login_key/social_login_key.json @@ -9431,7 +9431,7 @@ msgstr "Planer omogućen" #. Label of the enabled (Check) field in DocType 'Notification Settings' #: frappe/desk/doctype/notification_settings/notification_settings.json msgid "Enabled System Notification" -msgstr "" +msgstr "Omogućeno sistemsko obaveštenje" #: frappe/email/doctype/email_account/email_account.py:1101 msgid "Enabled email inbox for user {0}" @@ -10129,7 +10129,7 @@ msgstr "Dodatni parametri" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "FAILURE" -msgstr "" +msgstr "NEUSPEH" #. Option for the 'Social Login Provider' (Select) field in DocType 'Social #. Login Key' @@ -10273,7 +10273,7 @@ msgstr "Neuspešan pokušaj prijave na Frappe Cloud" #: frappe/email/doctype/email_account/email_account.py:232 msgid "Failed to retrieve the list of IMAP folders from the server. Please ensure the mailbox is accessible and the account has permission to list folders." -msgstr "" +msgstr "Neuspešno preuzimanje liste IMAP direktorijuma sa servera. Proverite da li je poštansko sanduče dostupno i da li nalog ima dozvolu za prikaz direktorijuma." #: frappe/email/doctype/email_queue/email_queue.py:311 msgid "Failed to send email with subject:" @@ -11340,7 +11340,7 @@ msgstr "Jedinica frakcije" #. Label of a Desktop Icon #: frappe/desktop_icon/framework.json msgid "Framework" -msgstr "" +msgstr "Framework" #. Option for the 'Social Login Provider' (Select) field in DocType 'Social #. Login Key' @@ -12236,7 +12236,7 @@ msgstr "Naslov" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/system.json msgid "Health Report" -msgstr "" +msgstr "Izveštaj o stanju sistema" #. Option for the 'Type' (Select) field in DocType 'Dashboard Chart' #: frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -12655,7 +12655,7 @@ msgstr "IMAP datoteka" #: frappe/email/doctype/email_account/email_account.py:235 #: frappe/email/doctype/email_account/email_account.py:263 msgid "IMAP Folder Not Found" -msgstr "" +msgstr "IMAP direktorijum nije pronađen" #. Label of the ip_address (Data) field in DocType 'Activity Log' #. Label of the ip_address (Data) field in DocType 'Comment' @@ -13415,7 +13415,7 @@ msgstr "Pogrešan verifikacioni kod" #: frappe/public/js/frappe/views/gantt/gantt_view.js:88 msgid "Incorrect configuration" -msgstr "" +msgstr "Neispravna konfiguracija" #: frappe/model/document.py:1733 msgid "Incorrect value in row {0}:" @@ -16980,7 +16980,7 @@ msgstr "MyISAM" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "NEVER" -msgstr "" +msgstr "NIKADA" #: frappe/workflow/doctype/workflow/workflow.js:19 msgid "NOTE: If you add states or transitions in the table, it will be reflected in the Workflow Builder but you will have to position them manually. Also Workflow Builder is currently in BETA." @@ -17751,7 +17751,7 @@ msgstr "Nema podataka za izvoz" #: frappe/public/js/frappe/views/reports/query_report.js:1543 msgid "No data to perform this action" -msgstr "" +msgstr "Nema podataka za izvršavanje ove radnje" #: frappe/contacts/doctype/address/address.py:247 msgid "No default Address Template found. Please create a new one from Setup > Printing and Branding > Address Template." @@ -17796,7 +17796,7 @@ msgstr "Nema dodatnih zapisa" #: frappe/public/js/frappe/views/reports/report_view.js:337 msgid "No matching entries in the current results" -msgstr "" +msgstr "Nema podudarnih zapisa u trenutnim rezultatima" #: frappe/templates/includes/search_template.html:49 msgid "No matching records. Search something new" @@ -18430,7 +18430,7 @@ msgstr "OAuth greška" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/integrations.json msgid "OAuth Provider" -msgstr "" +msgstr "OAuth provajder" #. Name of a DocType #. Label of a Link in the Integrations Workspace @@ -18699,7 +18699,7 @@ msgstr "Dozvoli uređivanje samo za" #: frappe/core/doctype/module_def/module_def.py:95 msgid "Only Custom Modules can be renamed." -msgstr "" +msgstr "Isključivo prilagođeni moduli mogu biti preimenovani." #: frappe/core/doctype/doctype/doctype.py:1652 msgid "Only Options allowed for Data field are:" @@ -19970,7 +19970,7 @@ msgstr "Molimo Vas da dodate validan komentar." #: frappe/public/js/frappe/views/reports/query_report.js:1544 msgid "Please adjust filters to include some data" -msgstr "" +msgstr "Prilagodite filtere kako biste uključili neke podatke" #: frappe/core/doctype/user/user.py:1122 msgid "Please ask your administrator to verify your sign-up" @@ -20030,7 +20030,7 @@ msgstr "Molimo Vas da kliknete na sledeći link da biste postavili novu lozinku" #: frappe/public/js/frappe/views/gantt/gantt_view.js:89 msgid "Please configure the start field for this Doctype in the controller file." -msgstr "" +msgstr "Molimo Vas da konfigurišete početno polje za ovaj DocType u datoteci kontrolera." #: frappe/www/confirm_workflow_action.html:4 msgid "Please confirm your action to {0} this document." @@ -21133,7 +21133,7 @@ msgstr "Ljubičasto" #. Label of a Workspace Sidebar Item #: frappe/workspace_sidebar/integrations.json msgid "Push Notification" -msgstr "" +msgstr "Push obaveštenje" #. Name of a DocType #. Label of a Link in the Integrations Workspace @@ -22226,16 +22226,16 @@ msgstr "Odgovori svima" #. Name of a DocType #: frappe/email/doctype/reply_to_address/reply_to_address.json msgid "Reply To Address" -msgstr "" +msgstr "Adresa za odgovor" #: frappe/email/doctype/email_account/email_account.py:278 msgid "Reply To email is required" -msgstr "" +msgstr "Adresa za odgovor je obavezna" #. Label of the reply_to_addresses (Table) field in DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Reply-To Addresses" -msgstr "" +msgstr "Adrese za odgovor" #. Label of the report (Check) field in DocType 'Custom DocPerm' #. Label of the report (Link) field in DocType 'Custom Role' @@ -23277,19 +23277,19 @@ msgstr "SSL/TLS režim" #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "SUCCESS" -msgstr "" +msgstr "USPEH" #. Option for the 'Delivery Status Notification Type' (Select) field in DocType #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "SUCCESS,FAILURE" -msgstr "" +msgstr "USPEH, NEUSPEH" #. Option for the 'Delivery Status Notification Type' (Select) field in DocType #. 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "SUCCESS,FAILURE,DELAY" -msgstr "" +msgstr "USPEH, NEUSPEH, ODLAGANJE" #: frappe/public/js/frappe/color_picker/color_picker.js:20 msgid "SWATCHES" @@ -24116,7 +24116,7 @@ msgstr "Izaberi dve verzije za prikaz razlika." #. DocType 'Email Account' #: frappe/email/doctype/email_account/email_account.json msgid "Select which delivery events should trigger a delivery status notification (DSN) from the SMTP server." -msgstr "" +msgstr "Izaberite koji događaji isporuke treba da pokrenu obaveštenje o statusu isporuke (DNS) sa SMTP servera." #: frappe/public/js/frappe/form/link_selector.js:24 #: frappe/public/js/frappe/form/multi_select_dialog.js:80 @@ -27099,7 +27099,7 @@ msgstr "Komentar ne može biti prazan" #: frappe/email/doctype/email_account/email_account.py:290 msgid "The configured SMTP server does not support DSN (Delivery Status Notification)." -msgstr "" +msgstr "Konfigurisani SMTP server ne podržava DSN (obaveštenje o statusu isporuke)." #: frappe/templates/emails/workflow_action.html:9 msgid "The contents of this email are strictly confidential. Please do not forward this email to anyone." @@ -27161,7 +27161,7 @@ msgstr "Sledeća skripta zaglavlja će dodati trenutni datum u element klase 'he #: frappe/email/doctype/email_account/email_account.py:257 msgid "The following configured IMAP folder(s) were not found on the server:
Please verify the folder names exactly as they appear on the server (folder names are case-sensitive)." -msgstr "" +msgstr "Sledeći konfigurisani IMAP direktorijumi nisu pronađeni na serveru:
Molimo Vas da proverite nazive direktorijuma tačno onako kako su prikazani na serveru (velika i mala slova su bitna za nazive datoteka)." #: frappe/core/doctype/data_import/importer.py:1092 msgid "The following values are invalid: {0}. Values must be one of {1}" @@ -27253,7 +27253,7 @@ msgstr "Izabrani dokument {0} nije {1}." #: frappe/email/doctype/email_account/email_account.py:247 msgid "The server did not return any IMAP folders for this account." -msgstr "" +msgstr "Server nije vratio nijedan IMAP direktorijum za ovaj nalog." #: frappe/utils/response.py:343 msgid "The system is being updated. Please refresh again after a few moments." @@ -29089,7 +29089,7 @@ msgstr "Otpremi" #: frappe/public/js/frappe/file_uploader/FileUploader.vue:663 msgid "Upload Failed" -msgstr "" +msgstr "Otpremanje je neuspešno" #: frappe/public/js/print_format_builder/LetterHeadEditor.vue:93 msgid "Upload Image" @@ -30783,7 +30783,7 @@ msgstr "Stavka bočne trake radnog prostora" #: frappe/desk/doctype/workspace/workspace.js:58 msgid "Workspace added to desktop" -msgstr "" +msgstr "Radni prostor je dodat na radnu površinu" #: frappe/public/js/frappe/views/workspace/workspace.js:558 msgid "Workspace {0} created" @@ -31698,7 +31698,7 @@ msgstr "npr. \"Podrška\", \"Prodaja\", \"Petar Petrović\"" #: frappe/public/js/frappe/ui/toolbar/awesome_bar.js:230 msgid "e.g. (55 + 434) / 4" -msgstr "" +msgstr "na primer (55 + 434) / 4" #. Description of the 'Incoming Server' (Data) field in DocType 'Email Account' #. Description of the 'Incoming Server' (Data) field in DocType 'Email Domain' @@ -32860,7 +32860,7 @@ msgstr "{0} od {1} ({2} redova sa zavisnim podacima)" #: frappe/public/js/frappe/views/reports/report_view.js:456 msgid "{0} of {1} records match (filtered on visible rows only)" -msgstr "" +msgstr "{0} od {1} zapisa odgovara kriterijumu (filtrirano samo po vidljivim redovima)" #: frappe/utils/data.py:1571 msgctxt "Money in words" diff --git a/frappe/model/meta.py b/frappe/model/meta.py index add084d38e..3dc3181dac 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -894,6 +894,12 @@ def get_field_currency(df, doc=None): if frappe.get_meta(doc.parenttype).has_field(df.get("options")): # only get_value if parent has currency field currency = frappe.db.get_value(doc.parenttype, doc.parent, df.get("options")) + if not currency: + # Parent may not be in DB yet (new document being saved). + # Use the in-memory parent document reference if available. + parent = getattr(doc, "parent_doc", None) + if parent: + currency = parent.get(df.get("options")) if currency: frappe.local.field_currency.setdefault((doc.doctype, ref_docname), frappe._dict()).setdefault( diff --git a/frappe/printing/doctype/letter_head/letter_head.json b/frappe/printing/doctype/letter_head/letter_head.json index 4ddafc47db..aa40d0a751 100644 --- a/frappe/printing/doctype/letter_head/letter_head.json +++ b/frappe/printing/doctype/letter_head/letter_head.json @@ -127,12 +127,12 @@ { "fieldname": "image_height", "fieldtype": "Float", - "label": "Image Height" + "label": "Image Height (px)" }, { "fieldname": "image_width", "fieldtype": "Float", - "label": "Image Width" + "label": "Image Width (px)" }, { "depends_on": "eval:doc.footer_source==='Image' && doc.letter_head_name", @@ -148,12 +148,12 @@ { "fieldname": "footer_image_height", "fieldtype": "Float", - "label": "Image Height" + "label": "Image Height (px)" }, { "fieldname": "footer_image_width", "fieldtype": "Float", - "label": "Image Width" + "label": "Image Width (px)" }, { "fieldname": "footer_align", @@ -203,7 +203,7 @@ "links": [], "make_attachments_public": 1, "max_attachments": 3, - "modified": "2026-02-24 20:53:14.297567", + "modified": "2026-02-25 14:37:57.061516", "modified_by": "Administrator", "module": "Printing", "name": "Letter Head", diff --git a/frappe/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index bae82ba040..a40101ba87 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -35,6 +35,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder { this.show_start(); } else { this.page.set_title(this.print_format.name); + this.page.sidebar.toggle(true); this.setup_print_format(); } } @@ -65,6 +66,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder { this.page.main.html(frappe.render_template("print_format_builder_start", {})); this.page.clear_actions(); this.page.set_title(__("Print Format Builder")); + this.page.sidebar.toggle(false); this.start_edit_print_format(); this.start_new_print_format(); } diff --git a/frappe/public/js/billing.bundle.js b/frappe/public/js/billing.bundle.js index 79e0609c58..be249d877b 100644 --- a/frappe/public/js/billing.bundle.js +++ b/frappe/public/js/billing.bundle.js @@ -33,7 +33,14 @@ $(document).ready(function () { !frappe.is_mobile() && frappe.user.has_role("System Manager"); if (visiblity_condition && isFCUser) { - addChatBubble(); + frappe.router.on("change", function () { + if (frappe.get_route()[0] == "") { + addChatBubble(); + toggleChatBubble(true); + } else { + toggleChatBubble(false); + } + }); } if (isFCUser) { $.extend(card_args, { @@ -92,9 +99,10 @@ function addChatBubble() { const all_apps = frappe.utils.get_installed_apps(); const desk_apps = ["erpnext", "hrms"]; - const apps_allowed = frappe.utils.is_sub_array(all_apps, desk_apps); - if (checkBusinessHours && apps_allowed) { + const apps_allowed = desk_apps.some((app) => all_apps.includes(app)); + if (checkBusinessHours() && apps_allowed) { let chat_banner = document.createElement("script"); + chat_banner.setAttribute("id", "chat_widget_trigger"); chat_banner.innerHTML = '(function(d,t){var BASE_URL="https://chat.frappe.cloud";var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src=BASE_URL+"/packs/js/sdk.js";g.async=true;s.parentNode.insertBefore(g,s);g.onload=function(){window.chatwootSDK.run({websiteToken:"LdmfJzftdJGEcFjoTqk8CrSq",baseUrl:BASE_URL})}})(document,"script");'; document.body.append(chat_banner); @@ -104,8 +112,24 @@ function addChatBubble() { } function checkBusinessHours() { - let currentTime = new Date(); - const istTime = new Date(currentTime.toLocaleString("en-US", { timeZone: "Asia/Kolkata" })); + let current_time = new Date(); + const ist_time = new Date(current_time.toLocaleString("en-US", { timeZone: "Asia/Kolkata" })); - return istTime.getHours() >= 11 && istTime.getHours() < 18; + const hours = ist_time.getHours(); + const day = ist_time.getDay(); + + const is_weekend = day === 0 || day === 6; + const is_business_hour = hours >= 11 && hours < 18; + + return !is_weekend && is_business_hour; +} + +function toggleChatBubble(toggle) { + if (toggle) { + $(".woot-widget-holder").show(); + $("#cw-bubble-holder").show(); + } else { + $(".woot-widget-holder").hide(); + $("#cw-bubble-holder").hide(); + } } diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue index 10012c82ad..f21d925efe 100644 --- a/frappe/public/js/frappe/file_uploader/FileUploader.vue +++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue @@ -514,22 +514,7 @@ function check_restrictions(file) { return is_correct_type && valid_file_size; } -function set_loading_state(dialog, loading) { - let $btn = dialog?.get_primary_btn(); - if (loading) { - $btn?.css("width", $btn.outerWidth()); - $btn?.html(``); - $btn?.prop("disabled", true); - dialog?.get_secondary_btn().prop("disabled", true); - } else { - $btn?.css("width", ""); - $btn?.html(__("Upload")); - $btn?.prop("disabled", false); - dialog?.get_secondary_btn().prop("disabled", false); - } -} -function upload_files(dialog) { - set_loading_state(dialog, true); +function upload_files() { if (show_file_browser.value) { promise = upload_via_file_browser(); } else if (show_web_link.value) { @@ -542,7 +527,7 @@ function upload_files(dialog) { } else { promise = frappe.run_serially(files.value.map((file, i) => () => upload_file(file, i))); } - return promise.finally(() => set_loading_state(dialog, false)); + return promise; } function upload_via_file_browser() { let selected_file = file_browser.value.selected_node; diff --git a/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js b/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js index 44e9d9add6..999cc675ea 100644 --- a/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js +++ b/frappe/public/js/frappe/file_uploader/file_uploader.bundle.js @@ -151,6 +151,7 @@ class FileUploader { const dialog_opts = { title: title || __("Upload"), primary_action_label: __("Upload"), + primary_action_loading_label: __("Uploading"), primary_action: () => this.upload_files(), on_page_show: () => { this.uploader.wrapper_ready = true; diff --git a/frappe/public/js/frappe/form/column.js b/frappe/public/js/frappe/form/column.js index 92ec3d8917..e46cd49ef6 100644 --- a/frappe/public/js/frappe/form/column.js +++ b/frappe/public/js/frappe/form/column.js @@ -37,24 +37,35 @@ export default class Column { } resize_all_columns() { - // distribute all columns equally - let columns = this.section.wrapper.find(".form-column").length; + // distribute visible columns equally + let all_columns = this.section.wrapper.find(".form-column"); + let visible_columns = all_columns.filter(":not(.hide-control)"); + let columns = visible_columns.length || all_columns.length; let colspan = cint(12 / columns); if (columns == 5) { colspan = 20; } - this.section.wrapper - .find(".form-column") - .removeClass() - .addClass("form-column") - .addClass("col-sm-" + colspan); + all_columns.each(function () { + const $col = $(this); + const is_hidden = $col.hasClass("hide-control"); + $col.removeClass() + .addClass("form-column") + .addClass("col-sm-" + colspan); + if (is_hidden) { + $col.addClass("hide-control"); + } + }); } add_field() {} refresh() { + if (!this.df) return; + const hide = this.df.hidden || this.df.hidden_due_to_dependency; + this.wrapper.toggleClass("hide-control", !!hide); + this.resize_all_columns(); this.section.refresh(); } } diff --git a/frappe/public/js/frappe/form/controls/color.js b/frappe/public/js/frappe/form/controls/color.js index e9f88faec5..65c9f1b1d3 100644 --- a/frappe/public/js/frappe/form/controls/color.js +++ b/frappe/public/js/frappe/form/controls/color.js @@ -2,7 +2,7 @@ import Picker from "../../color_picker/color_picker"; frappe.ui.form.ControlColor = class ControlColor extends frappe.ui.form.ControlData { make_input() { - this.df.placeholder = this.df.placeholder || __("Choose a color"); + this.df.placeholder = __(this.df.placeholder) || __("Choose a color"); super.make_input(); this.make_color_input(); } diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 3cd3446d2f..69f2b5d519 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -241,7 +241,7 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp this.$input .attr("data-fieldtype", this.df.fieldtype) .attr("data-fieldname", this.df.fieldname) - .attr("placeholder", this.df.placeholder || ""); + .attr("placeholder", __(this.df.placeholder || "")); if (this.doctype) { this.$input.attr("data-doctype", this.doctype); } diff --git a/frappe/public/js/frappe/form/controls/icon.js b/frappe/public/js/frappe/form/controls/icon.js index a964153d2b..6f86665c2b 100644 --- a/frappe/public/js/frappe/form/controls/icon.js +++ b/frappe/public/js/frappe/form/controls/icon.js @@ -2,7 +2,7 @@ import Picker from "../../icon_picker/icon_picker"; frappe.ui.form.ControlIcon = class ControlIcon extends frappe.ui.form.ControlData { make_input() { - this.df.placeholder = this.df.placeholder || __("Choose an icon"); + this.df.placeholder = __(this.df.placeholder) || __("Choose an icon"); super.make_input(); this.get_all_icons(); this.make_icon_input(); diff --git a/frappe/public/js/frappe/form/controls/select.js b/frappe/public/js/frappe/form/controls/select.js index eee88cb149..c6802b5984 100644 --- a/frappe/public/js/frappe/form/controls/select.js +++ b/frappe/public/js/frappe/form/controls/select.js @@ -28,7 +28,7 @@ frappe.ui.form.ControlSelect = class ControlSelect extends frappe.ui.form.Contro const placeholder_html = `
- ${this.df.placeholder} + ${__(this.df.placeholder)}
`; if (this.only_input) { this.$wrapper.append(placeholder_html); diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 6bbb9c1aa0..0264f0ae3b 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -235,7 +235,7 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for theme: this.df.theme || "snow", readOnly: this.disabled || this.df.read_only, bounds: this.quill_container[0], - placeholder: this.df.placeholder || "", + placeholder: __(this.df.placeholder || ""), }; // In a grid row where space is constrained, hide the toolbar. diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 2570864ee3..2859cb9f5b 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1574,15 +1574,10 @@ frappe.ui.form.Form = class FrappeForm { var scroll_to = frappe.route_options.scroll_to; delete frappe.route_options.scroll_to; - var selector = []; - for (var key in scroll_to) { - var value = scroll_to[key]; - selector.push(repl('[data-%(key)s="%(value)s"]', { key: key, value: value })); - } - - selector = $(selector.join(" ")); - if (selector.length) { - frappe.utils.scroll_to(selector); + if (this.scroll_to_field(scroll_to)) { + const url = new URL(window.location); + url.searchParams.delete("scroll_to"); + history.replaceState(null, null, url); } } else if (window.location.hash) { if ($(window.location.hash).length) { @@ -2106,7 +2101,7 @@ frappe.ui.form.Form = class FrappeForm { } // scroll to input - frappe.utils.scroll_to($el, true, 15); + frappe.utils.scroll_to($el, true, 15, $(".main-section")); // focus if text field if (focus) { diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 348bd13840..616c794936 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -1033,7 +1033,7 @@ export default class GridRow { let is_focused = false; var $col = $( - `
` + `
` ) .attr("data-fieldname", df.fieldname) .attr("data-fieldtype", df.fieldtype) @@ -1095,7 +1095,9 @@ export default class GridRow { return out; }); - $col.field_area = $('
').appendTo($col).toggle(false); + $col.field_area = $('
') + .appendTo($col) + .toggle(false); $col.static_area = $('
').appendTo($col).html(txt); // set title attribute to see full label for columns in the heading row diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 893d8abe3e..d1dc39a231 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -745,7 +745,7 @@ frappe.ui.form.Layout = class Layout { if (f.df.fieldtype === "Table") { for (const row of f.grid?.grid_rows || []) { - row.refresh_dependency(); + row?.refresh_dependency(); } } } diff --git a/frappe/public/js/frappe/form/linked_with.js b/frappe/public/js/frappe/form/linked_with.js index 12c5dc4f6b..2aaa3dcb95 100644 --- a/frappe/public/js/frappe/form/linked_with.js +++ b/frappe/public/js/frappe/form/linked_with.js @@ -20,7 +20,8 @@ frappe.ui.form.LinkedWith = class LinkedWith { make_dialog() { this.dialog = new frappe.ui.Dialog({ - title: __("Linked With"), + title: __("Links"), + minimizable: true, }); this.dialog.on_page_show = () => { @@ -39,22 +40,40 @@ frappe.ui.form.LinkedWith = class LinkedWith { make_html() { let html = ""; const linked_docs = this.frm.__linked_docs; - const linked_doctypes = Object.keys(linked_docs); + const linked_doctypes = Object.keys(linked_docs).filter((dt) => { + const entry = linked_docs[dt]; + return (entry.docs && entry.docs.length) || entry.hidden_count > 0; + }); if (linked_doctypes.length === 0) { html = __("Not Linked to any record"); } else { - html = linked_doctypes - .map((doctype) => { - const docs = linked_docs[doctype]; - return ` -
- ${this.make_doc_head(doctype)} - ${docs.map((doc) => this.make_doc_row(doc, doctype)).join("")} + html = ` +
+ ${__("Following documents are linked with {0}", [ + frappe.utils + .get_form_link(this.frm.doctype, this.frm.docname, true) + .bold(), + ])}
- `; - }) - .join(""); + ${linked_doctypes + .map((doctype) => { + const { docs, hidden_count } = linked_docs[doctype]; + let rows = (docs || []) + .map((doc) => this.make_doc_row(doc, doctype)) + .join(""); + if (hidden_count > 0) { + rows += this.make_hidden_count_row(hidden_count); + } + return ` +
+ ${this.make_doc_head(doctype)} + ${rows} +
+ `; + }) + .join("")} + `; } $(this.dialog.body).html(html); @@ -68,6 +87,16 @@ frappe.ui.form.LinkedWith = class LinkedWith { `; } + make_hidden_count_row(count) { + return `
+
+
+ ${count == 1 ? __("{0} restricted document", [count]) : __("{0} restricted documents", [count])} +
+
+
`; + } + make_doc_row(doc, doctype) { return `
diff --git a/frappe/public/js/frappe/form/reminders.js b/frappe/public/js/frappe/form/reminders.js index 5c8fa16060..667ca805d6 100644 --- a/frappe/public/js/frappe/form/reminders.js +++ b/frappe/public/js/frappe/form/reminders.js @@ -48,8 +48,7 @@ export class ReminderManager { ], primary_action_label: __("Create"), primary_action: () => { - this.create_reminder(); - this.dialog.hide(); + return this.create_reminder().then(() => this.dialog.hide()); }, secondary_action_label: __("Cancel"), secondary_action: () => { @@ -84,7 +83,7 @@ export class ReminderManager { } create_reminder() { - frappe + return frappe .xcall("frappe.automation.doctype.reminder.reminder.create_new_reminder", { remind_at: this.dialog.get_value("remind_at"), description: this.dialog.get_value("description"), diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js index e3c2cd1e4e..4a182d47e2 100644 --- a/frappe/public/js/frappe/form/save.js +++ b/frappe/public/js/frappe/form/save.js @@ -231,7 +231,7 @@ frappe.ui.form.check_mandatory = function (frm) { } function scroll_to(fieldname) { - if (frm.scroll_to_field(fieldname)) { + if (frm.scroll_to_field(fieldname, false)) { frm.scroll_set = true; } } diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 6b867fdf51..4b487f2c08 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -114,9 +114,7 @@ frappe.ui.form.AssignToDialog = class AssignToDialog { let args = me.dialog.get_values(); if (args && args.assign_to) { - me.dialog.set_message("Assigning..."); - - frappe.call({ + return frappe.call({ method: me.method, args: $.extend(args, { doctype: me.doctype, @@ -125,15 +123,12 @@ frappe.ui.form.AssignToDialog = class AssignToDialog { bulk_assign: me.bulk_assign || false, re_assign: me.re_assign || false, }), - btn: me.dialog.get_primary_btn(), callback: function (r) { if (!r.exc) { if (me.callback) { me.callback(r); } me.dialog && me.dialog.hide(); - } else { - me.dialog.clear_message(); } }, }); diff --git a/frappe/public/js/frappe/form/sidebar/attachments.js b/frappe/public/js/frappe/form/sidebar/attachments.js index bb36a2f65b..894856dcec 100644 --- a/frappe/public/js/frappe/form/sidebar/attachments.js +++ b/frappe/public/js/frappe/form/sidebar/attachments.js @@ -180,8 +180,18 @@ frappe.ui.form.Attachments = class Attachments { file_url = "/files/" + attachment.file_name; } } + + const is_web_url = /^(https?:)?\/\//i.test(file_url); + + file_url = encodeURI(file_url); + // hash is not escaped, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI - return encodeURI(file_url).replace(/#/g, "%23"); + // only encode hash if it's a local file path, not a web URL + if (!is_web_url) { + file_url = file_url.replace(/#/g, "%23"); + } + + return file_url; } get_file_id_from_file_url(file_url) { var fid; diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index dd85c3da0d..0b794a03c3 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -452,9 +452,7 @@ export default class BulkOperations { primary_action: () => { let args = dialog.get_values(); if (args && args.tags) { - dialog.set_message("Adding Tags..."); - - frappe.call({ + return frappe.call({ method: "frappe.desk.doctype.tag.tag.add_tags", args: { tags: args.tags, diff --git a/frappe/public/js/frappe/list/list_filter.js b/frappe/public/js/frappe/list/list_filter.js index e46c59eca9..f3ff15587b 100644 --- a/frappe/public/js/frappe/list/list_filter.js +++ b/frappe/public/js/frappe/list/list_filter.js @@ -120,7 +120,7 @@ export default class ListFilter { fields: fields, primary_action_label: __("Create"), primary_action: (values) => { - this.bind_save_filter(dialog, values.filter_name, values?.is_global); + return this.bind_save_filter(dialog, values.filter_name, values?.is_global); }, }); dialog.show(); @@ -138,7 +138,7 @@ export default class ListFilter { dialog.fields_dict.filter_name.set_description(__("Duplicate Filter Name")); return; } - this.save_filter(value, is_global).then(() => { + return this.save_filter(value, is_global).then(() => { this.refresh_list_filter(); dialog.hide(); }); diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index d393f33cb7..7692dcfdfc 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -332,7 +332,8 @@ $.extend(frappe.meta, { } else if (df && df.fieldtype === "Currency") { precision = cint(frappe.defaults.get_default("currency_precision")); if (!precision) { - var number_format = get_number_format(); + var currency = frappe.meta.get_field_currency(df, doc); + var number_format = get_number_format(currency); var number_format_info = get_number_format_info(number_format); precision = number_format_info.precision; } diff --git a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js index 53d8405ceb..3fb854c15d 100644 --- a/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js +++ b/frappe/public/js/frappe/ui/address_autocomplete/autocomplete_dialog.js @@ -48,9 +48,6 @@ frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { ], primary_action_label: __("Create Address"), primary_action: () => { - // Insert the address into the database - dialog.hide(); - const address = this.parse_selected_value(); address["doctype"] = "Address"; address["links"] = [ @@ -59,7 +56,8 @@ frappe.ui.AddressAutocompleteDialog = class AddressAutocompleteDialog { link_name: this.link_name, }, ]; - frappe.db.insert(address).then((doc) => { + return frappe.db.insert(address).then((doc) => { + dialog.hide(); this.after_insert && this.after_insert(doc); }); }, diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 036dc0c1cd..40a491a03d 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -207,6 +207,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { this.has_primary_action = true; var me = this; const primary_btn = this.get_primary_btn().removeClass("hide").html(label); + const spinner = ``; if (typeof click == "function") { primary_btn.off("click").on("click", function () { me.primary_action_fulfilled = true; @@ -215,7 +216,35 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { // if no values then return var values = me.get_values(); if (!values) return; - click && click.apply(me, [values]); + const action = click.apply(me, [values]); + if (action && typeof action.then === "function") { + const loading_label = me.primary_action_loading_label; + primary_btn + .css({ + "min-width": primary_btn.outerWidth(), + "min-height": primary_btn.outerHeight(), + }) + .prop("disabled", true) + .addClass("btn-primary-dark") + .html( + `
+ ${spinner} + ${ + loading_label + ? `${loading_label}` + : "" + } +
` + ); + + Promise.resolve(action).finally(() => { + primary_btn + .css({ "min-width": "", "min-height": "" }) + .prop("disabled", false) + .removeClass("btn-primary-dark") + .html(label); + }); + } }); } return primary_btn; diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index 98ce569ab5..bf6021fc3c 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -18,12 +18,35 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { } } + resolve_date_default_keywords(def_value, fieldtype) { + if (!def_value || typeof def_value !== "string") return def_value; + + def_value = def_value.toLowerCase(); + + if (def_value == "today" && fieldtype == "Date") { + return frappe.datetime.get_today(); + } + + if (def_value == "now") { + if (fieldtype == "Datetime") { + return frappe.datetime.now_datetime(); + } + if (fieldtype == "Time") { + return frappe.datetime.now_time(); + } + } + + return def_value; + } + make() { let me = this; if (this.fields) { super.make(); this.refresh(); - // set default + + let defaults = {}; + $.each(this.fields_list, function (i, field) { let def_value = field.df["default"]; // loose equality check matches undefined also @@ -33,12 +56,14 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { ) return; - if (def_value == "Today" && field.df["fieldtype"] == "Date") { - def_value = frappe.datetime.get_today(); + if (["Date", "Datetime", "Time"].includes(field.df.fieldtype)) { + def_value = me.resolve_date_default_keywords(def_value, field.df.fieldtype); } - field.set_input(def_value); - // if default and has depends_on, render its fields. + defaults[field.df.fieldname] = def_value; + }); + + this.set_values(defaults).then(() => { me.refresh_dependency(); }); diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 4b8e013e13..3c6a85f480 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -206,7 +206,7 @@ frappe.msgprint = function (msg, title, is_minimizable, re_route) { typeof data.primary_action.server_action === "string" ) { data.primary_action.action = () => { - frappe.call({ + return frappe.call({ method: data.primary_action.server_action, args: data.primary_action.args, callback() { diff --git a/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue b/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue index 9ee883cf31..6104e4d7a3 100644 --- a/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue +++ b/frappe/public/js/frappe/ui/user_onboarding/OnboardingPanel.vue @@ -306,7 +306,7 @@ function markReset(step) {
{{ __(step.action_label) }} diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js index 3d425aae08..2b6d4f4911 100644 --- a/frappe/public/js/frappe/utils/dashboard_utils.js +++ b/frappe/public/js/frappe/utils/dashboard_utils.js @@ -261,7 +261,7 @@ frappe.dashboard_utils = { primary_action: (values) => { values.name = docname; values.set_standard = frappe.boot.developer_mode; - frappe.xcall(method, { args: values }).then(() => { + return frappe.xcall(method, { args: values }).then(() => { let dashboard_route_html = `${values.dashboard}`; let message = __("{0} {1} added to Dashboard {2}", [ doctype, @@ -270,9 +270,8 @@ frappe.dashboard_utils = { ]); frappe.msgprint(message); + dialog.hide(); }); - - dialog.hide(); }, }); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index e61e0f3b78..674ff8c8c6 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -328,7 +328,7 @@ Object.assign(frappe.utils, { scroll_top = typeof element == "number" ? element - cint(additional_offset) - : this.get_scroll_position(element, additional_offset); + : this.get_scroll_position(element, additional_offset, element_to_be_scrolled); } if (scroll_top < 0) { @@ -366,10 +366,33 @@ Object.assign(frappe.utils, { element_to_be_scrolled.scrollTop(scroll_top); } }, - get_scroll_position: function (element, additional_offset) { - let header_offset = - $(".navbar").height() + $(".page-head:visible").height() || $(".navbar").height(); - return $(element).offset().top - header_offset - cint(additional_offset); + get_scroll_position: function (element, additional_offset, element_to_be_scrolled) { + const get_offset_relative_to_container = () => { + let offset = 0; + + let el = element instanceof HTMLElement ? element : element[0]; + const container = element_to_be_scrolled ? element_to_be_scrolled[0] : null; + + while (el && el !== container && el.offsetParent) { + offset += el.offsetTop; + el = el.offsetParent; + } + + return offset; + }; + + const get_header_offset = () => { + const navbar_height = $(".navbar").height() || 0; + const page_head_height = $(".page-head:visible").height() || 0; + const tabs_container_height = $(".form-tabs-list:visible").height() || 0; + + return navbar_height + page_head_height + tabs_container_height; + }; + + const element_offset_top = get_offset_relative_to_container(); + const header_offset = get_header_offset(); + + return element_offset_top - header_offset - cint(additional_offset); }, filter_dict: function (dict, filters) { var ret = []; diff --git a/frappe/public/js/frappe/views/dashboard/dashboard_view.js b/frappe/public/js/frappe/views/dashboard/dashboard_view.js index 2adaee49fb..9011902091 100644 --- a/frappe/public/js/frappe/views/dashboard/dashboard_view.js +++ b/frappe/public/js/frappe/views/dashboard/dashboard_view.js @@ -449,7 +449,7 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView { : chart.chart_type; chart.document_type = this.doctype; chart.filters_json = "[]"; - frappe + return frappe .xcall( "frappe.desk.doctype.dashboard_chart.dashboard_chart.create_dashboard_chart", { args: chart } @@ -460,6 +460,7 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView { name: doc.chart_name, label: chart.label, }); + dialog.hide(); }); } else { this.chart_group.new_widget.on_create({ @@ -467,8 +468,8 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView { label: __(chart.chart), name: chart.chart, }); + dialog.hide(); } - dialog.hide(); }, }); dialog.show(); diff --git a/frappe/public/js/frappe/views/interaction.js b/frappe/public/js/frappe/views/interaction.js index ba4ebe63d9..b6d6f80cfc 100644 --- a/frappe/public/js/frappe/views/interaction.js +++ b/frappe/public/js/frappe/views/interaction.js @@ -17,7 +17,7 @@ frappe.views.InteractionComposer = class InteractionComposer { fields: me.get_fields(), primary_action_label: __("Create"), primary_action: function () { - me.create_action(); + return me.create_action(); }, }); diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 8693fce7fb..f790aea680 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -2109,7 +2109,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }, ], primary_action: (values) => { - frappe.call({ + return frappe.call({ method: "frappe.desk.query_report.save_report", args: { reference_report: this.report_name, diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 68d523b0fa..9bedbf0e11 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -99,16 +99,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { }); } - setup_paging_area() { - super.setup_paging_area(); - const message = __( - "For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10)." - ); - this.$paging_area.before( - `${message}` - ); - } - setup_sort_selector() { this.sort_selector = new frappe.ui.SortSelector({ parent: this.filter_area.$filter_list_wrapper, @@ -430,6 +420,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { } setup_inline_filter_observer() { + this.setup_inline_filter_help_icons(); + this.$datatable_wrapper.on( "keyup", ".dt-filter", @@ -439,6 +431,29 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { ); } + setup_inline_filter_help_icons() { + const message = __( + "For comparison, use >5, <10 or =324.\nFor ranges, use 5:10 (for values between 5 & 10)." + ); + + this.$datatable_wrapper.find(".dt-filter").each((_, input) => { + const $input = $(input); + + if ($input.siblings(".comparison-help-icon").length) { + return; + } + + const $icon = $( + `${frappe.utils.icon( + "info", + "xs" + )}` + ); + + $input.after($icon); + }); + } + update_count_for_inline_filter() { if (!this.datatable) return; diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index f9c1d049cc..eda2048a67 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -570,8 +570,14 @@ export default class ChartWidget extends Widget { let setup_dashboard_chart = () => { const chart_args = this.get_chart_args(); + const is_circular_chart = ["Pie", "Donut", "Percentage"].includes(this.chart_doc.type); + if (!this.dashboard_chart) { this.dashboard_chart = frappe.utils.make_chart(this.chart_wrapper[0], chart_args); + } else if (is_circular_chart) { + this.chart_wrapper.empty(); + delete this.dashboard_chart; + this.dashboard_chart = frappe.utils.make_chart(this.chart_wrapper[0], chart_args); } else { this.dashboard_chart.update(this.data); } @@ -619,6 +625,7 @@ export default class ChartWidget extends Widget { colors: colors, height: this.height, maxSlices: this.chart_doc.number_of_groups || max_slices, + truncateLegends: 0, axisOptions: { xIsSeries: this.chart_doc.timeseries, shortenYAxisNumbers: 1, diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index fa03f4d84f..16f7d6b4d3 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -270,6 +270,7 @@ .col:last-child { border: none; + background-color: var(--fg-color); } .btn-open-row { @@ -309,8 +310,8 @@ border-radius: 0px; border: 0px; padding-top: 10px; - padding-bottom: calc(var(--padding-md) - 3px); - height: auto; + padding-bottom: 10px; + height: 100%; } .link-btn { @@ -430,6 +431,7 @@ .frappe-control { margin-bottom: 0px !important; position: relative; + flex-grow: 1; } .col-sm-6 { @@ -779,7 +781,7 @@ .data-row.row { flex-wrap: nowrap; } -.frappe-control[data-fieldtype="Table"].form-group:has(.column-limit-reached) { +.frappe-control[data-fieldtype="Table"].form-group:has(.column-limit-reached):not(.highlight) { overflow-x: clip; } .column-limit-reached { diff --git a/frappe/public/scss/common/modal.scss b/frappe/public/scss/common/modal.scss index 9cd60935c2..ca1ce88ae1 100644 --- a/frappe/public/scss/common/modal.scss +++ b/frappe/public/scss/common/modal.scss @@ -2,6 +2,12 @@ h5.modal-title { margin: 0px !important; } +@keyframes spin { + to { + transform: rotate(360deg); + } +} + // Hack to fix incorrect padding applied by Bootstrap body.modal-open[style^="padding-right"] { padding-right: 12px !important; @@ -103,6 +109,11 @@ body.modal-open[style^="padding-right"] { button:not(:last-child) { margin-right: var(--margin-xs); } + + .btn-primary-dark { + min-width: 80px; + max-width: 200px; + } } & > * { diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index 6ac02526f5..65aa1c5b94 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -371,6 +371,27 @@ input.list-header-checkbox { .list-item-table { border: 1px solid $border-color; border-radius: 3px; + + .list-row-head { + border-radius: unset; + } + + .list-row-container { + border-bottom: 1px solid $border-color; + border-radius: unset; + + &:last-child { + border-bottom: none; + } + } + + .list-row-container:hover { + border-radius: unset; + } + + .list-row-container .list-row { + border-bottom: none; + } } .list-item { diff --git a/frappe/public/scss/desk/report.scss b/frappe/public/scss/desk/report.scss index c476bbe5c0..a84b4719c3 100644 --- a/frappe/public/scss/desk/report.scss +++ b/frappe/public/scss/desk/report.scss @@ -93,6 +93,37 @@ border-radius: var(--border-radius); } } + +.report-view { + .layout-main-section { + height: calc(100vh - var(--page-head-height)); + display: flex; + flex-direction: column; + overflow: hidden; + + .page-form { + flex-shrink: 0; + } + + .frappe-list { + flex-grow: 1; + display: flex; + flex-direction: column; + overflow: hidden; + + .result, + .no-result { + flex-grow: 1; + overflow: auto; + } + + .comparison-message { + display: none; + } + } + } +} + @include media-breakpoint-up(sm) { .report-view { width: calc(100% - 220px); @@ -129,6 +160,36 @@ @include get_textstyle("base", "regular"); } +.report-view { + .datatable .dt-row-filter .dt-cell__content { + position: relative; + } + + .datatable .dt-row-filter .dt-filter.dt-input { + padding-inline-end: 1.5rem; + } + + .datatable .dt-row-filter .comparison-help-icon { + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + display: inline-flex; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; + + .icon { + stroke: currentColor; + } + } + + .datatable .dt-row-filter .dt-filter.dt-input:focus + .comparison-help-icon { + opacity: 1; + pointer-events: auto; + } +} + .list-count { margin-right: var(--margin-sm); @include get_textstyle("base", "regular"); diff --git a/frappe/templates/print_format/macros/AttachImage.html b/frappe/templates/print_format/macros/AttachImage.html index 796662f67a..53d173a2a9 100644 --- a/frappe/templates/print_format/macros/AttachImage.html +++ b/frappe/templates/print_format/macros/AttachImage.html @@ -2,6 +2,6 @@ {%- block value -%}
- {{ df.label }} + {{ _(df.label) }}
{%- endblock -%} diff --git a/frappe/templates/print_format/macros/Data.html b/frappe/templates/print_format/macros/Data.html index 722c42ce1a..d04593f330 100644 --- a/frappe/templates/print_format/macros/Data.html +++ b/frappe/templates/print_format/macros/Data.html @@ -1,7 +1,7 @@ {% if value %}
{%- block label -%} -
{{ df.label }}
+
{{ _(df.label) }}
{%- endblock -%} {%- block value -%}
{{ doc.get_formatted(df.fieldname) }}
diff --git a/frappe/templates/print_format/macros/Signature.html b/frappe/templates/print_format/macros/Signature.html index 128ff2a927..909e49f0ab 100644 --- a/frappe/templates/print_format/macros/Signature.html +++ b/frappe/templates/print_format/macros/Signature.html @@ -2,6 +2,6 @@ {%- block value -%}
- {{ df.label }} + {{ _(df.label) }}
{%- endblock -%} diff --git a/frappe/templates/print_format/macros/Table.html b/frappe/templates/print_format/macros/Table.html index 27c0be961c..a967c923d3 100644 --- a/frappe/templates/print_format/macros/Table.html +++ b/frappe/templates/print_format/macros/Table.html @@ -1,7 +1,7 @@ {% if doc.get(df.fieldname) %}
- {{ df.label }} + {{ _(df.label) }}
{% set columns = df.table_columns %} @@ -9,7 +9,7 @@ {% for column in columns %} {% endfor %} diff --git a/frappe/templates/print_format/print_format.html b/frappe/templates/print_format/print_format.html index b9fb95a9d3..2e494b4321 100644 --- a/frappe/templates/print_format/print_format.html +++ b/frappe/templates/print_format/print_format.html @@ -21,7 +21,7 @@ {% for section in layout.sections %}
{% if section.label %} - + {% endif %}
diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index e93349f4db..9dad6463e6 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -369,6 +369,12 @@ def prepare_header_footer(soup: BeautifulSoup): # {"header-html": "/tmp/frappe-pdf-random.html"} options[html_id] = fname + + if html_id == "header-html": + options["margin-top"] = "25mm" + elif html_id == "footer-html": + options["margin-bottom"] = "25mm" + else: if html_id == "header-html": options["margin-top"] = "15mm" diff --git a/frappe/website/doctype/website_settings/website_settings.js b/frappe/website/doctype/website_settings/website_settings.js index 07051588bd..a09101b062 100644 --- a/frappe/website/doctype/website_settings/website_settings.js +++ b/frappe/website/doctype/website_settings/website_settings.js @@ -16,6 +16,10 @@ frappe.ui.form.on("Website Settings", { frm.add_custom_button(__("View Website"), () => { window.open("/", "_blank"); }); + + // Check if templates have fields and show/hide edit button + frm.events.check_template_has_fields(frm, "navbar_template"); + frm.events.check_template_has_fields(frm, "footer_template"); }, set_banner_from_image: function (frm) { @@ -100,11 +104,36 @@ frappe.ui.form.on("Website Settings", { frappe.show_alert(__("Please select {0}", [frm.get_docfield(template_field).label])); return; } + let values = JSON.parse(frm.doc[values_field] || "{}"); open_web_template_values_editor(template, values).then((new_values) => { frm.set_value(values_field, JSON.stringify(new_values)); }); }, + + check_template_has_fields(frm, template_field) { + let template = frm.doc[template_field]; + let button_field = "edit_" + template_field + "_values"; + + if (!template || template === "Standard Navbar" || template === "Standard Footer") { + frm.toggle_display(button_field, false); + return; + } + + frappe.model.with_doc("Web Template", template, () => { + let doc = frappe.model.get_doc("Web Template", template); + let has_fields = doc.fields && doc.fields.length > 0; + frm.toggle_display(button_field, has_fields); + }); + }, + + navbar_template(frm) { + frm.events.check_template_has_fields(frm, "navbar_template"); + }, + + footer_template(frm) { + frm.events.check_template_has_fields(frm, "footer_template"); + }, }); frappe.ui.form.on("Top Bar Item", { diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.js b/frappe/website/doctype/website_slideshow/website_slideshow.js index 60a683eae8..7dea1370cb 100644 --- a/frappe/website/doctype/website_slideshow/website_slideshow.js +++ b/frappe/website/doctype/website_slideshow/website_slideshow.js @@ -31,7 +31,7 @@ frappe.ui.form.on("Website Slideshow", { ], primary_action_label: __("Add to table"), primary_action: ({ reference_doctype, reference_name }) => { - frappe.db + return frappe.db .get_list("File", { fields: ["file_url"], filters: { diff --git a/pyproject.toml b/pyproject.toml index 306aab4d3a..5d097cb7af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ # We depend on internal attributes, # do NOT add loose requirements on PyMySQL versions. "PyMySQL==1.1.2", - "pypdf==6.7.2", + "pypdf==6.7.3", "PyPika @ git+https://github.com/frappe/pypika@2c50e6142b2d61d2d243e466fdd5dc03b3d918f2", "mysqlclient==2.2.7", "PyQRCode~=1.2.1", diff --git a/yarn.lock b/yarn.lock index 689fa38fba..44040b1a72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2055,9 +2055,9 @@ mime@^1.4.1: integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== minimatch@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.3.tgz#6a5cba9b31f503887018f579c89f81f61162e624" - integrity sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA== + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7"
- {{ column.label }} + {{ _(column.label) }}