Merge branch 'frappe:develop' into chore/add-brazilian-portuguese-language
This commit is contained in:
commit
547278c6a1
95 changed files with 2537 additions and 1733 deletions
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -683,7 +683,10 @@ def validate_oauth(authorization_header):
|
|||
uri, http_method, body, headers, required_scopes
|
||||
)
|
||||
if valid:
|
||||
frappe.set_user(frappe.db.get_value("OAuth Bearer Token", token, "user"))
|
||||
user = frappe.db.get_value("OAuth Bearer Token", token, "user")
|
||||
if not frappe.db.get_value("User", user, "enabled"):
|
||||
frappe.throw(_("User {0} is disabled").format(user), frappe.AuthenticationError)
|
||||
frappe.set_user(user)
|
||||
frappe.local.form_dict = form_dict
|
||||
except AttributeError:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ def run_tests(
|
|||
@click.option("--use-orchestrator", is_flag=True, help="Use orchestrator to run parallel tests")
|
||||
@click.option("--dry-run", is_flag=True, default=False, help="Dont actually run tests")
|
||||
@click.option("--lightmode", is_flag=True, default=False, help="Skips all before test setup")
|
||||
@click.option("--failfast", is_flag=True, default=False, help="Exit on first failure occurred")
|
||||
@pass_context
|
||||
def run_parallel_tests(
|
||||
context: CliCtxObj,
|
||||
|
|
@ -383,6 +384,7 @@ def run_parallel_tests(
|
|||
use_orchestrator=False,
|
||||
dry_run=False,
|
||||
lightmode=False,
|
||||
failfast=False,
|
||||
):
|
||||
from traceback_with_variables import activate_by_import
|
||||
|
||||
|
|
@ -404,6 +406,7 @@ def run_parallel_tests(
|
|||
total_builds=total_builds,
|
||||
dry_run=dry_run,
|
||||
lightmode=lightmode,
|
||||
failfast=failfast,
|
||||
)
|
||||
mode = "Orchestrator" if use_orchestrator else "Parallel"
|
||||
banner = f"""
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
"settings_dropdown",
|
||||
"help_dropdown",
|
||||
"announcements_section",
|
||||
"announcement_widget"
|
||||
"announcement_widget",
|
||||
"announcement_widget_color",
|
||||
"dismissible_announcement_widget"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -47,16 +49,27 @@
|
|||
"label": "Announcements"
|
||||
},
|
||||
{
|
||||
"description": "These announcements will appear inside a dismissible alert below the Navbar.",
|
||||
"description": "These announcements will appear inside an alert below the Navbar.",
|
||||
"fieldname": "announcement_widget",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Announcement Widget",
|
||||
"max_height": "10em"
|
||||
},
|
||||
{
|
||||
"fieldname": "announcement_widget_color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Widget Color"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "dismissible_announcement_widget",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Dismissible"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-01 14:09:54.587137",
|
||||
"modified": "2026-02-05 15:19:55.524034",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Navbar Settings",
|
||||
|
|
@ -74,8 +87,9 @@
|
|||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ class NavbarSettings(Document):
|
|||
from frappe.types import DF
|
||||
|
||||
announcement_widget: DF.TextEditor | None
|
||||
announcement_widget_color: DF.Color | None
|
||||
app_logo: DF.AttachImage | None
|
||||
dismissible_announcement_widget: DF.Check
|
||||
help_dropdown: DF.Table[NavbarItem]
|
||||
settings_dropdown: DF.Table[NavbarItem]
|
||||
# end: auto-generated types
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ frappe.ui.form.on("Recorder", {
|
|||
});
|
||||
|
||||
let index_grid = frm.fields_dict.suggested_indexes.grid;
|
||||
index_grid.wrapper.find(".grid-footer").toggle(true);
|
||||
index_grid.wrapper.find(".grid-footer").toggleClass("hidden", false);
|
||||
index_grid.toggle_checkboxes(true);
|
||||
index_grid.df.cannot_delete_rows = true;
|
||||
index_grid.add_custom_button(__("Add Indexes"), function () {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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": """
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
|
@ -202,7 +197,6 @@ frappe.ui.form.on("Number Card", {
|
|||
render_filters_table: function (frm) {
|
||||
frm.set_df_property("filters_section", "hidden", 0);
|
||||
let is_document_type = frm.doc.type == "Document Type";
|
||||
let is_dynamic_filter = (f) => ["Date", "DateRange"].includes(f.fieldtype) && f.default;
|
||||
|
||||
let wrapper = $(frm.get_field("filters_json").wrapper).empty();
|
||||
let table = $(`<table class="table table-bordered" style="cursor:${
|
||||
|
|
@ -219,24 +213,14 @@ frappe.ui.form.on("Number Card", {
|
|||
</table>`).appendTo(wrapper);
|
||||
|
||||
if (frm.has_perm("write")) {
|
||||
$(`<p class="text-muted small">${__("Click table to edit")}</p>`).appendTo(wrapper);
|
||||
$(`<p class="text-muted small mt-2">${__("Click table to edit")}</p>`).appendTo(
|
||||
wrapper
|
||||
);
|
||||
}
|
||||
|
||||
let filters = JSON.parse(frm.doc.filters_json || "[]");
|
||||
let filters_set = false;
|
||||
|
||||
// Set dynamic filters for reports
|
||||
if (frm.doc.type == "Report") {
|
||||
let set_filters = false;
|
||||
frm.filters.forEach((f) => {
|
||||
if (is_dynamic_filter(f)) {
|
||||
filters[f.fieldname] = f.default;
|
||||
set_filters = true;
|
||||
}
|
||||
});
|
||||
set_filters && frm.set_value("filters_json", JSON.stringify(filters));
|
||||
}
|
||||
|
||||
let fields = [];
|
||||
if (is_document_type) {
|
||||
fields = [
|
||||
|
|
@ -290,7 +274,7 @@ frappe.ui.form.on("Number Card", {
|
|||
}
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Set Filters"),
|
||||
fields: fields.filter((f) => !is_dynamic_filter(f)),
|
||||
fields,
|
||||
primary_action: function () {
|
||||
let values = this.get_values();
|
||||
if (values) {
|
||||
|
|
@ -304,7 +288,7 @@ frappe.ui.form.on("Number Card", {
|
|||
frm.trigger("render_filters_table");
|
||||
}
|
||||
},
|
||||
primary_action_label: __("Set"),
|
||||
primary_action_label: __("Update"),
|
||||
});
|
||||
|
||||
if (is_document_type) {
|
||||
|
|
@ -340,8 +324,6 @@ frappe.ui.form.on("Number Card", {
|
|||
|
||||
frm.set_df_property("dynamic_filters_section", "hidden", 0);
|
||||
|
||||
let is_document_type = frm.doc.type == "Document Type";
|
||||
|
||||
let wrapper = $(frm.get_field("dynamic_filters_json").wrapper).empty();
|
||||
|
||||
frm.dynamic_filter_table = $(`<table class="table table-bordered" style="cursor:${
|
||||
|
|
@ -349,29 +331,21 @@ frappe.ui.form.on("Number Card", {
|
|||
}; margin:0px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">${__("Filter")}</th>
|
||||
<th style="width: 20%">${__("Condition")}</th>
|
||||
<th>${__("Value")}</th>
|
||||
<th style="width: 30%">${__("Filter")}</th>
|
||||
<th>${__("Expression")}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>`).appendTo(wrapper);
|
||||
|
||||
frm.dynamic_filters =
|
||||
frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
|
||||
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||
: null;
|
||||
if (frm.has_perm("write")) {
|
||||
$(`<p class="text-muted small mt-2">${__("Click table to edit")}</p>`).appendTo(
|
||||
wrapper
|
||||
);
|
||||
}
|
||||
|
||||
frm.trigger("set_dynamic_filters_in_table");
|
||||
|
||||
let filters = JSON.parse(frm.doc.filters_json || "[]");
|
||||
|
||||
let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog(
|
||||
is_document_type,
|
||||
filters,
|
||||
frm.dynamic_filters
|
||||
);
|
||||
|
||||
frm.dynamic_filter_table.on("click", () => {
|
||||
if (!frm.has_perm("write")) {
|
||||
return;
|
||||
|
|
@ -380,65 +354,215 @@ frappe.ui.form.on("Number Card", {
|
|||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frappe.throw(__("Cannot edit filters for standard number cards"));
|
||||
}
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Set Dynamic Filters"),
|
||||
fields: fields,
|
||||
primary_action: () => {
|
||||
let values = dialog.get_values();
|
||||
dialog.hide();
|
||||
let dynamic_filters = [];
|
||||
for (let key of Object.keys(values)) {
|
||||
if (is_document_type) {
|
||||
let [doctype, fieldname] = key.split(":");
|
||||
dynamic_filters.push([doctype, fieldname, "=", values[key]]);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_document_type) {
|
||||
frm.set_value("dynamic_filters_json", JSON.stringify(dynamic_filters));
|
||||
} else {
|
||||
frm.set_value("dynamic_filters_json", JSON.stringify(values));
|
||||
}
|
||||
frm.trigger("set_dynamic_filters_in_table");
|
||||
},
|
||||
primary_action_label: __("Set"),
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
dialog.set_values(frm.dynamic_filters);
|
||||
frm.trigger("show_dynamic_filter_dialog");
|
||||
});
|
||||
},
|
||||
|
||||
set_dynamic_filters_in_table: function (frm) {
|
||||
frm.dynamic_filters =
|
||||
frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
|
||||
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||
: null;
|
||||
show_dynamic_filter_dialog: function (frm) {
|
||||
if (frm.doc.type === "Document Type") {
|
||||
if (!frm.doc.document_type) {
|
||||
frappe.msgprint(__("Please select a Document Type first"));
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doctype(frm.doc.document_type, () => {
|
||||
frm.trigger("show_doctype_dynamic_filter_dialog");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frm.dynamic_filters) {
|
||||
const filter_row = $(`<tr><td colspan="3" class="text-muted text-center">
|
||||
if (!frm.doc.report_name) {
|
||||
frappe.msgprint(__("Please select a Report first"));
|
||||
return;
|
||||
}
|
||||
if (!frm.filters?.length) {
|
||||
frappe.msgprint(__("No filters available for this report"));
|
||||
return;
|
||||
}
|
||||
frm.trigger("show_report_dynamic_filter_dialog");
|
||||
},
|
||||
|
||||
show_doctype_dynamic_filter_dialog: function (frm) {
|
||||
const meta = frappe.get_meta(frm.doc.document_type);
|
||||
const field_options = meta.fields
|
||||
.filter((df) => df.fieldname && !frappe.model.no_value_type.includes(df.fieldtype))
|
||||
.map((df) => ({ label: df.label || df.fieldname, value: df.fieldname }));
|
||||
|
||||
frappe.model.std_fields.forEach((df) => {
|
||||
field_options.push({ label: df.label, value: df.fieldname });
|
||||
});
|
||||
|
||||
frm.events.show_dynamic_filter_dialog_common(frm, field_options, frm.doc.document_type);
|
||||
},
|
||||
|
||||
show_report_dynamic_filter_dialog: function (frm) {
|
||||
const field_options = frm.filters
|
||||
.filter((f) => f.fieldname)
|
||||
.map((f) => ({ label: f.label || f.fieldname, value: f.fieldname }));
|
||||
|
||||
frm.events.show_dynamic_filter_dialog_common(frm, field_options, frm.doc.report_name);
|
||||
},
|
||||
|
||||
show_dynamic_filter_dialog_common: function (frm, field_options, doctype_or_report) {
|
||||
let dynamic_filters =
|
||||
frm.doc.dynamic_filters_json?.length > 2
|
||||
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||
: [];
|
||||
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Set Dynamic Filters"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "help_text",
|
||||
options: frm.events.get_dynamic_filter_help_text(),
|
||||
},
|
||||
{ fieldtype: "HTML", fieldname: "filter_area" },
|
||||
],
|
||||
size: "large",
|
||||
primary_action: () => {
|
||||
const filters = [];
|
||||
dialog.$wrapper.find(".dynamic-filter-row").each(function () {
|
||||
const $row = $(this);
|
||||
const fieldname = $row.data("selected_fieldname");
|
||||
const expression = $row.find(".filter-expression").val();
|
||||
if (fieldname && expression) {
|
||||
filters.push([doctype_or_report, fieldname, "=", expression]);
|
||||
}
|
||||
});
|
||||
dialog.hide();
|
||||
frm.set_value("dynamic_filters_json", JSON.stringify(filters));
|
||||
frm.trigger("set_dynamic_filters_in_table");
|
||||
},
|
||||
primary_action_label: __("Update"),
|
||||
});
|
||||
|
||||
const add_filter_row = frm.events.build_dynamic_filter_interface(
|
||||
dialog.fields_dict.filter_area.$wrapper,
|
||||
field_options,
|
||||
doctype_or_report
|
||||
);
|
||||
|
||||
if (dynamic_filters?.length) {
|
||||
dynamic_filters.forEach((filter) => {
|
||||
add_filter_row(filter[1], filter[3]);
|
||||
});
|
||||
} else {
|
||||
add_filter_row();
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
},
|
||||
|
||||
get_dynamic_filter_help_text: function () {
|
||||
return `<p class="text-muted small">
|
||||
${__("Enter expressions that will be evaluated when the card is displayed. For example:")}<br>
|
||||
<code>frappe.defaults.get_user_default("Company")</code><br>
|
||||
<code>frappe.datetime.get_today()</code><br>
|
||||
</p>`;
|
||||
},
|
||||
|
||||
build_dynamic_filter_interface: function ($filter_area, field_options, doctype_or_report) {
|
||||
const filter_html = `
|
||||
<div>
|
||||
<table class="table table-bordered" style="margin-bottom: 12px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 35%">${__("Field")}</th>
|
||||
<th style="width: 60%">${__("Expression")}</th>
|
||||
<th style="width: 5%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="filter-rows"></tbody>
|
||||
</table>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<button class="text-muted add-filter btn btn-xs">
|
||||
+ ${__("Add Filter")}
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-xs clear-filters">
|
||||
${__("Clear Filters")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$filter_area.html(filter_html);
|
||||
|
||||
const filter_fields = field_options.map((opt) => ({
|
||||
fieldname: opt.value,
|
||||
label: opt.label,
|
||||
parent: doctype_or_report,
|
||||
}));
|
||||
|
||||
const add_filter_row = (fieldname = "", expression = "") => {
|
||||
const row_html = `
|
||||
<tr class="dynamic-filter-row">
|
||||
<td class="fieldname-select-area"></td>
|
||||
<td>
|
||||
<input type="text" class="form-control input-xs filter-expression">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a class="remove-filter text-muted" style="cursor: pointer;">
|
||||
<svg class="icon icon-sm">
|
||||
<use href="#icon-close" class="close"></use>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
const $row = $(row_html);
|
||||
|
||||
const field_select = new frappe.ui.FieldSelect({
|
||||
parent: $row.find(".fieldname-select-area"),
|
||||
doctype: doctype_or_report,
|
||||
filter_fields: filter_fields,
|
||||
input_class: "input-xs",
|
||||
select: (_, selected_fieldname) => {
|
||||
$row.data("selected_fieldname", selected_fieldname);
|
||||
},
|
||||
});
|
||||
|
||||
if (fieldname) {
|
||||
field_select.set_value(doctype_or_report, fieldname);
|
||||
$row.data("selected_fieldname", fieldname);
|
||||
}
|
||||
|
||||
$row.find(".filter-expression").val(expression);
|
||||
$row.data("field_select", field_select);
|
||||
$filter_area.find(".filter-rows").append($row);
|
||||
};
|
||||
|
||||
$filter_area.on("click", ".add-filter", () => add_filter_row());
|
||||
$filter_area.on("click", ".remove-filter", function () {
|
||||
$(this).closest("tr").remove();
|
||||
});
|
||||
$filter_area.on("click", ".clear-filters", () => {
|
||||
$filter_area.find(".filter-rows").empty();
|
||||
add_filter_row();
|
||||
});
|
||||
|
||||
return add_filter_row;
|
||||
},
|
||||
|
||||
set_dynamic_filters_in_table: function (frm) {
|
||||
let dynamic_filters =
|
||||
frm.doc.dynamic_filters_json?.length > 2
|
||||
? JSON.parse(frm.doc.dynamic_filters_json)
|
||||
: [];
|
||||
|
||||
if (!dynamic_filters?.length) {
|
||||
const filter_row = $(`<tr><td colspan="2" class="text-muted text-center">
|
||||
${__("Click to Set Dynamic Filters")}</td></tr>`);
|
||||
frm.dynamic_filter_table.find("tbody").html(filter_row);
|
||||
} else {
|
||||
let filter_rows = "";
|
||||
if ($.isArray(frm.dynamic_filters)) {
|
||||
frm.dynamic_filters.forEach((filter) => {
|
||||
filter_rows += `<tr>
|
||||
<td>${filter[1]}</td>
|
||||
<td>${filter[2] || ""}</td>
|
||||
<td>${filter[3]}</td>
|
||||
</tr>`;
|
||||
});
|
||||
} else {
|
||||
let condition = "=";
|
||||
for (let [key, val] of Object.entries(frm.dynamic_filters)) {
|
||||
filter_rows += `<tr>
|
||||
<td>${key}</td>
|
||||
<td>${condition}</td>
|
||||
<td>${val || ""}</td>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
dynamic_filters.forEach((filter) => {
|
||||
filter_rows += `<tr>
|
||||
<td>${filter[1]}</td>
|
||||
<td><code>${filter[3] || ""}</code></td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
frm.dynamic_filter_table.find("tbody").html(filter_rows);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:label",
|
||||
"creation": "2020-04-15 18:06:39.444683",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
|
|
@ -72,7 +73,8 @@
|
|||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "color",
|
||||
|
|
@ -229,10 +231,11 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2025-09-17 21:00:11.351605",
|
||||
"modified": "2026-02-25 16:33:09.032056",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Number Card",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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-25 23:15\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Bosnian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -2870,7 +2870,7 @@ msgstr "Dodjela je Završena"
|
|||
#. Label of the assignment_days (Table) field in DocType 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Days"
|
||||
msgstr "Dani Dodjeljivanja"
|
||||
msgstr "Dani Dodjele"
|
||||
|
||||
#. Name of a DocType
|
||||
#. Label of the assignment_rule (Link) field in DocType 'ToDo'
|
||||
|
|
@ -2888,7 +2888,7 @@ msgstr "Dan Dodjele Pravila"
|
|||
#. Name of a DocType
|
||||
#: frappe/automation/doctype/assignment_rule_user/assignment_rule_user.json
|
||||
msgid "Assignment Rule User"
|
||||
msgstr "Korisnik Dodjele Pravila"
|
||||
msgstr "Korisnik Pravila Dodjele"
|
||||
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.py:55
|
||||
msgid "Assignment Rule is not allowed on document type {0}"
|
||||
|
|
|
|||
|
|
@ -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-25 23:14\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Persian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -6314,7 +6314,7 @@ msgstr "سفارشیسازی"
|
|||
|
||||
#: frappe/custom/doctype/customize_form/customize_form.js:89
|
||||
msgid "Customize Child Table"
|
||||
msgstr "سفارشی کردن جدول فرزند"
|
||||
msgstr "سفارشیسازی جدول فرزند"
|
||||
|
||||
#: frappe/public/js/frappe/views/dashboard/dashboard_view.js:38
|
||||
msgid "Customize Dashboard"
|
||||
|
|
@ -6339,7 +6339,7 @@ msgstr "سفارشیسازی فرم - {0}"
|
|||
#. Name of a DocType
|
||||
#: frappe/custom/doctype/customize_form_field/customize_form_field.json
|
||||
msgid "Customize Form Field"
|
||||
msgstr "سفارشی کردن فیلد فرم"
|
||||
msgstr "سفارشیسازی فیلد فرم"
|
||||
|
||||
#: frappe/public/js/frappe/list/list_view.js:1994
|
||||
msgctxt "Customize qucik filters of List View"
|
||||
|
|
@ -18808,7 +18808,7 @@ msgstr ""
|
|||
|
||||
#: frappe/core/doctype/doctype/doctype.py:1699
|
||||
msgid "Options for Rating field can range from 3 to 10"
|
||||
msgstr "گزینههای فیلد رتبه بندی میتواند از 3 تا 10 باشد"
|
||||
msgstr "گزینههای فیلد رتبهبندی میتواند از 3 تا 10 باشد"
|
||||
|
||||
#: frappe/custom/doctype/custom_field/custom_field.js:96
|
||||
msgid "Options for select. Each option on a new line."
|
||||
|
|
|
|||
|
|
@ -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-25 23:14\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Croatian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -2870,7 +2870,7 @@ msgstr "Dodjela je Završena"
|
|||
#. Label of the assignment_days (Table) field in DocType 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Days"
|
||||
msgstr "Dani Dodjeljivanja"
|
||||
msgstr "Dani Dodjele"
|
||||
|
||||
#. Name of a DocType
|
||||
#. Label of the assignment_rule (Link) field in DocType 'ToDo'
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -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 <b>BETA</b>."
|
||||
|
|
@ -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:<br><ul>{0}</ul>Please verify the folder names exactly as they appear on the server (folder names are case-sensitive)."
|
||||
msgstr ""
|
||||
msgstr "Следећи конфигурисани IMAP директоријуми нису пронађени на серверу:<br><ul>{0}</ul>Молимо Вас да проверите називе директоријума тачно онако како су приказани на серверу (велика и мала слова су битна за називе датотека)."
|
||||
|
||||
#: 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"
|
||||
|
|
|
|||
|
|
@ -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 <b>BETA</b>."
|
||||
|
|
@ -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:<br><ul>{0}</ul>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:<br><ul>{0}</ul>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"
|
||||
|
|
|
|||
|
|
@ -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-28 23:51\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: Swedish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -2868,7 +2868,7 @@ msgstr "Tilldelning Klar"
|
|||
#. Label of the assignment_days (Table) field in DocType 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Days"
|
||||
msgstr "Automation Dagar"
|
||||
msgstr "Tilldelning Dagar"
|
||||
|
||||
#. Name of a DocType
|
||||
#. Label of the assignment_rule (Link) field in DocType 'ToDo'
|
||||
|
|
@ -2876,7 +2876,7 @@ msgstr "Automation Dagar"
|
|||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
#: frappe/desk/doctype/todo/todo.json frappe/workspace_sidebar/automation.json
|
||||
msgid "Assignment Rule"
|
||||
msgstr "Automation Regel"
|
||||
msgstr "Tilldelning Regel"
|
||||
|
||||
#. Name of a DocType
|
||||
#: frappe/automation/doctype/assignment_rule_day/assignment_rule_day.json
|
||||
|
|
@ -2890,13 +2890,13 @@ msgstr "Automation Regel Användare"
|
|||
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.py:55
|
||||
msgid "Assignment Rule is not allowed on document type {0}"
|
||||
msgstr "Automation Regel är ej tillåten på dokument typ {0}"
|
||||
msgstr "Tilldelning Regel är ej tillåten på dokument typ {0}"
|
||||
|
||||
#. Label of the assignment_rules_section (Section Break) field in DocType
|
||||
#. 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Assignment Rules"
|
||||
msgstr "Automation Regler"
|
||||
msgstr "Tilldelning Regler"
|
||||
|
||||
#: frappe/desk/doctype/notification_log/notification_log.py:153
|
||||
msgid "Assignment Update on {0}"
|
||||
|
|
@ -8565,7 +8565,7 @@ msgstr "Förfallo Datum"
|
|||
#. Label of the due_date_based_on (Select) field in DocType 'Assignment Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Due Date Based On"
|
||||
msgstr "Förfallo Datum Baserad På"
|
||||
msgstr "Förfallodatum baserat på"
|
||||
|
||||
#: frappe/public/js/frappe/form/grid_row_form.js:44
|
||||
#: frappe/public/js/frappe/form/toolbar.js:445
|
||||
|
|
@ -9859,7 +9859,7 @@ msgstr "Exempel: Anges detta till 24:00 loggas användare ut om de inte är akti
|
|||
#. Rule'
|
||||
#: frappe/automation/doctype/assignment_rule/assignment_rule.json
|
||||
msgid "Example: {{ subject }}"
|
||||
msgstr "Exempel: {{subject}}"
|
||||
msgstr "Exempel: {{ subject }}"
|
||||
|
||||
#. Option for the 'File Type' (Select) field in DocType 'Data Export'
|
||||
#: frappe/core/doctype/data_export/data_export.json
|
||||
|
|
@ -11577,7 +11577,7 @@ msgstr "Könsartad"
|
|||
|
||||
#: frappe/www/contact.html:29
|
||||
msgid "General"
|
||||
msgstr "Allmän"
|
||||
msgstr "Allmänt"
|
||||
|
||||
#. Label of the generate_keys (Button) field in DocType 'User'
|
||||
#: frappe/core/doctype/user/user.json
|
||||
|
|
@ -14260,7 +14260,7 @@ msgstr "Är Privat"
|
|||
#: frappe/desk/doctype/dashboard_chart/dashboard_chart.json
|
||||
#: frappe/desk/doctype/number_card/number_card.json
|
||||
msgid "Is Public"
|
||||
msgstr "Är Allmän"
|
||||
msgstr "Är Publik"
|
||||
|
||||
#. Label of the is_published_field (Data) field in DocType 'DocType'
|
||||
#: frappe/core/doctype/doctype/doctype.json
|
||||
|
|
@ -15978,7 +15978,7 @@ msgstr "Skapa {0}"
|
|||
|
||||
#: frappe/website/doctype/web_page/web_page.js:77
|
||||
msgid "Makes the page public"
|
||||
msgstr "Allmän Sida"
|
||||
msgstr "Publik Sida"
|
||||
|
||||
#: frappe/desk/page/setup_wizard/install_fixtures.py:28
|
||||
msgid "Male"
|
||||
|
|
@ -21025,17 +21025,17 @@ msgstr "Leverantör Namn"
|
|||
#: frappe/public/js/frappe/views/interaction.js:78
|
||||
#: frappe/public/js/frappe/views/workspace/workspace.js:458
|
||||
msgid "Public"
|
||||
msgstr "Allmän"
|
||||
msgstr "Publik"
|
||||
|
||||
#. Label of the public_files_size (Float) field in DocType 'System Health
|
||||
#. Report'
|
||||
#: frappe/desk/doctype/system_health_report/system_health_report.json
|
||||
msgid "Public Files (MB)"
|
||||
msgstr "Allmänna Filer (MB)"
|
||||
msgstr "Publika Filer (MB)"
|
||||
|
||||
#: frappe/templates/emails/file_backup_notification.html:5
|
||||
msgid "Public Files Backup:"
|
||||
msgstr "Allmänna Filer Säkerhetskopiering:"
|
||||
msgstr "Publika Filer Säkerhetskopiering:"
|
||||
|
||||
#. Label of the publish (Check) field in DocType 'Package Release'
|
||||
#: frappe/core/doctype/package_release/package_release.json
|
||||
|
|
@ -27569,7 +27569,7 @@ msgstr "Den här filen är offentlig och kan nås av vem som helst, även utan a
|
|||
|
||||
#: frappe/core/doctype/file/file.js:22
|
||||
msgid "This file is public. It can be accessed without authentication."
|
||||
msgstr "Denna fil är allmän fil. Den kan nås utan autentisering."
|
||||
msgstr "Denna fil är publik. Den kan nås utan autentisering."
|
||||
|
||||
#: frappe/public/js/frappe/form/form.js:1248
|
||||
msgid "This form has been modified after you have loaded it"
|
||||
|
|
@ -29789,7 +29789,7 @@ msgstr "Värde måste vara ett av {0}"
|
|||
#. 'OAuth Client'
|
||||
#: frappe/integrations/doctype/oauth_client/oauth_client.json
|
||||
msgid "Value of \"None\" implies a public client. In such a case Client Secret is not given to the client and token exchange makes use of PKCE."
|
||||
msgstr "Värdet \"None\" innebär allmän klient. I ett sådant fall ges inte Klient Hemlighet till klient och token utbyte använder PKCE."
|
||||
msgstr "Värdet \"None\" innebär publik klient. I ett sådant fall ges inte Klient Hemlighet till klient och token utbyte använder PKCE."
|
||||
|
||||
#. Label of the value_to_validate (Data) field in DocType 'Onboarding Step'
|
||||
#: frappe/desk/doctype/onboarding_step/onboarding_step.json
|
||||
|
|
@ -31299,7 +31299,7 @@ msgstr "Du behöver '{0}' behörighet på {1} {2} för att utföra denna åtgär
|
|||
#: frappe/desk/doctype/workspace/workspace.py:132
|
||||
#: frappe/desk/doctype/workspace_sidebar/workspace_sidebar.py:74
|
||||
msgid "You need to be Workspace Manager to delete a public workspace."
|
||||
msgstr "Du måste vara Arbetsyta Ansvarig för att ta bort allmän arbetsyta."
|
||||
msgstr "Du måste vara Arbetsyta Ansvarig för att ta bort publik arbetsyta."
|
||||
|
||||
#: frappe/desk/doctype/workspace/workspace.py:78
|
||||
msgid "You need to be Workspace Manager to edit this document"
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ from typing import TYPE_CHECKING
|
|||
|
||||
import frappe
|
||||
import frappe.permissions
|
||||
from frappe import _, bold
|
||||
from frappe import _, bold, scrub
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.model.naming import validate_name
|
||||
from frappe.model.naming import is_autoincremented, validate_name
|
||||
from frappe.model.utils.user_settings import sync_user_settings, update_user_settings_data
|
||||
from frappe.query_builder import Field
|
||||
from frappe.utils.data import cint, cstr, sbool
|
||||
|
|
@ -412,6 +412,11 @@ def rename_doctype(doctype: str, old: str, new: str) -> None:
|
|||
# change parenttype for fieldtype Table
|
||||
update_parenttype_values(old, new)
|
||||
|
||||
# if autoincrement is enabled, update sequence name
|
||||
meta = frappe.get_meta(new)
|
||||
if is_autoincremented(new, meta):
|
||||
update_sequence_name(old, new)
|
||||
|
||||
|
||||
def update_child_docs(old: str, new: str, meta: "Meta") -> None:
|
||||
# update "parent"
|
||||
|
|
@ -656,6 +661,15 @@ def update_parenttype_values(old: str, new: str):
|
|||
frappe.qb.update(table).set(table.parenttype, new).where(table.parenttype == old).run()
|
||||
|
||||
|
||||
def update_sequence_name(old: str, new: str, slug: str = "_id_seq"):
|
||||
old_sequence_name = scrub(old + slug)
|
||||
new_sequence_name = scrub(new + slug)
|
||||
if frappe.db.db_type == "mariadb":
|
||||
frappe.db.sql_ddl(f"RENAME TABLE `{old_sequence_name}` TO `{new_sequence_name}`")
|
||||
else:
|
||||
frappe.db.sql_ddl(f'ALTER SEQUENCE "{old_sequence_name}" RENAME TO "{new_sequence_name}"')
|
||||
|
||||
|
||||
def rename_dynamic_links(doctype: str, old: str, new: str):
|
||||
Singles = frappe.qb.DocType("Singles")
|
||||
for df in get_dynamic_link_map().get(doctype, []):
|
||||
|
|
|
|||
|
|
@ -29,13 +29,16 @@ TEST_WEIGHT_OVERRIDES = {
|
|||
|
||||
|
||||
class ParallelTestRunner:
|
||||
def __init__(self, app, site, build_number=1, total_builds=1, dry_run=False, lightmode=False):
|
||||
def __init__(
|
||||
self, app, site, build_number=1, total_builds=1, dry_run=False, lightmode=False, failfast=False
|
||||
):
|
||||
self.app = app
|
||||
self.site = site
|
||||
self.build_number = frappe.utils.cint(build_number) or 1
|
||||
self.total_builds = frappe.utils.cint(total_builds)
|
||||
self.dry_run = dry_run
|
||||
self.lightmode = lightmode
|
||||
self.failfast = failfast
|
||||
self.test_file_list = []
|
||||
self.total_test_weight = 0
|
||||
self.test_result = None
|
||||
|
|
@ -81,7 +84,9 @@ class ParallelTestRunner:
|
|||
self.total_test_weight = sum(self.get_test_weight(test) for test in self.test_file_list)
|
||||
|
||||
def run_tests(self):
|
||||
self.test_result = TestResult(stream=sys.stderr, descriptions=True, verbosity=2)
|
||||
self.test_result = TestResult(
|
||||
stream=sys.stderr, descriptions=True, verbosity=2, failfast=self.failfast
|
||||
)
|
||||
|
||||
for test_file_info in self.test_file_list:
|
||||
self.run_tests_for_file(test_file_info)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
@ -89,19 +96,40 @@ function openFrappeCloudDashboard() {
|
|||
}
|
||||
|
||||
function addChatBubble() {
|
||||
if (checkBusinessHours()) {
|
||||
const all_apps = frappe.utils.get_installed_apps();
|
||||
const desk_apps = ["erpnext", "hrms"];
|
||||
|
||||
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);
|
||||
const root = document.documentElement;
|
||||
root.style.setProperty("--s-700", "var(--gray-50)");
|
||||
root.style.setProperty("--s-700", "var(--gray-500)");
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(`<i class="fa fa-spinner fa-spin"></i>`);
|
||||
$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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import "./base_input";
|
|||
import "./data";
|
||||
import "./int";
|
||||
import "./float";
|
||||
import "./percent";
|
||||
import "./currency";
|
||||
import "./date";
|
||||
import "./time";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,5 +32,3 @@ frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlI
|
|||
return this.df.precision || cint(frappe.boot.sysdefaults.float_precision, null);
|
||||
}
|
||||
};
|
||||
|
||||
frappe.ui.form.ControlPercent = frappe.ui.form.ControlFloat;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
13
frappe/public/js/frappe/form/controls/percent.js
Normal file
13
frappe/public/js/frappe/form/controls/percent.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
frappe.ui.form.ControlPercent = class ControlPercent extends frappe.ui.form.ControlFloat {
|
||||
format_for_input(value) {
|
||||
if (value === null || value === undefined || isNaN(Number(value))) {
|
||||
return "";
|
||||
}
|
||||
const precision = value.toString().split(".")[1]?.length || 0;
|
||||
return format_number(
|
||||
value,
|
||||
this.get_number_format(),
|
||||
Math.min(this.get_precision(), precision)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -28,7 +28,7 @@ frappe.ui.form.ControlSelect = class ControlSelect extends frappe.ui.form.Contro
|
|||
const placeholder_html = `<div class="placeholder ellipsis text-extra-muted ${
|
||||
is_xs_input ? "xs" : ""
|
||||
}">
|
||||
<span>${this.df.placeholder}</span>
|
||||
<span>${__(this.df.placeholder)}</span>
|
||||
</div>`;
|
||||
if (this.only_input) {
|
||||
this.$wrapper.append(placeholder_html);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -94,12 +94,15 @@ frappe.form.formatters = {
|
|||
if (value === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const valuePrecision = value.toString().split(".")[1]?.length || 0;
|
||||
const precision =
|
||||
docfield.precision ||
|
||||
cint(frappe.boot.sysdefaults && frappe.boot.sysdefaults.float_precision) ||
|
||||
2;
|
||||
return frappe.form.formatters._right(format_number(value, null, precision) + "%", options);
|
||||
return frappe.form.formatters._right(
|
||||
format_number(value, null, Math.min(precision, valuePrecision)) + "%",
|
||||
options
|
||||
);
|
||||
},
|
||||
Rating: function (value, docfield) {
|
||||
let rating_html = "";
|
||||
|
|
|
|||
|
|
@ -671,9 +671,11 @@ export default class Grid {
|
|||
this.wrapper.find(".grid-footer").addClass("hidden");
|
||||
}
|
||||
|
||||
// don't be tempted to use the `.hidden` class here
|
||||
// it is used in other logic for the same buttons and will cause conflicts
|
||||
this.wrapper
|
||||
.find(".grid-add-row, .grid-add-multiple-rows, .grid-upload")
|
||||
.toggleClass("hidden", !is_editable);
|
||||
.toggleClass("d-none", !is_editable);
|
||||
}
|
||||
|
||||
setup_fields() {
|
||||
|
|
|
|||
|
|
@ -1033,7 +1033,7 @@ export default class GridRow {
|
|||
let is_focused = false;
|
||||
|
||||
var $col = $(
|
||||
`<div class="col grid-static-col col-xs-${colsize} ${add_class}" style="${add_style}"></div>`
|
||||
`<div class="col grid-static-col flex col-xs-${colsize} ${add_class}" style="${add_style}"></div>`
|
||||
)
|
||||
.attr("data-fieldname", df.fieldname)
|
||||
.attr("data-fieldtype", df.fieldtype)
|
||||
|
|
@ -1095,7 +1095,9 @@ export default class GridRow {
|
|||
return out;
|
||||
});
|
||||
|
||||
$col.field_area = $('<div class="field-area"></div>').appendTo($col).toggle(false);
|
||||
$col.field_area = $('<div class="field-area flex flex-grow-1"></div>')
|
||||
.appendTo($col)
|
||||
.toggle(false);
|
||||
$col.static_area = $('<div class="static-area ellipsis"></div>').appendTo($col).html(txt);
|
||||
|
||||
// set title attribute to see full label for columns in the heading row
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 `
|
||||
<div class="list-item-table margin-bottom">
|
||||
${this.make_doc_head(doctype)}
|
||||
${docs.map((doc) => this.make_doc_row(doc, doctype)).join("")}
|
||||
html = `
|
||||
<div class="margin-bottom">
|
||||
${__("Following documents are linked with {0}", [
|
||||
frappe.utils
|
||||
.get_form_link(this.frm.doctype, this.frm.docname, true)
|
||||
.bold(),
|
||||
])}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.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 `
|
||||
<div class="list-item-table margin-bottom">
|
||||
${this.make_doc_head(doctype)}
|
||||
${rows}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("")}
|
||||
`;
|
||||
}
|
||||
|
||||
$(this.dialog.body).html(html);
|
||||
|
|
@ -68,6 +87,16 @@ frappe.ui.form.LinkedWith = class LinkedWith {
|
|||
`;
|
||||
}
|
||||
|
||||
make_hidden_count_row(count) {
|
||||
return `<div class="list-row-container">
|
||||
<div class="level list-row small text-muted">
|
||||
<div class="level-left">
|
||||
${count == 1 ? __("{0} restricted document", [count]) : __("{0} restricted documents", [count])}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
make_doc_row(doc, doctype) {
|
||||
return `<div class="list-row-container">
|
||||
<div class="level list-row small">
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" style="width: 13px; height: 13px; animation: spin 1s linear infinite;"><circle opacity=".25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path opacity=".25" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/></svg>`;
|
||||
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(
|
||||
`<div class="d-flex align-items-center justify-content-center" style="gap: 0.45rem;">
|
||||
${spinner}
|
||||
${
|
||||
loading_label
|
||||
? `<span class="text-muted" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${loading_label}</span>`
|
||||
: ""
|
||||
}
|
||||
</div>`
|
||||
);
|
||||
|
||||
Promise.resolve(action).finally(() => {
|
||||
primary_btn
|
||||
.css({ "min-width": "", "min-height": "" })
|
||||
.prop("disabled", false)
|
||||
.removeClass("btn-primary-dark")
|
||||
.html(label);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return primary_btn;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ frappe.ui.Page = class Page {
|
|||
}
|
||||
|
||||
setup_main_sidebar_toggle() {
|
||||
$(".sidebar-toggle-btn.navbar-brand").on("click", (event) => {
|
||||
this.wrapper.find(".sidebar-toggle-btn.navbar-brand").on("click", (event) => {
|
||||
frappe.app.sidebar.set_height();
|
||||
frappe.app.sidebar.toggle_width();
|
||||
frappe.app.sidebar.prevent_scroll();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<div class="sticky-top">
|
||||
{% if !localStorage.getItem("dismissed_announcement_widget") && strip_html(navbar_settings.announcement_widget) != '' %}
|
||||
<div class="announcement-widget form-message p-2 m-0" style="position: relative; z-index: -1; border-radius: 0; background-color: var(--bg-blue);">
|
||||
{% if (!navbar_settings.dismissible_announcement_widget || !localStorage.getItem("dismissed_announcement_widget")) && strip_html(navbar_settings.announcement_widget) != '' %}
|
||||
<div class="announcement-widget form-message p-2 m-0" style="position: relative; z-index: -1; border-radius: 0; background-color: {{ navbar_settings.announcement_widget_color || 'var(--bg-blue)' }};">
|
||||
<div class="container flex justify-between align-center mx-auto">
|
||||
{{ navbar_settings.announcement_widget }}
|
||||
{% if navbar_settings.dismissible_announcement_widget %}
|
||||
<div class="close-message p-0 mr-2" style="position: relative;">
|
||||
{{ frappe.utils.icon("close") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ frappe.ui.toolbar.Toolbar = class {
|
|||
if (
|
||||
frappe.boot.read_only ||
|
||||
frappe.boot.user.impersonated_by ||
|
||||
(!localStorage.getItem("dismissed_announcement_widget") &&
|
||||
((!localStorage.getItem("dismissed_announcement_widget") ||
|
||||
!frappe.boot.navbar_settings.dismissible_announcement_widget) &&
|
||||
strip_html(frappe.boot.navbar_settings.announcement_widget) != "") ||
|
||||
frappe.is_mobile()
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ function markReset(step) {
|
|||
</div>
|
||||
<div v-else>
|
||||
<span
|
||||
class="text-base onb-step-text"
|
||||
class="text-base onb-step-text text-extra-muted"
|
||||
style="text-decoration-line: line-through"
|
||||
>
|
||||
{{ __(step.action_label) }}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ frappe.dashboard_utils = {
|
|||
|
||||
remove_common_static_filter_values(static_filters, dynamic_filters) {
|
||||
if (dynamic_filters) {
|
||||
if ($.isArray(static_filters)) {
|
||||
if (Array.isArray(static_filters)) {
|
||||
static_filters = static_filters.filter((static_filter) => {
|
||||
for (let dynamic_filter of dynamic_filters) {
|
||||
if (
|
||||
|
|
@ -207,29 +207,26 @@ frappe.dashboard_utils = {
|
|||
? JSON.parse(doc.dynamic_filters_json)
|
||||
: null;
|
||||
|
||||
if (!dynamic_filters || !Object.keys(dynamic_filters).length) {
|
||||
if (!dynamic_filters?.length) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
if (Array.isArray(dynamic_filters)) {
|
||||
dynamic_filters.forEach((f) => {
|
||||
try {
|
||||
f[3] = eval(f[3]);
|
||||
} catch (e) {
|
||||
frappe.throw(__("Invalid expression set in filter {0} ({1})", [f[1], f[0]]));
|
||||
}
|
||||
});
|
||||
dynamic_filters.forEach((f) => {
|
||||
try {
|
||||
f[3] = eval(f[3]);
|
||||
} catch (e) {
|
||||
frappe.throw(__("Invalid expression set in filter {0} ({1})", [f[1], f[0]]));
|
||||
}
|
||||
});
|
||||
|
||||
if (!filters) {
|
||||
filters = dynamic_filters;
|
||||
} else if (Array.isArray(filters)) {
|
||||
filters = [...filters, ...dynamic_filters];
|
||||
} else {
|
||||
for (let key of Object.keys(dynamic_filters)) {
|
||||
try {
|
||||
const val = eval(dynamic_filters[key]);
|
||||
dynamic_filters[key] = val;
|
||||
} catch (e) {
|
||||
frappe.throw(__("Invalid expression set in filter {0}", [key]));
|
||||
}
|
||||
}
|
||||
Object.assign(filters, dynamic_filters);
|
||||
dynamic_filters.forEach((f) => {
|
||||
filters[f[1]] = f[3];
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
|
|
@ -264,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 = `<a href = "/desk/dashboard/${values.dashboard}">${values.dashboard}</a>`;
|
||||
let message = __("{0} {1} added to Dashboard {2}", [
|
||||
doctype,
|
||||
|
|
@ -273,9 +270,8 @@ frappe.dashboard_utils = {
|
|||
]);
|
||||
|
||||
frappe.msgprint(message);
|
||||
dialog.hide();
|
||||
});
|
||||
|
||||
dialog.hide();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = [];
|
||||
|
|
@ -2229,4 +2252,16 @@ Object.assign(frappe.utils, {
|
|||
}
|
||||
return value;
|
||||
},
|
||||
get_installed_apps() {
|
||||
return frappe.boot.app_data.map((app) => {
|
||||
return app.app_name;
|
||||
});
|
||||
},
|
||||
is_sub_array(big, small) {
|
||||
let i = 0;
|
||||
for (let num of big) {
|
||||
if (num === small[i]) i++;
|
||||
}
|
||||
return i === small.length;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -762,9 +762,39 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
let data = r.message;
|
||||
this.hide_status();
|
||||
clearInterval(this.interval);
|
||||
|
||||
clearInterval(this.stale_report_interval);
|
||||
this.refreshed_at = frappe.datetime.now_datetime();
|
||||
this.execution_time = data.execution_time || 0.1;
|
||||
|
||||
const check_if_report_is_stale = () => {
|
||||
let generated_at = this.prepared_report
|
||||
? this.prepared_report_document.report_end_time
|
||||
: this.refreshed_at;
|
||||
let pretty_diff = frappe.datetime.comment_when(generated_at);
|
||||
const days_old = frappe.datetime.get_day_diff(
|
||||
frappe.datetime.now_datetime(),
|
||||
generated_at
|
||||
);
|
||||
const minutes_old = frappe.datetime.get_minute_diff(
|
||||
frappe.datetime.now_datetime(),
|
||||
generated_at
|
||||
);
|
||||
if (days_old > 1) {
|
||||
pretty_diff = `<span style="color:var(--red-600)">${pretty_diff}</span>`;
|
||||
}
|
||||
if (minutes_old >= 1) {
|
||||
this.show_status(`
|
||||
<div class="indicator orange pl-1">
|
||||
<span>
|
||||
${__("This report was generated {0}.", [pretty_diff])}
|
||||
</span>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
};
|
||||
|
||||
this.stale_report_interval = setInterval(check_if_report_is_stale, 60000);
|
||||
|
||||
if (data.custom_filters) {
|
||||
this.set_filters(data.custom_filters);
|
||||
this.previous_filters = data.custom_filters;
|
||||
|
|
@ -787,6 +817,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
});
|
||||
}
|
||||
this.add_prepared_report_buttons(data.doc);
|
||||
check_if_report_is_stale();
|
||||
}
|
||||
|
||||
if (data.report_summary) {
|
||||
|
|
@ -865,28 +896,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
let pretty_diff = frappe.datetime.comment_when(doc.report_end_time);
|
||||
const days_old = frappe.datetime.get_day_diff(
|
||||
frappe.datetime.now_datetime(),
|
||||
doc.report_end_time
|
||||
);
|
||||
if (days_old > 1) {
|
||||
pretty_diff = `<span style="color:var(--red-600)">${pretty_diff}</span>`;
|
||||
}
|
||||
const part1 = __("This report was generated {0}.", [pretty_diff]);
|
||||
const part2 = __("To get the updated report, click on {0}.", [__("Rebuild")]);
|
||||
const part3 = __("See all past reports.");
|
||||
|
||||
this.show_status(`
|
||||
<div class="indicator orange">
|
||||
<span>
|
||||
${part1}
|
||||
${part2}
|
||||
<a href="/desk/List/Prepared%20Report?report_name=${this.report_name}"> ${part3}</a>
|
||||
</span>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
|
||||
// Three cases
|
||||
|
|
@ -2109,7 +2118,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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
`<span class="comparison-message text-extra-muted">${message}</span>`
|
||||
);
|
||||
}
|
||||
|
||||
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 = $(
|
||||
`<span class="comparison-help-icon text-muted" title="${message}">${frappe.utils.icon(
|
||||
"info",
|
||||
"xs"
|
||||
)}</span>`
|
||||
);
|
||||
|
||||
$input.after($icon);
|
||||
});
|
||||
}
|
||||
|
||||
update_count_for_inline_filter() {
|
||||
if (!this.datatable) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
<script setup>
|
||||
import { ref, computed, nextTick } from "vue";
|
||||
import { ref, computed, nextTick, watch } from "vue";
|
||||
import { useStore } from "../store";
|
||||
|
||||
let store = useStore();
|
||||
|
||||
let title = ref("Workflow Details");
|
||||
|
||||
watch(
|
||||
() => store.workflow_doc?.document_type,
|
||||
async (newDocType) => {
|
||||
if (!newDocType) return;
|
||||
await store.update_is_submittable();
|
||||
store.reset_non_submittable_states();
|
||||
}
|
||||
);
|
||||
|
||||
let doc = computed(() => {
|
||||
return store.workflow.selected ? store.workflow.selected.data : store.workflow_doc;
|
||||
});
|
||||
|
|
@ -61,6 +70,7 @@ let properties = computed(() => {
|
|||
v-model="doc[df.fieldname]"
|
||||
:data-fieldname="df.fieldname"
|
||||
:data-fieldtype="df.fieldtype"
|
||||
:read_only="df.fieldname === 'doc_status' ? !store.is_submittable : false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export const useStore = defineStore("workflow-builder-store", () => {
|
|||
let statefields = ref([]);
|
||||
let transitionfields = ref([]);
|
||||
let ref_history = ref(null);
|
||||
let is_submittable = ref(true);
|
||||
|
||||
async function fetch() {
|
||||
await frappe.model.clear_doc("Workflow", workflow_name.value);
|
||||
|
|
@ -61,6 +62,9 @@ export const useStore = defineStore("workflow-builder-store", () => {
|
|||
|
||||
workflow.value.elements = get_workflow_elements(workflow_doc.value, workflow_data);
|
||||
|
||||
await update_is_submittable();
|
||||
reset_non_submittable_states();
|
||||
|
||||
setup_undo_redo();
|
||||
setup_breadcrumbs();
|
||||
}
|
||||
|
|
@ -135,13 +139,46 @@ export const useStore = defineStore("workflow-builder-store", () => {
|
|||
frappe.breadcrumbs.$breadcrumbs.append(breadcrumbs);
|
||||
}
|
||||
|
||||
async function update_is_submittable() {
|
||||
if (!workflow_doc.value?.document_type) {
|
||||
is_submittable.value = true;
|
||||
return;
|
||||
}
|
||||
await frappe.model.with_doctype(workflow_doc.value.document_type);
|
||||
is_submittable.value =
|
||||
frappe.get_meta(workflow_doc.value.document_type)?.is_submittable || false;
|
||||
}
|
||||
|
||||
function reset_non_submittable_states() {
|
||||
if (is_submittable.value) return;
|
||||
|
||||
let has_affected_states = false;
|
||||
workflow.value.elements.forEach((el) => {
|
||||
if (el.type === "state" && el.data.doc_status && el.data.doc_status !== "Draft") {
|
||||
has_affected_states = true;
|
||||
el.data.doc_status = "Draft";
|
||||
}
|
||||
});
|
||||
|
||||
if (has_affected_states) {
|
||||
frappe.msgprint({
|
||||
title: __("Doc Status Reset"),
|
||||
message: __(
|
||||
"The <strong>Doc Status</strong> for all states has been reset to <strong>Draft</strong> because <strong>{0}</strong> is not submittable",
|
||||
[workflow_doc.value.document_type]
|
||||
),
|
||||
indicator: "orange",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function get_state_df(data) {
|
||||
let doc_status_map = {
|
||||
Draft: 0,
|
||||
Submitted: 1,
|
||||
Cancelled: 2,
|
||||
};
|
||||
data.doc_status = doc_status_map[data.doc_status];
|
||||
data.doc_status = is_submittable.value ? doc_status_map[data.doc_status] : 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -234,5 +271,8 @@ export const useStore = defineStore("workflow-builder-store", () => {
|
|||
reset_changes,
|
||||
save_changes,
|
||||
setup_undo_redo,
|
||||
is_submittable,
|
||||
update_is_submittable,
|
||||
reset_non_submittable_states,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -142,10 +142,6 @@ body {
|
|||
-webkit-transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
|
|
@ -159,6 +155,10 @@ body {
|
|||
@extend .d-none;
|
||||
}
|
||||
|
||||
.hide {
|
||||
@extend .d-none;
|
||||
}
|
||||
|
||||
.margin {
|
||||
margin: var(--margin-sm);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
& > * {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
{% if image or user_info.image %}
|
||||
<img
|
||||
class="avatar-frame standard-image"
|
||||
src="{{ image or user_info.image }}"
|
||||
src="{{ (image or user_info.image) |e }}"
|
||||
title="{{ full_name|e or user_info.name|e }}">
|
||||
{% else %}
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
{%- block value -%}
|
||||
<div class="value">
|
||||
<img class="w-100" src="{{ value }}" alt="{{ df.label }}">
|
||||
<img class="w-100" src="{{ value }}" alt="{{ _(df.label) }}">
|
||||
</div>
|
||||
{%- endblock -%}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% if value %}
|
||||
<div class="field {{ df.section.field_orientation or '' }}" {{ field_attributes(df) }}>
|
||||
{%- block label -%}
|
||||
<div class="label">{{ df.label }}</div>
|
||||
<div class="label">{{ _(df.label) }}</div>
|
||||
{%- endblock -%}
|
||||
{%- block value -%}
|
||||
<div class="value">{{ doc.get_formatted(df.fieldname) }}</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
{%- block value -%}
|
||||
<div class="value">
|
||||
<img src="{{ value }}" alt="{{ df.label }}">
|
||||
<img src="{{ value }}" alt="{{ _(df.label) }}">
|
||||
</div>
|
||||
{%- endblock -%}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{% if doc.get(df.fieldname) %}
|
||||
<div class="child-table" {{ field_attributes(df) }}>
|
||||
<div class="label">
|
||||
{{ df.label }}
|
||||
{{ _(df.label) }}
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
{% set columns = df.table_columns %}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
<tr class="table-row">
|
||||
{% for column in columns %}
|
||||
<th class="column-header" width="{{ column.width }}%" {{ field_attributes(column) }}>
|
||||
{{ column.label }}
|
||||
{{ _(column.label) }}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
{% for section in layout.sections %}
|
||||
<div class="section {{ resolve_class({'page-break': section.page_break}) }}">
|
||||
{% if section.label %}
|
||||
<div class="section-label">{{ section.label }}</div>
|
||||
<div class="section-label">{{ _(section.label) }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="section-columns row">
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
class TestResult(unittest.TextTestResult):
|
||||
def __init__(self, stream, descriptions, verbosity):
|
||||
def __init__(self, stream, descriptions, verbosity, failfast=False):
|
||||
super().__init__(stream, descriptions, verbosity)
|
||||
self.failfast = failfast
|
||||
self._old_stdout = []
|
||||
self._old_stderr = []
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ def clean_html(html):
|
|||
"tbody",
|
||||
"td",
|
||||
"tr",
|
||||
"a",
|
||||
},
|
||||
clean_content_tags=REMOVE_CONTENT_TAGS,
|
||||
strip_comments=True,
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ def get_context(context):
|
|||
context.boot = get_boot_data()
|
||||
context.boot["link_title_doctypes"] = frappe.boot.get_link_title_doctypes()
|
||||
|
||||
context.webform_banner_image = self.banner_image
|
||||
context.webform_banner_image = context.get("banner_image") or self.banner_image
|
||||
context.pop("banner_image", None)
|
||||
|
||||
def add_metatags(self, context):
|
||||
|
|
|
|||
|
|
@ -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", {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -108,17 +108,21 @@ class TestWorkflow(IntegrationTestCase):
|
|||
self.assertEqual(workflow_actions[0].status, "Completed")
|
||||
|
||||
def test_if_workflow_set_on_action(self):
|
||||
self.workflow._update_state_docstatus = True
|
||||
self.workflow.states[1].doc_status = 1
|
||||
self.workflow.save()
|
||||
todo = create_new_todo()
|
||||
self.assertEqual(todo.docstatus, 0)
|
||||
todo.submit()
|
||||
self.assertEqual(todo.docstatus, 1)
|
||||
self.assertEqual(todo.workflow_state, "Approved")
|
||||
dt = create_new_submittable_doctype()
|
||||
workflow = create_submittable_workflow(dt.name)
|
||||
doc = frappe.get_doc({"doctype": dt.name, "test_field": "test"}).insert()
|
||||
|
||||
self.workflow.states[1].doc_status = 0
|
||||
self.workflow.save()
|
||||
workflow._update_state_docstatus = True
|
||||
workflow.states[1].doc_status = 1
|
||||
workflow.save()
|
||||
|
||||
self.assertEqual(doc.docstatus, 0)
|
||||
doc.submit()
|
||||
self.assertEqual(doc.docstatus, 1)
|
||||
self.assertEqual(doc.workflow_state, "Approved")
|
||||
|
||||
workflow.states[1].doc_status = 0
|
||||
workflow.save()
|
||||
|
||||
def test_syntax_error_in_transition_rule(self):
|
||||
self.workflow.transitions[0].condition = 'doc.status =! "Closed"'
|
||||
|
|
@ -350,6 +354,55 @@ def create_new_todo():
|
|||
return frappe.get_doc(doctype="ToDo", description="workflow " + random_string(10)).insert()
|
||||
|
||||
|
||||
def create_new_submittable_doctype():
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "DocType",
|
||||
"module": "Core",
|
||||
"name": "Test Submittable Doc",
|
||||
"custom": 1,
|
||||
"is_submittable": 1,
|
||||
"fields": [
|
||||
{"label": "Field", "fieldname": "test_field", "fieldtype": "Data"},
|
||||
{
|
||||
"label": "Workflow State",
|
||||
"fieldname": "workflow_state",
|
||||
"fieldtype": "Link",
|
||||
"options": "Workflow State",
|
||||
},
|
||||
],
|
||||
"permissions": [{"role": "System Manager", "read": 1, "write": 1, "submit": 1, "cancel": 1}],
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
|
||||
|
||||
def create_submittable_workflow(doctype):
|
||||
workflow = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Workflow",
|
||||
"workflow_name": "Submittable Workflow",
|
||||
"document_type": doctype,
|
||||
"workflow_state_field": "workflow_state",
|
||||
"is_active": 1,
|
||||
"states": [
|
||||
{"state": "Pending", "allow_edit": "All"},
|
||||
{"state": "Approved", "allow_edit": "System Manager", "doc_status": 0},
|
||||
],
|
||||
"transitions": [
|
||||
{
|
||||
"state": "Pending",
|
||||
"action": "Approve",
|
||||
"next_state": "Approved",
|
||||
"allowed": "System Manager",
|
||||
"allow_self_approval": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
|
||||
return workflow
|
||||
|
||||
|
||||
def create_new_note(doc):
|
||||
note = frappe.new_doc("Note")
|
||||
note.title = "workflow - " + doc.name
|
||||
|
|
|
|||
|
|
@ -109,9 +109,10 @@ frappe.ui.form.on("Workflow", {
|
|||
return;
|
||||
}
|
||||
frappe.model.with_doctype(doc.document_type, () => {
|
||||
const fieldnames = frappe
|
||||
.get_meta(doc.document_type)
|
||||
.fields.filter((field) => !frappe.model.no_value_type.includes(field.fieldtype))
|
||||
const meta = frappe.get_meta(doc.document_type);
|
||||
const is_submittable = meta.is_submittable;
|
||||
const fieldnames = meta.fields
|
||||
.filter((field) => !frappe.model.no_value_type.includes(field.fieldtype))
|
||||
.map((field) => field.fieldname);
|
||||
|
||||
frm.fields_dict.states.grid.update_docfield_property(
|
||||
|
|
@ -119,6 +120,33 @@ frappe.ui.form.on("Workflow", {
|
|||
"options",
|
||||
[""].concat(fieldnames)
|
||||
);
|
||||
|
||||
frm.fields_dict.states.grid.update_docfield_property(
|
||||
"doc_status",
|
||||
"read_only",
|
||||
!is_submittable
|
||||
);
|
||||
|
||||
if (!is_submittable) {
|
||||
let has_affected_states = false;
|
||||
frm.doc.states.forEach((row) => {
|
||||
if (parseInt(row.doc_status || 0) !== 0) {
|
||||
has_affected_states = true;
|
||||
row.doc_status = "0";
|
||||
}
|
||||
});
|
||||
if (has_affected_states) {
|
||||
frm.refresh_field("states");
|
||||
frappe.msgprint({
|
||||
title: __("Doc Status Reset"),
|
||||
message: __(
|
||||
"The <strong>Doc Status</strong> for all states has been reset to <strong>0</strong> because <strong>{0}</strong> is not submittable",
|
||||
[frm.doc.document_type]
|
||||
),
|
||||
indicator: "orange",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
create_warning_dialog: function (frm) {
|
||||
|
|
|
|||
|
|
@ -90,23 +90,38 @@ class Workflow(Document):
|
|||
|
||||
frappe.throw(frappe._("{0} not a valid State").format(state))
|
||||
|
||||
meta = frappe.get_meta(self.document_type)
|
||||
is_submittable = meta.is_submittable
|
||||
|
||||
if not is_submittable:
|
||||
for state in self.states:
|
||||
if cint(state.doc_status) != 0:
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"Workflow State '{0}' has Document Status {1}, but DocType '{2}' is not submittable. "
|
||||
"Only Document Status 0 (Draft) is allowed for non-submittable DocTypes."
|
||||
).format(state.state, state.doc_status, self.document_type)
|
||||
)
|
||||
|
||||
for t in self.transitions:
|
||||
state = get_state(t.state)
|
||||
next_state = get_state(t.next_state)
|
||||
state_docstatus = cint(state.doc_status)
|
||||
next_state_docstatus = cint(next_state.doc_status)
|
||||
|
||||
if state.doc_status == "2":
|
||||
if state_docstatus == 2:
|
||||
frappe.throw(
|
||||
frappe._("Cannot change state of Cancelled Document. Transition row {0}").format(t.idx)
|
||||
)
|
||||
|
||||
if state.doc_status == "1" and next_state.doc_status == "0":
|
||||
if state_docstatus == 1 and next_state_docstatus == 0:
|
||||
frappe.throw(
|
||||
frappe._(
|
||||
"Submitted Document cannot be converted back to draft. Transition row {0}"
|
||||
).format(t.idx)
|
||||
)
|
||||
|
||||
if state.doc_status == "0" and next_state.doc_status == "2":
|
||||
if state_docstatus == 0 and next_state_docstatus == 2:
|
||||
frappe.throw(frappe._("Cannot cancel before submitting. See Transition {0}").format(t.idx))
|
||||
|
||||
def set_active(self):
|
||||
|
|
|
|||
|
|
@ -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.1",
|
||||
"pypdf==6.7.4",
|
||||
"PyPika @ git+https://github.com/frappe/pypika@2c50e6142b2d61d2d243e466fdd5dc03b3d918f2",
|
||||
"mysqlclient==2.2.7",
|
||||
"PyQRCode~=1.2.1",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue