Fix #111: Zwei zusätzliche CC-E-Mail-Adressen pro Kunde
This commit is contained in:
parent
1fec39da9b
commit
d4602368e7
9 changed files with 110 additions and 6 deletions
10
Changes.md
10
Changes.md
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
7
migrations/084_customer_cc_emails.sql
Normal file
7
migrations/084_customer_cc_emails.sql
Normal 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;
|
||||||
|
|
@ -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,
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1-260629
|
1-260702
|
||||||
|
|
|
||||||
57
tests/48_CustomerCcEmailsTest.php
Normal file
57
tests/48_CustomerCcEmailsTest.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue