first commit
This commit is contained in:
parent
314954dd18
commit
461d6424f0
|
|
@ -0,0 +1,37 @@
|
|||
import frappe
|
||||
|
||||
def boot_session_handler(bootinfo):
|
||||
# Формируем данные для всех Doctype, где есть вкладки
|
||||
all_doctypes = frappe.get_all("DocType", fields=["name"])
|
||||
tab_hierarchy_data = {}
|
||||
|
||||
for doctype in all_doctypes:
|
||||
meta = frappe.get_meta(doctype["name"])
|
||||
|
||||
# Проверяем, есть ли вкладки (Tab Break) в Doctype
|
||||
if not any(field.fieldtype == "Tab Break" for field in meta.fields):
|
||||
continue
|
||||
|
||||
tabs = []
|
||||
subtabs = {}
|
||||
|
||||
for field in meta.fields:
|
||||
if field.fieldtype == "Tab Break":
|
||||
tab_name = field.label
|
||||
js_parent = field.js_parent_subtab or None
|
||||
|
||||
if js_parent:
|
||||
if js_parent not in subtabs:
|
||||
subtabs[js_parent] = []
|
||||
subtabs[js_parent].append(tab_name)
|
||||
else:
|
||||
tabs.append(tab_name)
|
||||
|
||||
# Добавляем данные для Doctype
|
||||
tab_hierarchy_data[doctype["name"]] = {
|
||||
"tabs": tabs,
|
||||
"subtabs": subtabs
|
||||
}
|
||||
|
||||
# Передаём данные на клиентскую сторону через bootinfo
|
||||
bootinfo.tab_hierarchy = tab_hierarchy_data
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
# Удаляем кастомное поле 'parent' из Doctype 'DocField', если оно существует
|
||||
field_name = "DocField-js_parent_subtab"
|
||||
if frappe.db.exists("Custom Field", {"name": field_name}):
|
||||
frappe.delete_doc("Custom Field", field_name, ignore_permissions=True)
|
||||
print(f"Custom Field '{field_name}' было удалено.")
|
||||
else:
|
||||
print(f"Custom Field '{field_name}' не существует.")
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,592 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-02-22 01:27:33",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"label_and_type",
|
||||
"label",
|
||||
"fieldtype",
|
||||
"fieldname",
|
||||
"precision",
|
||||
"length",
|
||||
"non_negative",
|
||||
"hide_days",
|
||||
"hide_seconds",
|
||||
"reqd",
|
||||
"is_virtual",
|
||||
"search_index",
|
||||
"column_break_18",
|
||||
"options",
|
||||
"sort_options",
|
||||
"show_dashboard",
|
||||
"link_filters",
|
||||
"defaults_section",
|
||||
"default",
|
||||
"column_break_6",
|
||||
"fetch_from",
|
||||
"fetch_if_empty",
|
||||
"visibility_section",
|
||||
"hidden",
|
||||
"bold",
|
||||
"allow_in_quick_entry",
|
||||
"translatable",
|
||||
"print_hide",
|
||||
"print_hide_if_no_value",
|
||||
"report_hide",
|
||||
"column_break_28",
|
||||
"depends_on",
|
||||
"collapsible",
|
||||
"collapsible_depends_on",
|
||||
"hide_border",
|
||||
"list__search_settings_section",
|
||||
"in_list_view",
|
||||
"in_standard_filter",
|
||||
"in_preview",
|
||||
"column_break_35",
|
||||
"in_filter",
|
||||
"in_global_search",
|
||||
"permissions",
|
||||
"read_only",
|
||||
"allow_on_submit",
|
||||
"ignore_user_permissions",
|
||||
"allow_bulk_edit",
|
||||
"column_break_13",
|
||||
"permlevel",
|
||||
"ignore_xss_filter",
|
||||
"constraints_section",
|
||||
"unique",
|
||||
"no_copy",
|
||||
"set_only_once",
|
||||
"remember_last_selected_value",
|
||||
"column_break_38",
|
||||
"mandatory_depends_on",
|
||||
"read_only_depends_on",
|
||||
"display",
|
||||
"print_width",
|
||||
"width",
|
||||
"max_height",
|
||||
"columns",
|
||||
"column_break_22",
|
||||
"description",
|
||||
"documentation_url",
|
||||
"oldfieldname",
|
||||
"oldfieldtype"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "label_and_type",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"oldfieldname": "label",
|
||||
"oldfieldtype": "Data",
|
||||
"print_width": "163",
|
||||
"search_index": 1,
|
||||
"width": "163"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Autocomplete\nAttach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nJSON\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nPhone\nRead Only\nRating\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime",
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"sort_options": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Name",
|
||||
"oldfieldname": "fieldname",
|
||||
"oldfieldtype": "Data",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Mandatory",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"label": "Precision",
|
||||
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "search_index",
|
||||
"fieldtype": "Check",
|
||||
"label": "Index",
|
||||
"oldfieldname": "search_index",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.is_virtual",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View",
|
||||
"print_width": "70px",
|
||||
"width": "70px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List Filter"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Global Search"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Translatable"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype===\"Section Break\"",
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"label": "Collapsible",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible",
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Collapsible Depends On (JS)",
|
||||
"max_height": "3rem",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options",
|
||||
"oldfieldname": "options",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Default",
|
||||
"max_height": "3rem",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Fetch From"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If unchecked, the value will always be re-fetched on save.",
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch on Save if Empty"
|
||||
},
|
||||
{
|
||||
"fieldname": "permissions",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Permissions"
|
||||
},
|
||||
{
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Display Depends On (JS)",
|
||||
"length": 255,
|
||||
"max_height": "3rem",
|
||||
"oldfieldname": "depends_on",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden",
|
||||
"oldfieldname": "hidden",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read Only",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "set_only_once",
|
||||
"fieldtype": "Check",
|
||||
"label": "Set only once"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.fieldtype == \"Table\"",
|
||||
"fieldname": "allow_bulk_edit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Bulk Edit"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list(['Section Break', 'Column Break', 'Tab Break'], doc.fieldtype)",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"label": "Perm Level",
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore User Permissions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: parent.is_submittable",
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow on Submit",
|
||||
"oldfieldname": "allow_on_submit",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Report Hide",
|
||||
"oldfieldname": "report_hide",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.fieldtype == 'Link')",
|
||||
"fieldname": "remember_last_selected_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Remember Last Selected Value"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
},
|
||||
{
|
||||
"fieldname": "display",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Display"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Filter",
|
||||
"oldfieldname": "in_filter",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "no_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "No Copy",
|
||||
"oldfieldname": "no_copy",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide",
|
||||
"oldfieldname": "print_hide",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide If No Value"
|
||||
},
|
||||
{
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Print Width",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Width",
|
||||
"length": 10,
|
||||
"oldfieldname": "width",
|
||||
"oldfieldtype": "Data",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_22",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fieldname": "oldfieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"oldfieldname": "oldfieldname",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "oldfieldtype",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"oldfieldname": "oldfieldtype",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On (JS)",
|
||||
"max_height": "3rem",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On (JS)",
|
||||
"max_height": "3rem",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_38",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Section Break'",
|
||||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
|
||||
"fieldname": "non_negative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Non Negative"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "defaults_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Defaults",
|
||||
"max_height": "2rem"
|
||||
},
|
||||
{
|
||||
"fieldname": "visibility_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Visibility"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_28",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "constraints_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Constraints"
|
||||
},
|
||||
{
|
||||
"fieldname": "js_parent_subtab",
|
||||
"label": "JS Parent Subtab",
|
||||
"fieldtype": "Data",
|
||||
"description": "Укажите имя родительской вкладки для этой вкладки."
|
||||
},
|
||||
{
|
||||
"fieldname": "max_height",
|
||||
"fieldtype": "Data",
|
||||
"label": "Max Height",
|
||||
"length": 10
|
||||
},
|
||||
{
|
||||
"fieldname": "list__search_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "List / Search Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_35",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype===\"Tab Break\"",
|
||||
"fieldname": "show_dashboard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Dashboard"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_virtual",
|
||||
"fieldtype": "Check",
|
||||
"label": "Virtual"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!in_list([\"Tab Break\", \"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
|
||||
"fieldname": "documentation_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Documentation URL",
|
||||
"options": "URL"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.fieldtype === 'Select'",
|
||||
"fieldname": "sort_options",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sort Options"
|
||||
},
|
||||
{
|
||||
"fieldname": "link_filters",
|
||||
"fieldtype": "JSON",
|
||||
"label": "Link Filters"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-01 15:55:44.007917",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
|
|
@ -1,17 +1,32 @@
|
|||
app_name = "custom_subtabs"
|
||||
app_title = "Custom Subtabs"
|
||||
app_publisher = "Jey ERP"
|
||||
app_description = "Custom subtabs"
|
||||
app_publisher = "Jey Soft"
|
||||
app_description = "custom subtubs for frappe doctypes"
|
||||
app_email = "info@jeyerp.az"
|
||||
app_license = "unlicense"
|
||||
app_license = "mit"
|
||||
|
||||
# Apps
|
||||
# ------------------
|
||||
|
||||
# required_apps = []
|
||||
|
||||
# Each item in the list will be shown as an app in the apps page
|
||||
# add_to_apps_screen = [
|
||||
# {
|
||||
# "name": "custom_subtabs",
|
||||
# "logo": "/assets/custom_subtabs/logo.png",
|
||||
# "title": "Custom Subtabs",
|
||||
# "route": "/custom_subtabs",
|
||||
# "has_permission": "custom_subtabs.api.permission.has_app_permission"
|
||||
# }
|
||||
# ]
|
||||
|
||||
# Includes in <head>
|
||||
# ------------------
|
||||
|
||||
# include js, css files in header of desk.html
|
||||
# app_include_css = "/assets/custom_subtabs/css/custom_subtabs.css"
|
||||
# app_include_js = "/assets/custom_subtabs/js/custom_subtabs.js"
|
||||
app_include_css = "/assets/custom_subtabs/css/custom_subtabs.css"
|
||||
app_include_js = "/assets/custom_subtabs/js/custom_subtabs.js"
|
||||
|
||||
# include js, css files in header of web template
|
||||
# web_include_css = "/assets/custom_subtabs/css/custom_subtabs.css"
|
||||
|
|
@ -227,3 +242,17 @@ app_license = "unlicense"
|
|||
# "Logging DocType Name": 30 # days to retain logs
|
||||
# }
|
||||
|
||||
|
||||
app_include_js = [
|
||||
"/assets/custom_subtabs/js/custom_subtabs.js"
|
||||
]
|
||||
|
||||
fixtures = [
|
||||
{"doctype": "DocType", "filters": [["name", "in", ["DocField"]]]}
|
||||
]
|
||||
|
||||
after_migrate = [
|
||||
"custom_subtabs.patches.add_parent_field.execute"
|
||||
]
|
||||
|
||||
boot_session = "custom_subtabs.custom_subtabs.boot_session_handler"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
from frappe.custom.doctype.custom_field.custom_field import CustomField
|
||||
import frappe
|
||||
|
||||
class CustomFieldOverride(CustomField):
|
||||
def on_update(self):
|
||||
super().on_update()
|
||||
self.setup_nested_tabs()
|
||||
|
||||
def setup_nested_tabs(self):
|
||||
"""Автоматически подчиняет вкладки, основываясь на поле `js_parent_subtab`."""
|
||||
doctype_meta = frappe.get_meta(self.dt)
|
||||
fields = doctype_meta.fields
|
||||
|
||||
parent_mapping = {}
|
||||
|
||||
# Создаем структуру родительских вкладок
|
||||
for field in fields:
|
||||
if field.fieldtype == "Tab Break" and field.js_parent_subtab:
|
||||
parent_mapping[field.js_parent_subtab] = field.fieldname
|
||||
|
||||
# Устанавливаем подчинение
|
||||
for field in fields:
|
||||
if field.fieldtype == "Tab Break" and field.js_parent_subtab:
|
||||
parent_name = field.js_parent_subtab
|
||||
if parent_name in parent_mapping:
|
||||
parent_fieldname = parent_mapping[parent_name]
|
||||
field.parent_tab = parent_fieldname # Подчиняем вкладку
|
||||
frappe.db.set_value("Custom Field", field.name, "parent_tab", parent_fieldname)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
# Проверяем, существует ли такое свойство
|
||||
if frappe.db.exists("Custom Field", {"fieldname": "js_parent_subtab", "dt": "DocField"}):
|
||||
frappe.msgprint("Свойство 'js_parent_subtab' уже существует. Пропускаем создание.")
|
||||
return
|
||||
|
||||
# Создаём новое свойство
|
||||
frappe.get_doc({
|
||||
"doctype": "Custom Field",
|
||||
"dt": "DocField", # Указываем DocType, для которого добавляем
|
||||
"fieldname": "js_parent_subtab",
|
||||
"label": "JS Parent Subtab",
|
||||
"fieldtype": "Data",
|
||||
"insert_after": "reqd", # Вставляем после поля "Mandatory"
|
||||
"description": "Укажите имя родительского таба"
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
frappe.msgprint("Поле 'js_parent_subtab' успешно добавлено в настройки вкладок.")
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
/* Убираем анимацию скрытия Bootstrap */
|
||||
.tab-pane.fade:not(.show) {
|
||||
opacity: 0 !important; /* Только убираем анимацию, не меняем display */
|
||||
}
|
||||
|
||||
/* Скрываем все вкладки, кроме активной */
|
||||
.tab-pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Показываем активную вкладку */
|
||||
.tab-pane.active {
|
||||
display: block !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
frappe.ui.form.Layout.prototype.setup_tab_events = function () {
|
||||
console.log("Setting up tab events...");
|
||||
|
||||
setTimeout(() => {
|
||||
console.log("Initializing tabs...");
|
||||
|
||||
const tabs = this.wrapper.find("ul.form-tabs > li.nav-item");
|
||||
console.log(`Found ${tabs.length} main tabs.`);
|
||||
|
||||
const doctype = this.frm.doctype;
|
||||
const hierarchy = frappe.boot.tab_hierarchy[doctype];
|
||||
|
||||
if (!hierarchy) {
|
||||
console.warn(`No tab hierarchy found for Doctype: ${doctype}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const subtabs = hierarchy.subtabs || {};
|
||||
|
||||
// Process subtabs and parent tabs
|
||||
tabs.each((index, tab) => {
|
||||
const tabLabel = $(tab).text().trim();
|
||||
if (subtabs[tabLabel]) {
|
||||
console.log(`Processing subtabs for parent tab: '${tabLabel}'`);
|
||||
|
||||
let subtabContainer = $(tab).find(".sub-tab-container");
|
||||
if (!subtabContainer.length) {
|
||||
subtabContainer = $("<ul class='sub-tab-container nav'></ul>");
|
||||
$(tab).append(subtabContainer);
|
||||
}
|
||||
|
||||
subtabs[tabLabel].forEach((subtab) => {
|
||||
const subtabElement = tabs.filter((_, el) => $(el).text().trim() === subtab);
|
||||
|
||||
if (subtabElement.length) {
|
||||
const subtabClone = subtabElement.clone();
|
||||
subtabClone.addClass("sub-tab").find("a").removeAttr("href");
|
||||
subtabContainer.append(subtabClone);
|
||||
|
||||
// Удаляем лишние классы и стили у субтаба
|
||||
subtabElement.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle main tab clicks
|
||||
$(".nav-item").on("click", function () {
|
||||
const clickedTab = $(this);
|
||||
const tabId = clickedTab.find("a").attr("aria-controls");
|
||||
|
||||
console.log(`Activating main tab: '${clickedTab.text().trim()}' (ID: ${tabId})`);
|
||||
|
||||
// Hide all tabs
|
||||
$(".form-tab-content > .tab-pane").each(function () {
|
||||
const pane = $(this);
|
||||
pane.removeClass("active show").css("display", "none").addClass("tab-pane fade");
|
||||
});
|
||||
|
||||
// Activate the clicked tab
|
||||
const tabContent = document.querySelector(`#${tabId}`);
|
||||
if (tabContent) {
|
||||
tabContent.style.display = "block";
|
||||
tabContent.classList.add("active", "show");
|
||||
} else {
|
||||
console.warn(`Tab content not found for ID: ${tabId}`);
|
||||
}
|
||||
|
||||
clickedTab.addClass("active");
|
||||
});
|
||||
|
||||
// Handle subtab clicks
|
||||
$(".sub-tab").on("click", function (event) {
|
||||
event.stopPropagation(); // Предотвращаем срабатывание событий родительских вкладок
|
||||
|
||||
const clickedSubtab = $(this);
|
||||
const subtabId = clickedSubtab.find("a").attr("aria-controls");
|
||||
const subtabName = clickedSubtab.text().trim();
|
||||
|
||||
console.log(`Activating subtab: '${subtabName}' (ID: ${subtabId})`);
|
||||
|
||||
// Найти имя родительской вкладки через объект иерархии
|
||||
let parentTabName = null;
|
||||
Object.keys(subtabs).forEach((parent) => {
|
||||
if (subtabs[parent].includes(subtabName)) {
|
||||
parentTabName = parent;
|
||||
console.log(`Found parent tab for '${subtabName}': '${parentTabName}'`);
|
||||
}
|
||||
});
|
||||
|
||||
if (!parentTabName) {
|
||||
console.warn(`Parent tab not found for subtab: '${subtabName}'`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получить ID родительской вкладки через href
|
||||
const parentTabId = $(`ul.form-tabs a:contains('${parentTabName}')`).attr("aria-controls");
|
||||
|
||||
if (!parentTabId) {
|
||||
console.warn(`Parent tab ID not found for: '${parentTabName}'`);
|
||||
return;
|
||||
} else {
|
||||
console.log(`Parent tab ID for '${parentTabName}': ${parentTabId}`);
|
||||
}
|
||||
|
||||
// Обработать родительскую вкладку
|
||||
const parentTabContent = document.querySelector(`#${parentTabId}`);
|
||||
if (parentTabContent) {
|
||||
console.log(`Parent tab content found:`, parentTabContent);
|
||||
|
||||
// Убираем классы и стили, чтобы показать родительскую вкладку
|
||||
parentTabContent.style.display = "block";
|
||||
parentTabContent.classList.add("active", "show");
|
||||
parentTabContent.classList.remove("tab-pane", "fade");
|
||||
|
||||
console.log(`Parent tab '${parentTabName}' is now visible.`);
|
||||
} else {
|
||||
console.warn(`Parent tab content not found for ID: ${parentTabId}`);
|
||||
}
|
||||
|
||||
// Активировать текущую субвкладку
|
||||
const subtabContent = document.querySelector(`#${subtabId}`);
|
||||
if (subtabContent) {
|
||||
console.log(`Subtab content found:`, subtabContent);
|
||||
|
||||
// Убираем классы и стили, чтобы показать субвкладку
|
||||
subtabContent.style.display = "block";
|
||||
subtabContent.classList.add("active", "show");
|
||||
subtabContent.classList.remove("tab-pane", "fade");
|
||||
|
||||
console.log(`Subtab '${subtabName}' content activated.`);
|
||||
} else {
|
||||
console.warn(`Subtab content not found for ID: ${subtabId}`);
|
||||
}
|
||||
|
||||
clickedSubtab.addClass("active");
|
||||
|
||||
// Скрыть все другие вкладки, кроме текущей субвкладки и родительской
|
||||
$(".form-tab-content > .tab-pane").each(function () {
|
||||
const pane = $(this);
|
||||
if (pane.attr("id") !== subtabId && pane.attr("id") !== parentTabId) {
|
||||
console.log(`Hiding tab content for ID: ${pane.attr("id")}`);
|
||||
|
||||
pane.removeClass("active show").css("display", "none").addClass("tab-pane fade");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Reset subtabs when switching to another main tab
|
||||
$(".nav-item").on("click", function () {
|
||||
$(".sub-tab").each(function () {
|
||||
const subtabId = $(this).find("a").attr("aria-controls");
|
||||
const subtabPane = document.querySelector(`#${subtabId}`);
|
||||
if (subtabPane) {
|
||||
subtabPane.style.display = "none";
|
||||
subtabPane.classList.add("tab-pane", "fade");
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 500); // Задержка для загрузки DOM
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import frappe
|
||||
|
||||
def process_nested_tabs(doc, method):
|
||||
"""
|
||||
Обработчик для вложенных вкладок.
|
||||
Автоматически связывает вкладки, если у них есть указанный `js_parent_subtab`.
|
||||
"""
|
||||
if not hasattr(doc, 'fields') or not isinstance(doc.fields, list):
|
||||
return
|
||||
|
||||
# Получаем все вкладки из полей
|
||||
tabs = [field for field in doc.fields if field.get("fieldtype") == "Tab"]
|
||||
tab_map = {tab.get("fieldname"): tab for tab in tabs}
|
||||
|
||||
# Проверяем каждую вкладку на наличие `js_parent_subtab`
|
||||
for tab in tabs:
|
||||
parent_name = tab.get("js_parent_subtab")
|
||||
if parent_name and parent_name in tab_map:
|
||||
parent_tab = tab_map[parent_name]
|
||||
|
||||
# Убедимся, что поле `sub_tabs` существует
|
||||
if not parent_tab.get("sub_tabs"):
|
||||
parent_tab["sub_tabs"] = []
|
||||
|
||||
# Добавляем текущую вкладку как дочернюю
|
||||
parent_tab["sub_tabs"].append(tab)
|
||||
|
||||
# Обновляем поля и сохраняем документ
|
||||
doc.fields = doc.fields # Переприсваиваем для безопасности
|
||||
doc.save()
|
||||
Loading…
Reference in New Issue