diff --git a/application/ManualInvoice/ManualInvoiceController.php b/application/ManualInvoice/ManualInvoiceController.php
new file mode 100644
index 000000000..91b019515
--- /dev/null
+++ b/application/ManualInvoice/ManualInvoiceController.php
@@ -0,0 +1,22 @@
+ 'invoiceNumber', 'text' => 'Rechnungsnr.', 'table' => ['sortable' => true, 'filter' => 'search']],
+ ['key' => 'customerName', 'text' => 'Kunde', 'table' => ['sortable' => true, 'filter' => 'search']],
+ ['key' => 'invoiceDate', 'text' => 'Datum', 'table' => ['sortable' => true, 'filter' => 'date']],
+ ['key' => 'totalAmount', 'text' => 'Betrag', 'table' => ['sortable' => true, 'formatter' => 'formatPrice']],
+ ['key' => 'status', 'text' => 'Status', 'table' => ['filter' => 'select', 'filterOptions' => [
+ ['value' => 'draft', 'text' => 'Entwurf'],
+ ['value' => 'sent', 'text' => 'Gesendet'],
+ ['value' => 'paid', 'text' => 'Bezahlt'],
+ ]]],
+ ['key' => 'actions', 'text' => 'Aktionen', 'modal' => false, 'table' => ['filter' => false, 'sortable' => false]],
+ ];
+ //@formatter:on
+}
\ No newline at end of file
diff --git a/application/ManualInvoice/ManualInvoiceModel.php b/application/ManualInvoice/ManualInvoiceModel.php
new file mode 100644
index 000000000..3f3eb170d
--- /dev/null
+++ b/application/ManualInvoice/ManualInvoiceModel.php
@@ -0,0 +1,186 @@
+ 1, 'invoiceNumber' => 'RE-2025-001', 'customerName' => 'Musterfirma GmbH', 'billingAddressId' => 1,
+ 'invoiceDate' => strtotime('2025-09-11'), 'dueDate' => strtotime('2025-09-25'), 'totalAmount' => 948.00, 'status' => 'paid',
+ 'positions' => json_encode([
+ ['product_name' => 'IT-Support-Stunden', 'product_info' => 'Remote-Hilfe für Mitarbeiter', 'start_date' => '2025-09-10', 'end_date' => '2025-09-10', 'amount' => 2, 'price' => 120.00, 'vatrate' => 20],
+ ['product_name' => 'Netzwerk-Switch 24-Port', 'product_info' => 'Modell: XYZ-24G', 'start_date' => '2025-09-10', 'end_date' => '2025-09-10', 'amount' => 1, 'price' => 550.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Vielen Dank für Ihren Auftrag.', 'taxText' => ''
+ ],
+ [
+ 'id' => 2, 'invoiceNumber' => 'RE-2025-002', 'customerName' => 'Beispiel AG', 'billingAddressId' => 2,
+ 'invoiceDate' => strtotime('2025-09-14'), 'dueDate' => strtotime('2025-09-28'), 'totalAmount' => 720.00, 'status' => 'sent',
+ 'positions' => json_encode([
+ ['product_name' => 'Beratung Digitalisierungsstrategie', 'product_info' => 'Workshop am 05.09.2025', 'start_date' => '2025-09-05', 'end_date' => '2025-09-05', 'amount' => 4, 'price' => 150.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Wir freuen uns auf eine weiterhin gute Zusammenarbeit.', 'taxText' => ''
+ ],
+ [
+ 'id' => 3, 'invoiceNumber' => 'RE-2025-003', 'customerName' => 'John Doe Services', 'billingAddressId' => 3,
+ 'invoiceDate' => strtotime('2025-09-16'), 'dueDate' => strtotime('2025-09-30'), 'totalAmount' => 912.00, 'status' => 'draft',
+ 'positions' => json_encode([
+ ['product_name' => 'Kabelverlegung LWL', 'product_info' => 'Inhouse-Verkabelung Bürogebäude', 'start_date' => '2025-09-15', 'end_date' => '2025-09-15', 'amount' => 8, 'price' => 85.00, 'vatrate' => 20],
+ ['product_name' => 'LWL-Kabel 8 Fasern', 'product_info' => 'Pro Meter', 'start_date' => '2025-09-15', 'end_date' => '2025-09-15', 'amount' => 100, 'price' => 0.80, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Bei Fragen stehen wir Ihnen gerne zur Verfügung.', 'taxText' => ''
+ ],
+ [
+ 'id' => 4, 'invoiceNumber' => 'RE-2025-004', 'customerName' => 'Bau & Co KG', 'billingAddressId' => 4,
+ 'invoiceDate' => strtotime('2025-09-06'), 'dueDate' => strtotime('2025-09-20'), 'totalAmount' => 1890.00, 'status' => 'paid',
+ 'positions' => json_encode([
+ ['product_name' => 'Netzwerk-Grundinstallation Baustelle', 'product_info' => 'Containerdorf Einrichtung', 'start_date' => '2025-09-02', 'end_date' => '2025-09-02', 'amount' => 1, 'price' => 1200.00, 'vatrate' => 20],
+ ['product_name' => 'Stunden Elektriker', 'product_info' => 'Anpassungen Verteilerkasten', 'start_date' => '2025-09-02', 'end_date' => '2025-09-02', 'amount' => 5, 'price' => 75.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Vielen Dank für Ihren Auftrag.', 'taxText' => ''
+ ],
+ [
+ 'id' => 5, 'invoiceNumber' => 'RE-2025-005', 'customerName' => 'Creative Solutions', 'billingAddressId' => 5,
+ 'invoiceDate' => strtotime('2025-09-15'), 'dueDate' => strtotime('2025-09-29'), 'totalAmount' => 1920.00, 'status' => 'sent',
+ 'positions' => json_encode([
+ ['product_name' => 'Web-Entwicklung', 'product_info' => 'Umsetzung Landingpage "Herbst-Aktion"', 'start_date' => '2025-09-01', 'end_date' => '2025-09-12', 'amount' => 10, 'price' => 110.00, 'vatrate' => 20],
+ ['product_name' => 'Domain-Registrierung (.at)', 'product_info' => 'herbst-aktion.at', 'start_date' => '2025-09-01', 'end_date' => '2026-08-31', 'amount' => 1, 'price' => 500.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Wir freuen uns auf eine weiterhin gute Zusammenarbeit.', 'taxText' => ''
+ ],
+ [
+ 'id' => 6, 'invoiceNumber' => 'RE-2025-006', 'customerName' => 'Logistik Express', 'billingAddressId' => 6,
+ 'invoiceDate' => strtotime('2025-08-28'), 'dueDate' => strtotime('2025-09-11'), 'totalAmount' => 3432.00, 'status' => 'paid',
+ 'positions' => json_encode([
+ ['product_name' => 'Software-Lizenz WMS Pro', 'product_info' => 'Jahreslizenz für 10 User', 'start_date' => '2025-09-01', 'end_date' => '2026-08-31', 'amount' => 1, 'price' => 2500.00, 'vatrate' => 20],
+ ['product_name' => 'Mitarbeiterschulung WMS', 'product_info' => 'Vor Ort am 27.08.2025', 'start_date' => '2025-08-27', 'end_date' => '2025-08-27', 'amount' => 4, 'price' => 90.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Vielen Dank für Ihren Auftrag.', 'taxText' => ''
+ ],
+ [
+ 'id' => 7, 'invoiceNumber' => 'RE-2025-007', 'customerName' => 'Gastro Profi', 'billingAddressId' => 7,
+ 'invoiceDate' => strtotime('2025-09-10'), 'dueDate' => strtotime('2025-09-24'), 'totalAmount' => 2577.60, 'status' => 'draft',
+ 'positions' => json_encode([
+ ['product_name' => 'Kassensystem "GastroTouch"', 'product_info' => '2x Terminal, 1x Bondrucker', 'start_date' => '2025-09-09', 'end_date' => '2025-09-09', 'amount' => 2, 'price' => 899.00, 'vatrate' => 20],
+ ['product_name' => 'Installationspauschale', 'product_info' => 'Inkl. Einschulung', 'start_date' => '2025-09-09', 'end_date' => '2025-09-09', 'amount' => 1, 'price' => 350.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Bei Fragen stehen wir Ihnen gerne zur Verfügung.', 'taxText' => ''
+ ],
+ [
+ 'id' => 8, 'invoiceNumber' => 'RE-2025-008', 'customerName' => 'Sicherheitsdienst Huber', 'billingAddressId' => 8,
+ 'invoiceDate' => strtotime('2025-09-01'), 'dueDate' => strtotime('2025-09-15'), 'totalAmount' => 1782.00, 'status' => 'sent',
+ 'positions' => json_encode([
+ ['product_name' => 'IP Kamera 4K Dome', 'product_info' => 'Modell SEC-4K-D', 'start_date' => '2025-08-29', 'end_date' => '2025-08-29', 'amount' => 8, 'price' => 180.00, 'vatrate' => 20],
+ ['product_name' => 'Monatliche Wartungspauschale', 'product_info' => 'September 2025', 'start_date' => '2025-09-01', 'end_date' => '2025-09-30', 'amount' => 1, 'price' => 45.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Wir freuen uns auf eine weiterhin gute Zusammenarbeit.', 'taxText' => ''
+ ],
+ [
+ 'id' => 9, 'invoiceNumber' => 'RE-2025-009', 'customerName' => 'Praxis Dr. Eder', 'billingAddressId' => 9,
+ 'invoiceDate' => strtotime('2025-09-12'), 'dueDate' => strtotime('2025-09-26'), 'totalAmount' => 3090.00, 'status' => 'draft',
+ 'positions' => json_encode([
+ ['product_name' => 'Arbeitsstunden IT-Migration', 'product_info' => 'Serverumzug und Client-Setup', 'start_date' => '2025-09-11', 'end_date' => '2025-09-11', 'amount' => 5, 'price' => 95.00, 'vatrate' => 20],
+ ['product_name' => 'Server-Hardware "MedServ"', 'product_info' => 'Spez. für Arztpraxen', 'start_date' => '2025-09-11', 'end_date' => '2025-09-11', 'amount' => 1, 'price' => 1800.00, 'vatrate' => 20],
+ ['product_name' => 'Datensicherungslösung "CloudSafe"', 'product_info' => 'Einrichtungspauschale', 'start_date' => '2025-09-11', 'end_date' => '2025-09-11', 'amount' => 1, 'price' => 300.00, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Bei Fragen stehen wir Ihnen gerne zur Verfügung.', 'taxText' => ''
+ ],
+ [
+ 'id' => 10, 'invoiceNumber' => 'RE-2025-010', 'customerName' => 'Architekturbüro Planweit', 'billingAddressId' => 10,
+ 'invoiceDate' => strtotime('2025-09-08'), 'dueDate' => strtotime('2025-09-22'), 'totalAmount' => 357.60, 'status' => 'paid',
+ 'positions' => json_encode([
+ ['product_name' => 'Plotter Service', 'product_info' => 'Wartung und Reinigung', 'start_date' => '2025-09-04', 'end_date' => '2025-09-04', 'amount' => 1, 'price' => 250.00, 'vatrate' => 20],
+ ['product_name' => 'Netzwerkkabel Cat7', 'product_info' => 'Pro Meter', 'start_date' => '2025-09-04', 'end_date' => '2025-09-04', 'amount' => 40, 'price' => 1.20, 'vatrate' => 20],
+ ]),
+ 'closingText' => 'Vielen Dank für Ihren Auftrag.', 'taxText' => ''
+ ],
+];
+
+return $mockData;
+}
+
+
+class ManualInvoiceModel extends TTCrudBaseModel {
+ public int $id;
+ public ?string $invoiceNumber;
+ public ?int $invoiceDate;
+ public ?int $dueDate;
+ public int $billingAddressId;
+ public ?string $customerName;
+ public ?float $totalAmount;
+ public string $status;
+ public string $positions;
+ public string $closingText;
+ public string $taxText;
+
+ private static function applyFilter(array $data, array $filter): array {
+ if (empty($filter)) {
+ return $data;
+ }
+ return array_filter($data, function ($row) use ($filter) {
+ foreach ($filter as $key => $value) {
+ if (!isset($row[$key]) || empty($value)) {
+ continue;
+ }
+ if (is_array($value)) { // Handle date ranges
+ if (isset($value['from']) && $row[$key] < $value['from']) return false;
+ if (isset($value['to']) && $row[$key] > $value['to']) return false;
+ } else if (is_array($row[$key])) {
+ if (!in_array($value, $row[$key])) return false;
+ } else if (stripos($row[$key], $value) === false) {
+ return false;
+ }
+ }
+ return true;
+ });
+ }
+
+ public static function getAll($filter = [], $limit = null, $offset = 0, $order = ["key" => null]): array
+
+ {
+ $mockData = getMockData();
+ $filteredData = self::applyFilter($mockData, $filter);
+
+ if ($order['key'] !== null) {
+ usort($filteredData, function ($a, $b) use ($order) {
+ if ($a[$order['key']] == $b[$order['key']]) return 0;
+ if ($order['order'] === 'ASC') {
+ return $a[$order['key']] < $b[$order['key']] ? -1 : 1;
+ } else {
+ return $a[$order['key']] > $b[$order['key']] ? -1 : 1;
+ }
+ });
+ }
+
+ if ($limit !== null) {
+ return array_slice($filteredData, $offset, $limit);
+ }
+ return $filteredData;
+ }
+
+ public static function count($filter = []): int {
+ $mockData = getMockData();
+ return count(self::applyFilter($mockData, $filter));
+ }
+
+ public static function get($id) {
+ $mockData = getMockData();
+ foreach ($mockData as $row)
+ if ($row['id'] == $id)
+ return new self($row);
+ return null;
+ }
+
+ public static function create($data) {
+ error_log("ManualInvoiceModel::create called with: " . json_encode($data));
+ return time();
+ }
+
+ public static function update($data) {
+ error_log("ManualInvoiceModel::update called with: " . json_encode($data));
+ return 1;
+ }
+
+ public static function delete($id) {
+ error_log("ManualInvoiceModel::delete called with ID: " . $id);
+ return 1;
+ }
+}
\ No newline at end of file
diff --git a/public/js/pages/ManualInvoice/ManualInvoice.css b/public/js/pages/ManualInvoice/ManualInvoice.css
new file mode 100644
index 000000000..4d2b28d3b
--- /dev/null
+++ b/public/js/pages/ManualInvoice/ManualInvoice.css
@@ -0,0 +1,243 @@
+/* --- Main Overlay and Layout --- */
+.manual-invoice-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ background-color: rgba(0, 0, 0, 0.6);
+ z-index: 1050;
+ display: flex;
+ overflow: hidden;
+}
+
+.invoice-editor-pane, .invoice-preview-pane {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.invoice-editor-pane {
+ flex: 0 0 50%;
+ background-color: #f4f5f7;
+ padding: 1rem;
+ overflow-y: hidden;
+}
+
+.invoice-preview-pane {
+ flex: 1 1 auto;
+ background-color: #525659;
+ padding: 2rem;
+ overflow-y: auto;
+ display: flex;
+ justify-content: center;
+}
+
+.info-bar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ background-color: rgba(0, 83, 132, 0.9);
+ color: white;
+ padding: 0.5rem;
+ text-align: center;
+ z-index: 1051;
+ font-size: 0.9rem;
+}
+
+/* --- Responsive Layout & Toggle --- */
+@media (max-width: 1919px) {
+ .invoice-editor-pane, .invoice-preview-pane {
+ width: 100%;
+ flex-basis: 100%;
+ position: absolute;
+ }
+
+ .manual-invoice-overlay.editor-active-small .invoice-preview-pane {
+ transform: translateX(100%);
+ }
+
+ .manual-invoice-overlay.preview-active-small .invoice-editor-pane {
+ transform: translateX(-100%);
+ }
+
+ .manual-invoice-overlay.preview-active-small .invoice-preview-pane {
+ transform: translateX(0);
+ }
+}
+
+/* --- Editor Pane Specifics --- */
+.editor-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #dee2e6;
+ flex-shrink: 0;
+}
+
+.editor-header h3 {
+ margin: 0;
+}
+
+.editor-actions {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.editor-content {
+ flex-grow: 1;
+ overflow-y: auto;
+ padding-top: 1rem;
+ padding-right: 10px;
+}
+
+.editor-content .card {
+ margin-bottom: 1rem;
+}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 1rem;
+}
+
+/* --- Invoice Preview Styles (mimicking PDF) --- */
+.invoice-preview-document {
+ width: 210mm;
+ min-height: 297mm;
+ background-color: white;
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
+ font-family: "Open Sans", sans-serif, Verdana;
+ font-size: 12px;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+}
+
+.preview-header-table {
+ width: 100%;
+ border-collapse: collapse;
+ padding: 2cm 2cm 0 2cm;
+}
+
+.customer-details {
+ vertical-align: bottom;
+ font-size: 14px;
+ padding-left: 30pt;
+ width: 65%;
+}
+
+.invoice-details-cell {
+ vertical-align: bottom;
+}
+
+.invoice-details-box {
+ border: 2px solid #e1e1e1;
+ padding: 6px;
+ font-size: 12px;
+}
+
+.invoice-details-box table td {
+ padding: 2px 4px;
+}
+
+.invoice-details-box table td:first-child {
+ text-align: right;
+ font-weight: bold;
+}
+
+.separator {
+ margin: 24px 2cm 0 2cm;
+ height: 1px;
+ background-color: black;
+}
+
+.preview-main {
+ padding: 1rem 2cm 0 2cm;
+ flex-grow: 1;
+}
+
+.positions-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 11px;
+}
+
+.positions-table th {
+ font-weight: bold;
+ border-bottom: 1px solid black;
+ padding: 8px 4px;
+ height: 28px;
+ vertical-align: middle;
+}
+
+.positions-table td {
+ padding: 6px 4px;
+}
+
+.positions-table tbody tr.uneven {
+ background-color: #ebebeb;
+}
+
+.positions-table .matchcode {
+ padding-left: 12pt;
+ font-size: 10px;
+ color: #555;
+}
+
+.totals-section {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.totals-table {
+ width: 50%;
+ border-collapse: collapse;
+}
+
+.totals-table td, .totals-table th {
+ padding: 4px;
+ text-align: left;
+}
+
+.totals-table td:last-child {
+ text-align: right;
+}
+
+.totals-table .netto {
+ font-weight: bold;
+ background-color: #ebebeb;
+ border-top: 1px solid black;
+ border-bottom: 1px solid black;
+}
+
+.totals-table .ust {
+ font-size: 11px;
+ border-bottom: 1px solid #ddd;
+}
+
+.totals-table .brutto {
+ font-weight: bold;
+ background-color: #ebebeb;
+ border-bottom: 3px double black;
+}
+
+.payment-info {
+ margin-top: 20pt;
+}
+
+.preview-footer {
+ padding: 1rem 2cm 2cm 2cm;
+ margin-top: auto;
+ border-top: 1px solid #e0e0e0;
+ font-size: 10px;
+ position: relative;
+}
+
+.preview-footer .page-number {
+ text-align: right;
+}
\ No newline at end of file
diff --git a/public/js/pages/ManualInvoice/ManualInvoice.js b/public/js/pages/ManualInvoice/ManualInvoice.js
new file mode 100644
index 000000000..c21809295
--- /dev/null
+++ b/public/js/pages/ManualInvoice/ManualInvoice.js
@@ -0,0 +1,372 @@
+Vue.component('manual-invoice', {
+ template: `
+