diff --git a/app/Domains/CostUnit/Controllers/ExportController.php b/app/Domains/CostUnit/Controllers/ExportController.php index 5d14f4c..d258088 100644 --- a/app/Domains/CostUnit/Controllers/ExportController.php +++ b/app/Domains/CostUnit/Controllers/ExportController.php @@ -93,7 +93,7 @@ class ExportController extends CommonController { 'amount' => $invoice->amount, 'recipient_name' => $invoice->contact_bank_owner, 'recipient_iban' => $invoice->contact_bank_iban, - 'payment_purpose' => $invoice->payment_purpose ?? 'Auslagenerstattung Rechnungsnummer ' . $invoice->invoice_number, + 'payment_purpose' => $invoice->paymentPurposeText(), ]); } } diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 2639720..ddfa818 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -71,6 +71,26 @@ class Invoice extends InstancedModel 'denied_reason', ]; + /** + * Der Verwendungszweck für Überweisung, Buchungstext und Anzeige. + * + * Der Freitext gewinnt, wenn einer erfasst wurde. Sonst benennt der Text den Vorgang: eine + * Beitragserstattung ist keine Auslage des Teilis, sondern die Rücknahme seiner Zahlung -- auf dem + * Kontoauszug muss der Unterschied erkennbar sein. Genannt wird die Abrechnungsnummer, und zwar als + * Belegnummer: unter "Rechnungsnummer" gibt es sie nirgends. + */ + public function paymentPurposeText() : string { + if ($this->payment_purpose !== null) { + return $this->payment_purpose; + } + + $subject = $this->type === InvoiceType::INVOICE_TYPE_PARTICIPATION_REFUND + ? 'Beitragserstattung' + : 'Auslagenerstattung'; + + return $subject . ' Belegnummer ' . $this->invoice_number; + } + public function costUnit() : BelongsTo{ return $this->belongsTo(CostUnit::class); } diff --git a/app/Resources/InvoiceResource.php b/app/Resources/InvoiceResource.php index 14b46a1..59810b7 100644 --- a/app/Resources/InvoiceResource.php +++ b/app/Resources/InvoiceResource.php @@ -39,7 +39,7 @@ class InvoiceResource { $returnData['id'] = $this->invoice->id; $returnData['donation'] = $this->invoice->donation; $returnData['externalPayment'] = null !== $this->invoice->payment_purpose; - $returnData['paymentPurpose'] = $this->invoice->payment_purpose ?? 'Auslagenerstattung Rechnungsnummer ' . $returnData['invoiceNumber']; + $returnData['paymentPurpose'] = $this->invoice->paymentPurposeText(); $returnData['accountOwner'] = $this->invoice->contact_bank_owner ?? '--'; $returnData['accountIban'] = $this->invoice->contact_bank_iban ?? '--'; $returnData['status'] = $this->invoice->status; diff --git a/tests/Feature/RefundInvoiceTest.php b/tests/Feature/RefundInvoiceTest.php index 936297b..ceb7f21 100644 --- a/tests/Feature/RefundInvoiceTest.php +++ b/tests/Feature/RefundInvoiceTest.php @@ -70,6 +70,16 @@ class RefundInvoiceTest extends TestCase DB::table('participation_fee_types')->insert(['slug' => 'fixed', 'name' => 'Fix']); DB::table('cost_unit_types')->insert(['slug' => CostUnitType::COST_UNIT_TYPE_EVENT, 'name' => 'Veranstaltung']); DB::table('invoice_status')->insert(['slug' => InvoiceStatus::INVOICE_STATUS_NEW]); + + // Ein gewöhnlicher Aufwandstyp zum Vergleich; die Beitragserstattung bringt die Migration mit. + DB::table('invoice_types')->insert([ + 'slug' => InvoiceType::INVOICE_TYPE_TRAVELLING, + 'name' => 'Fahrtkosten', + 'sort_order' => 1, + 'selectable' => true, + 'counts_as_expense' => true, + ]); + PaymentMethod::create(['slug' => PaymentMethod::PAYMENT_ACCOUNT_TRANSACTION]); EfzStatus::create(['slug' => EfzStatus::EFZ_STATUS_NOT_REQUIRED, 'name' => 'Nicht erforderlich']); @@ -433,4 +443,55 @@ class RefundInvoiceTest extends TestCase $this->assertStringContainsString('300,00 Euro', Invoice::first()->comment); $this->assertEqualsWithDelta(80.0, $refund->participant->fresh()->amount_paid->getAmount(), 0.001); } + + /* + |-------------------------------------------------------------------------- + | Der Verwendungszweck der Überweisung + |-------------------------------------------------------------------------- + */ + + public function test_the_payment_purpose_of_a_refund_names_the_refund(): void + { + $this->runRefund(); + + $invoice = Invoice::first(); + + // Auf dem Kontoauszug des Teilis muss der Vorgang stehen, den es gab: Er hatte keine Auslage, + // er bekommt seinen Beitrag zurück. + $this->assertSame( + 'Beitragserstattung Belegnummer ' . $invoice->invoice_number, + $invoice->paymentPurposeText() + ); + } + + public function test_an_ordinary_invoice_keeps_the_expense_wording(): void + { + $invoice = Invoice::create([ + 'tenant' => $this->tenant->slug, + 'cost_unit_id' => $this->makeCostUnit()->id, + 'invoice_number' => '2026-0042', + 'status' => InvoiceStatus::INVOICE_STATUS_NEW, + 'type' => InvoiceType::INVOICE_TYPE_TRAVELLING, + 'contact_name' => 'Mika Muster', + 'amount' => 42.0, + ]); + + $this->assertSame('Auslagenerstattung Belegnummer 2026-0042', $invoice->paymentPurposeText()); + } + + public function test_a_free_text_purpose_wins(): void + { + $invoice = Invoice::create([ + 'tenant' => $this->tenant->slug, + 'cost_unit_id' => $this->makeCostUnit()->id, + 'invoice_number' => '2026-0043', + 'status' => InvoiceStatus::INVOICE_STATUS_NEW, + 'type' => InvoiceType::INVOICE_TYPE_TRAVELLING, + 'contact_name' => 'Mika Muster', + 'amount' => 42.0, + 'payment_purpose' => 'Sommerlager', + ]); + + $this->assertSame('Sommerlager', $invoice->paymentPurposeText()); + } }