diff --git a/msp/fixtures/custom_field.json b/msp/fixtures/custom_field.json index 9492505..da846f0 100644 --- a/msp/fixtures/custom_field.json +++ b/msp/fixtures/custom_field.json @@ -1,38 +1,297 @@ [ { - "name": "Contact-teams_user_id", + "alignment": "", + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "button_color": "", + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Sales Invoice Item", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "supply_subscription", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 1, + "insert_after": "description", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Supply Subscription", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2026-04-14 09:07:51.038936", + "module": "MSP", + "name": "Sales Invoice Item-supply_subscription", + "no_copy": 0, + "non_negative": 0, + "options": "Supply Subscription", + "permlevel": 0, + "placeholder": null, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "alignment": "", + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "button_color": "", + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Delivery Note Item", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "supply_subscription", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 1, + "insert_after": "description", + "is_system_generated": 1, + "is_virtual": 0, + "label": "Supply Subscription", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2026-04-14 09:07:51.230497", + "module": "MSP", + "name": "Delivery Note Item-supply_subscription", + "no_copy": 0, + "non_negative": 0, + "options": "Supply Subscription", + "permlevel": 0, + "placeholder": null, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null + }, + { + "alignment": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "button_color": null, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": null, + "description": "MS Graph User-ID fuer Chat-Zuordnung", + "docstatus": 0, "doctype": "Custom Field", "dt": "Contact", - "module": "MSP", + "fetch_from": null, + "fetch_if_empty": 0, "fieldname": "teams_user_id", "fieldtype": "Data", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "department", + "is_system_generated": 0, + "is_virtual": 0, "label": "Teams User ID", - "description": "MS Graph User-ID fuer Chat-Zuordnung", - "insert_after": "department" + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2026-04-14 09:07:08.458841", + "module": "MSP", + "name": "Contact-teams_user_id", + "no_copy": 0, + "non_negative": 0, + "options": null, + "permlevel": 0, + "placeholder": null, + "precision": null, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null }, { - "name": "Contact-is_external_contractor", + "alignment": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "button_color": null, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": "0", + "depends_on": null, + "description": "Dienstleister (z.B. Solutions Factory)", + "docstatus": 0, "doctype": "Custom Field", "dt": "Contact", - "module": "MSP", + "fetch_from": null, + "fetch_if_empty": 0, "fieldname": "is_external_contractor", "fieldtype": "Check", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "teams_user_id", + "is_system_generated": 0, + "is_virtual": 0, "label": "Is External Contractor", - "description": "Dienstleister (z.B. Solutions Factory)", - "default": "0", - "insert_after": "teams_user_id" + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2026-04-14 09:07:08.493826", + "module": "MSP", + "name": "Contact-is_external_contractor", + "no_copy": 0, + "non_negative": 0, + "options": null, + "permlevel": 0, + "placeholder": null, + "precision": null, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null }, { - "name": "Contact-contractor_for", + "alignment": null, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "button_color": null, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "default": null, + "depends_on": "eval:doc.is_external_contractor", + "description": "Deren Arbeit zaehlt als Kundenzeit", + "docstatus": 0, "doctype": "Custom Field", "dt": "Contact", - "module": "MSP", + "fetch_from": null, + "fetch_if_empty": 0, "fieldname": "contractor_for", "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "is_external_contractor", + "is_system_generated": 0, + "is_virtual": 0, "label": "Contractor For", + "length": 0, + "link_filters": null, + "mandatory_depends_on": null, + "modified": "2026-04-14 09:07:08.528954", + "module": "MSP", + "name": "Contact-contractor_for", + "no_copy": 0, + "non_negative": 0, "options": "Customer", - "description": "Deren Arbeit zaehlt als Kundenzeit", - "depends_on": "eval:doc.is_external_contractor", - "insert_after": "is_external_contractor" + "permlevel": 0, + "placeholder": null, + "precision": null, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "read_only_depends_on": null, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "show_dashboard": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0, + "width": null } -] +] \ No newline at end of file diff --git a/msp/msp/doctype/supplier_import_line/__init__.py b/msp/msp/doctype/supplier_import_line/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/supplier_import_line/supplier_import_line.json b/msp/msp/doctype/supplier_import_line/supplier_import_line.json new file mode 100644 index 0000000..37d2cc1 --- /dev/null +++ b/msp/msp/doctype/supplier_import_line/supplier_import_line.json @@ -0,0 +1,180 @@ +{ + "actions": [], + "allow_rename": 0, + "creation": "2026-04-14 09:12:00.000000", + "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", + "istable": 1, + "field_order": [ + "source_row", + "customer", + "customer_external_ref", + "item", + "vendor_product_id", + "qty", + "rate", + "amount", + "period_start", + "period_end", + "subscription_external_id", + "subscription_start", + "billing_plan", + "term_duration", + "booking_type", + "marketplace_ref", + "order_ref", + "target_doc_type", + "target_doc_name", + "target_doc_row", + "line_status", + "error_message", + "raw_payload" + ], + "fields": [ + { + "fieldname": "source_row", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Source Row", + "description": "Zeilennummer in der Quelldatei" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer" + }, + { + "fieldname": "customer_external_ref", + "fieldtype": "Data", + "label": "Customer External Ref" + }, + { + "fieldname": "item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "options": "Item" + }, + { + "fieldname": "vendor_product_id", + "fieldtype": "Data", + "label": "Vendor Product ID" + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty" + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount" + }, + { + "fieldname": "period_start", + "fieldtype": "Date", + "label": "Period Start" + }, + { + "fieldname": "period_end", + "fieldtype": "Date", + "label": "Period End" + }, + { + "fieldname": "subscription_external_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Subscription ID" + }, + { + "fieldname": "subscription_start", + "fieldtype": "Date", + "label": "Subscription Start" + }, + { + "fieldname": "billing_plan", + "fieldtype": "Select", + "label": "Billing Plan", + "options": "\nMonthly\nAnnual\nTriennial" + }, + { + "fieldname": "term_duration", + "fieldtype": "Select", + "label": "Term Duration", + "options": "\nP1M\nP1Y\nP3Y" + }, + { + "fieldname": "booking_type", + "fieldtype": "Select", + "label": "Booking Type", + "options": "\nNew\nRenewal\nBilling\nUpgrade\nCancel" + }, + { + "fieldname": "marketplace_ref", + "fieldtype": "Data", + "label": "Marketplace Ref" + }, + { + "fieldname": "order_ref", + "fieldtype": "Data", + "label": "Order Ref" + }, + { + "fieldname": "target_doc_type", + "fieldtype": "Select", + "label": "Target DocType", + "options": "\nSales Invoice\nDelivery Note" + }, + { + "fieldname": "target_doc_name", + "fieldtype": "Dynamic Link", + "options": "target_doc_type", + "label": "Target Document", + "in_list_view": 1 + }, + { + "fieldname": "target_doc_row", + "fieldtype": "Data", + "label": "Target Row" + }, + { + "fieldname": "line_status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "options": "Pending\nCreated\nSkipped\nError", + "default": "Pending" + }, + { + "fieldname": "error_message", + "fieldtype": "Small Text", + "label": "Error Message" + }, + { + "fieldname": "raw_payload", + "fieldtype": "Code", + "label": "Raw Payload", + "options": "JSON" + } + ], + "index_web_pages_for_search": 0, + "modified": "2026-04-14 09:12:00.000000", + "modified_by": "Administrator", + "module": "MSP", + "name": "Supplier Import Line", + "owner": "Administrator", + "permissions": [], + "sort_field": "idx", + "sort_order": "ASC", + "states": [], + "track_changes": 0 +} diff --git a/msp/msp/doctype/supplier_import_line/supplier_import_line.py b/msp/msp/doctype/supplier_import_line/supplier_import_line.py new file mode 100644 index 0000000..624dfd8 --- /dev/null +++ b/msp/msp/doctype/supplier_import_line/supplier_import_line.py @@ -0,0 +1,5 @@ +from frappe.model.document import Document + + +class SupplierImportLine(Document): + pass diff --git a/msp/msp/doctype/supplier_import_profile/__init__.py b/msp/msp/doctype/supplier_import_profile/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/supplier_import_profile/supplier_import_profile.json b/msp/msp/doctype/supplier_import_profile/supplier_import_profile.json new file mode 100644 index 0000000..446b7d6 --- /dev/null +++ b/msp/msp/doctype/supplier_import_profile/supplier_import_profile.json @@ -0,0 +1,221 @@ +{ + "actions": [], + "allow_rename": 0, + "autoname": "field:profile_name", + "creation": "2026-04-14 09:10:00.000000", + "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "profile_name", + "enabled", + "column_break_header", + "supplier", + "vendor", + "parser_section", + "parser_key", + "file_type", + "column_break_parser", + "default_output_mode", + "list_price_discount_pct", + "defaults_section", + "default_company", + "default_cost_center", + "default_warehouse", + "column_break_defaults", + "default_taxes_and_charges", + "default_payment_terms_template", + "default_tc_name", + "text_section", + "title_prefix", + "title_prefix_credit_note", + "intro_text_invoice", + "intro_text_credit_note", + "fallback_section", + "fallback_customer" + ], + "fields": [ + { + "fieldname": "profile_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Profile Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "enabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Enabled", + "default": "1" + }, + { + "fieldname": "column_break_header", + "fieldtype": "Column Break" + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Supplier", + "options": "Supplier", + "reqd": 1 + }, + { + "fieldname": "vendor", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Vendor", + "options": "\nMicrosoft\nG DATA\nHornetsecurity\nCodeTwo\nOther" + }, + { + "fieldname": "parser_section", + "fieldtype": "Section Break", + "label": "Parser" + }, + { + "fieldname": "parser_key", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Parser Key", + "reqd": 1, + "description": "Technischer Schlüssel, der auf die Python-Parser-Klasse verweist (z. B. adn_monthly_csv_v1)" + }, + { + "fieldname": "file_type", + "fieldtype": "Select", + "label": "File Type", + "options": "CSV\nExcel\nJSON\nAPI", + "default": "CSV" + }, + { + "fieldname": "column_break_parser", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_output_mode", + "fieldtype": "Select", + "label": "Default Output Mode", + "options": "Sales Invoice\nDelivery Note", + "default": "Sales Invoice", + "reqd": 1 + }, + { + "fieldname": "list_price_discount_pct", + "fieldtype": "Float", + "label": "Listen­preis-Rabatt (%)", + "description": "Standardrabatt auf Listenpreise für diesen Lieferanten (ersetzt Magic-Number, z. B. 17,5)" + }, + { + "fieldname": "defaults_section", + "fieldtype": "Section Break", + "label": "Dokument-Defaults" + }, + { + "fieldname": "default_company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "default_cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "default_warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse", + "description": "Für Delivery Notes erforderlich" + }, + { + "fieldname": "column_break_defaults", + "fieldtype": "Column Break" + }, + { + "fieldname": "default_taxes_and_charges", + "fieldtype": "Link", + "label": "Taxes and Charges", + "options": "Sales Taxes and Charges Template" + }, + { + "fieldname": "default_payment_terms_template", + "fieldtype": "Link", + "label": "Payment Terms Template", + "options": "Payment Terms Template" + }, + { + "fieldname": "default_tc_name", + "fieldtype": "Link", + "label": "Terms and Conditions", + "options": "Terms and Conditions" + }, + { + "fieldname": "text_section", + "fieldtype": "Section Break", + "label": "Titel & Texte" + }, + { + "fieldname": "title_prefix", + "fieldtype": "Data", + "label": "Title Prefix (Invoice)", + "description": "z. B. \"Abrechnung Microsoft Abo \" — Monat/Jahr wird beim Import angehängt" + }, + { + "fieldname": "title_prefix_credit_note", + "fieldtype": "Data", + "label": "Title Prefix (Credit Note)" + }, + { + "fieldname": "intro_text_invoice", + "fieldtype": "Text Editor", + "label": "Intro Text (Invoice)" + }, + { + "fieldname": "intro_text_credit_note", + "fieldtype": "Text Editor", + "label": "Intro Text (Credit Note)" + }, + { + "fieldname": "fallback_section", + "fieldtype": "Section Break", + "label": "Fallback" + }, + { + "fieldname": "fallback_customer", + "fieldtype": "Link", + "label": "Fallback Customer", + "options": "Customer", + "description": "Wird verwendet, wenn der Endkunde in der Datei nicht im ERP gefunden wird" + } + ], + "index_web_pages_for_search": 0, + "modified": "2026-04-14 09:10:00.000000", + "modified_by": "Administrator", + "module": "MSP", + "name": "Supplier Import Profile", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/msp/msp/doctype/supplier_import_profile/supplier_import_profile.py b/msp/msp/doctype/supplier_import_profile/supplier_import_profile.py new file mode 100644 index 0000000..a0ba644 --- /dev/null +++ b/msp/msp/doctype/supplier_import_profile/supplier_import_profile.py @@ -0,0 +1,8 @@ +from frappe.model.document import Document + + +class SupplierImportProfile(Document): + def validate(self): + if self.default_output_mode == "Delivery Note" and not self.default_warehouse: + from frappe import throw, _ + throw(_('Für Output Mode „Delivery Note“ muss ein Default Warehouse gesetzt sein.')) diff --git a/msp/msp/doctype/supplier_import_run/__init__.py b/msp/msp/doctype/supplier_import_run/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/supplier_import_run/supplier_import_run.json b/msp/msp/doctype/supplier_import_run/supplier_import_run.json new file mode 100644 index 0000000..b0324e4 --- /dev/null +++ b/msp/msp/doctype/supplier_import_run/supplier_import_run.json @@ -0,0 +1,184 @@ +{ + "actions": [], + "allow_rename": 0, + "autoname": "naming_series:", + "creation": "2026-04-14 09:15:00.000000", + "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "profile", + "status", + "column_break_head", + "file", + "output_mode", + "acknowledge_format_drift", + "start_import", + "timing_section", + "started_at", + "finished_at", + "column_break_timing", + "line_count", + "document_count", + "error_count", + "log_section", + "log", + "lines_section", + "lines" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "IMPRUN-.YYYY.-", + "reqd": 1 + }, + { + "fieldname": "profile", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Profile", + "options": "Supplier Import Profile", + "reqd": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Pending\nRunning\nSucceeded\nPartial\nFailed", + "default": "Pending", + "read_only": 1 + }, + { + "fieldname": "column_break_head", + "fieldtype": "Column Break" + }, + { + "fieldname": "file", + "fieldtype": "Attach", + "label": "Source File", + "reqd": 1 + }, + { + "fieldname": "output_mode", + "fieldtype": "Select", + "label": "Output Mode (Override)", + "options": "\nSales Invoice\nDelivery Note", + "description": "Leer = Profil-Default verwenden. Je Kunde kann Customer.billing_mode weiterhin auf Delivery Note zwingen." + }, + { + "fieldname": "acknowledge_format_drift", + "fieldtype": "Check", + "label": "Abweichendes Format akzeptieren", + "description": "Wenn gesetzt, werden Format-Warnungen vom Parser nur geloggt und nicht als Fehler behandelt" + }, + { + "fieldname": "start_import", + "fieldtype": "Button", + "label": "Start Import", + "options": "do_import" + }, + { + "fieldname": "timing_section", + "fieldtype": "Section Break", + "label": "Timing & Statistik" + }, + { + "fieldname": "started_at", + "fieldtype": "Datetime", + "label": "Started At", + "read_only": 1 + }, + { + "fieldname": "finished_at", + "fieldtype": "Datetime", + "label": "Finished At", + "read_only": 1 + }, + { + "fieldname": "column_break_timing", + "fieldtype": "Column Break" + }, + { + "fieldname": "line_count", + "fieldtype": "Int", + "label": "Lines", + "read_only": 1 + }, + { + "fieldname": "document_count", + "fieldtype": "Int", + "label": "Documents Created", + "read_only": 1 + }, + { + "fieldname": "error_count", + "fieldtype": "Int", + "label": "Errors", + "read_only": 1 + }, + { + "fieldname": "log_section", + "fieldtype": "Section Break", + "label": "Log", + "collapsible": 1 + }, + { + "fieldname": "log", + "fieldtype": "Long Text", + "label": "Log", + "read_only": 1 + }, + { + "fieldname": "lines_section", + "fieldtype": "Section Break", + "label": "Lines", + "collapsible": 1 + }, + { + "fieldname": "lines", + "fieldtype": "Table", + "label": "Lines", + "options": "Supplier Import Line" + } + ], + "index_web_pages_for_search": 0, + "modified": "2026-04-14 09:15:00.000000", + "modified_by": "Administrator", + "module": "MSP", + "name": "Supplier Import Run", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/msp/msp/doctype/supplier_import_run/supplier_import_run.py b/msp/msp/doctype/supplier_import_run/supplier_import_run.py new file mode 100644 index 0000000..0dda9ee --- /dev/null +++ b/msp/msp/doctype/supplier_import_run/supplier_import_run.py @@ -0,0 +1,11 @@ +import frappe +from frappe.model.document import Document + + +class SupplierImportRun(Document): + @frappe.whitelist() + def do_import(self): + """Delegiert den eigentlichen Import an msp.importers.run_orchestrator. + Die Implementation folgt in Phase 1; hier nur die Einstiegsstelle.""" + from msp.importers.run_orchestrator import execute_run + return execute_run(self) diff --git a/msp/msp/doctype/supply_subscription/__init__.py b/msp/msp/doctype/supply_subscription/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/supply_subscription/supply_subscription.json b/msp/msp/doctype/supply_subscription/supply_subscription.json new file mode 100644 index 0000000..d28fa8e --- /dev/null +++ b/msp/msp/doctype/supply_subscription/supply_subscription.json @@ -0,0 +1,213 @@ +{ + "actions": [], + "allow_rename": 0, + "autoname": "format:SUB-{external_id}", + "creation": "2026-04-14 09:00:00.000000", + "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "title", + "external_id", + "status", + "column_break_basics", + "customer", + "supplier", + "vendor", + "product_section", + "item", + "item_name", + "vendor_product_id", + "column_break_product", + "billing_plan", + "term_duration", + "qty_current", + "timeline_section", + "start_date", + "current_period_end", + "column_break_timeline", + "last_event_date", + "last_booking_type" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "read_only": 1 + }, + { + "fieldname": "external_id", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "External Subscription ID", + "reqd": 1, + "unique": 1, + "search_index": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Active\nLapsed\nCancelled", + "default": "Active" + }, + { + "fieldname": "column_break_basics", + "fieldtype": "Column Break" + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Customer", + "options": "Customer", + "reqd": 1 + }, + { + "fieldname": "supplier", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "vendor", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Vendor", + "options": "\nMicrosoft\nG DATA\nHornetsecurity\nCodeTwo\nOther" + }, + { + "fieldname": "product_section", + "fieldtype": "Section Break", + "label": "Product" + }, + { + "fieldname": "item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "options": "Item" + }, + { + "fetch_from": "item.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, + { + "fieldname": "vendor_product_id", + "fieldtype": "Data", + "in_standard_filter": 1, + "label": "Vendor Product ID", + "search_index": 1, + "description": "z. B. Microsoft Commercial Product ID (CFQ7TTC0...)" + }, + { + "fieldname": "column_break_product", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_plan", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Billing Plan", + "options": "\nMonthly\nAnnual\nTriennial" + }, + { + "fieldname": "term_duration", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Term Duration", + "options": "\nP1M\nP1Y\nP3Y" + }, + { + "fieldname": "qty_current", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Qty (current)" + }, + { + "fieldname": "timeline_section", + "fieldtype": "Section Break", + "label": "Laufzeit" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Subscription Start", + "description": "Ursprüngliches Anlagedatum beim Lieferanten" + }, + { + "fieldname": "current_period_end", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Aktuelles Periodenende", + "description": "Ende der zuletzt abgerechneten Periode" + }, + { + "fieldname": "column_break_timeline", + "fieldtype": "Column Break" + }, + { + "fieldname": "last_event_date", + "fieldtype": "Date", + "label": "Letzte Buchung", + "read_only": 1 + }, + { + "fieldname": "last_booking_type", + "fieldtype": "Data", + "label": "Letzter Buchungstyp", + "read_only": 1 + } + ], + "index_web_pages_for_search": 0, + "links": [ + { + "group": "Events", + "link_doctype": "Supply Subscription Event", + "link_fieldname": "supply_subscription" + } + ], + "modified": "2026-04-14 09:00:00.000000", + "modified_by": "Administrator", + "module": "MSP", + "name": "Supply Subscription", + "naming_rule": "Expression", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/msp/msp/doctype/supply_subscription/supply_subscription.py b/msp/msp/doctype/supply_subscription/supply_subscription.py new file mode 100644 index 0000000..00c63fb --- /dev/null +++ b/msp/msp/doctype/supply_subscription/supply_subscription.py @@ -0,0 +1,13 @@ +import frappe +from frappe.model.document import Document + + +class SupplySubscription(Document): + def before_save(self): + self.title = self._compose_title() + + def _compose_title(self) -> str: + parts = [self.customer or "?", self.item_name or self.item or self.vendor_product_id or "?"] + if self.vendor: + parts.append(self.vendor) + return " · ".join(p for p in parts if p) diff --git a/msp/msp/doctype/supply_subscription_event/__init__.py b/msp/msp/doctype/supply_subscription_event/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/msp/msp/doctype/supply_subscription_event/supply_subscription_event.json b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.json new file mode 100644 index 0000000..43442d4 --- /dev/null +++ b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.json @@ -0,0 +1,201 @@ +{ + "actions": [], + "allow_rename": 0, + "autoname": "naming_series:", + "creation": "2026-04-14 09:00:00.000000", + "doctype": "DocType", + "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "supply_subscription", + "booking_type", + "column_break_head", + "customer", + "supplier", + "period_section", + "period_start", + "period_end", + "column_break_period", + "qty", + "rate", + "amount", + "link_section", + "sales_invoice", + "sales_invoice_item_row", + "column_break_link", + "delivery_note", + "delivery_note_item_row", + "purchase_invoice", + "supplier_import_line" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "SUBE-.YYYY.-", + "reqd": 1 + }, + { + "fieldname": "supply_subscription", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Supply Subscription", + "options": "Supply Subscription", + "reqd": 1 + }, + { + "fieldname": "booking_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Booking Type", + "options": "\nNew\nRenewal\nBilling\nUpgrade\nCancel" + }, + { + "fieldname": "column_break_head", + "fieldtype": "Column Break" + }, + { + "fetch_from": "supply_subscription.customer", + "fieldname": "customer", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Customer", + "options": "Customer", + "read_only": 1 + }, + { + "fetch_from": "supply_subscription.supplier", + "fieldname": "supplier", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Supplier", + "options": "Supplier", + "read_only": 1 + }, + { + "fieldname": "period_section", + "fieldtype": "Section Break", + "label": "Zeitraum & Mengen" + }, + { + "fieldname": "period_start", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Period Start", + "reqd": 1 + }, + { + "fieldname": "period_end", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Period End", + "reqd": 1 + }, + { + "fieldname": "column_break_period", + "fieldtype": "Column Break" + }, + { + "fieldname": "qty", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Qty" + }, + { + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate" + }, + { + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount" + }, + { + "fieldname": "link_section", + "fieldtype": "Section Break", + "label": "Verknüpfungen" + }, + { + "fieldname": "sales_invoice", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Sales Invoice", + "options": "Sales Invoice" + }, + { + "fieldname": "sales_invoice_item_row", + "fieldtype": "Data", + "label": "Sales Invoice Item (row)", + "description": "name der Zeile in tabSales Invoice Item" + }, + { + "fieldname": "column_break_link", + "fieldtype": "Column Break" + }, + { + "fieldname": "delivery_note", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Delivery Note", + "options": "Delivery Note" + }, + { + "fieldname": "delivery_note_item_row", + "fieldtype": "Data", + "label": "Delivery Note Item (row)" + }, + { + "fieldname": "purchase_invoice", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Purchase Invoice", + "options": "Purchase Invoice" + }, + { + "fieldname": "supplier_import_line", + "fieldtype": "Data", + "label": "Supplier Import Line (row)", + "description": "Rückverweis auf die Zeile im Supplier Import Run" + } + ], + "index_web_pages_for_search": 0, + "modified": "2026-04-14 09:00:00.000000", + "modified_by": "Administrator", + "module": "MSP", + "name": "Supply Subscription Event", + "naming_rule": "By \"Naming Series\" field", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1 + } + ], + "sort_field": "period_end", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/msp/msp/doctype/supply_subscription_event/supply_subscription_event.py b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.py new file mode 100644 index 0000000..ba6f50d --- /dev/null +++ b/msp/msp/doctype/supply_subscription_event/supply_subscription_event.py @@ -0,0 +1,36 @@ +import frappe +from frappe.model.document import Document + + +class SupplySubscriptionEvent(Document): + def on_update(self): + self._refresh_subscription_denormals() + + def on_trash(self): + self._refresh_subscription_denormals() + + def _refresh_subscription_denormals(self): + """Aktualisiert last_event_date / last_booking_type / current_period_end + auf der übergeordneten Supply Subscription auf Basis des letzten Events.""" + if not self.supply_subscription: + return + latest = frappe.db.get_all( + "Supply Subscription Event", + filters={"supply_subscription": self.supply_subscription}, + fields=["period_end", "booking_type", "qty"], + order_by="period_end desc", + limit=1, + ) + if not latest: + frappe.db.set_value("Supply Subscription", self.supply_subscription, { + "last_event_date": None, + "last_booking_type": None, + }) + return + row = latest[0] + frappe.db.set_value("Supply Subscription", self.supply_subscription, { + "last_event_date": row["period_end"], + "last_booking_type": row["booking_type"], + "current_period_end": row["period_end"], + "qty_current": row.get("qty") or 0, + })