Payment reminder mails
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||||
|
|
||||||
|
use App\Mail\ParticipantPaymentMails\ParticipantPaymentMissingPaymentMail;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
|
||||||
|
class SendMissingPaymentMailsCommand {
|
||||||
|
function __construct(public SendMissingPaymentMailsRequest $request) {}
|
||||||
|
|
||||||
|
public function execute() : SendMissingPaymentMailsResponse {
|
||||||
|
$response = new SendMissingPaymentMailsResponse();
|
||||||
|
|
||||||
|
foreach ($this->request->eventParticipants->getParticipantsWithMissingPayments($this->request->event, $this->request->httpRequest) as $participant) {
|
||||||
|
$participantResource = $participant->toResource()->toArray($this->request->httpRequest);
|
||||||
|
if (!$participantResource['needs_payment']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mail::to($participant->email_1)->send(new ParticipantPaymentMissingPaymentMail(
|
||||||
|
participant: $participant,
|
||||||
|
));
|
||||||
|
|
||||||
|
if ($participant->email_2 !== null && $participant->email_2 !== $participant->email_1) {
|
||||||
|
Mail::to($participant->email_2)->send(new ParticipantPaymentMissingPaymentMail(
|
||||||
|
participant: $participant,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->remindedParticipants++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->success = true;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||||
|
|
||||||
|
use App\Models\Event;
|
||||||
|
use App\Repositories\EventParticipantRepository;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SendMissingPaymentMailsRequest {
|
||||||
|
function __construct(
|
||||||
|
public Event $event,
|
||||||
|
public EventParticipantRepository $eventParticipants,
|
||||||
|
public Request $httpRequest
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Actions\SendMissingPaymentMails;
|
||||||
|
|
||||||
|
class SendMissingPaymentMailsResponse {
|
||||||
|
function __construct(
|
||||||
|
public bool $success = false,
|
||||||
|
public int $remindedParticipants = 0
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -22,8 +22,8 @@ use Illuminate\Http\Response;
|
|||||||
use function Symfony\Component\String\b;
|
use function Symfony\Component\String\b;
|
||||||
|
|
||||||
class DetailsController extends CommonController {
|
class DetailsController extends CommonController {
|
||||||
public function __invoke(int $eventId) {
|
public function __invoke(string $eventId) {
|
||||||
$event = $this->events->getById($eventId);
|
$event = $this->events->getByIdentifier($eventId);
|
||||||
return new InertiaProvider('Event/Details', ['event' => $event])->render();
|
return new InertiaProvider('Event/Details', ['event' => $event])->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
33
app/Domains/Event/Controllers/PaymentReminderController.php
Normal file
33
app/Domains/Event/Controllers/PaymentReminderController.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Event\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\Event\Actions\SendMissingPaymentMails\SendMissingPaymentMailsCommand;
|
||||||
|
use App\Domains\Event\Actions\SendMissingPaymentMails\SendMissingPaymentMailsRequest;
|
||||||
|
use App\Scopes\CommonController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class PaymentReminderController extends CommonController
|
||||||
|
{
|
||||||
|
public function __invoke(string $eventIdentifier, Request $request)
|
||||||
|
{
|
||||||
|
$event = $this->events->getByIdentifier($eventIdentifier, true);
|
||||||
|
|
||||||
|
$sendPaymentReminderMailsRequest = new SendMissingPaymentMailsRequest(
|
||||||
|
event: $event,
|
||||||
|
eventParticipants: $this->eventParticipants,
|
||||||
|
httpRequest: $request
|
||||||
|
);
|
||||||
|
|
||||||
|
$sendPaymentReminderMailsCommand = new SendMissingPaymentMailsCommand(request: $sendPaymentReminderMailsRequest);
|
||||||
|
$sendPaymentReminderResponse = $sendPaymentReminderMailsCommand->execute();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => $sendPaymentReminderResponse->success,
|
||||||
|
'message' => $sendPaymentReminderResponse->success ?
|
||||||
|
sprintf('Es wurden %1$s Personen über fehlende Teilnahmebeiträge informiert', $sendPaymentReminderResponse->remindedParticipants) :
|
||||||
|
'Beim Senden der Benachrichtigungen ist ein Fehler aufgetreten.',
|
||||||
|
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use App\Domains\Event\Controllers\ParticipantPaymentController;
|
|||||||
use App\Domains\Event\Controllers\ParticipantReSignOnController;
|
use App\Domains\Event\Controllers\ParticipantReSignOnController;
|
||||||
use App\Domains\Event\Controllers\ParticipantSignOffController;
|
use App\Domains\Event\Controllers\ParticipantSignOffController;
|
||||||
use App\Domains\Event\Controllers\ParticipantUpdateController;
|
use App\Domains\Event\Controllers\ParticipantUpdateController;
|
||||||
|
use App\Domains\Event\Controllers\PaymentReminderController;
|
||||||
use App\Domains\Event\Controllers\SignupController;
|
use App\Domains\Event\Controllers\SignupController;
|
||||||
use App\Middleware\IdentifyTenant;
|
use App\Middleware\IdentifyTenant;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -28,6 +29,8 @@ Route::prefix('api/v1')
|
|||||||
Route::post('/send', SendController::class);
|
Route::post('/send', SendController::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::get('{eventIdentifier}/send-payment-reminder', PaymentReminderController::class);
|
||||||
|
|
||||||
Route::prefix('/details/{eventId}') ->group(function () {
|
Route::prefix('/details/{eventId}') ->group(function () {
|
||||||
Route::get('/summary', [DetailsController::class, 'summary']);
|
Route::get('/summary', [DetailsController::class, 'summary']);
|
||||||
|
|
||||||
|
|||||||
@@ -161,17 +161,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (data.status !== 'success') {
|
if (data.status !== 'success') {
|
||||||
toas.error(data.message);
|
toast.error(data.message);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
console.log(data.event);
|
|
||||||
newEvent.value = data.event;
|
newEvent.value = data.event;
|
||||||
showParticipationFees.value = true;
|
showParticipationFees.value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function finishCreation() {
|
async function finishCreation() {
|
||||||
window.location.href = '/event/details/' + newEvent.value.id;
|
window.location.href = '/event/details/' + newEvent.value.identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {useAjax} from "../../../../../resources/js/components/ajaxHandler.js";
|
|||||||
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
import TextEditor from "../../../../Views/Components/TextEditor.vue";
|
||||||
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
import ErrorText from "../../../../Views/Components/ErrorText.vue";
|
||||||
import {toast} from "vue3-toastify";
|
import {toast} from "vue3-toastify";
|
||||||
|
import InfoText from "../../../../Views/Components/InfoText.vue";
|
||||||
const { request } = useAjax();
|
const { request } = useAjax();
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const errorMessage = ref(null)
|
const errorMessage = ref(null)
|
||||||
|
const infoMessage = ref(null)
|
||||||
|
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'closeComposer',
|
'closeComposer',
|
||||||
@@ -49,6 +51,10 @@ function close() {
|
|||||||
|
|
||||||
|
|
||||||
async function sendMail() {
|
async function sendMail() {
|
||||||
|
document.getElementById('sendMessageButton').style.display = 'none';
|
||||||
|
infoMessage.value = 'Die Rundmail wird nun gesendet. Dies kann einen Moment dauern. Bitte verlasse diese Seite nicht.'
|
||||||
|
|
||||||
|
toast.info('Die Rundmail wird nun gesendet. Dies kann einen Moment dauern. Bitte verlasse diese Seite nicht.')
|
||||||
const response = await request('/api/v1/event/' + props.event.identifier + '/mailing/send', {
|
const response = await request('/api/v1/event/' + props.event.identifier + '/mailing/send', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
@@ -59,10 +65,14 @@ async function sendMail() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
infoMessage.value = null
|
||||||
|
document.getElementById('sendMessageButton').style.display = 'block';
|
||||||
close();
|
close();
|
||||||
toast.success(response.message)
|
toast.success(response.message)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
infoMessage.value = null
|
||||||
|
document.getElementById('sendMessageButton').style.display = 'block';
|
||||||
errorMessage.value = response.message
|
errorMessage.value = response.message
|
||||||
toast.error(response.message)
|
toast.error(response.message)
|
||||||
}
|
}
|
||||||
@@ -94,8 +104,8 @@ const form = reactive({
|
|||||||
<strong><ErrorText :message="errorMessage" /></strong>
|
<strong><ErrorText :message="errorMessage" /></strong>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<info-text :message="infoMessage" />
|
||||||
<input type="button" @click="sendMail" value="Senden" class="" />
|
<input type="button" id="sendMessageButton" @click="sendMail" value="Senden" class="" />
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import Modal from "../../../../Views/Components/Modal.vue";
|
import Modal from "../../../../Views/Components/Modal.vue";
|
||||||
import MailCompose from "./MailCompose.vue";
|
import MailCompose from "./MailCompose.vue";
|
||||||
import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue";
|
import FullScreenModal from "../../../../Views/Components/FullScreenModal.vue";
|
||||||
|
import {toast} from "vue3-toastify";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: Object,
|
data: Object,
|
||||||
@@ -54,6 +55,17 @@
|
|||||||
function mailToGroup() {
|
function mailToGroup() {
|
||||||
mailCompose.value = true
|
mailCompose.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendPaymentReminder() {
|
||||||
|
toast.info("Die Nachrichten werden gesendet. Bitte verlasse diese Seite nicht.");
|
||||||
|
const response = await fetch("/api/v1/event/" + props.data.event.identifier + "/send-payment-reminder/" );
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.success) {
|
||||||
|
toast.success(data.message)
|
||||||
|
} else {
|
||||||
|
toast.error(data.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -90,8 +102,8 @@
|
|||||||
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/photo-permission-list'">
|
<a :href="'/event/details/' + props.data.event.identifier + '/pdf/photo-permission-list'">
|
||||||
<input type="button" value="Foto-Erlaubnis (PDF)" />
|
<input type="button" value="Foto-Erlaubnis (PDF)" />
|
||||||
</a><br/>
|
</a><br/>
|
||||||
<input type="button" class="fix-button" value="Zahlungserinnerung senden" /><br/>
|
<input type="button" @click="sendPaymentReminder" class="fix-button" value="Zahlungserinnerung senden" /><br/>
|
||||||
<input type="button" class="deny-button" value="Letzte Mahnung senden" /><br/>
|
<input type="button" class="deny-button" value="Letzte Mahnung senden" style="display: none" /><br/>
|
||||||
<input type="button" value="Rundmail senden" @click="mailToGroup" /><br/>
|
<input type="button" value="Rundmail senden" @click="mailToGroup" /><br/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class GlobalDataProvider {
|
|||||||
|
|
||||||
|
|
||||||
foreach ($eventRepository->listAll() as $event) {
|
foreach ($eventRepository->listAll() as $event) {
|
||||||
$navigation['events'][] = ['url' => '/event/details/' . $event->id, 'display' => $event->name];
|
$navigation['events'][] = ['url' => '/event/details/' . $event->identifier, 'display' => $event->name];
|
||||||
}
|
}
|
||||||
|
|
||||||
$navigation['events'][] = ['url' => '/event/new', 'display' => 'Neue Veranstaltung'];
|
$navigation['events'][] = ['url' => '/event/new', 'display' => 'Neue Veranstaltung'];
|
||||||
|
|||||||
@@ -75,6 +75,17 @@ class EventParticipantRepository {
|
|||||||
return $participants;
|
return $participants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getParticipantsWithMissingPayments(Event$event, Request $request) : array {
|
||||||
|
$participants = [];
|
||||||
|
foreach ($event->participants()->whereNull('unregistered_at')
|
||||||
|
->whereColumn('amount', '<>', 'amount_paid')
|
||||||
|
->get() as $participant) {
|
||||||
|
$participants[] = $participant;
|
||||||
|
};
|
||||||
|
|
||||||
|
return $participants;
|
||||||
|
}
|
||||||
|
|
||||||
public function getParticipantsWithIntolerances(Event $event, Request $request) : array {
|
public function getParticipantsWithIntolerances(Event $event, Request $request) : array {
|
||||||
$participants = [];
|
$participants = [];
|
||||||
foreach ($event->participants()->whereNotNull('intolerances')->whereNot('intolerances' , '=', '')->get() as $participant) {
|
foreach ($event->participants()->whereNotNull('intolerances')->whereNot('intolerances' , '=', '')->get() as $participant) {
|
||||||
|
|||||||
10
app/Views/Components/InfoText.vue
Normal file
10
app/Views/Components/InfoText.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
message: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<small style="margin-bottom: 20px;" class="info_text" v-if="props.message">{{ props.message }}</small>
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -68,6 +68,12 @@ input[type="submit"]:hover,
|
|||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info_text {
|
||||||
|
color: #3369d3;
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.error_text {
|
.error_text {
|
||||||
color: red;
|
color: red;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
Reference in New Issue
Block a user