diff --git a/msp/doctype/msp_documentation/msp_documentation.js b/msp/doctype/msp_documentation/msp_documentation.js
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/msp/doctype/msp_documentation/msp_documentation.js
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/msp/msp/doctype/it_object/it_object.json b/msp/msp/doctype/it_object/it_object.json
index 6381bb7..588cc14 100644
--- a/msp/msp/doctype/it_object/it_object.json
+++ b/msp/msp/doctype/it_object/it_object.json
@@ -36,7 +36,10 @@
"monitoring_link",
"oitc_host_uuid",
"rmm_agent_id",
- "rmm_instance"
+ "rmm_instance",
+ "documentation_section",
+ "visible_in_documentation",
+ "documentation_text"
],
"fields": [
{
@@ -215,14 +218,35 @@
"fieldname": "rmm_software",
"fieldtype": "Markdown Editor",
"label": "RMM Software"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "eval: !doc.documentation_text",
+ "fieldname": "documentation_section",
+ "fieldtype": "Section Break",
+ "label": "Documentation"
+ },
+ {
+ "description": "This will be visible on the on the MSP Documentation",
+ "fieldname": "documentation_text",
+ "fieldtype": "Text Editor",
+ "label": "Documentation Text"
+ },
+ {
+ "default": "0",
+ "description": "If checked, this object will be selected when creating a MSP Documentation.",
+ "fieldname": "visible_in_documentation",
+ "fieldtype": "Check",
+ "label": "visible in Documentation"
}
],
"image_field": "image",
"links": [],
- "modified": "2023-06-08 23:00:31.338542",
+ "modified": "2025-03-10 14:55:30.929828",
"modified_by": "Administrator",
"module": "MSP",
"name": "IT Object",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -265,6 +289,7 @@
"search_fields": "title, main_ip, item",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "title",
"track_changes": 1,
"track_views": 1
diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.js b/msp/msp/doctype/msp_documentation/msp_documentation.js
index 313c602..1d29415 100644
--- a/msp/msp/doctype/msp_documentation/msp_documentation.js
+++ b/msp/msp/doctype/msp_documentation/msp_documentation.js
@@ -5,22 +5,80 @@
frappe.ui.form.on('MSP Documentation', {
refresh(frm) {
frm.add_custom_button('1. Get Tactical Agents', function(){
+ frappe.dom.freeze('Fetching Tactical Agents...');
frappe.call({
method: 'msp.tactical-rmm.get_agents_pretty',
args: { documentation: frm.doc.name },
- callback:function(r){
- console.log(r.message)
- frm.reload_doc()
+ callback: function(r) {
+ frappe.dom.unfreeze();
+ if (r.exc) {
+ frappe.msgprint({
+ title: __('Error'),
+ indicator: 'red',
+ message: __('Failed to fetch tactical agents. Please try again.')
+ });
+ return;
+ }
+ frappe.show_alert({
+ message: __('Successfully fetched tactical agents'),
+ indicator: 'green'
+ });
+ frm.reload_doc();
}
});
}, 'Workflow');
- frm.add_custom_button('2. office suche', function(){
+
+ frm.add_custom_button('2. Office Search', function(){
+ frappe.dom.freeze('Searching for Office installations...');
frappe.call({
method: 'msp.tactical-rmm.search_office',
args: { documentation: frm.doc.name },
- callback:function(r){
- console.log(r.message)
- frm.reload_doc()
+ callback: function(r) {
+ frappe.dom.unfreeze();
+ if (r.exc) {
+ frappe.msgprint({
+ title: __('Error'),
+ indicator: 'red',
+ message: __('Failed to complete Office search. Please try again.')
+ });
+ return;
+ }
+ frappe.show_alert({
+ message: __('Office search completed'),
+ indicator: 'green'
+ });
+ frm.reload_doc();
+ }
+ });
+ }, 'Workflow');
+
+ // Add new button for IT Objects documentation
+ frm.add_custom_button('3. Generate IT Objects', function(){
+ frappe.dom.freeze('Generating IT Objects documentation...');
+ frappe.call({
+ method: 'msp.tools.get_documentation_html',
+ args: {
+ it_landscape: frm.doc.landscape
+ },
+ callback: function(r) {
+ frappe.dom.unfreeze();
+ if (r.exc) {
+ frappe.msgprint({
+ title: __('Error'),
+ indicator: 'red',
+ message: __('Failed to generate IT Objects documentation. Please try again.')
+ });
+ return;
+ }
+ if (r.message) {
+ frm.set_value('it_objects', r.message);
+ frm.save().then(() => {
+ frappe.show_alert({
+ message: __('IT Objects documentation generated successfully'),
+ indicator: 'green'
+ });
+ });
+ }
}
});
}, 'Workflow');
diff --git a/msp/msp/doctype/msp_documentation/msp_documentation.json b/msp/msp/doctype/msp_documentation/msp_documentation.json
index 5aaab09..9fea7c3 100644
--- a/msp/msp/doctype/msp_documentation/msp_documentation.json
+++ b/msp/msp/doctype/msp_documentation/msp_documentation.json
@@ -11,11 +11,12 @@
"customer",
"customer_name",
"tactical_rmm_tenant_caption",
+ "tactical_rmm_site_name",
"generation_date",
- "introduction",
+ "introduction_text",
+ "it_objects",
"server_list",
"workstation_list",
- "system_list",
"backup",
"aditional_data"
],
@@ -34,16 +35,6 @@
"options": "Customer",
"read_only": 1
},
- {
- "fieldname": "introduction",
- "fieldtype": "Markdown Editor",
- "label": "Introduction"
- },
- {
- "fieldname": "system_list",
- "fieldtype": "Markdown Editor",
- "label": "System List"
- },
{
"fetch_from": "landscape.tactical_rmm_tenant_caption",
"fieldname": "tactical_rmm_tenant_caption",
@@ -52,7 +43,7 @@
},
{
"fieldname": "backup",
- "fieldtype": "Markdown Editor",
+ "fieldtype": "Text Editor",
"label": "Backup"
},
{
@@ -67,12 +58,12 @@
},
{
"fieldname": "server_list",
- "fieldtype": "Markdown Editor",
+ "fieldtype": "Text Editor",
"label": "Server List"
},
{
"fieldname": "workstation_list",
- "fieldtype": "Markdown Editor",
+ "fieldtype": "Text Editor",
"label": "Workstation List"
},
{
@@ -81,11 +72,27 @@
"fieldtype": "Data",
"label": "Customer Name",
"read_only": 1
+ },
+ {
+ "description": "Explain: What is the target of this particular Documentation? Give an over the solution you are describing.",
+ "fieldname": "introduction_text",
+ "fieldtype": "Text Editor",
+ "label": "Introduction Text"
+ },
+ {
+ "fieldname": "it_objects",
+ "fieldtype": "Text Editor",
+ "label": "IT Objects"
+ },
+ {
+ "fieldname": "tactical_rmm_site_name",
+ "fieldtype": "Data",
+ "label": "Tactical RMM Site Name"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2023-06-09 07:14:25.256192",
+ "modified": "2025-03-11 08:06:34.303790",
"modified_by": "Administrator",
"module": "MSP",
"name": "MSP Documentation",
@@ -105,5 +112,6 @@
}
],
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/msp/msp/print_format/__init__.py b/msp/msp/print_format/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/msp/msp/print_format/html_msp_documentation/__init__.py b/msp/msp/print_format/html_msp_documentation/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/msp/msp/print_format/html_msp_documentation/html_msp_documentation.json b/msp/msp/print_format/html_msp_documentation/html_msp_documentation.json
new file mode 100644
index 0000000..ac2d2aa
--- /dev/null
+++ b/msp/msp/print_format/html_msp_documentation/html_msp_documentation.json
@@ -0,0 +1,32 @@
+{
+ "absolute_value": 0,
+ "align_labels_right": 0,
+ "creation": "2025-03-11 00:26:31.608123",
+ "custom_format": 0,
+ "default_print_language": "de",
+ "disabled": 0,
+ "doc_type": "MSP Documentation",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "font_size": 0,
+ "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
\\r\\n\\t
\\r\\n\\t\\t
\\r\\n\\t\\t\\t

\\r\\n\\t\\t
\\r\\n\\t
\\r\\n\\t
\\r\\n\\t\\t
\\r\\nIT Documentation\\r\\n\\t\\t
{{ doc.name }}
\\r\\n\\t
\\r\\n\\t
\\r\\n\\t\\t

\\r\\n\\t
\\r\\n
\\r\\n\\r\\n\\t
\\r\\n\\t\\t
\\r\\n\\t\\r\\n
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"customer\", \"print_hide\": 0, \"label\": \"Customer\"}, {\"fieldname\": \"customer_name\", \"print_hide\": 0, \"label\": \"Customer Name\"}, {\"fieldname\": \"generation_date\", \"print_hide\": 0, \"label\": \"Generation Date\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"landscape\", \"print_hide\": 0, \"label\": \"Landscape\"}, {\"fieldname\": \"tactical_rmm_tenant_caption\", \"print_hide\": 0, \"label\": \"Tactical RMM Tenant Caption\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"Introduction
\"}, {\"fieldname\": \"introduction_text\", \"print_hide\": 0, \"label\": \"Introduction Text\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"
\"}, {\"fieldtype\": \"Section Break\", \"label\": \"IT Objects\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"it_objects\", \"print_hide\": 0, \"label\": \"IT Objects\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Systems from RMM\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"Server Systems
\\n
\"}, {\"fieldname\": \"server_list\", \"print_hide\": 0, \"label\": \"Server List\"}, {\"fieldname\": \"_custom_html\", \"print_hide\": 0, \"label\": \"Custom HTML\", \"fieldtype\": \"HTML\", \"options\": \"Workstation Systems
\\n
\"}, {\"fieldname\": \"workstation_list\", \"print_hide\": 0, \"label\": \"Workstation List\"}, {\"fieldtype\": \"Section Break\", \"label\": \"Backups\"}, {\"fieldtype\": \"Column Break\"}, {\"fieldname\": \"backup\", \"print_hide\": 0, \"label\": \"Backup\"}, {\"fieldname\": \"aditional_data\", \"print_hide\": 0, \"label\": \"Aditional Data\"}]",
+ "idx": 0,
+ "line_breaks": 0,
+ "margin_bottom": 0.0,
+ "margin_left": 0.0,
+ "margin_right": 0.0,
+ "margin_top": 0.0,
+ "modified": "2025-03-11 00:27:41.672487",
+ "modified_by": "D.Malinowski@itsdave.de",
+ "module": "MSP",
+ "name": "HTML MSP Documentation",
+ "owner": "D.Malinowski@itsdave.de",
+ "page_number": "Hide",
+ "print_format_builder": 1,
+ "print_format_builder_beta": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 1,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/msp/msp/workspace/technik/technik.json b/msp/msp/workspace/technik/technik.json
index 408626d..10f81bb 100644
--- a/msp/msp/workspace/technik/technik.json
+++ b/msp/msp/workspace/technik/technik.json
@@ -77,7 +77,7 @@
"type": "Link"
}
],
- "modified": "2024-07-30 07:36:09.918509",
+ "modified": "2024-10-11 10:44:54.148354",
"modified_by": "D.Malinowski@itsdave.de",
"module": "MSP",
"name": "Technik",
@@ -87,7 +87,7 @@
"public": 1,
"quick_lists": [],
"roles": [],
- "sequence_id": 2.0,
+ "sequence_id": 3.0,
"shortcuts": [],
"title": "Technik"
}
\ No newline at end of file
diff --git a/msp/tactical-rmm.py b/msp/tactical-rmm.py
index de743bc..609d637 100644
--- a/msp/tactical-rmm.py
+++ b/msp/tactical-rmm.py
@@ -5,6 +5,7 @@ import requests
import json
from pprint import pprint
import re
+from .tools import render_card_html, render_single_card
@frappe.whitelist()
def get_agents(it_landscape, rmm_instance = None, tactical_rmm_tenant_caption = None):
@@ -88,36 +89,70 @@ def get_agents_pretty(documentation):
documentation_doc = frappe.get_doc("MSP Documentation", documentation)
if not documentation_doc.tactical_rmm_tenant_caption:
- frappe.throw("Tennant Caption missing")
+ frappe.throw("Tenant Caption missing")
client_name = documentation_doc.tactical_rmm_tenant_caption
-
+ site_name = documentation_doc.tactical_rmm_site_name
agents = get_all_agents()
- print(agents)
- agent_list = []
- workstation_list = []
- server_list = []
- #if not agent_list:
- # frappe.throw("API Abfrage hat keine Agents geliefert.")
-
-
- for agent in agents:
- if agent["client_name"] == client_name:
- agent_list.append(agent)
- if agent["monitoring_type"] == "workstation":
- workstation_list.append(agent)
- if agent["monitoring_type"] == "server":
- server_list.append(agent)
- pprint(agent)
- output = make_agent_md_output(agent_list)
- output_workstation = make_agent_md_output(workstation_list)
- output_server = make_agent_md_output(server_list)
+ # Filter and organize agents
+ agent_list = []
+ for agent in agents:
+ # Filter by client_name and optionally by site_name if provided
+ if agent["client_name"] == client_name and (not site_name or agent["site_name"] == site_name):
+ # Format agent data for card rendering
+ agent_item = {
+ 'title': agent['hostname'], # Using hostname as title
+ 'type': agent['monitoring_type'], # workstation/server
+ 'ip': agent['local_ips'],
+ 'location': agent['site_name'],
+ 'metadata': {
+ 'Operating System': agent['operating_system'],
+ 'Hardware Model': render_model(agent['make_model']),
+ 'Serial Number': agent.get('serial_number', ''),
+ 'CPU': ", ".join(agent['cpu_model']) if isinstance(agent['cpu_model'], list) else agent['cpu_model'],
+ 'Graphics': agent['graphics'],
+ 'Storage': ", ".join(agent['physical_disks']) if isinstance(agent['physical_disks'], list) else agent['physical_disks'],
+ 'Public IP': agent['public_ip'],
+ 'Last Seen': agent['last_seen'],
+ 'Last User': agent['logged_username']
+ },
+ 'description': agent.get('description', '')
+ }
+ agent_list.append(agent_item)
- documentation_doc.system_list = output
- documentation_doc.workstation_list = output_workstation
- documentation_doc.server_list = output_server
+ # Check if any agents were found
+ if not agent_list:
+ no_agents_message = f"No agents found for client '{client_name}'"
+ if site_name:
+ no_agents_message += f" at site '{site_name}'"
+ no_agents_message += ".
"
+
+ # Update the documentation with the message
+ documentation_doc.system_list = no_agents_message
+ documentation_doc.workstation_list = no_agents_message
+ documentation_doc.server_list = no_agents_message
+ documentation_doc.save()
+
+ return []
+
+ # Generate HTML using the shared render_card_html function from tools.py
+ all_agents_html = render_card_html(agent_list, "tactical")
+
+ # Filter lists for workstations and servers
+ workstation_list = [a for a in agent_list if a['type'].lower() == 'workstation']
+ server_list = [a for a in agent_list if a['type'].lower() == 'server']
+
+ # Generate separate HTML for workstations and servers
+ workstation_html = render_card_html(workstation_list, "tactical")
+ server_html = render_card_html(server_list, "tactical")
+
+ # Update the documentation
+ documentation_doc.system_list = all_agents_html
+ documentation_doc.workstation_list = workstation_html
+ documentation_doc.server_list = server_html
documentation_doc.save()
+
return agent_list
@@ -179,29 +214,32 @@ def get_patches_for_agent(agent_id=None):
def make_agent_md_output(agents):
- md_output = ""
+ # Prepare items for card rendering
+ items = []
for agent in agents:
- md_output += f'''
-#### {agent["hostname"]}
-- OS: {agent["operating_system"]}
-- CPU: {agent["cpu_model"]}
-- GPU: {agent["graphics"]}
-- Disks: {agent["physical_disks"]}
-- Model: {render_model(agent["make_model"])}
-- Serial Number: {agent["serial_number"]}
-- Type: {agent["monitoring_type"]}
-- Site: {agent["site_name"]}
-- Local IPs: {agent["local_ips"]}
-- Public IP: {agent["public_ip"]}
-- Last Seen: {agent["last_seen"]}
-- Last User: {agent["logged_username"]}
-'''
- if agent["description"]:
- md_output += f'''Description:
-{agent["description"]}
-'''
+ items.append({
+ 'title': agent['hostname'],
+ 'type': agent['monitoring_type'],
+ 'ip': agent['local_ips'],
+ 'location': agent['site_name'],
+ 'status': agent['status'],
+ 'metadata': {
+ 'OS': agent['operating_system'],
+ 'CPU': ", ".join(agent['cpu_model']) if isinstance(agent['cpu_model'], list) else agent['cpu_model'],
+ 'GPU': agent['graphics'],
+ 'Disks': ", ".join(agent['physical_disks']) if isinstance(agent['physical_disks'], list) else agent['physical_disks'],
+ 'Model': render_model(agent['make_model']),
+ 'Serial Number': agent.get('serial_number'),
+ 'Type': agent['monitoring_type'],
+ 'Site': agent['site_name'],
+ 'Public IP': agent['public_ip'],
+ 'Last Seen': agent['last_seen'],
+ 'Last User': agent['logged_username']
+ },
+ 'description': agent.get('description')
+ })
- return md_output
+ return render_card_html(items, "agent")
def render_model(model):
diff --git a/msp/tools.py b/msp/tools.py
index 3e293d5..1990352 100644
--- a/msp/tools.py
+++ b/msp/tools.py
@@ -8,6 +8,8 @@ import pandas as pd
import requests
from io import StringIO
from requests.auth import HTTPBasicAuth
+import re
+from datetime import datetime
@frappe.whitelist()
def checklist_fetch_from_template(values, name):
@@ -562,71 +564,219 @@ def get_status_from_ticket():
print(f"{field}: {value}")
print("---")
-# import frappe
-# import re
-# from frappe.utils import get_datetime
-# import pprint
-# @frappe.whitelist()
-# def sort_invoice_items(invoice_name):
-# invoice = frappe.get_doc("Sales Invoice", invoice_name)
-# item_list = [item.name for item in invoice.items]
-# print(item_list)
-
-# def extract_datetime(description):
-# match = re.search(r"(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2})", description)
-# if match:
-# return get_datetime(match.group(1))
-# return None # Falls kein Datum gefunden wird.
-
-# # 1️⃣ Ursprüngliche Reihenfolge ausgeben
-# debug_output = ["🔍 Ursprüngliche Reihenfolge:"]
-# for item in invoice.items:
-# debug_output.append(f"IDX: {item.idx}, Name: {item.name}")
-
-# # Listen für sortierbare und unsortierbare Items
-# items_with_date = []
-# items_without_date = []
-
-# for item in invoice.items:
-# item_date = extract_datetime(item.description) if item.description else None
-# if item_date:
-# items_with_date.append((item_date, item))
-# else:
-# items_without_date.append(item)
-
-# # 2️⃣ Debug: Extrahierte Datumswerte ausgeben
-# debug_output.append("\n📅 Extrahierte Datumswerte:")
-# for date, item in items_with_date:
-# debug_output.append(f"Beschreibung: {item.description} → {date}")
-
-# # Sortiere die Items mit Datum nach Datum
-# sorted_items_with_date = sorted(items_with_date, key=lambda x: x[0])
-
-# # Kombiniere sortierte mit unsortierten Items
-# sorted_items = [item for _, item in sorted_items_with_date] + items_without_date
-
-# # 3️⃣ Debug: Geplante Sortierung ausgeben
-# debug_output.append("\n🔄 Neue geplante Reihenfolge:")
-# for idx, item in enumerate(sorted_items, start=1):
-# debug_output.append(f"Neuer IDX: {idx}, Beschreibung: {item.description}")
-
-# # **Direkt neue idx-Werte setzen**
-# for new_idx, item in enumerate(sorted_items, start=1):
-# item.idx = new_idx
-
-# invoice.save()
-# frappe.db.commit()
-
-# # 4️⃣ Debug: Finale Reihenfolge nach Speicherung ausgeben
-# debug_output.append("\n✅ Finale Reihenfolge nach Speicherung:")
-# updated_invoice = frappe.get_doc("Sales Invoice", invoice_name)
-# for item in updated_invoice.items:
-# debug_output.append(f"IDX: {item.idx}, Beschreibung: {item.description}")
-
-# print(debug_output)
+
+def render_card_html(items, item_type):
+ """
+ Generate formatted HTML cards for displaying IT objects or RMM agents.
-# return "Sortierung abgeschlossen. Überprüfe das Log für Details."
+ Args:
+ items: List of dictionaries containing item data
+ item_type: Type of items ("it_object", "tactical", or "agent")
+
+ Returns:
+ Formatted HTML string with cards
+ """
+ html = '''
+
+ '''
+
+ # Group items by type if needed
+ if item_type == "tactical":
+ server_items = [i for i in items if i.get('type', '').lower() == 'server']
+ workstation_items = [i for i in items if i.get('type', '').lower() == 'workstation']
+
+ html += ''
+ if server_items:
+ html += '
Server Systems
'
+ html += ''.join(render_single_card(item) for item in server_items)
+
+ if workstation_items:
+ html += 'Workstation Systems
'
+ html += ''.join(render_single_card(item) for item in workstation_items)
+ html += ''
+ else:
+ html += ''.join(render_single_card(item) for item in items)
+
+ return html
+
+def render_single_card(item):
+ """
+ Generate HTML for a single card.
+
+ Args:
+ item: Dictionary containing item data
+
+ Returns:
+ HTML string for a single card
+ """
+ # Get the basic information
+ title = item.get('title', '')
+ type_value = item.get('type', '').capitalize()
+ ip = item.get('ip', '') or item.get('metadata', {}).get('IP', '') or item.get('metadata', {}).get('Local IPs', '')
+ location = item.get('location', '')
+
+ # Create a header that matches the previous style
+ header = f"{title}"
+ if type_value:
+ header += f" ({type_value})"
+ if ip:
+ header += f" - {ip}"
+
+ html = f'''
+ '
+ return html
+
+@frappe.whitelist()
+def get_documentation_html(it_landscape):
+ """Generate formatted HTML cards for all IT objects in a landscape that are marked for documentation."""
+
+ it_objects = frappe.get_all(
+ "IT Object",
+ filters={
+ "visible_in_documentation": 1,
+ "it_landscape": it_landscape
+ },
+ fields=[
+ "title", "type", "location", "location_full_path",
+ "status", "main_ip", "image", "documentation_text"
+ ]
+ )
+
+ if not it_objects:
+ return "No documented IT objects found in this landscape.
"
+
+ # Prepare items for card rendering
+ items = []
+ for obj in it_objects:
+ items.append({
+ 'title': obj.title,
+ 'type': obj.type,
+ 'ip': obj.main_ip,
+ 'location': obj.location_full_path or obj.location,
+ 'status': obj.status,
+ 'metadata': {
+ 'Type': obj.type,
+ 'Status': obj.status
+ },
+ 'description': obj.documentation_text
+ })
+
+ return render_card_html(items, "it_object")
+
import frappe
import re
@@ -638,7 +788,6 @@ def get_datetime(date_str):
return datetime.strptime(date_str, "%d.%m.%Y %H:%M")
except ValueError:
return None
-
@frappe.whitelist()
def sort_invoice_items(invoice_name):
@@ -679,91 +828,6 @@ def sort_invoice_items(invoice_name):
return True
-# @frappe.whitelist()
-# def sort_invoice_items(invoice_name):
-# # Hole die Rechnung
-# invoice = frappe.get_doc("Sales Invoice", invoice_name)
-
-# # Liste der Item-Namen zur späteren Referenz (falls benötigt)
-# item_list = [item.name for item in invoice.items]
-# print(item_list)
-
-# def extract_datetime(description):
-# print(f"Beschreibung: {description}") # Debug-Ausgabe der Beschreibung
-# match = re.search(r"(\d{2}\.\d{2}\.\d{4} \d{2}:\d{2})", description)
-# if match:
-# print(f"Gefundenes Datum: {match.group(1)}") # Zeige das gefundene Datum an
-# print(get_datetime(match.group(1)))
-# return get_datetime(match.group(1))
-# return None
-
-
-# # 1️⃣ Debug: Ursprüngliche Reihenfolge der Items mit zusätzlicher Information
-# debug_output = ["🔍 Ursprüngliche Reihenfolge:"]
-# for item in invoice.items:
-# item_date = extract_datetime(item.description) if item.description else None
-# debug_output.append(f"Name: {item.name}, Ursprünglicher IDX: {item.idx}, Extrahiertes Datum: {item_date}")
-# print(debug_output)
-
-# # Listen für Items mit und ohne Datum
-# items_with_date = []
-# items_without_date = []
-
-# # Trenne Items mit und ohne Datum
-# for item in invoice.items:
-# item_date = extract_datetime(item.description) if item.description else None
-# if item_date:
-# items_with_date.append((item_date, item.name,item.idx))
-# else:
-# items_without_date.append(item.name, item.idx)
-# print("items unsortiert")
-# print(items_with_date)
-# sorted_items_with_date = sorted(items_with_date, key=lambda x: x[0])
-# print("items sortiert")
- print(sorted_items_with_date)
-
-
-
-
-
-
-
- # # 2️⃣ Debug: Zeige extrahierte Datumswerte
- # debug_output.append("\n📅 Extrahierte Datumswerte:")
- # for date, item in items_with_date:
- # debug_output.append(f"Beschreibung: {item.description} → {date}")
-
- # # Sortiere die Items mit Datum nach dem Datum
- # sorted_items_with_date = sorted(items_with_date, key=lambda x: x[0])
-
- # # Kombiniere die sortierten Items mit den unsortierten Items
- # sorted_items = [item for _, item in sorted_items_with_date] + items_without_date
-
- # # 3️⃣ Debug: Zeige die geplante neue Reihenfolge
- # debug_output.append("\n🔄 Neue geplante Reihenfolge (mit neuen Index):")
- # for new_idx, item in enumerate(sorted_items, start=1):
- # item_date = extract_datetime(item.description) if item.description else None
- # debug_output.append(f"Name: {item.name}, Ursprünglicher IDX: {item.idx}, Extrahiertes Datum: {item_date}, Neuer IDX: {new_idx}")
-
- # # Setze die neuen idx-Werte auf die Items
- # for new_idx, item in enumerate(sorted_items, start=1):
- # item.idx = new_idx
-
- # # Speichere die Rechnung und committe die Änderung in der Datenbank
- # invoice.save()
- # frappe.db.commit()
-
- # # 4️⃣ Debug: Zeige finale Reihenfolge nach Speicherung
- # debug_output.append("\n✅ Finale Reihenfolge nach Speicherung:")
- # updated_invoice = frappe.get_doc("Sales Invoice", invoice_name)
- # for item in updated_invoice.items:
- # debug_output.append(f"IDX: {item.idx}, Beschreibung: {item.description}")
-
- # # Debug-Log ausgeben
- # print(debug_output)
-
- # return "Sortierung abgeschlossen. Überprüfe das Log für Details."
-