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)
- **`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.
- 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

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]);
}
// 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->render('layout/base.php', [
'pageTitle' => 'Rechnung ' . ($invoice['rechnungs_nr'] ?? 'Entwurf'),
@ -142,6 +147,7 @@ class InvoiceController extends BaseController
'originalrechnung' => $originalrechnung,
'bookings' => $bookings,
'accounts' => $accounts,
'customer' => $customer ?: null,
]),
]);
}

View file

@ -39,8 +39,8 @@ class Customer
$nr = self::generateKundennummer();
Database::execute(
'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)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
email, email_cc1, email_cc2, telefon, ust_id, steuernummer, payment_days, notes, mail_subject_template, mail_body_template)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[
$data['kundennummer'] ?? $nr,
$data['firma'] ?? null,
@ -52,6 +52,8 @@ class Customer
$data['ort'],
$data['land'] ?? 'DE',
$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['ust_id'] ?? null,
$data['steuernummer'] ?? null,
@ -68,7 +70,7 @@ class Customer
{
Database::execute(
'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()
WHERE id=?',
[
@ -81,6 +83,8 @@ class Customer
$data['ort'],
$data['land'] ?? 'DE',
$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['ust_id'] ?? null,
$data['steuernummer'] ?? null,

View file

@ -60,6 +60,22 @@
</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-group">
<label for="ust_id">Umsatzsteuer-ID</label>

View file

@ -370,11 +370,13 @@ $badgeClass = match($invoice['status']) {
</div>
<div class="form-group">
<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 class="form-group">
<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 style="display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.25rem">
<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');
}
}