Merge pull request 'Fix #111: Zwei zusätzliche CC-E-Mail-Adressen pro Kunde' (#112) from fds2610-bot/Steuerkram:feature/kunden-cc-emails into main

Reviewed-on: https://codeberg.org/fds2610/Steuerkram/pulls/112
This commit is contained in:
fds2610 2026-07-03 07:11:22 +02:00
commit 436cacf683
9 changed files with 110 additions and 6 deletions

View file

@ -2,6 +2,16 @@
--- ---
## 2026-07-02 – Feature: Zusätzliche CC-E-Mail-Adressen pro Kunde (Issue #111)
- **`migrations/084_customer_cc_emails.sql`:** Neue Spalten `email_cc1`, `email_cc2` in `customers`
- **`src/Models/Customer.php`:** `create()` und `update()` speichern die beiden CC-Adressen (Whitespace wird zu NULL normalisiert)
- **`src/Views/customers/form.php`:** Zwei neue Eingabefelder „E-Mail CC 1/2" unter der Haupt-E-Mail
- **`src/Controllers/InvoiceController.php`:** `show()` lädt den Kunden und übergibt ihn an die Detailansicht
- **`src/Views/invoices/show.php`:** CC-Felder im E-Mail-Sende-Modal werden mit den hinterlegten Kunden-CC-Adressen vorbelegt
---
## 2026-06-29 – Feature: Ereignisse-Log für Nextcloud-Import (Issue #109) ## 2026-06-29 – Feature: Ereignisse-Log für Nextcloud-Import (Issue #109)
- **`migrations/083_system_events.sql`:** Neue Tabelle `system_events` (id, channel, message, created_at) - **`migrations/083_system_events.sql`:** Neue Tabelle `system_events` (id, channel, message, created_at)

View file

@ -42,6 +42,8 @@ CRUD für Lieferantenstammdaten. Lieferanten können Belegen zugeordnet werden;
CRUD für Kundenstammdaten. Kunden werden beim Erstellen von Ausgangsrechnungen ausgewählt. CRUD für Kundenstammdaten. Kunden werden beim Erstellen von Ausgangsrechnungen ausgewählt.
- Pro Kunde können neben der Haupt-E-Mail-Adresse **zwei zusätzliche CC-Adressen** hinterlegt werden. Beim Rechnungsversand werden diese automatisch in die CC-Felder des E-Mail-Dialogs übernommen.
--- ---
## Ausgangsrechnungen ## Ausgangsrechnungen

View file

@ -0,0 +1,7 @@
-- 084_customer_cc_emails.sql
-- Zwei zusätzliche CC-E-Mail-Adressen pro Kunde, die beim Rechnungsversand
-- automatisch in die CC-Felder übernommen werden.
ALTER TABLE customers
ADD COLUMN IF NOT EXISTS email_cc1 VARCHAR(255) NULL DEFAULT NULL AFTER email,
ADD COLUMN IF NOT EXISTS email_cc2 VARCHAR(255) NULL DEFAULT NULL AFTER email_cc1;

View file

@ -131,6 +131,11 @@ class InvoiceController extends BaseController
$accounts = Account::all(['aktiv' => 1]); $accounts = Account::all(['aktiv' => 1]);
} }
// Kunde für CC-Vorbelegung im E-Mail-Modal
$customer = !empty($invoice['customer_id'])
? \Steuerkram\Models\Customer::find((int)$invoice['customer_id'])
: null;
$view = new View(); $view = new View();
$view->render('layout/base.php', [ $view->render('layout/base.php', [
'pageTitle' => 'Rechnung ' . ($invoice['rechnungs_nr'] ?? 'Entwurf'), 'pageTitle' => 'Rechnung ' . ($invoice['rechnungs_nr'] ?? 'Entwurf'),
@ -142,6 +147,7 @@ class InvoiceController extends BaseController
'originalrechnung' => $originalrechnung, 'originalrechnung' => $originalrechnung,
'bookings' => $bookings, 'bookings' => $bookings,
'accounts' => $accounts, 'accounts' => $accounts,
'customer' => $customer ?: null,
]), ]),
]); ]);
} }

View file

