Diversen
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Models\ServiceRequest;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Communication;
|
||||
use App\Mail\ServiceRequestNotification;
|
||||
use App\Mail\PickupConfirmation;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -115,11 +116,11 @@ class ServiceRequestController extends Controller
|
||||
$validated = $request->validate([
|
||||
'actual_repair_date' => 'nullable|date|after:today',
|
||||
'actual_repair_time' => 'nullable|date_format:H:i',
|
||||
'status' => 'nullable|in:pending,confirmed,completed,cancelled',
|
||||
'status' => 'nullable|in:pending,confirmed,mail_sent,completed,cancelled',
|
||||
], [
|
||||
'actual_repair_date.after' => 'Werkelijke reparatie datum moet in de toekomst liggen',
|
||||
'actual_repair_time.date_format' => 'Werkelijke reparatie tijd moet een geldig tijdformaat hebben (HH:MM)',
|
||||
'status.in' => 'Status moet een van de volgende waarden hebben: in afwachting, bevestigd, voltooid, geannuleerd',
|
||||
'status.in' => 'Status moet een van de volgende waarden hebben: in afwachting, bevestigd, bevestigd mail verstuurd, voltooid, geannuleerd',
|
||||
]);
|
||||
|
||||
$serviceRequest->update($validated);
|
||||
@@ -252,6 +253,74 @@ class ServiceRequestController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send pickup confirmation email to customer
|
||||
*/
|
||||
public function sendPickupConfirmation(ServiceRequest $serviceRequest): JsonResponse
|
||||
{
|
||||
try {
|
||||
if (empty($serviceRequest->email)) {
|
||||
return response()->json([
|
||||
'message' => 'Geen e-mailadres beschikbaar voor deze service aanvraag'
|
||||
], 400);
|
||||
}
|
||||
|
||||
if (empty($serviceRequest->actual_repair_date) || empty($serviceRequest->actual_repair_time)) {
|
||||
return response()->json([
|
||||
'message' => 'Werkelijke reparatie datum en tijd zijn vereist'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Get the custom message from settings
|
||||
$customMessage = Setting::getValue('pickup_confirmation_email_text', 'Uw fiets is klaar en kan worden opgehaald.');
|
||||
|
||||
// Format the pickup date and time
|
||||
$pickupDate = \Carbon\Carbon::parse($serviceRequest->actual_repair_date)->format('d-m-Y');
|
||||
$pickupTime = $serviceRequest->actual_repair_time;
|
||||
|
||||
// Send email
|
||||
Mail::to($serviceRequest->email)->send(
|
||||
new PickupConfirmation(
|
||||
$serviceRequest,
|
||||
$customMessage,
|
||||
$pickupDate,
|
||||
$pickupTime
|
||||
)
|
||||
);
|
||||
|
||||
// Update status to 'mail_sent'
|
||||
$serviceRequest->update(['status' => 'mail_sent']);
|
||||
|
||||
// Store communication record
|
||||
$communication = Communication::create([
|
||||
'service_request_id' => $serviceRequest->id,
|
||||
'type' => 'email',
|
||||
'message' => 'Ophaalbevestiging verzonden - Datum: ' . $pickupDate . ', Tijd: ' . $pickupTime,
|
||||
'direction' => 'admin_to_customer',
|
||||
'recipient' => $serviceRequest->email,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
|
||||
Log::info('Ophaalbevestiging e-mail verzonden naar klant', [
|
||||
'service_request_id' => $serviceRequest->id,
|
||||
'email' => $serviceRequest->email,
|
||||
'pickup_date' => $pickupDate,
|
||||
'pickup_time' => $pickupTime
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Ophaalbevestiging succesvol verzonden',
|
||||
'communication' => $communication,
|
||||
'service_request' => $serviceRequest->fresh()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'message' => 'Ophaalbevestiging verzenden mislukt',
|
||||
'error' => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get communication history
|
||||
*/
|
||||
@@ -261,7 +330,7 @@ class ServiceRequestController extends Controller
|
||||
$communications = $serviceRequest->communications()
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
|
||||
return response()->json($communications);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\ServiceRequest;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PickupConfirmation extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public ServiceRequest $serviceRequest,
|
||||
public string $customMessage,
|
||||
public string $pickupDate,
|
||||
public string $pickupTime
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Uw fiets is klaar voor ophalen - ' . $this->serviceRequest->name,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.pickup-confirmation',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,8 @@ class ServiceRequest extends Model
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'requested_date' => 'date',
|
||||
'requested_time' => 'datetime:H:i',
|
||||
'actual_repair_date' => 'date',
|
||||
'actual_repair_time' => 'datetime:H:i',
|
||||
'requested_date' => 'date:Y-m-d',
|
||||
'actual_repair_date' => 'date:Y-m-d',
|
||||
];
|
||||
|
||||
public function communications()
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// For SQLite, we need to recreate the table to modify the enum
|
||||
// First, create a temporary table with the new structure
|
||||
Schema::create('service_requests_temp', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->text('address');
|
||||
$table->string('place');
|
||||
$table->string('email')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->text('repair_description');
|
||||
$table->date('requested_date');
|
||||
$table->time('requested_time');
|
||||
$table->date('actual_repair_date')->nullable();
|
||||
$table->time('actual_repair_time')->nullable();
|
||||
$table->enum('status', ['pending', 'confirmed', 'mail_sent', 'completed', 'cancelled'])->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Copy data from old table to temp table
|
||||
DB::statement('INSERT INTO service_requests_temp SELECT * FROM service_requests');
|
||||
|
||||
// Drop the old table
|
||||
Schema::dropIfExists('service_requests');
|
||||
|
||||
// Rename temp table to original name
|
||||
Schema::rename('service_requests_temp', 'service_requests');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Recreate the table with original enum values
|
||||
Schema::create('service_requests_temp', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->text('address');
|
||||
$table->string('place');
|
||||
$table->string('email')->nullable();
|
||||
$table->string('phone')->nullable();
|
||||
$table->text('repair_description');
|
||||
$table->date('requested_date');
|
||||
$table->time('requested_time');
|
||||
$table->date('actual_repair_date')->nullable();
|
||||
$table->time('actual_repair_time')->nullable();
|
||||
$table->enum('status', ['pending', 'confirmed', 'completed', 'cancelled'])->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Copy data, converting 'mail_sent' status to 'confirmed'
|
||||
DB::statement("INSERT INTO service_requests_temp SELECT id, name, address, place, email, phone, repair_description, requested_date, requested_time, actual_repair_date, actual_repair_time, CASE WHEN status = 'mail_sent' THEN 'confirmed' ELSE status END, created_at, updated_at FROM service_requests");
|
||||
|
||||
// Drop the current table
|
||||
Schema::dropIfExists('service_requests');
|
||||
|
||||
// Rename temp table to original name
|
||||
Schema::rename('service_requests_temp', 'service_requests');
|
||||
}
|
||||
};
|
||||
@@ -38,6 +38,18 @@ class SettingsSeeder extends Seeder
|
||||
'key' => 'working_hours',
|
||||
'value' => 'Maandag-Vrijdag: 08:00 - 18:00, Zaterdag: 09:00 - 16:00',
|
||||
'description' => 'Bedrijfsopeningstijden'
|
||||
],
|
||||
[
|
||||
'key' => 'pickup_confirmation_email_text',
|
||||
'value' => 'Beste klant,
|
||||
|
||||
Uw fiets is klaar en kan worden opgehaald op het onderstaande tijdstip.
|
||||
|
||||
Wij zien u graag tegemoet!
|
||||
|
||||
Met vriendelijke groet,
|
||||
Fiets Service Centrum',
|
||||
'description' => 'Standaard tekst voor ophaalbevestiging e-mail'
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="Professionele fietsreparatie en onderhoudsdiensten" />
|
||||
<title>Bike Service - Jouw fiets, onze zorg</title>
|
||||
<script type="module" crossorigin src="/assets/index-CTOGI7pY.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-jfsEyp3I.css">
|
||||
<script type="module" crossorigin src="/assets/index-aYPxxDKl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DAYhzhBJ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Uw fiets is klaar voor ophalen</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
.header {
|
||||
background: linear-gradient(135deg, #2c5f5d 0%, #3d7a77 100%);
|
||||
color: white;
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.logo {
|
||||
max-width: 200px;
|
||||
height: auto;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.header h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content {
|
||||
padding: 30px;
|
||||
}
|
||||
.message {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #2c5f5d;
|
||||
margin-bottom: 30px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.pickup-info {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
border: 3px solid #2c5f5d;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.pickup-info h2 {
|
||||
color: #2c5f5d;
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
||||
}
|
||||
.info-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.info-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 15px;
|
||||
min-width: 30px;
|
||||
}
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #2c5f5d;
|
||||
min-width: 140px;
|
||||
}
|
||||
.info-value {
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.customer-info {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.customer-info h3 {
|
||||
color: #2c5f5d;
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.customer-row {
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.customer-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.customer-value {
|
||||
color: #333;
|
||||
}
|
||||
.footer {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
}
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.highlight {
|
||||
background: #8b6f47;
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="{{ config('app.url') }}/images/bikeservice_logo_navbar.svg" alt="Fiets Service Logo" class="logo" />
|
||||
<h1>🚲 Uw fiets is klaar!</h1>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="message">
|
||||
{{ $customMessage }}
|
||||
</div>
|
||||
|
||||
<div class="pickup-info">
|
||||
<h2>📅 Ophaalgegevens</h2>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-icon">📅</div>
|
||||
<div>
|
||||
<div class="info-label">Datum van ophalen:</div>
|
||||
<div class="info-value">{{ $pickupDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-icon">🕐</div>
|
||||
<div>
|
||||
<div class="info-label">Tijd van ophalen:</div>
|
||||
<div class="info-value">{{ $pickupTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="highlight">
|
||||
Vergeet niet uw afhaalbewijs mee te nemen!
|
||||
</div>
|
||||
|
||||
<div class="customer-info">
|
||||
<h3>Uw gegevens</h3>
|
||||
<div class="customer-row">
|
||||
<div class="customer-label">Naam:</div>
|
||||
<div class="customer-value">{{ $serviceRequest->name }}</div>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<div class="customer-label">Adres:</div>
|
||||
<div class="customer-value">{{ $serviceRequest->address }}, {{ $serviceRequest->place }}</div>
|
||||
</div>
|
||||
@if($serviceRequest->phone)
|
||||
<div class="customer-row">
|
||||
<div class="customer-label">Telefoon:</div>
|
||||
<div class="customer-value">{{ $serviceRequest->phone }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><strong>Fiets Service Centrum</strong></p>
|
||||
<p>Dit is een automatisch gegenereerde e-mail.</p>
|
||||
<p>Heeft u vragen? Neem dan contact met ons op.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -48,6 +48,7 @@ Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::apiResource('service-requests', ServiceRequestController::class);
|
||||
Route::post('service-requests/{serviceRequest}/send-email', [ServiceRequestController::class, 'sendEmail']);
|
||||
Route::post('service-requests/{serviceRequest}/send-sms', [ServiceRequestController::class, 'sendSms']);
|
||||
Route::post('service-requests/{serviceRequest}/send-pickup-confirmation', [ServiceRequestController::class, 'sendPickupConfirmation']);
|
||||
Route::get('service-requests/{serviceRequest}/communications', [ServiceRequestController::class, 'communications']);
|
||||
|
||||
Route::apiResource('blocked-dates', BlockedDateController::class);
|
||||
|
||||
Reference in New Issue
Block a user