167 lines
6.4 KiB
PHP
167 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace App\Domains\Invoice\Actions\CreateInvoice;
|
|
|
|
use App\Enumerations\InvoiceStatus;
|
|
use App\Enumerations\InvoiceType;
|
|
use App\Enumerations\TravelReason;
|
|
use App\Mail\InvoiceMails\InvoiceMailsNewInvoiceMail;
|
|
use App\Mail\InvoiceMails\InvoiceMailsSubmittedConfirmationMail;
|
|
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
|
|
use App\Models\Invoice;
|
|
use Illuminate\Support\Facades\Mail;
|
|
|
|
class CreateInvoiceCommand {
|
|
private CreateInvoiceRequest $request;
|
|
|
|
public function __construct(CreateInvoiceRequest $request) {
|
|
$this->request = $request;
|
|
}
|
|
|
|
public function execute() : CreateInvoiceResponse {
|
|
$response = new CreateInvoiceResponse();
|
|
|
|
$rejection = $this->rejectTravelReason();
|
|
if ($rejection !== null) {
|
|
$response->message = $rejection;
|
|
|
|
return $response;
|
|
}
|
|
|
|
if ($this->request->accountIban === 'undefined') {
|
|
$this->request->accountIban = null;
|
|
}
|
|
|
|
$travelReason = $this->travelReason();
|
|
|
|
$invoice = Invoice::create([
|
|
'tenant' => currentTenant()->slug,
|
|
'cost_unit_id' => $this->request->costUnit->id,
|
|
'invoice_number' => $this->generateInvoiceNumber(),
|
|
'status' => InvoiceStatus::INVOICE_STATUS_NEW,
|
|
'type' => $this->request->invoiceType,
|
|
'type_other' => $this->request->invoiceTypeExtended,
|
|
'purpose' => $this->purpose($travelReason),
|
|
'donation' => $this->request->isDonation,
|
|
'user_id' => $this->request->paymentPurpose === null ? $this->request->userId : null,
|
|
'contact_name' => $this->request->contactName,
|
|
'contact_email' => $this->request->contactEmail,
|
|
'contact_phone' => $this->request->contactPhone,
|
|
'contact_bank_owner' => $this->request->accountOwner,
|
|
'contact_bank_iban' => $this->request->accountIban,
|
|
'amount' => $this->request->totalAmount,
|
|
'distance' => $this->request->distance,
|
|
'travel_direction' => $this->request->travelRoute,
|
|
'travel_reason' => $travelReason,
|
|
'payment_purpose' => $this->request->paymentPurpose,
|
|
'comment' => $this->request->notices,
|
|
'document_filename' => $this->request->receiptFile !== null ? $this->request->receiptFile->fullPath : null,
|
|
]);
|
|
|
|
if ($invoice !== null) {
|
|
$response->success = true;
|
|
$response->invoice = $invoice;
|
|
|
|
if ($invoice->contact_email !== null) {
|
|
Mail::to($invoice->contact_email)->send(new InvoiceMailsSubmittedConfirmationMail(
|
|
invoice: $invoice,
|
|
costUnit: $this->request->costUnit,
|
|
));
|
|
}
|
|
}
|
|
|
|
if ($this->request->costUnit->mail_on_new) {
|
|
$recipients = [currentTenant()->email_finance];
|
|
|
|
foreach ($this->request->costUnit->treasurers()->get() as $treasurer) {
|
|
if (!in_array($treasurer->email, $recipients)) {
|
|
$recipients[] = $treasurer->email;
|
|
}
|
|
}
|
|
|
|
foreach ($recipients as $recipient) {
|
|
Mail::to($recipient)->send(new InvoiceMailsNewInvoiceMail(
|
|
invoice: $invoice,
|
|
costUnit: $this->request->costUnit,
|
|
));
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
|
|
/**
|
|
* Der Zahlungsgrund, einmal beim Anlegen festgehalten.
|
|
*
|
|
* Danach ist er eine eigene Angabe: Die Kassenwart*in kann ihn korrigieren, und nichts schreibt ihn
|
|
* mehr um. Bei Fahrtkosten setzt er sich aus Reisegrund und den gefahrenen Personen zusammen, sonst
|
|
* trägt ihn "Was wurde eingekauft". Wo nichts erfasst wird -- Beitragserstattungen -- bleibt er leer.
|
|
*
|
|
* Steht er schon fest, wird er übernommen: Eine Abrechnungskorrektur kopiert den Beleg, und ein von
|
|
* Hand gesetzter Grund darf dabei nicht verloren gehen.
|
|
*/
|
|
private function purpose(?string $travelReason) : ?string {
|
|
if (trim((string) $this->request->purpose) !== '') {
|
|
return $this->request->purpose;
|
|
}
|
|
|
|
$purpose = Invoice::joinPurposeParts(
|
|
$this->request->invoiceType === InvoiceType::INVOICE_TYPE_TRAVELLING
|
|
// Der Name des Grundes, nicht sein Schlüssel: In der Belegliste soll "Materialtransport"
|
|
// stehen, nicht "material_transport".
|
|
? [TravelReason::text($travelReason), $this->request->travellers]
|
|
: [$this->request->invoiceTypeExtended]
|
|
);
|
|
|
|
return $purpose === '' ? null : $purpose;
|
|
}
|
|
|
|
/**
|
|
* Was in `travel_reason` landet: der Schlüssel des gewählten Grundes -- oder, bei "Anderer Grund",
|
|
* der Text selbst. Der Schlüssel `other` sagt für sich nichts aus, der Text alles.
|
|
*
|
|
* Ein unbekannter Wert ist deshalb kein Fehler, sondern genau dieser Fall: So kommen auch
|
|
* Bestandsbelege und Kopien durch, die ihren Freitext schon mitbringen.
|
|
*/
|
|
private function travelReason() : ?string {
|
|
$reason = TravelReason::find($this->request->travelReason);
|
|
|
|
if ($reason === null) {
|
|
return $this->request->travelReason;
|
|
}
|
|
|
|
return $reason->requires_note
|
|
? trim((string) $this->request->travelReasonNote)
|
|
: $reason->slug;
|
|
}
|
|
|
|
/**
|
|
* Sicherheitsnetz hinter der Oberfläche: Dort geht es erst weiter, wenn die Erläuterung steht. Über
|
|
* einen direkten Aufruf ginge das sonst vorbei, und ein "Anderer Grund" ohne Text sagt nichts aus --
|
|
* gespeichert würde ein leerer Reisegrund.
|
|
*/
|
|
private function rejectTravelReason() : ?string {
|
|
$reason = TravelReason::find($this->request->travelReason);
|
|
|
|
if ($reason === null || !$reason->requires_note) {
|
|
return null;
|
|
}
|
|
|
|
return trim((string) $this->request->travelReasonNote) === ''
|
|
? 'Bitte gib an, was der Grund für die Reise war.'
|
|
: null;
|
|
}
|
|
|
|
private function generateInvoiceNumber() : string {
|
|
$lastInvoiceNumber = Invoice::query()
|
|
->where('tenant', currentTenant()->slug)
|
|
->whereYear('created_at', date('Y'))
|
|
->count();
|
|
|
|
$invoiceNumber = $lastInvoiceNumber + 1;
|
|
$invoiceNumber = str_pad($invoiceNumber, 4, '0', STR_PAD_LEFT);
|
|
return sprintf('%1$s-%2$s', date('Y'), $invoiceNumber);
|
|
}
|
|
}
|