Improved handling for new invoices

This commit is contained in:
2026-04-18 23:42:39 +02:00
parent 33a9271013
commit 4878f750bd
16 changed files with 206 additions and 32 deletions

View File

@@ -3,7 +3,10 @@
namespace App\Domains\Invoice\Actions\CreateInvoice;
use App\Enumerations\InvoiceStatus;
use App\Mail\InvoiceMails\InvoiceMailsNewInvoiceMail;
use App\Mail\ParticipantParticipationMails\EventSignUpSuccessfullMail;
use App\Models\Invoice;
use Illuminate\Support\Facades\Mail;
class CreateInvoiceCommand {
private CreateInvoiceRequest $request;
@@ -36,6 +39,7 @@ class CreateInvoiceCommand {
'amount' => $this->request->totalAmount,
'distance' => $this->request->distance,
'travel_direction' => $this->request->travelRoute,
'travel_reason' => $this->request->travelReason,
'passengers' => $this->request->passengers,
'transportation' => $this->request->transportations,
'document_filename' => $this->request->receiptFile !== null ? $this->request->receiptFile->fullPath : null,
@@ -46,6 +50,23 @@ class CreateInvoiceCommand {
$response->invoice = $invoice;
}
if ($this->request->costUnit->mail_on_new) {
$recipients = [app('tenant')->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;
}

View File

@@ -22,6 +22,7 @@ class CreateInvoiceRequest {
public float $totalAmount;
public bool $isDonation;
public ?int $userId;
public ?string $travelReason;
public function __construct(
@@ -41,6 +42,7 @@ class CreateInvoiceRequest {
?int $distance = null,
?int $passengers = null,
?int $transportations,
?string $travelReason = null,
) {
$this->costUnit = $costUnit;
@@ -59,6 +61,7 @@ class CreateInvoiceRequest {
$this->totalAmount = $totalAmount;
$this->isDonation = $isDonation;
$this->userId = $userId;
$this->travelReason = $travelReason;
if ($accountIban === 'undefined') {
$this->accountIban = null;

View File

@@ -84,6 +84,7 @@ class CreateInvoiceReceiptCommand {
$travelPartTemplate = <<<HTML
<tr><td>Reiseweg:</td><td>%1\$s</td></tr>
<tr><td>Grund der Reise:</td><td>%6\$s</td></tr>
<tr><td>Gesamtlänge der Strecke:</td><td>%2\$s km x %3\$s / km</td></tr>
<tr><td>Materialtransport:</td><td>%4\$s</td></tr>
<tr><td>Mitfahrende im PKW:</td><td>%5\$s</td></tr>
@@ -95,7 +96,8 @@ HTML;
$invoiceReadable['distance'],
$invoiceReadable['distanceAllowance'],
$invoiceReadable['transportation'],
$invoiceReadable['passengers']
$invoiceReadable['passengers'],
$invoiceReadable['travelReason'] ,
);
$invoiceTravelPart = '<tr><td>Kosten für ÖPNV:</td><td>' . $invoiceReadable['amount'] . '</td></tr>';;

View File

@@ -64,31 +64,32 @@ class NewInvoiceController extends CommonController {
case InvoiceType::INVOICE_TYPE_TRAVELLING:
if ($uploadedFile !== null) {
$amount = Amount::fromString($request->get('amount'))->getAmount();
$amount = Amount::fromString($request->input('amount'))->getAmount();
$distance = null;
} else {
$distance = Amount::fromString($request->get('amount'))->getRoundedAmount();
$distance = Amount::fromString($request->input('amount'))->getRoundedAmount();
$amount = $distance * $costUnit->distance_allowance;
}
$createInvoiceRequest = new CreateInvoiceRequest(
$costUnit,
$request->get('name'),
$request->input('name'),
InvoiceType::INVOICE_TYPE_TRAVELLING,
$amount,
$uploadedFile,
'donation' === $request->get('decision') ? true : false,
'donation' === $request->input('decision') ? true : false,
$this->users->getCurrentUserDetails()['userId'],
$request->get('contactEmail'),
$request->get('telephone'),
$request->get('accountOwner'),
$request->get('accountIban'),
$request->input('email'),
$request->input('telephone'),
$request->input('accountOwner'),
$request->input('accountIban'),
null,
$request->get('otherText'),
$request->input('otherText'),
$distance,
$request->get('havePassengers'),
$request->get('materialTransportation'),
$request->input('havePassengers'),
$request->input('materialTransportation'),
$request->input('travelReason'),
);
break;
@@ -96,21 +97,22 @@ class NewInvoiceController extends CommonController {
default:
$createInvoiceRequest = new CreateInvoiceRequest(
$costUnit,
$request->get('name'),
$request->input('name'),
$invoiceType,
Amount::fromString($request->get('amount'))->getAmount(),
Amount::fromString($request->input('amount'))->getAmount(),
$uploadedFile,
'donation' === $request->get('decision') ? true : false,
'donation' === $request->input('decision') ? true : false,
$this->users->getCurrentUserDetails()['userId'],
$request->get('contactEmail'),
$request->get('telephone'),
$request->get('accountOwner'),
$request->get('accountIban'),
$request->get('otherText'),
$request->input('email'),
$request->input('telephone'),
$request->input('accountOwner'),
$request->input('accountIban'),
$request->input('otherText'),
null,
null,
$request->get('havePassengers'),
$request->get('materialTransportation'),
$request->input('havePassengers'),
$request->input('materialTransportation'),
null
);
break;
@@ -124,15 +126,10 @@ class NewInvoiceController extends CommonController {
'success'
);
return response()->json([
'status' => 'success',
'message' => 'Alright'
]);
}
dd($request->all());
}
}

View File

@@ -17,6 +17,11 @@ const props = defineProps({
<td>{{props.invoice.travelRoute}}</td>
</tr>
<tr>
<th>Grund der Reise</th>
<td>{{props.invoice.travelReason}}</td>
</tr>
<tr>
<th>Gesamte Wegstrecke</th>
<td>{{props.invoice.distance}} km</td>

View File

@@ -109,6 +109,7 @@ function handleFileChange(event) {
:userIban="data.userIban"
:userAccountOwner="data.userAccountOwner"
:receipt="receipt"
travelReason=""
@close="finalStep = false"
/>

View File

@@ -25,9 +25,10 @@ const props = defineProps({
userIban: String,
havePassengers: Number,
materialTransportation: Boolean,
travelReason: String,
})
console.log(props.receipt)
console.log(props)
const finalStep = ref(true)
const userName = ref(props.userName)
@@ -49,6 +50,8 @@ async function sendData() {
errorMsg.value = ''
success.value = false
console.log(props)
const formData = new FormData()
formData.append('name', userName.value)
@@ -61,6 +64,7 @@ async function sendData() {
formData.append('accountIban', userIban.value)
formData.append('havePassengers', props.havePassengers ? 1 : 0)
formData.append('materialTransportation', props.materialTransportation ? 1 : 0)
formData.append('travelReason', props.travelReason)
if (props.receipt) {
formData.append('receipt', props.receipt)

View File

@@ -18,6 +18,7 @@ const data = defineProps({
const { request } = useAjax();
const distanceAllowance = ref(null);
const travelDirection = ref(null);
const travelReason = ref(null);
const have_receipt = ref('')
const havePassengers = ref(false);
const materialTransportation = ref(false);
@@ -58,6 +59,15 @@ function handleFileChange(event) {
</fieldset><br /><br />
<fieldset v-if="travelDirection !== null">
<legend><span style="font-weight: bolder;">Was war der Grund für deine Reise?</span></legend>
<input
type="text"
name="travel-reason"
v-model="travelReason"
/>
</fieldset><br /><br />
<fieldset v-if="travelReason !== null">
<legend><span style="font-weight: bolder;">Bist du mit dem ÖPNV gefahren oder besitzt du einen Beleg</span></legend>
<input type="button" style="border-radius: 0; width: 100px;" @click="have_receipt='yes'" value="Ja" />
<input type="button" style="border-radius: 0; width: 100px;" @click="getDistanceAllowance" value="Nein" />
@@ -92,6 +102,7 @@ function handleFileChange(event) {
:userIban="data.userIban"
:userAccountOwner="data.userAccountOwner"
:receipt="receipt"
:travelReason="travelReason"
@close="finalStep = false"
/>
</fieldset>
@@ -142,6 +153,7 @@ function handleFileChange(event) {
:userTelephone="data.userTelephone"
:userIban="data.userIban"
:userAccountOwner="data.userAccountOwner"
:travelReason="travelReason"
@close="finalStep = false"
/>

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Mail\InvoiceMails;
use App\Models\CostUnit;
use App\Models\Event;
use App\Models\EventParticipant;
use App\Models\Invoice;
use App\ValueObjects\Amount;
use Illuminate\Http\Request;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class InvoiceMailsNewInvoiceMail extends Mailable {
public function __construct(
private Invoice $invoice,
private CostUnit $costUnit,
)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: 'Neue Abrechnung in Kostenstelle ' . $this->costUnit->name,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.invoices.new_invoice',
with: [
'costUnitName' => $this->costUnit->name,
'invoiceAmount' => Amount::fromString($this->invoice->amount)->toString(),
'invoiceType' => $this->invoice->invoiceType()->name,
],
);
}
/**
* Get the attachments for the message.
*
* @return array<int, Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -58,6 +58,7 @@ class Invoice extends InstancedModel
'comment',
'changes',
'travel_direction',
'travel_reason',
'passengers',
'transportation',
'document_filename',

View File

@@ -33,6 +33,7 @@ class Tenant extends CommonModel
'slug',
'name',
'email',
'email_finance',
'url',
'account_name',
'account_iban',

View File

@@ -132,7 +132,7 @@ class CostUnitRepository {
if (strlen($costUnit->name) > 15) {
$costUnit->name = substr($costUnit->name, 8, 13) . '...';
}
$costUnit->totalAmount = $this->sumupAmounts($costUnit)->toString();
$costUnit->totalAmount = $this->sumupUnhandledAmounts($costUnit)->toString();
$returnData[] = $costUnit;
}
@@ -174,8 +174,27 @@ class CostUnitRepository {
$amount->addAmount(Amount::fromString($invoice->amount));
}
return $amount;
}
public function sumupUnhandledAmounts(CostUnit $costUnit, bool $donatedAmount = false) : Amount {
$amount = new Amount(0, '');
foreach ($costUnit->invoices()->get() as $invoice) {
if (!in_array($invoice->status,
[InvoiceStatus::INVOICE_STATUS_NEW, InvoiceStatus::INVOICE_STATUS_APPROVED])) {
continue;
}
if (
($donatedAmount && !$invoice->donation) ||
(!$donatedAmount && $invoice->donation)
) {
continue;
}
$amount->addAmount(Amount::fromString($invoice->amount));
}
return $amount;
}
public function countInvoices(CostUnit $costUnit) : array {

View File

@@ -5,6 +5,7 @@ namespace App\Resources;
use App\Enumerations\InvoiceStatus;
use App\Enumerations\InvoiceType;
use App\Models\Invoice;
use App\Models\User;
use App\ValueObjects\Amount;
class InvoiceResource {
@@ -20,6 +21,13 @@ class InvoiceResource {
if ($this->invoice->invoiceType()->slug === InvoiceType::INVOICE_TYPE_OTHER) {
$returnData['invoiceType'] .= ' (' . $this->invoice->type_other . ')';
}
$approvedBy = '--';
$approvedAt = '--';
if ($this->invoice->approved_at !== null) {
$approvedBy = User::findOrFail($this->invoice->approved_by)->getOfficialName();
$approvedAt = \DateTime::createFromFormat('Y-m-d H:i:s', $this->invoice->approved_at)->format('d.m.Y H:i');
}
$returnData['invoiceTypeShort'] = $this->invoice->invoiceType()->name;
$returnData['costUnitName'] = $this->invoice->costUnit()->first()->name;
@@ -40,6 +48,7 @@ class InvoiceResource {
$returnData['changes'] = $this->invoice->changes ?? '--';
$returnData['deniedReason'] = $this->invoice->denied_reason ?? '--';
$returnData['travelDirection'] = $this->invoice->travel_direction ?? '--';
$returnData['travelReason'] = $this->invoice->travel_reason ?? '--';
$returnData['distance'] = $this->invoice->distance;
$returnData['distanceAllowance'] = new Amount($this->invoice->costUnit()->first()->distance_allowance, '')->toString();
$returnData['passengers'] = $this->invoice->passengers ? 'Ja' : 'Nein';
@@ -50,8 +59,8 @@ class InvoiceResource {
$returnData['internalType'] = $this->invoice->type;
$returnData['donationText'] = $this->invoice->donation ? 'Ja' : 'Nein';
$returnData['createdAt'] = \DateTime::createFromFormat('Y-m-d H:i:s', $this->invoice->created_at)->format('d.m.Y H:i');
$returnData['approvedAt'] = '--';
$returnData['approvedBy'] = '--';
$returnData['approvedAt'] = $approvedAt;
$returnData['approvedBy'] = $approvedBy;
if ($this->invoice->status === InvoiceStatus::INVOICE_STATUS_APPROVED) {
$returnData['approvedAt'] = \DateTime::createFromFormat('Y-m-d H:i:s', $this->invoice->approved_at)->format('d.m.Y H:i');

View File

@@ -11,6 +11,7 @@ return new class extends Migration {
$table->string('slug')->unique();
$table->string('name');
$table->string('email');
$table->string('email_finance');
$table->string('url');
$table->string('account_name');
$table->string('account_iban');

View File

@@ -40,6 +40,7 @@ return new class extends Migration {
$table->string('comment')->nullable();
$table->longText('changes')->nullable();
$table->string('travel_direction')->nullable();
$table->string('travel_reason')->nullable();
$table->boolean('passengers')->nullable();
$table->boolean('transportation')->nullable();
$table->string('document_filename')->nullable();

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hallo,</h1>
<p>
für die Kostenstelle "{{$costUnitName}}" ist die Benachrichtigung für neue Abrechnungen aktiviert.<br/>
Soeben wurde eine neue Abrechnung erfasst:
</p>
<table cellpadding="0" cellspacing="0" border="0"
style="width: 100%; max-width: 640px; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 14px; color: #1f2937;">
<tr>
<td style="padding: 8px 12px; width: 180px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Betrag:
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{$invoiceAmount}}
</td>
</tr>
<tr>
<td style="padding: 8px 12px; font-weight: 600; color: #4b5563; border-bottom: 1px solid #e5e7eb;">
Grund der Abrechnung:
</td>
<td style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb;">
{{$invoiceType}}
</td>
</tr>
</table>
<p>
Weitere Details zur Abrechnung wurden aus Datenschutzgründen der Mail nicht hinzugefügt.<br />
Bitte öffne die Kostenstelle "{{ $costUnitName }}" im Abrechnungssystem mareike, um weitere Informationen zu erhalten und die Abrechnung bearbeiten zu können.
</p>
</body>
</html>