@ -39,8 +39,8 @@ class Customer
$nr = self::generateKundennummer(); $nr = self::generateKundennummer();
Database::execute( Database::execute(
'INSERT INTO customers (kundennummer, firma, anrede, vorname, nachname, strasse, plz, ort, land, 'INSERT INTO customers (kundennummer, firma, anrede, vorname, nachname, strasse, plz, ort, land,
email, telefon, ust_id, steuernummer, payment_days, notes, mail_subject_template, mail_body_template) email, email_cc1, email_cc2, telefon, ust_id, steuernummer, payment_days, notes, mail_subject_template, mail_body_template)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ [
$data['kundennummer'] ?? $nr, $data['kundennummer'] ?? $nr,
$data['firma'] ?? null, $data['firma'] ?? null,
@ -52,6 +52,8 @@ class Customer
$data['ort'], $data['ort'],
$data['land'] ?? 'DE', $data['land'] ?? 'DE',
$data['email'] ?? null, $data['email'] ?? null,
trim($data['email_cc1'] ?? '') !== '' ? trim($data['email_cc1']) : null,
trim($data['email_cc2'] ?? '') !== '' ? trim($data['email_cc2']) : null,
$data['telefon'] ?? null, $data['telefon'] ?? null,
$data['ust_id'] ?? null, $data['ust_id'] ?? null,
$data['steuernummer'] ?? null, $data['steuernummer'] ?? null,
@ -68,7 +70,7 @@ class Customer
{ {
Database::execute( Database::execute(
'UPDATE customers SET firma=?, anrede=?, vorname=?, nachname=?, strasse=?, plz=?, ort=?, land=?, 'UPDATE customers SET firma=?, anrede=?, vorname=?, nachname=?, strasse=?, plz=?, ort=?, land=?,
email=?, telefon=?, ust_id=?, steuernummer=?, payment_days=?, notes=?, email=?, email_cc1=?, email_cc2=?, telefon=?, ust_id=?, steuernummer=?, payment_days=?, notes=?,
mail_subject_template=?, mail_body_template=?, updated_at=NOW() mail_subject_template=?, mail_body_template=?, updated_at=NOW()
WHERE id=?', WHERE id=?',
[ [
@ -81,6 +83,8 @@ class Customer
$data['ort'], $data['ort'],
$data['land'] ?? 'DE', $data['land'] ?? 'DE',
$data['email'] ?? null, $data['email'] ?? null,
trim($data['email_cc1'] ?? '') !== '' ? trim($data['email_cc1']) : null,
trim($data['email_cc2'] ?? '') !== '' ? trim($data['email_cc2']) : null,
$data['telefon'] ?? null, $data['telefon'] ?? null,
$data['ust_id'] ?? null, $data['ust_id'] ?? null,
$data['steuernummer'] ?? null, $data['steuernummer'] ?? null,

View file

@ -60,6 +60,22 @@
</div> </div>
</div> </div>
<div class="form-grid form-grid-2">
<div class="form-group">
<label for="email_cc1">E-Mail CC 1 <span style="font-weight:normal;color:var(--color-text-muted)">(optional)</span></label>
<input type="email" id="email_cc1" name="email_cc1" value="<?= $h($customer['email_cc1'] ?? '') ?>"
placeholder="cc1@example.com">
</div>
<div class="form-group">
<label for="email_cc2">E-Mail CC 2 <span style="font-weight:normal;color:var(--color-text-muted)">(optional)</span></label>
<input type="email" id="email_cc2" name="email_cc2" value="<?= $h($customer['email_cc2'] ?? '') ?>"
placeholder="cc2@example.com">
</div>
</div>
<p style="font-size:.78rem;color:var(--color-text-muted);margin:-.5rem 0 1rem">
Zusätzliche CC-Adressen werden beim Rechnungsversand automatisch als Kopie vorgeschlagen.
</p>
<div class="form-grid form-grid-3"> <div class="form-grid form-grid-3">
<div class="form-group"> <div class="form-group">
<label for="ust_id">Umsatzsteuer-ID</label> <label for="ust_id">Umsatzsteuer-ID</label>

View file

@ -370,11 +370,13 @@ $badgeClass = match($invoice['status']) {
</div> </div>
<div class="form-group"> <div class="form-group">
<label>CC 1 <span style="font-weight:normal;color:#888">(optional)</span></label> <label>CC 1 <span style="font-weight:normal;color:#888">(optional)</span></label>
<input type="email" name="email_cc1" placeholder="cc1@example.com"> <input type="email" name="email_cc1" placeholder="cc1@example.com"
value="<?= $h($customer['email_cc1'] ?? '') ?>">
</div> </div>
<div class="form-group"> <div class="form-group">
<label>CC 2 <span style="font-weight:normal;color:#888">(optional)</span></label> <label>CC 2 <span style="font-weight:normal;color:#888">(optional)</span></label>
<input type="email" name="email_cc2" placeholder="cc2@example.com"> <input type="email" name="email_cc2" placeholder="cc2@example.com"
value="<?= $h($customer['email_cc2'] ?? '') ?>">
</div> </div>
<div style="display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.25rem"> <div style="display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.25rem">
<button type="button" class="btn btn-secondary" <button type="button" class="btn btn-secondary"

View file

@ -1 +1 @@
1-260629 1-260702

View file

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
use Steuerkram\Models\Customer;
class CustomerCcEmailsTest extends TestCase
{
protected string $testName = 'Test 48: Zusätzliche CC-E-Mail-Adressen pro Kunde';
public function run(): void
{
// Kunde mit zwei CC-Adressen anlegen
$id = Customer::create([
'nachname' => 'CC-Test',
'strasse' => 'Musterweg 1',
'plz' => '12345',
'ort' => 'Musterstadt',
'email' => 'haupt@example.com',
'email_cc1' => 'kopie1@example.com',
'email_cc2' => 'kopie2@example.com',
]);
$this->assertGreaterThan(0, $id, 'Kunde angelegt');
$customer = Customer::find($id);
$this->assertEquals('kopie1@example.com', $customer['email_cc1'], 'CC1 gespeichert');
$this->assertEquals('kopie2@example.com', $customer['email_cc2'], 'CC2 gespeichert');
// Leere CC-Felder werden als NULL gespeichert
Customer::update($id, [
'nachname' => 'CC-Test',
'strasse' => 'Musterweg 1',
'plz' => '12345',
'ort' => 'Musterstadt',
'email' => 'haupt@example.com',
'email_cc1' => '',
'email_cc2' => ' ',
]);
$customer = Customer::find($id);
$this->assert($customer['email_cc1'] === null, 'Leeres CC1 wird NULL');
$this->assert($customer['email_cc2'] === null, 'Whitespace-CC2 wird NULL');
// CC1 aktualisieren, mit Trimmen
Customer::update($id, [
'nachname' => 'CC-Test',
'strasse' => 'Musterweg 1',
'plz' => '12345',
'ort' => 'Musterstadt',
'email' => 'haupt@example.com',
'email_cc1' => ' neu@example.com ',
'email_cc2' => 'kopie2@example.com',
]);
$customer = Customer::find($id);
$this->assertEquals('neu@example.com', $customer['email_cc1'], 'CC1 getrimmt aktualisiert');
$this->assertEquals('kopie2@example.com', $customer['email_cc2'], 'CC2 aktualisiert');
}
}