phase 2 dev
This commit is contained in:
parent
5a7478907e
commit
ba48745809
59 changed files with 2692 additions and 1994 deletions
156
app/Console/Commands/BookingsAuditStornoPriceTotal.php
Normal file
156
app/Console/Commands/BookingsAuditStornoPriceTotal.php
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Booking;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Findet Buchungen, bei denen nach Storno price_total nicht mehr zu price_canceled passt
|
||||
* (typisch nach Speichern / calculate_price_total ohne Storno-Schutz).
|
||||
*
|
||||
* php artisan bookings:audit-storno-price-total
|
||||
* php artisan bookings:audit-storno-price-total --export=/tmp/mismatch.csv
|
||||
* php artisan bookings:audit-storno-price-total --fix (setzt price_total = price_canceled)
|
||||
*/
|
||||
class BookingsAuditStornoPriceTotal extends Command
|
||||
{
|
||||
protected $signature = 'bookings:audit-storno-price-total
|
||||
{--fix : Setzt price_total auf price_canceled für betroffene Zeilen}
|
||||
{--export= : CSV-Datei (relativ zum Projektroot oder absolut)}
|
||||
{--id=* : Nur diese Buchungs-IDs (booking.id)}';
|
||||
|
||||
protected $description = 'Abgleich Storno: price_total vs. price_canceled (inkl. optional --fix / --export)';
|
||||
|
||||
private const EPS = 0.009;
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$query = Booking::query()
|
||||
->whereNotNull('canceled')
|
||||
->whereNotNull('price_canceled')
|
||||
->orderBy('id');
|
||||
|
||||
$ids = $this->option('id');
|
||||
if ($ids !== []) {
|
||||
$query->whereIn('id', $ids);
|
||||
}
|
||||
|
||||
$mismatch = [];
|
||||
$fixable = [];
|
||||
|
||||
foreach ($query->cursor() as $booking) {
|
||||
$pt = (float) $booking->getPriceTotalRaw();
|
||||
$pc = (float) $booking->getPriceCanceledRaw();
|
||||
if (abs($pt - $pc) <= self::EPS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasStornoRow = $booking->booking_strono()->exists();
|
||||
$hasStornoDoc = $booking->hasDocument('storno');
|
||||
|
||||
$mismatch[] = [
|
||||
'id' => $booking->id,
|
||||
'merlin_order_number' => $booking->merlin_order_number,
|
||||
'price' => $booking->getPriceRaw(),
|
||||
'price_canceled' => $pc,
|
||||
'price_total_db' => $pt,
|
||||
'price_balance' => $booking->getPriceBalanceRaw(),
|
||||
'canceled_pct' => $booking->getCanceledRaw(),
|
||||
'booking_storno_row' => $hasStornoRow ? 'ja' : 'nein',
|
||||
'storno_pdf' => $hasStornoDoc ? 'ja' : 'nein',
|
||||
'diff' => round($pt - $pc, 2),
|
||||
];
|
||||
$fixable[] = $booking->id;
|
||||
}
|
||||
|
||||
$this->warn('Storno mit gesetztem price_canceled, aber price_total weicht ab:');
|
||||
$this->newLine();
|
||||
|
||||
if ($mismatch === []) {
|
||||
$this->info('Keine Abweichungen gefunden.');
|
||||
} else {
|
||||
$this->table(
|
||||
['id', 'MyJack', 'price', 'price_canceled', 'price_total', 'diff', 'storno_row', 'storno_pdf'],
|
||||
collect($mismatch)->map(function ($r) {
|
||||
return [
|
||||
$r['id'],
|
||||
$r['merlin_order_number'],
|
||||
$r['price'],
|
||||
$r['price_canceled'],
|
||||
$r['price_total_db'],
|
||||
$r['diff'],
|
||||
$r['booking_storno_row'],
|
||||
$r['storno_pdf'],
|
||||
];
|
||||
})->all()
|
||||
);
|
||||
$this->newLine();
|
||||
$this->line('Anzahl: '.count($mismatch));
|
||||
}
|
||||
|
||||
$exportPath = $this->option('export');
|
||||
if ($exportPath && $mismatch !== []) {
|
||||
$path = $this->resolveExportPath($exportPath);
|
||||
$fp = fopen($path, 'w');
|
||||
if ($fp === false) {
|
||||
$this->error('Export nicht schreibbar: '.$path);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
fputcsv($fp, array_keys($mismatch[0]), ';');
|
||||
foreach ($mismatch as $row) {
|
||||
fputcsv($fp, $row, ';');
|
||||
}
|
||||
fclose($fp);
|
||||
$this->info('CSV geschrieben: '.$path);
|
||||
}
|
||||
|
||||
if ($this->option('fix')) {
|
||||
if ($fixable === []) {
|
||||
$this->info('Nichts zu korrigieren.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
if (! $this->confirm('price_total für '.count($fixable).' Buchung(en) auf price_canceled setzen?', true)) {
|
||||
$this->warn('Abgebrochen.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$updated = 0;
|
||||
DB::transaction(function () use ($fixable, &$updated) {
|
||||
foreach ($fixable as $id) {
|
||||
$b = Booking::query()->lockForUpdate()->find($id);
|
||||
if (! $b) {
|
||||
continue;
|
||||
}
|
||||
$pc = (float) $b->getPriceCanceledRaw();
|
||||
$pt = (float) $b->getPriceTotalRaw();
|
||||
if (abs($pt - $pc) <= self::EPS) {
|
||||
continue;
|
||||
}
|
||||
$b->price_total = round($pc, 2);
|
||||
$b->save();
|
||||
$updated++;
|
||||
}
|
||||
});
|
||||
$this->info("Korrigiert: {$updated} Buchung(en).");
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->line('Hinweis: Buchungen mit canceled gesetzt, aber price_canceled NULL, werden hier nicht geprüft.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function resolveExportPath(string $exportPath): string
|
||||
{
|
||||
if ($exportPath[0] === '/' || preg_match('#^[A-Za-z]:\\\\#', $exportPath)) {
|
||||
return $exportPath;
|
||||
}
|
||||
|
||||
return base_path(trim($exportPath, '/'));
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ class ContactsFindDuplicates extends Command
|
|||
|
||||
// ── HIGH: gleiche E-Mail ──────────────────────────────────────────
|
||||
if ($this->shouldCheck('HIGH')) {
|
||||
$emailDupes = DB::table('customer')
|
||||
$emailDupes = DB::table('contacts')
|
||||
->select('email', DB::raw('COUNT(*) as cnt'), DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC) as ids'))
|
||||
->whereNotNull('email')
|
||||
->where('email', '!=', '')
|
||||
|
|
@ -60,7 +60,7 @@ class ContactsFindDuplicates extends Command
|
|||
|
||||
// ── MEDIUM: Name + Vorname + Geburtsdatum ────────────────────────
|
||||
if ($this->shouldCheck('MEDIUM')) {
|
||||
$nameBdDupes = DB::table('customer')
|
||||
$nameBdDupes = DB::table('contacts')
|
||||
->select(
|
||||
'name', 'firstname', 'birthdate',
|
||||
DB::raw('COUNT(*) as cnt'),
|
||||
|
|
@ -88,7 +88,7 @@ class ContactsFindDuplicates extends Command
|
|||
|
||||
// ── LOW: Name + Vorname + PLZ ─────────────────────────────────────
|
||||
if ($this->shouldCheck('LOW')) {
|
||||
$nameZipDupes = DB::table('customer')
|
||||
$nameZipDupes = DB::table('contacts')
|
||||
->select(
|
||||
'name', 'firstname', 'zip',
|
||||
DB::raw('COUNT(*) as cnt'),
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class ContactsMergeDuplicates extends Command
|
|||
|
||||
private function findByEmail(): array
|
||||
{
|
||||
return DB::table('customer')
|
||||
return DB::table('contacts')
|
||||
->select('email', DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids'))
|
||||
->whereNotNull('email')
|
||||
->where('email', '!=', '')
|
||||
|
|
@ -104,7 +104,7 @@ class ContactsMergeDuplicates extends Command
|
|||
|
||||
private function findByNameBirthdate(): array
|
||||
{
|
||||
return DB::table('customer')
|
||||
return DB::table('contacts')
|
||||
->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids'))
|
||||
->whereNotNull('name')
|
||||
->whereNotNull('firstname')
|
||||
|
|
@ -119,7 +119,7 @@ class ContactsMergeDuplicates extends Command
|
|||
|
||||
private function findByNameZip(): array
|
||||
{
|
||||
return DB::table('customer')
|
||||
return DB::table('contacts')
|
||||
->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids'))
|
||||
->whereNotNull('name')
|
||||
->whereNotNull('firstname')
|
||||
|
|
@ -173,11 +173,11 @@ class ContactsMergeDuplicates extends Command
|
|||
private function mergeInto(int $masterId, int $dupeId): void
|
||||
{
|
||||
// 1. Leads umhängen
|
||||
$leadCount = DB::table('lead')->where('customer_id', $dupeId)->count();
|
||||
$leadCount = DB::table('inquiries')->where('customer_id', $dupeId)->count();
|
||||
if ($leadCount > 0) {
|
||||
$this->line(" lead.customer_id: {$leadCount} Zeile(n) → #{$masterId}");
|
||||
if (!$this->dryRun) {
|
||||
DB::table('lead')
|
||||
DB::table('inquiries')
|
||||
->where('customer_id', $dupeId)
|
||||
->update(['customer_id' => $masterId]);
|
||||
}
|
||||
|
|
@ -220,7 +220,7 @@ class ContactsMergeDuplicates extends Command
|
|||
|
||||
// 5. Duplikat als zusammengeführt markieren
|
||||
if (!$this->dryRun) {
|
||||
DB::table('customer')
|
||||
DB::table('contacts')
|
||||
->where('id', $dupeId)
|
||||
->update([
|
||||
'merged_into_id' => $masterId,
|
||||
|
|
|
|||
|
|
@ -193,7 +193,8 @@ class SyncNewsletterKulturreisen extends Command
|
|||
'description' => 'Kontakt durch Kulturreisen-Buchung erstellt',
|
||||
'metadata' => [
|
||||
'booking_id' => $booking->id,
|
||||
'lead_id' => $booking->lead_id,
|
||||
// Metadaten-Key bleibt `lead_id`; Wert kommt aus booking.inquiry_id.
|
||||
'lead_id' => $booking->inquiry_id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class BookingController extends Controller
|
|||
$ret = [
|
||||
'url_v1' => make_old_url('/index.php/booking/' . $booking->id . '/edit'),
|
||||
'url_v3' => route('booking_detail', $booking->id),
|
||||
'lead_id' => $booking->lead_id
|
||||
// API-Feld bleibt `lead_id` aus Abwärtskompatibilität für API-Konsumenten;
|
||||
// Wert kommt nach Modul 3 Phase 2 aus booking.inquiry_id.
|
||||
'lead_id' => $booking->inquiry_id
|
||||
];
|
||||
return response()->json(['success' => "import", "ret" => $ret], $this->successStatus);
|
||||
|
||||
|
|
|
|||
|
|
@ -172,10 +172,10 @@ class ReportBookingController extends Controller
|
|||
->orderColumn('end_date', 'end_date $1')
|
||||
->orderColumn('price', 'price $1')
|
||||
->orderColumn('booking_date', 'booking_date $1')
|
||||
->orderColumn('customer.fullName', 'customer.firstname $1')
|
||||
->orderColumn('customer.firstname', 'customer.firstname $1')
|
||||
->orderColumn('customer.name', 'customer.name $1')
|
||||
//->orderColumn('lead.status_id', 'lead.status_id $1')
|
||||
->orderColumn('customer.fullName', 'contacts.firstname $1')
|
||||
->orderColumn('customer.firstname', 'contacts.firstname $1')
|
||||
->orderColumn('customer.name', 'contacts.name $1')
|
||||
//->orderColumn('lead.status_id', 'inquiries.status_id $1')
|
||||
//->orderColumn('is_cleared', 'is_cleared $1')
|
||||
->rawColumns(['id', 'lead.status_id', 'service_provider.names'])
|
||||
->make(true);
|
||||
|
|
@ -384,9 +384,9 @@ class ReportBookingController extends Controller
|
|||
->orderColumn('end_date', 'end_date $1')
|
||||
->orderColumn('price', 'price $1')
|
||||
->orderColumn('booking_date', 'booking_date $1')
|
||||
->orderColumn('customer.firstname', 'customer.firstname $1')
|
||||
->orderColumn('customer.name', 'customer.name $1')
|
||||
//->orderColumn('lead.status_id', 'lead.status_id $1')
|
||||
->orderColumn('customer.firstname', 'contacts.firstname $1')
|
||||
->orderColumn('customer.name', 'contacts.name $1')
|
||||
//->orderColumn('lead.status_id', 'inquiries.status_id $1')
|
||||
//->orderColumn('is_cleared', 'is_cleared $1')
|
||||
->rawColumns(['id', 'old_crm', 'check_total', 'lead.status_id'])
|
||||
->make(true);
|
||||
|
|
|
|||
|
|
@ -189,10 +189,10 @@ class ReportController extends Controller
|
|||
->orderColumn('end_date', 'end_date $1')
|
||||
->orderColumn('price', 'price $1')
|
||||
->orderColumn('booking_date', 'booking_date $1')
|
||||
->orderColumn('customer.fullName', 'customer.firstname $1')
|
||||
->orderColumn('customer.firstname', 'customer.firstname $1')
|
||||
->orderColumn('customer.name', 'customer.name $1')
|
||||
//->orderColumn('lead.status_id', 'lead.status_id $1')
|
||||
->orderColumn('customer.fullName', 'contacts.firstname $1')
|
||||
->orderColumn('customer.firstname', 'contacts.firstname $1')
|
||||
->orderColumn('customer.name', 'contacts.name $1')
|
||||
//->orderColumn('lead.status_id', 'inquiries.status_id $1')
|
||||
//->orderColumn('is_cleared', 'is_cleared $1')
|
||||
->rawColumns(['id', 'lead.status_id', 'service_provider.names'])
|
||||
->make(true);
|
||||
|
|
@ -400,10 +400,10 @@ class ReportController extends Controller
|
|||
$price = 0;
|
||||
$isset = [];
|
||||
foreach ($all as $v){
|
||||
if(!in_array($v->booking->lead_id, $isset)){
|
||||
if(!in_array($v->booking->inquiry_id, $isset)){
|
||||
$price += $v->booking->isCanceled() ? $v->booking->getPriceCanceledRaw() : $v->booking->getPriceRaw();
|
||||
}
|
||||
$isset[] = $v->booking->lead_id;
|
||||
$isset[] = $v->booking->inquiry_id;
|
||||
|
||||
}
|
||||
return Util::_number_format($price);
|
||||
|
|
@ -416,8 +416,8 @@ class ReportController extends Controller
|
|||
$price = 0;
|
||||
$isset = [];
|
||||
foreach ($all as $v){
|
||||
$price += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->getPriceTotalRaw();
|
||||
$isset[] = $v->booking->lead_id;
|
||||
$price += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->getPriceTotalRaw();
|
||||
$isset[] = $v->booking->inquiry_id;
|
||||
}
|
||||
return Util::_number_format($price);
|
||||
})
|
||||
|
|
@ -429,8 +429,8 @@ class ReportController extends Controller
|
|||
$proceeds = 0;
|
||||
$isset = [];
|
||||
foreach ($all as $v){
|
||||
$proceeds += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->proceeds(true);
|
||||
$isset[] = $v->booking->lead_id;
|
||||
$proceeds += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->proceeds(true);
|
||||
$isset[] = $v->booking->inquiry_id;
|
||||
}
|
||||
return Util::_number_format($proceeds);
|
||||
/*$all = $query->get();
|
||||
|
|
@ -562,7 +562,7 @@ class ReportController extends Controller
|
|||
$ctemps[$export->booking->id][] = array(
|
||||
'Zähler' => $new ? $export->getCounter() : "",
|
||||
'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "",
|
||||
'CRM Nr' => $new ? $export->booking->lead_id : "",
|
||||
'CRM Nr' => $new ? $export->booking->inquiry_id : "",
|
||||
'Kunde' => $new ? $export->booking->customer->name : "",
|
||||
'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "",
|
||||
'Organisation' => $new ? ($export->booking->isCanceled() ? $export->booking->price_canceled : $export->booking->price) : "",
|
||||
|
|
@ -630,7 +630,7 @@ class ReportController extends Controller
|
|||
$ctemps[$export->booking->id][] = array(
|
||||
'Zähler' => $new ? $export->getCounter() : "",
|
||||
'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "",
|
||||
'CRM Nr' => $new ? $export->booking->lead_id : "",
|
||||
'CRM Nr' => $new ? $export->booking->inquiry_id : "",
|
||||
'Kunde' => $new ? $export->booking->customer->name : "",
|
||||
'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "",
|
||||
'Reiseland' => $new && $export->booking->travel_country ? $export->booking->travel_country->name : "",
|
||||
|
|
@ -725,9 +725,9 @@ class ReportController extends Controller
|
|||
->orderColumn('end_date', 'end_date $1')
|
||||
->orderColumn('price', 'price $1')
|
||||
->orderColumn('booking_date', 'booking_date $1')
|
||||
->orderColumn('customer.firstname', 'customer.firstname $1')
|
||||
->orderColumn('customer.name', 'customer.name $1')
|
||||
//->orderColumn('lead.status_id', 'lead.status_id $1')
|
||||
->orderColumn('customer.firstname', 'contacts.firstname $1')
|
||||
->orderColumn('customer.name', 'contacts.name $1')
|
||||
//->orderColumn('lead.status_id', 'inquiries.status_id $1')
|
||||
//->orderColumn('is_cleared', 'is_cleared $1')
|
||||
->rawColumns(['id', 'old_crm', 'check_total', 'lead.status_id'])
|
||||
->make(true);
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class ReportLeadsController extends Controller
|
|||
{
|
||||
|
||||
$query = Lead::with('customer')->with('sf_guard_user')->with('status')
|
||||
->select('lead.*');
|
||||
->select('inquiries.*');
|
||||
//->where('deleted_at', '=', null);
|
||||
|
||||
/*
|
||||
|
|
@ -132,7 +132,7 @@ class ReportLeadsController extends Controller
|
|||
// $q->select('sent_at')->where('sent_at', DB::raw("(select max('sent_at') customer_mails)")); //)
|
||||
})->orderBy(
|
||||
LeadMail::select('sent_at')
|
||||
->whereColumn('lead_id', 'lead.id')
|
||||
->whereColumn('lead_id', 'inquiries.id')
|
||||
->orderBy('sent_at', 'DESC')
|
||||
->limit(1)
|
||||
, $order);
|
||||
|
|
@ -161,9 +161,9 @@ class ReportLeadsController extends Controller
|
|||
$orderByNum = [
|
||||
0 => 'id',
|
||||
1 => 'customer_id',
|
||||
2 => 'customer.firstname',
|
||||
3 => 'customer.name',
|
||||
4 => 'customer.email',
|
||||
2 => 'contacts.firstname',
|
||||
3 => 'contacts.name',
|
||||
4 => 'contacts.email',
|
||||
5 => 'request_date',
|
||||
6 => 'travel_country',
|
||||
7 => 'sf_guard_user.last_name',
|
||||
|
|
|
|||
|
|
@ -104,10 +104,10 @@ class ReportProviderController extends Controller
|
|||
$price = 0;
|
||||
$isset = [];
|
||||
foreach ($all as $v){
|
||||
if(!in_array($v->booking->lead_id, $isset)){
|
||||
if(!in_array($v->booking->inquiry_id, $isset)){
|
||||
$price += $v->booking->isCanceled() ? $v->booking->getPriceCanceledRaw() : $v->booking->getPriceRaw();
|
||||
}
|
||||
$isset[] = $v->booking->lead_id;
|
||||
$isset[] = $v->booking->inquiry_id;
|
||||
|
||||
}
|
||||
return Util::_number_format($price);
|
||||
|
|
@ -120,8 +120,8 @@ class ReportProviderController extends Controller
|
|||
$price = 0;
|
||||
$isset = [];
|
||||
foreach ($all as $v){
|
||||
$price += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->getPriceTotalRaw();
|
||||
$isset[] = $v->booking->lead_id;
|
||||
$price += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->getPriceTotalRaw();
|
||||
$isset[] = $v->booking->inquiry_id;
|
||||
}
|
||||
return Util::_number_format($price);
|
||||
})
|
||||
|
|
@ -133,8 +133,8 @@ class ReportProviderController extends Controller
|
|||
$proceeds = 0;
|
||||
$isset = [];
|
||||
foreach ($all as $v){
|
||||
$proceeds += in_array($v->booking->lead_id, $isset) ? 0 : $v->booking->proceeds(true);
|
||||
$isset[] = $v->booking->lead_id;
|
||||
$proceeds += in_array($v->booking->inquiry_id, $isset) ? 0 : $v->booking->proceeds(true);
|
||||
$isset[] = $v->booking->inquiry_id;
|
||||
}
|
||||
return Util::_number_format($proceeds);
|
||||
/*$all = $query->get();
|
||||
|
|
@ -272,7 +272,7 @@ class ReportProviderController extends Controller
|
|||
$ctemps[$export->booking->id][] = array(
|
||||
'Zähler' => $new ? $export->getCounter() : "",
|
||||
'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "",
|
||||
'CRM Nr' => $new ? $export->booking->lead_id : "",
|
||||
'CRM Nr' => $new ? $export->booking->inquiry_id : "",
|
||||
'Kunde' => $new ? $export->booking->customer->name : "",
|
||||
'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "",
|
||||
'bis' => $new ? $export->booking->getEndDateFormat() : "",
|
||||
|
|
@ -346,7 +346,7 @@ class ReportProviderController extends Controller
|
|||
$ctemps[$export->booking->id][] = array(
|
||||
'Zähler' => $new ? $export->getCounter() : "",
|
||||
'MyJack Nr.' => $new ? $export->booking->merlin_order_number : "",
|
||||
'CRM Nr' => $new ? $export->booking->lead_id : "",
|
||||
'CRM Nr' => $new ? $export->booking->inquiry_id : "",
|
||||
'Kunde' => $new ? $export->booking->customer->name : "",
|
||||
'Reisedatum' => $new ? $export->booking->getStartDateFormat() : "",
|
||||
'bis' => $new ? $export->booking->getEndDateFormat() : "",
|
||||
|
|
|
|||
|
|
@ -157,11 +157,11 @@ class ContactController extends Controller
|
|||
}
|
||||
|
||||
DB::transaction(function () use ($masterId, $dupeId) {
|
||||
DB::table('lead')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]);
|
||||
DB::table('inquiries')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]);
|
||||
DB::table('booking')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]);
|
||||
DB::table('customer_mails')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]);
|
||||
DB::table('lead_mails')->where('customer_id', $dupeId)->update(['customer_id' => $masterId]);
|
||||
DB::table('customer')->where('id', $dupeId)->update([
|
||||
DB::table('contacts')->where('id', $dupeId)->update([
|
||||
'merged_into_id' => $masterId,
|
||||
'merged_at' => now(),
|
||||
]);
|
||||
|
|
@ -175,16 +175,16 @@ class ContactController extends Controller
|
|||
private function countDuplicateGroups(string $type): int
|
||||
{
|
||||
return match ($type) {
|
||||
'email' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('email')->where('email', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('email')->havingRaw('COUNT(*) > 1')->get()->count(),
|
||||
'name_birthdate' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'birthdate')->havingRaw('COUNT(*) > 1')->get()->count(),
|
||||
'name_zip' => DB::table('customer')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('zip')->where('zip', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'zip')->havingRaw('COUNT(*) > 1')->get()->count(),
|
||||
'email' => DB::table('contacts')->selectRaw('COUNT(*) as cnt')->whereNotNull('email')->where('email', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('email')->havingRaw('COUNT(*) > 1')->get()->count(),
|
||||
'name_birthdate' => DB::table('contacts')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'birthdate')->havingRaw('COUNT(*) > 1')->get()->count(),
|
||||
'name_zip' => DB::table('contacts')->selectRaw('COUNT(*) as cnt')->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('zip')->where('zip', '!=', '')->whereNull('merged_into_id')->whereNull('deleted_at')->groupBy('name', 'firstname', 'zip')->havingRaw('COUNT(*) > 1')->get()->count(),
|
||||
default => 0,
|
||||
};
|
||||
}
|
||||
|
||||
private function findByEmail(): array
|
||||
{
|
||||
return DB::table('customer')
|
||||
return DB::table('contacts')
|
||||
->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')
|
||||
->whereNotNull('email')->where('email', '!=', '')
|
||||
->whereNull('merged_into_id')->whereNull('deleted_at')
|
||||
|
|
@ -194,7 +194,7 @@ class ContactController extends Controller
|
|||
|
||||
private function findByNameBirthdate(): array
|
||||
{
|
||||
return DB::table('customer')
|
||||
return DB::table('contacts')
|
||||
->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')
|
||||
->whereNotNull('name')->whereNotNull('firstname')->whereNotNull('birthdate')
|
||||
->whereNull('merged_into_id')->whereNull('deleted_at')
|
||||
|
|
@ -204,7 +204,7 @@ class ContactController extends Controller
|
|||
|
||||
private function findByNameZip(): array
|
||||
{
|
||||
return DB::table('customer')
|
||||
return DB::table('contacts')
|
||||
->selectRaw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids')
|
||||
->whereNotNull('name')->whereNotNull('firstname')
|
||||
->whereNotNull('zip')->where('zip', '!=', '')
|
||||
|
|
@ -228,8 +228,8 @@ class ContactController extends Controller
|
|||
// Reihenfolge wichtig: select() zuerst, dann withCount() — sonst überschreibt
|
||||
// select() die COUNT-Subqueries die withCount() per addSelect() eingetragen hat.
|
||||
$query = $showDeleted
|
||||
? Contact::onlyTrashed()->withoutGlobalScope('not_merged')->select('customer.*')->withCount(['leads', 'bookings'])
|
||||
: Contact::select('customer.*')->withCount(['leads', 'bookings']);
|
||||
? Contact::onlyTrashed()->withoutGlobalScope('not_merged')->select('contacts.*')->withCount(['leads', 'bookings'])
|
||||
: Contact::select('contacts.*')->withCount(['leads', 'bookings']);
|
||||
|
||||
// Zusatzfilter aus der UI (werden als extra GET-Parameter gesendet)
|
||||
if ($request->filled('filter_has_leads')) {
|
||||
|
|
@ -271,11 +271,11 @@ class ContactController extends Controller
|
|||
->addColumn('leads_count', fn(Contact $contact) => $contact->leads_count)
|
||||
->addColumn('bookings_count', fn(Contact $contact) => $contact->bookings_count)
|
||||
->addColumn('deleted_at', fn(Contact $contact) => $contact->deleted_at?->format('d.m.Y H:i') ?? '')
|
||||
->orderColumn('id', 'customer.id $1')
|
||||
->orderColumn('deleted_at', 'customer.deleted_at $1')
|
||||
->filterColumn('id', function ($query, $keyword) {
|
||||
->orderColumn('contacts.id', 'contacts.id $1')
|
||||
->orderColumn('deleted_at', 'contacts.deleted_at $1')
|
||||
->filterColumn('contacts.id', function ($query, $keyword) {
|
||||
if ($keyword !== '') {
|
||||
$query->where('customer.id', 'LIKE', '%' . $keyword . '%');
|
||||
$query->where('contacts.id', 'LIKE', '%' . $keyword . '%');
|
||||
}
|
||||
})
|
||||
->filterColumn('name', function ($query, $keyword) {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class CustomerController extends Controller
|
|||
|
||||
public function getCustomers()
|
||||
{
|
||||
$query = Customer::with('salutation')->select('customer.*');
|
||||
$query = Customer::with('salutation')->select('contacts.*');
|
||||
|
||||
return \DataTables::eloquent($query)
|
||||
->addColumn('action_edit', function (Customer $customer) {
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ class LeadController extends Controller
|
|||
|
||||
public function getLeads()
|
||||
{
|
||||
$query = Lead::with('customer')->with('sf_guard_user')->with('status')->select('lead.*');
|
||||
$query = Lead::with('customer')->with('sf_guard_user')->with('status')->select('inquiries.*');
|
||||
|
||||
return \DataTables::eloquent($query)
|
||||
->addColumn('action_edit', function (Lead $lead) {
|
||||
|
|
@ -296,7 +296,7 @@ class LeadController extends Controller
|
|||
// $q->select('sent_at')->where('sent_at', DB::raw("(select max('sent_at') customer_mails)")); //)
|
||||
})->orderBy(
|
||||
LeadMail::select('sent_at')
|
||||
->whereColumn('lead_id', 'lead.id')
|
||||
->whereColumn('lead_id', 'inquiries.id')
|
||||
->orderBy('sent_at', 'DESC')
|
||||
->limit(1)
|
||||
, $order);
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class RequestController extends Controller
|
|||
wirte old where has state to new has travel_documents
|
||||
$bs = Booking::whereHas('arrangements', function($q){
|
||||
$q->where('state', '!=', NULL);
|
||||
})->where('lead_id', '!=', NULL)->where('new_drafts', 0)->get();
|
||||
})->where('inquiry_id', '!=', NULL)->where('new_drafts', 0)->get();
|
||||
|
||||
foreach ($bs as $b){
|
||||
$b->travel_documents = true;
|
||||
|
|
@ -101,7 +101,7 @@ class RequestController extends Controller
|
|||
private function getSearchRequests()
|
||||
{
|
||||
|
||||
$query = Booking::with('lead')->with('customer')->with('customer_mails')->with('customer_mails')->select('booking.*')->where('lead_id', '!=', NULL);
|
||||
$query = Booking::with('lead')->with('customer')->with('customer_mails')->with('customer_mails')->select('booking.*')->where('inquiry_id', '!=', NULL);
|
||||
|
||||
if (Request::get('full_firstname_search') != "") {
|
||||
$query->whereHas('customer', function ($q) {
|
||||
|
|
@ -241,7 +241,7 @@ class RequestController extends Controller
|
|||
}
|
||||
|
||||
if (Request::get('full_lead_id_search') != "") {
|
||||
$query->where('lead_id', 'LIKE', '%' . Request::get('full_lead_id_search') . '%');
|
||||
$query->where('inquiry_id', 'LIKE', '%' . Request::get('full_lead_id_search') . '%');
|
||||
}
|
||||
if (Request::get('full_booking_id_search') != "") {
|
||||
$query->where('id', 'LIKE', '%' . Request::get('full_booking_id_search') . '%');
|
||||
|
|
@ -398,10 +398,10 @@ class RequestController extends Controller
|
|||
return '<a data-order="' . $booking->id . '" href="' . make_old_url('booking/' . $booking->id . '/edit') . '" data-id="' . $booking->id . '">' . $booking->id . '</a>';
|
||||
})
|
||||
->addColumn('action_lead_edit', function (Booking $booking) {
|
||||
return '<a href="' . route('lead_detail', [$booking->lead_id]) . '" class="btn icon-btn btn-sm btn-primary"><span class="fa fa-edit"></span></a>';
|
||||
return '<a href="' . route('lead_detail', [$booking->inquiry_id]) . '" class="btn icon-btn btn-sm btn-primary"><span class="fa fa-edit"></span></a>';
|
||||
})
|
||||
->addColumn('lead_id', function (Booking $booking) {
|
||||
return '<a data-order="' . $booking->lead_id . '" href="' . make_old_url('leads/' . $booking->lead_id . '/edit') . '" data-id="' . $booking->lead_id . '">' . $booking->lead_id . '</a>';
|
||||
return '<a data-order="' . $booking->inquiry_id . '" href="' . make_old_url('leads/' . $booking->inquiry_id . '/edit') . '" data-id="' . $booking->inquiry_id . '">' . $booking->inquiry_id . '</a>';
|
||||
})
|
||||
->addColumn('travel_country_id', function (Booking $booking) {
|
||||
return '<span data-order="' . ($booking->travel_country_id ? $booking->travel_country_id : 0) . '">' . ($booking->travel_country_id ? $booking->travel_country->name : "-") . '</span>';
|
||||
|
|
@ -523,7 +523,7 @@ class RequestController extends Controller
|
|||
}
|
||||
})
|
||||
*/
|
||||
->orderColumn('lead_id', 'lead_id $1')
|
||||
->orderColumn('lead_id', 'inquiry_id $1')
|
||||
->orderColumn('id', 'id $1')
|
||||
->orderColumn('travel_country_id', 'travel_country_id $1')
|
||||
->orderColumn('travelagenda_id', 'travelagenda_id $1')
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use Illuminate\Database\Eloquent\Collection;
|
|||
* @property int $id
|
||||
* @property Carbon $booking_date
|
||||
* @property int $customer_id
|
||||
* @property int $lead_id
|
||||
* @property int $inquiry_id
|
||||
* @property bool $new_drafts
|
||||
* @property int $sf_guard_user_id
|
||||
* @property int $branch_id
|
||||
|
|
@ -212,7 +212,8 @@ class Booking extends Model
|
|||
|
||||
protected $casts = [
|
||||
'customer_id' => 'int',
|
||||
'lead_id' => 'int',
|
||||
'inquiry_id' => 'int',
|
||||
'offer_id' => 'int',
|
||||
'new_drafts' => 'bool',
|
||||
'sf_guard_user_id' => 'int',
|
||||
'branch_id' => 'int',
|
||||
|
|
@ -256,7 +257,8 @@ class Booking extends Model
|
|||
protected $fillable = [
|
||||
'booking_date',
|
||||
'customer_id',
|
||||
'lead_id',
|
||||
'inquiry_id',
|
||||
'offer_id',
|
||||
'new_drafts',
|
||||
'sf_guard_user_id',
|
||||
'branch_id',
|
||||
|
|
@ -393,9 +395,29 @@ class Booking extends Model
|
|||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lead/Inquiry der Buchung.
|
||||
* FK-Spalte `inquiry_id` (vormals `lead_id` — Modul 3 Phase 2 Rename).
|
||||
* Methodenname bleibt `lead()` für Legacy-Kompatibilität; {@see self::inquiry()}
|
||||
* ist der fachlich korrekte Alias und sollte in neuem Code verwendet werden.
|
||||
*/
|
||||
public function lead()
|
||||
{
|
||||
return $this->belongsTo(Lead::class);
|
||||
return $this->belongsTo(Lead::class, 'inquiry_id');
|
||||
}
|
||||
|
||||
public function inquiry()
|
||||
{
|
||||
return $this->belongsTo(Lead::class, 'inquiry_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Angebot, aus dem diese Buchung entstanden ist (Modul 6, Ticket B8).
|
||||
* Nullable — nicht jede Buchung hat einen Angebots-Vorlauf.
|
||||
*/
|
||||
public function offer()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Offer::class);
|
||||
}
|
||||
|
||||
public function sf_guard_user()
|
||||
|
|
@ -765,8 +787,6 @@ class Booking extends Model
|
|||
$total_children += $prices['children'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($travel_draft_item) {
|
||||
$travel_draft_item->setPriceAdultRaw($travel_price_adult);
|
||||
$travel_draft_item->setPriceChildrenRaw($travel_price_children);
|
||||
|
|
@ -775,10 +795,23 @@ class Booking extends Model
|
|||
$travel_draft_item->save();
|
||||
}
|
||||
$this->price = $total_adult + $total_children;
|
||||
$this->price_total = $this->getPriceRaw() + $this->getServiceTotal(true);
|
||||
$this->setPriceTotalForCurrentState();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gesamtpreis Reise (price_total): bei Storno mit gesetztem Storno-Betrag = price_canceled
|
||||
* (wie nach createPDF_Storno in BookingPDFRepository), sonst Reisepreis + Vermittlung.
|
||||
*/
|
||||
public function setPriceTotalForCurrentState(): void
|
||||
{
|
||||
if ($this->isCanceled() && $this->attributes['price_canceled'] !== null) {
|
||||
$this->price_total = round((float) $this->getPriceCanceledRaw(), 2);
|
||||
return;
|
||||
}
|
||||
$this->price_total = round((float) $this->getPriceRaw() + (float) $this->getServiceTotal(true), 2);
|
||||
}
|
||||
|
||||
public function getPriceAttribute()
|
||||
{
|
||||
return Util::_number_format($this->attributes['price']);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
* - merged_into_id + merged_at in $fillable
|
||||
* - mergedInto() / mergedContacts() Beziehungen
|
||||
*
|
||||
* Tabellen-Name: 'customer' (wird in Phase 2 in 'contacts' umbenannt).
|
||||
* Tabellen-Name: 'contacts' (nach Phase 2 — RENAME TABLE customer → contacts).
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $salutation_id
|
||||
|
|
@ -50,7 +50,7 @@ class Contact extends Model
|
|||
|
||||
protected $connection = 'mysql';
|
||||
|
||||
protected $table = 'customer';
|
||||
protected $table = 'contacts';
|
||||
|
||||
protected $casts = [
|
||||
'salutation_id' => 'int',
|
||||
|
|
|
|||
|
|
@ -88,7 +88,12 @@ class Customer extends Model
|
|||
|
||||
protected $connection = 'mysql';
|
||||
|
||||
protected $table = 'customer';
|
||||
/**
|
||||
* Modul 3 Phase 2: customer → contacts (RENAME TABLE).
|
||||
* Der Model-Name bleibt aus Kompatibilität zum Legacy-Code bestehen;
|
||||
* für die Neuimplementierung steht {@see Contact} bereit.
|
||||
*/
|
||||
protected $table = 'contacts';
|
||||
|
||||
protected $casts = [
|
||||
'salutation_id' => 'int',
|
||||
|
|
|
|||
|
|
@ -108,11 +108,16 @@ use Illuminate\Database\Eloquent\Collection;
|
|||
*/
|
||||
class Lead extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use HasFactory;
|
||||
|
||||
protected $connection = 'mysql';
|
||||
protected $connection = 'mysql';
|
||||
|
||||
protected $table = 'lead';
|
||||
/**
|
||||
* Modul 3 Phase 2: lead → inquiries (RENAME TABLE).
|
||||
* Model-Name bleibt (um Breaking Changes in der gesamten Codebase zu vermeiden);
|
||||
* fachlich ist das Modell jetzt eine "Inquiry" (Anfrage).
|
||||
*/
|
||||
protected $table = 'inquiries';
|
||||
|
||||
protected $casts = [
|
||||
'customer_id' => 'int',
|
||||
|
|
@ -121,8 +126,8 @@ class Lead extends Model
|
|||
'travelagenda_id' => 'int',
|
||||
'sf_guard_user_id' => 'int',
|
||||
'is_closed' => 'bool',
|
||||
'is_rebook' => 'bool',
|
||||
'initialcontacttype_id' => 'int',
|
||||
'is_rebook' => 'bool',
|
||||
'initialcontacttype_id' => 'int',
|
||||
'searchengine_id' => 'int',
|
||||
'status_id' => 'int',
|
||||
'website_id' => 'int',
|
||||
|
|
@ -149,7 +154,7 @@ class Lead extends Model
|
|||
'remarks',
|
||||
'sf_guard_user_id',
|
||||
'is_closed',
|
||||
'is_rebook',
|
||||
'is_rebook',
|
||||
'initialcontacttype_id',
|
||||
'searchengine_id',
|
||||
'searchengine_keywords',
|
||||
|
|
@ -164,24 +169,24 @@ class Lead extends Model
|
|||
'participant_birthdate',
|
||||
'participant_salutation_id'
|
||||
];
|
||||
protected $passolutionPDFs = [];
|
||||
|
||||
protected $passolutionPDFs = [];
|
||||
|
||||
public static $lead_mail_dirs = [
|
||||
11 => ['name' => 'Entwürfe', 'icon'=>'ion-md-create'],
|
||||
12 => ['name' => 'Papierkorb', 'icon'=>'ion-md-trash'],
|
||||
];
|
||||
11 => ['name' => 'Entwürfe', 'icon' => 'ion-md-create'],
|
||||
12 => ['name' => 'Papierkorb', 'icon' => 'ion-md-trash'],
|
||||
];
|
||||
|
||||
public function updateNextDueDate($date = false){
|
||||
public function updateNextDueDate($date = false)
|
||||
{
|
||||
|
||||
if(!$date){
|
||||
$carbon = Carbon::now();
|
||||
}else{
|
||||
if (!$date) {
|
||||
$carbon = Carbon::now();
|
||||
} else {
|
||||
$carbon = Carbon::parse($date);
|
||||
}
|
||||
$this->next_due_date = $carbon->modify('+ '.$this->status->handling_days.' days')->format("Y-m-d");
|
||||
$this->next_due_date = $carbon->modify('+ ' . $this->status->handling_days . ' days')->format("Y-m-d");
|
||||
$this->save();
|
||||
|
||||
}
|
||||
}
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
|
|
@ -222,18 +227,18 @@ class Lead extends Model
|
|||
return $this->belongsTo(TravelCategory::class, 'travelcategory_id');
|
||||
}
|
||||
|
||||
//on crm
|
||||
public function travel_country_crm()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Sym\TravelCountry', 'travelcountry_id', 'id');
|
||||
}
|
||||
//on crm
|
||||
public function travel_country_crm()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Sym\TravelCountry', 'travelcountry_id', 'id');
|
||||
}
|
||||
|
||||
|
||||
//on stern other DB
|
||||
public function travel_country()
|
||||
{
|
||||
return $this->belongsTo('App\Models\TravelCountry', 'travelcountry_id', 'crm_id');
|
||||
}
|
||||
//on stern other DB
|
||||
public function travel_country()
|
||||
{
|
||||
return $this->belongsTo('App\Models\TravelCountry', 'travelcountry_id', 'crm_id');
|
||||
}
|
||||
|
||||
|
||||
public function website()
|
||||
|
|
@ -243,7 +248,8 @@ class Lead extends Model
|
|||
|
||||
public function bookings()
|
||||
{
|
||||
return $this->hasMany(Booking::class);
|
||||
// Modul 3 Phase 2: FK heißt jetzt inquiry_id (vormals lead_id)
|
||||
return $this->hasMany(Booking::class, 'inquiry_id');
|
||||
}
|
||||
|
||||
public function inquiries()
|
||||
|
|
@ -267,149 +273,156 @@ class Lead extends Model
|
|||
}
|
||||
|
||||
public function lead_files()
|
||||
{
|
||||
{
|
||||
//no lead_mail_id
|
||||
return $this->hasMany(LeadFile::class, 'lead_id')->where('lead_mail_id', null);
|
||||
}
|
||||
return $this->hasMany(LeadFile::class, 'lead_id')->where('lead_mail_id', null);
|
||||
}
|
||||
|
||||
public function lead_mails()
|
||||
{
|
||||
return $this->hasMany(LeadMail::class, 'lead_id', 'id');
|
||||
}
|
||||
public function lead_mails()
|
||||
{
|
||||
return $this->hasMany(LeadMail::class, 'lead_id', 'id');
|
||||
}
|
||||
|
||||
public function lead_mails_sent_at()
|
||||
{
|
||||
return $this->hasMany(LeadMail::class, 'lead_id')->orderBy('sent_at', 'ASC');
|
||||
}
|
||||
public function lead_mails_sent_at()
|
||||
{
|
||||
return $this->hasMany(LeadMail::class, 'lead_id')->orderBy('sent_at', 'ASC');
|
||||
}
|
||||
|
||||
public function lead_mail_last()
|
||||
{
|
||||
return $this->hasOne(LeadMail::class, 'lead_id')->latest();
|
||||
}
|
||||
public function lead_mail_last()
|
||||
{
|
||||
return $this->hasOne(LeadMail::class, 'lead_id')->latest();
|
||||
}
|
||||
|
||||
public function lead_notices()
|
||||
{
|
||||
return $this->hasMany(LeadNotice::class, 'lead_id')->orderBy('updated_at', 'DESC');
|
||||
}
|
||||
public function lead_notices()
|
||||
{
|
||||
return $this->hasMany(LeadNotice::class, 'lead_id')->orderBy('updated_at', 'DESC');
|
||||
}
|
||||
|
||||
|
||||
public static function getSfGuardUserArray(){
|
||||
return SfGuardUser::where('is_active', 1)->get()->pluck('fullname', 'id');
|
||||
}
|
||||
public static function getSfGuardUserArray()
|
||||
{
|
||||
return SfGuardUser::where('is_active', 1)->get()->pluck('fullname', 'id');
|
||||
}
|
||||
|
||||
public static function getTravelCountryArray($emtpy = false){
|
||||
public static function getTravelCountryArray($emtpy = false)
|
||||
{
|
||||
$TravelCountry = TravelCountry::where('active_backend', 1)->orderBy('name')->get()->pluck('name', 'id');
|
||||
return $emtpy ? $TravelCountry->prepend('-', 0) : $TravelCountry;
|
||||
return $emtpy ? $TravelCountry->prepend('-', 0) : $TravelCountry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function getTravelCategoryArray($emtpy = false){
|
||||
public static function getTravelCategoryArray($emtpy = false)
|
||||
{
|
||||
$TravelCategory = TravelCategory::orderBy('name')->get()->pluck('name', 'id');
|
||||
return $emtpy ? $TravelCategory->prepend('-', 0) : $TravelCategory;
|
||||
|
||||
return $emtpy ? $TravelCategory->prepend('-', 0) : $TravelCategory;
|
||||
}
|
||||
|
||||
public static function getTravelAgendaArray($emtpy = false){
|
||||
public static function getTravelAgendaArray($emtpy = false)
|
||||
{
|
||||
$TravelAgenda = TravelAgenda::orderBy('name')->get()->pluck('name', 'id');
|
||||
return $emtpy ? $TravelAgenda->prepend('-', 0) : $TravelAgenda;
|
||||
return $emtpy ? $TravelAgenda->prepend('-', 0) : $TravelAgenda;
|
||||
}
|
||||
|
||||
public static function getStatusArray($emtpy = false){
|
||||
public static function getStatusArray($emtpy = false)
|
||||
{
|
||||
$Status = Status::orderBy('name')->get()->pluck('name', 'id');
|
||||
return $emtpy ? $Status->prepend('-', 0) : $Status;
|
||||
return $emtpy ? $Status->prepend('-', 0) : $Status;
|
||||
}
|
||||
|
||||
|
||||
public function getStatusBadge($booking = null)
|
||||
{
|
||||
if($this->status_id && $this->status){
|
||||
if ($this->status_id && $this->status) {
|
||||
$color = $this->status->color;
|
||||
$icon = "";
|
||||
if($this->status_id == 14 && $this->is_rebook){
|
||||
if ($this->status_id == 14 && $this->is_rebook) {
|
||||
$color = '#94ae59';
|
||||
$icon = '<i class="fa fa-check-circle"></i> ';
|
||||
}
|
||||
if($this->status_id == 14 && !$this->is_rebook){
|
||||
$icon = '<i class="fa fa-times-circle"></i> ';
|
||||
if ($this->status_id == 14 && !$this->is_rebook) {
|
||||
$icon = '<i class="fa fa-times-circle"></i> ';
|
||||
}
|
||||
if($this->status_id == 15){
|
||||
if ($this->status_id == 15) {
|
||||
$icon = '<i class="fa fa-balance-scale"></i> ';
|
||||
if($booking && $booking->lawyer_date){
|
||||
return '<span data-order="'.$this->status_id.'"><span class="badge badge-dark" style="background-color: '.$color.'">'.$icon.$booking->lawyer_date->format('d.m.Y').'</span></span>';
|
||||
if ($booking && $booking->lawyer_date) {
|
||||
return '<span data-order="' . $this->status_id . '"><span class="badge badge-dark" style="background-color: ' . $color . '">' . $icon . $booking->lawyer_date->format('d.m.Y') . '</span></span>';
|
||||
}
|
||||
}
|
||||
return '<span data-order="'.$this->status_id.'"><span class="badge badge-dark" style="background-color: '.$color.'">'.$icon.$this->status->name.'</span></span>';
|
||||
return '<span data-order="' . $this->status_id . '"><span class="badge badge-dark" style="background-color: ' . $color . '">' . $icon . $this->status->name . '</span></span>';
|
||||
}
|
||||
return '<span data-order="0">-</span>';
|
||||
}
|
||||
|
||||
public function getTravelCountryDestco($badge = true){
|
||||
public function getTravelCountryDestco($badge = true)
|
||||
{
|
||||
|
||||
$out = "";
|
||||
if($this->bookings->count()){
|
||||
if ($this->bookings->count()) {
|
||||
$out .= $badge ? '<span class="badge badge-success">' : '';
|
||||
foreach ($this->bookings as $booking){
|
||||
if($booking->travel_country_id && $booking->travel_country) {
|
||||
foreach ($this->bookings as $booking) {
|
||||
if ($booking->travel_country_id && $booking->travel_country) {
|
||||
$out .= $booking->travel_country->destco;
|
||||
}
|
||||
}
|
||||
$out .= $badge ? '</span>' : '';
|
||||
return $out;
|
||||
}
|
||||
if($this->travel_country){
|
||||
return $badge ? '<span class="badge badge-secondary">'.$this->travel_country->destco.'</span>' : $this->travel_country->destco;
|
||||
if ($this->travel_country) {
|
||||
return $badge ? '<span class="badge badge-secondary">' . $this->travel_country->destco . '</span>' : $this->travel_country->destco;
|
||||
}
|
||||
return "-";
|
||||
}
|
||||
|
||||
public function countLeadMailsBy($dir, $subdir=false){
|
||||
if($dir === 11){
|
||||
return $this->lead_mails->where('draft', true)->where('dir', '!=', 12)->count();
|
||||
}
|
||||
if($subdir){
|
||||
return $this->lead_mails->where('dir', $dir)->where('subdir', $subdir)->count();
|
||||
}
|
||||
return $this->lead_mails->where('dir', $dir)->count();
|
||||
}
|
||||
public function countLeadMailsBy($dir, $subdir = false)
|
||||
{
|
||||
if ($dir === 11) {
|
||||
return $this->lead_mails->where('draft', true)->where('dir', '!=', 12)->count();
|
||||
}
|
||||
if ($subdir) {
|
||||
return $this->lead_mails->where('dir', $dir)->where('subdir', $subdir)->count();
|
||||
}
|
||||
return $this->lead_mails->where('dir', $dir)->count();
|
||||
}
|
||||
|
||||
public function getPassolutionPDF($create = false, $resync = false){
|
||||
public function getPassolutionPDF($create = false, $resync = false)
|
||||
{
|
||||
|
||||
$nats = [];
|
||||
$nats = [];
|
||||
|
||||
if(count($this->passolutionPDFs)){
|
||||
return $this->passolutionPDFs;
|
||||
}
|
||||
if (count($this->passolutionPDFs)) {
|
||||
return $this->passolutionPDFs;
|
||||
}
|
||||
|
||||
if(!$this->travel_country){
|
||||
return $this->passolutionPDFs;
|
||||
}
|
||||
|
||||
$destco = $this->travel_country->destco;
|
||||
if (!$this->travel_country) {
|
||||
return $this->passolutionPDFs;
|
||||
}
|
||||
|
||||
$destco = $this->travel_country->destco;
|
||||
//default no travel_nationality
|
||||
$nats['de'] = 'de';
|
||||
|
||||
if($this->lead_participants->count()){
|
||||
foreach ($this->lead_participants as $participant){
|
||||
if($participant->travel_nationality){
|
||||
$nats[$participant->travel_nationality->nat] = $participant->travel_nationality->nat;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(empty($nats)){
|
||||
$nats['de'] = 'de';
|
||||
}
|
||||
foreach ($nats as $nat){
|
||||
$data = [
|
||||
'nat' => $nat,
|
||||
'destco' => $destco,
|
||||
];
|
||||
$passolution = new Passolution($data);
|
||||
$this->passolutionPDFs[] = $passolution->findOrCreatePDF($create, $resync);
|
||||
}
|
||||
return $this->passolutionPDFs;
|
||||
}
|
||||
|
||||
public function resyncPassolutionPDF(){
|
||||
return $this->getPassolutionPDF(true, true);
|
||||
}
|
||||
if ($this->lead_participants->count()) {
|
||||
foreach ($this->lead_participants as $participant) {
|
||||
if ($participant->travel_nationality) {
|
||||
$nats[$participant->travel_nationality->nat] = $participant->travel_nationality->nat;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($nats)) {
|
||||
$nats['de'] = 'de';
|
||||
}
|
||||
foreach ($nats as $nat) {
|
||||
$data = [
|
||||
'nat' => $nat,
|
||||
'destco' => $destco,
|
||||
];
|
||||
$passolution = new Passolution($data);
|
||||
$this->passolutionPDFs[] = $passolution->findOrCreatePDF($create, $resync);
|
||||
}
|
||||
return $this->passolutionPDFs;
|
||||
}
|
||||
|
||||
public function resyncPassolutionPDF()
|
||||
{
|
||||
return $this->getPassolutionPDF(true, true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,132 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Created by Reliese Model.
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Class Offer
|
||||
* Angebot (Modul 6).
|
||||
*
|
||||
* Ein Offer ist der logische Angebots-Kopf (Angebotsnummer, Status,
|
||||
* Referenzen). Die Inhalte (Texte, Positionen, PDF) liegen versionsweise
|
||||
* in {@see OfferVersion}. Nach dem ersten Versand ist jede Änderung
|
||||
* eine neue Version (Entscheidung 17.1 Entwicklungsplan).
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $lead_id
|
||||
* @property float $total
|
||||
* @property boolean $binary_data
|
||||
* @property string $offer_number
|
||||
* @property int $contact_id
|
||||
* @property int|null $inquiry_id
|
||||
* @property int|null $booking_id
|
||||
* @property string $status
|
||||
* @property int|null $current_version_id
|
||||
* @property int $created_by
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Lead $lead
|
||||
* @package App\Models
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer whereBinaryData($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer whereLeadId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer whereTotal($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Offer whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property-read Contact $contact
|
||||
* @property-read Lead|null $inquiry
|
||||
* @property-read Booking|null $booking
|
||||
* @property-read OfferVersion|null $currentVersion
|
||||
* @property-read Collection|OfferVersion[] $versions
|
||||
* @property-read Collection|OfferAccessToken[] $accessTokens
|
||||
* @property-read User $creator
|
||||
*/
|
||||
class Offer extends Model
|
||||
{
|
||||
protected $connection = 'mysql';
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $table = 'offer';
|
||||
public const STATUS_DRAFT = 'draft';
|
||||
public const STATUS_SENT = 'sent';
|
||||
public const STATUS_ACCEPTED = 'accepted';
|
||||
public const STATUS_DECLINED = 'declined';
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
public const STATUS_WITHDRAWN = 'withdrawn';
|
||||
|
||||
protected $casts = [
|
||||
'lead_id' => 'int',
|
||||
'total' => 'float',
|
||||
'binary_data' => 'boolean'
|
||||
];
|
||||
public const STATUSES = [
|
||||
self::STATUS_DRAFT,
|
||||
self::STATUS_SENT,
|
||||
self::STATUS_ACCEPTED,
|
||||
self::STATUS_DECLINED,
|
||||
self::STATUS_EXPIRED,
|
||||
self::STATUS_WITHDRAWN,
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'lead_id',
|
||||
'total',
|
||||
'binary_data'
|
||||
];
|
||||
protected $table = 'offers';
|
||||
|
||||
public function lead()
|
||||
{
|
||||
return $this->belongsTo(Lead::class);
|
||||
}
|
||||
protected $fillable = [
|
||||
'offer_number',
|
||||
'contact_id',
|
||||
'inquiry_id',
|
||||
'booking_id',
|
||||
'status',
|
||||
'current_version_id',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'contact_id' => 'int',
|
||||
'inquiry_id' => 'int',
|
||||
'booking_id' => 'int',
|
||||
'current_version_id' => 'int',
|
||||
'created_by' => 'int',
|
||||
];
|
||||
|
||||
public function contact(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Contact::class);
|
||||
}
|
||||
|
||||
public function inquiry(): BelongsTo
|
||||
{
|
||||
// Nach Modul 3 Phase 2: `Lead`-Model bildet die `inquiries`-Tabelle ab
|
||||
return $this->belongsTo(Lead::class, 'inquiry_id');
|
||||
}
|
||||
|
||||
public function booking(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Booking::class);
|
||||
}
|
||||
|
||||
public function currentVersion(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(OfferVersion::class, 'current_version_id');
|
||||
}
|
||||
|
||||
public function versions(): HasMany
|
||||
{
|
||||
return $this->hasMany(OfferVersion::class)->orderBy('version_no');
|
||||
}
|
||||
|
||||
public function accessTokens(): HasMany
|
||||
{
|
||||
return $this->hasMany(OfferAccessToken::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function scopeStatus(Builder $q, string $status): Builder
|
||||
{
|
||||
return $q->where('status', $status);
|
||||
}
|
||||
|
||||
public function scopeOpen(Builder $q): Builder
|
||||
{
|
||||
return $q->whereIn('status', [self::STATUS_DRAFT, self::STATUS_SENT]);
|
||||
}
|
||||
|
||||
public function isEditable(): bool
|
||||
{
|
||||
return in_array($this->status, [self::STATUS_DRAFT, self::STATUS_SENT], true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
120
app/Models/OfferAccessToken.php
Normal file
120
app/Models/OfferAccessToken.php
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Kundenseitiger Zugriffstoken für /angebot/{token} (Modul 6 / Phase D).
|
||||
*
|
||||
* In der Datenbank wird ausschließlich der SHA-256-Hash des Klartext-
|
||||
* Tokens gespeichert. Der Klartext wird einmalig bei der Erzeugung
|
||||
* zurückgegeben (siehe {@see self::generate()}) und an den Kunden
|
||||
* per Mail-Link ausgeliefert.
|
||||
*
|
||||
* Pro Angebot + Version existiert genau ein aktiver Token; wird eine
|
||||
* neue Version versendet, setzt der OfferService den Vorgänger auf
|
||||
* `revoked_at`.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $offer_id
|
||||
* @property int $offer_version_id
|
||||
* @property string $token_hash
|
||||
* @property Carbon|null $expires_at
|
||||
* @property Carbon|null $first_opened_at
|
||||
* @property Carbon|null $revoked_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read Offer $offer
|
||||
* @property-read OfferVersion $version
|
||||
*/
|
||||
class OfferAccessToken extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'offer_access_tokens';
|
||||
|
||||
protected $fillable = [
|
||||
'offer_id',
|
||||
'offer_version_id',
|
||||
'token_hash',
|
||||
'expires_at',
|
||||
'first_opened_at',
|
||||
'revoked_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'offer_id' => 'int',
|
||||
'offer_version_id' => 'int',
|
||||
'expires_at' => 'datetime',
|
||||
'first_opened_at' => 'datetime',
|
||||
'revoked_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function offer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Offer::class);
|
||||
}
|
||||
|
||||
public function version(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(OfferVersion::class, 'offer_version_id');
|
||||
}
|
||||
|
||||
public function scopeActive(Builder $q): Builder
|
||||
{
|
||||
return $q->whereNull('revoked_at')
|
||||
->where(function (Builder $q) {
|
||||
$q->whereNull('expires_at')->orWhere('expires_at', '>', now());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein neues Token für die angegebene Version und liefert
|
||||
* den Klartext-Token zurück (nur einmalig abrufbar). In der
|
||||
* Datenbank wird nur der Hash persistiert.
|
||||
*/
|
||||
public static function generate(
|
||||
Offer $offer,
|
||||
OfferVersion $version,
|
||||
?Carbon $expiresAt = null
|
||||
): array {
|
||||
$plain = Str::random(48);
|
||||
$hash = hash('sha256', $plain);
|
||||
|
||||
/** @var self $token */
|
||||
$token = self::create([
|
||||
'offer_id' => $offer->id,
|
||||
'offer_version_id' => $version->id,
|
||||
'token_hash' => $hash,
|
||||
'expires_at' => $expiresAt,
|
||||
]);
|
||||
|
||||
return ['plain' => $plain, 'token' => $token];
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup per Klartext-Token (konstantzeitig via DB-Unique-Index).
|
||||
*/
|
||||
public static function findByPlainToken(string $plain): ?self
|
||||
{
|
||||
return self::where('token_hash', hash('sha256', $plain))->first();
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
if ($this->revoked_at !== null) {
|
||||
return false;
|
||||
}
|
||||
if ($this->expires_at !== null && $this->expires_at->isPast()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
99
app/Models/OfferFile.php
Normal file
99
app/Models/OfferFile.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Datei-Anhang einer Angebotsversion (Modul 6).
|
||||
*
|
||||
* Struktur ist bewusst an {@see BookingFile} angelehnt (identifier,
|
||||
* filename, dir, original_name, ext, mine, size), damit der vorhandene
|
||||
* `FileRepository::store()` 1:1 wiederverwendet werden kann. `mine`
|
||||
* bleibt so geschrieben (statt `mime`) zur Konsistenz mit der
|
||||
* booking_files-Konvention.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $offer_version_id
|
||||
* @property string|null $identifier
|
||||
* @property string $filename
|
||||
* @property string $dir
|
||||
* @property string $original_name
|
||||
* @property string $ext
|
||||
* @property string $mine
|
||||
* @property int $size
|
||||
* @property bool $include_in_pdf
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read OfferVersion $version
|
||||
*/
|
||||
class OfferFile extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'offer_files';
|
||||
|
||||
protected $fillable = [
|
||||
'offer_version_id',
|
||||
'identifier',
|
||||
'filename',
|
||||
'dir',
|
||||
'original_name',
|
||||
'ext',
|
||||
'mine',
|
||||
'size',
|
||||
'include_in_pdf',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'offer_version_id' => 'int',
|
||||
'size' => 'int',
|
||||
'include_in_pdf' => 'bool',
|
||||
];
|
||||
|
||||
public static array $iconExt = [
|
||||
'default' => 'fa fa-file',
|
||||
'pdf' => 'fa fa-file-pdf',
|
||||
'jpg' => 'fa fa-file-image',
|
||||
'jpeg' => 'fa fa-file-image',
|
||||
'png' => 'fa fa-file-image',
|
||||
'doc' => 'fa fa-file-word',
|
||||
'docx' => 'fa fa-file-word',
|
||||
];
|
||||
|
||||
public function version(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(OfferVersion::class, 'offer_version_id');
|
||||
}
|
||||
|
||||
public function getIconExt(): string
|
||||
{
|
||||
return self::$iconExt[$this->ext] ?? self::$iconExt['default'];
|
||||
}
|
||||
|
||||
public function getURL(bool|string $do = false): string
|
||||
{
|
||||
return route('storage_file', [$this->id, 'offer', $do]);
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return \Storage::disk('offer')->path($this->dir . $this->filename);
|
||||
}
|
||||
|
||||
public function formatBytes(int $precision = 2): string
|
||||
{
|
||||
$size = $this->size;
|
||||
if ($size <= 0) {
|
||||
return (string) $size;
|
||||
}
|
||||
|
||||
$base = log($size) / log(1024);
|
||||
$suffixes = [' bytes', ' KB', ' MB', ' GB', ' TB'];
|
||||
|
||||
return round(1024 ** ($base - floor($base)), $precision) . $suffixes[floor($base)];
|
||||
}
|
||||
}
|
||||
86
app/Models/OfferItem.php
Normal file
86
app/Models/OfferItem.php
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* Position einer Angebotsversion (Modul 6).
|
||||
*
|
||||
* `travel_program_id` und `fewo_lodging_id` bleiben FK-frei, solange
|
||||
* Modul 12 (v2-Reiseverwaltung) noch nicht nach Laravel migriert ist.
|
||||
* `metadata` enthält einen Snapshot der Referenzdaten (Titel, Preis,
|
||||
* Leistungen) — so bleiben Positionen lesbar, auch wenn das Original
|
||||
* später gelöscht / migriert / umbenannt wird.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $offer_version_id
|
||||
* @property int $position
|
||||
* @property string $type
|
||||
* @property string $title
|
||||
* @property string|null $description
|
||||
* @property int $quantity
|
||||
* @property float $price_per_unit
|
||||
* @property float $total_price
|
||||
* @property int|null $travel_program_id
|
||||
* @property int|null $fewo_lodging_id
|
||||
* @property array|null $metadata
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read OfferVersion $version
|
||||
*/
|
||||
class OfferItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const TYPE_TRAVEL = 'travel';
|
||||
public const TYPE_SERVICE = 'service';
|
||||
public const TYPE_OPTION = 'option';
|
||||
public const TYPE_DISCOUNT = 'discount';
|
||||
public const TYPE_INSURANCE = 'insurance';
|
||||
public const TYPE_CUSTOM = 'custom';
|
||||
|
||||
protected $table = 'offer_items';
|
||||
|
||||
protected $fillable = [
|
||||
'offer_version_id',
|
||||
'position',
|
||||
'type',
|
||||
'title',
|
||||
'description',
|
||||
'quantity',
|
||||
'price_per_unit',
|
||||
'total_price',
|
||||
'travel_program_id',
|
||||
'fewo_lodging_id',
|
||||
'metadata',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'offer_version_id' => 'int',
|
||||
'position' => 'int',
|
||||
'quantity' => 'int',
|
||||
'price_per_unit' => 'decimal:2',
|
||||
'total_price' => 'decimal:2',
|
||||
'travel_program_id' => 'int',
|
||||
'fewo_lodging_id' => 'int',
|
||||
'metadata' => 'array',
|
||||
];
|
||||
|
||||
public function version(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(OfferVersion::class, 'offer_version_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Aus Menge × Einzelpreis den Positions-Gesamtpreis berechnen
|
||||
* (Rabatte negativ — gehört in den Service-Layer zur Summierung).
|
||||
*/
|
||||
public function calculateTotal(): float
|
||||
{
|
||||
return round($this->quantity * (float) $this->price_per_unit, 2);
|
||||
}
|
||||
}
|
||||
77
app/Models/OfferTemplate.php
Normal file
77
app/Models/OfferTemplate.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Wiederverwendbare Angebots-Vorlage (Modul 6).
|
||||
*
|
||||
* Liefert Default-Texte + Default-Positionen für neue Angebote.
|
||||
* `default_items` ist ein JSON-Array von Positionen im Schema
|
||||
* [{title, description, type, price_per_unit, quantity}, …].
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $branch_id
|
||||
* @property string $name
|
||||
* @property string|null $description
|
||||
* @property string|null $default_headline
|
||||
* @property string|null $default_intro
|
||||
* @property string|null $default_itinerary
|
||||
* @property string|null $default_closing
|
||||
* @property array|null $default_items
|
||||
* @property bool $is_active
|
||||
* @property int $created_by
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property-read Branch|null $branch
|
||||
* @property-read User $creator
|
||||
*/
|
||||
class OfferTemplate extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $table = 'offer_templates';
|
||||
|
||||
protected $fillable = [
|
||||
'branch_id',
|
||||
'name',
|
||||
'description',
|
||||
'default_headline',
|
||||
'default_intro',
|
||||
'default_itinerary',
|
||||
'default_closing',
|
||||
'default_items',
|
||||
'is_active',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'branch_id' => 'int',
|
||||
'default_items' => 'array',
|
||||
'is_active' => 'bool',
|
||||
'created_by' => 'int',
|
||||
];
|
||||
|
||||
public function branch(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Branch::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function scopeActive(Builder $q): Builder
|
||||
{
|
||||
return $q->where('is_active', true);
|
||||
}
|
||||
}
|
||||
126
app/Models/OfferVersion.php
Normal file
126
app/Models/OfferVersion.php
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\User;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Version eines Angebots (Modul 6).
|
||||
*
|
||||
* Jede versendete Fassung wird hier festgehalten — Texte, Positionen
|
||||
* und PDF bleiben damit unveränderlich, sobald ein Kunde sie per
|
||||
* Freigabe-Link einsehen kann. Neue Änderungen nach dem Versand
|
||||
* erzeugen eine neue Version (version_no = max+1, status = draft).
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $offer_id
|
||||
* @property int $version_no
|
||||
* @property string $status
|
||||
* @property Carbon|null $valid_until
|
||||
* @property float $total_price
|
||||
* @property string|null $headline
|
||||
* @property string|null $intro_text
|
||||
* @property string|null $itinerary_text
|
||||
* @property string|null $closing_text
|
||||
* @property int|null $template_id
|
||||
* @property string|null $pdf_path
|
||||
* @property bool $pdf_archived
|
||||
* @property Carbon|null $sent_at
|
||||
* @property Carbon|null $accepted_at
|
||||
* @property string|null $accepted_via
|
||||
* @property array|null $template_document_ids
|
||||
* @property int $created_by
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read Offer $offer
|
||||
* @property-read OfferTemplate|null $template
|
||||
* @property-read Collection|OfferItem[] $items
|
||||
* @property-read Collection|OfferFile[] $files
|
||||
* @property-read User $creator
|
||||
*/
|
||||
class OfferVersion extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_DRAFT = 'draft';
|
||||
public const STATUS_SENT = 'sent';
|
||||
public const STATUS_ACCEPTED = 'accepted';
|
||||
public const STATUS_DECLINED = 'declined';
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
public const STATUS_SUPERSEDED = 'superseded';
|
||||
|
||||
public const ACCEPTED_VIA_LINK = 'customer_link';
|
||||
public const ACCEPTED_VIA_ADMIN = 'admin';
|
||||
public const ACCEPTED_VIA_MAIL = 'email';
|
||||
|
||||
protected $table = 'offer_versions';
|
||||
|
||||
protected $fillable = [
|
||||
'offer_id',
|
||||
'version_no',
|
||||
'status',
|
||||
'valid_until',
|
||||
'total_price',
|
||||
'headline',
|
||||
'intro_text',
|
||||
'itinerary_text',
|
||||
'closing_text',
|
||||
'template_id',
|
||||
'pdf_path',
|
||||
'pdf_archived',
|
||||
'sent_at',
|
||||
'accepted_at',
|
||||
'accepted_via',
|
||||
'template_document_ids',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'offer_id' => 'int',
|
||||
'version_no' => 'int',
|
||||
'valid_until' => 'date',
|
||||
'total_price' => 'decimal:2',
|
||||
'template_id' => 'int',
|
||||
'pdf_archived' => 'bool',
|
||||
'sent_at' => 'datetime',
|
||||
'accepted_at' => 'datetime',
|
||||
'template_document_ids' => 'array',
|
||||
'created_by' => 'int',
|
||||
];
|
||||
|
||||
public function offer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Offer::class);
|
||||
}
|
||||
|
||||
public function template(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(OfferTemplate::class, 'template_id');
|
||||
}
|
||||
|
||||
public function items(): HasMany
|
||||
{
|
||||
return $this->hasMany(OfferItem::class)->orderBy('position');
|
||||
}
|
||||
|
||||
public function files(): HasMany
|
||||
{
|
||||
return $this->hasMany(OfferFile::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
public function isEditable(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_DRAFT;
|
||||
}
|
||||
}
|
||||
|
|
@ -63,13 +63,13 @@ class BookingPDFRepository extends BaseRepository
|
|||
{
|
||||
$document = new stdClass();
|
||||
$document->name = 'registration';
|
||||
$document->number = $this->model->lead_id;
|
||||
$document->number = $this->model->inquiry_id;
|
||||
$document->title = 'BUCHUNGSAUFTRAG';
|
||||
$document->voucher = null;
|
||||
$document->date = now();
|
||||
$document->total = $this->model->getPriceRaw();
|
||||
$dir = $this->getDirPath('pdf', 'booking', $document->date->format('Y'));
|
||||
$filename = "Buchnungsauftrag-" . $this->model->lead_id . ".pdf";
|
||||
$filename = "Buchnungsauftrag-" . $this->model->inquiry_id . ".pdf";
|
||||
$pdf_file = new CreatePDF('pdf.booking_registration');
|
||||
$data = [
|
||||
'booking' => $this->model,
|
||||
|
|
@ -85,7 +85,7 @@ class BookingPDFRepository extends BaseRepository
|
|||
{
|
||||
$document = new stdClass();
|
||||
$document->name = 'confirmation';
|
||||
$document->number = $this->model->lead_id;
|
||||
$document->number = $this->model->inquiry_id;
|
||||
$document->title = 'REISEBESTÄTIGUNG';
|
||||
$document->voucher = null;
|
||||
$document->date = now();
|
||||
|
|
@ -104,7 +104,7 @@ class BookingPDFRepository extends BaseRepository
|
|||
$document->final_payment_date = date('Y-m-d');
|
||||
}
|
||||
$dir = $this->getDirPath('pdf', 'booking', $document->date->format('Y'));
|
||||
$filename = "Reisebestätigung-" . $this->model->lead_id . ".pdf";
|
||||
$filename = "Reisebestätigung-" . $this->model->inquiry_id . ".pdf";
|
||||
|
||||
$pdf_file = new CreatePDF('pdf.booking_confirmation');
|
||||
$data = [
|
||||
|
|
@ -160,14 +160,14 @@ class BookingPDFRepository extends BaseRepository
|
|||
{
|
||||
$document = new stdClass();
|
||||
$document->name = 'voucher';
|
||||
$document->number = $this->model->lead_id;
|
||||
$document->number = $this->model->inquiry_id;
|
||||
$document->name = 'voucher';
|
||||
$document->title = $agency ? 'VOUCHER Agentur' : 'VOUCHER';
|
||||
$document->voucher = $agency ? 'agency' : 'client';
|
||||
$document->date = now();
|
||||
|
||||
$dir = $this->getDirPath('pdf', 'voucher', $document->date->format('Y'));
|
||||
$filename = ($agency ? 'VoucherAgentur' : 'Voucher') . "-" . $this->model->lead_id . ".pdf";
|
||||
$filename = ($agency ? 'VoucherAgentur' : 'Voucher') . "-" . $this->model->inquiry_id . ".pdf";
|
||||
|
||||
$pdf_file = new CreatePDF('pdf.booking_voucher');
|
||||
$data = [
|
||||
|
|
@ -224,7 +224,7 @@ class BookingPDFRepository extends BaseRepository
|
|||
//init document
|
||||
$document = new stdClass();
|
||||
$document->name = $identifier;
|
||||
$document->number = $this->model->lead_id;
|
||||
$document->number = $this->model->inquiry_id;
|
||||
$document->title = 'STORNOBESTÄTIGUNG';
|
||||
$document->voucher = null;
|
||||
$document->date = Carbon::parse($data['storno_print']);
|
||||
|
|
@ -253,7 +253,7 @@ class BookingPDFRepository extends BaseRepository
|
|||
|
||||
|
||||
$dir = $this->getDirPath('pdf', 'storno', $document->date->format('Y'));
|
||||
$filename = "Reisestornierung -" . $this->model->lead_id . ".pdf";
|
||||
$filename = "Reisestornierung -" . $this->model->inquiry_id . ".pdf";
|
||||
|
||||
$pdf_file = new CreatePDF('pdf.booking_storno');
|
||||
$data = [
|
||||
|
|
@ -288,7 +288,9 @@ class BookingPDFRepository extends BaseRepository
|
|||
$fill = [
|
||||
'booking_id' => $this->model->id,
|
||||
'customer_id' => $this->model->customer_id,
|
||||
'lead_id' => $this->model->lead_id,
|
||||
// booking_documents.lead_id ist ein Shadow-Feld von booking.inquiry_id;
|
||||
// die Spalte selbst wird von Phase 2 nicht umbenannt.
|
||||
'lead_id' => $this->model->inquiry_id,
|
||||
'identifier' => $identifier,
|
||||
'filename' => $filename,
|
||||
'dir' => $dir,
|
||||
|
|
|
|||
|
|
@ -157,11 +157,12 @@ class BookingRepository extends BaseRepository
|
|||
{
|
||||
|
||||
$this->model = Booking::findOrFail($id);
|
||||
$this->model->setPriceTotalForCurrentState();
|
||||
$fill = [
|
||||
'deposit_total' => $data['deposit_total'] ? Util::_clean_float($data['deposit_total']) : 0,
|
||||
'final_payment' => $data['final_payment'] ? Util::_clean_float($data['final_payment']) : 0,
|
||||
'final_payment_date' => $data['final_payment_date'] ? _reformat_date($data['final_payment_date']) : null,
|
||||
'price_total' => ($this->model->getPriceRaw() + $this->model->getServiceTotal(true)),
|
||||
'price_total' => $this->model->getPriceTotalRaw(),
|
||||
];
|
||||
$this->model->fill($fill);
|
||||
$this->model->save();
|
||||
|
|
@ -210,7 +211,7 @@ class BookingRepository extends BaseRepository
|
|||
}
|
||||
}
|
||||
}
|
||||
$this->model->price_total = ($this->model->getPriceRaw() + $this->model->getServiceTotal(true));
|
||||
$this->model->setPriceTotalForCurrentState();
|
||||
$this->model->save();
|
||||
|
||||
return $this->model;
|
||||
|
|
|
|||
|
|
@ -135,7 +135,8 @@ class CustomerMailRepository extends BaseRepository {
|
|||
$customer_mail->fill([
|
||||
'booking_id' => $booking->id,
|
||||
'customer_id' => $booking->customer_id,
|
||||
'lead_id' => $booking->lead_id,
|
||||
// customer_mails.lead_id-Spalte bleibt unverändert; Wert kommt aus booking.inquiry_id
|
||||
'lead_id' => $booking->inquiry_id,
|
||||
'is_answer' => $is_answer,
|
||||
'reply_id' => $reply_id,
|
||||
'email' => $mail_from,
|
||||
|
|
@ -153,7 +154,8 @@ class CustomerMailRepository extends BaseRepository {
|
|||
$customer_mail = CustomerMail::create([
|
||||
'booking_id' => $booking->id,
|
||||
'customer_id' => $booking->customer_id,
|
||||
'lead_id' => $booking->lead_id,
|
||||
// customer_mails.lead_id-Spalte bleibt unverändert; Wert kommt aus booking.inquiry_id
|
||||
'lead_id' => $booking->inquiry_id,
|
||||
'is_answer' => $is_answer,
|
||||
'reply_id' => $reply_id,
|
||||
'email' => $mail_from,
|
||||
|
|
@ -300,7 +302,7 @@ class CustomerMailRepository extends BaseRepository {
|
|||
$value->id = $customer_mail->booking_id;
|
||||
$value->booking = $booking;
|
||||
$value->show = 'single';
|
||||
$value->lead_title_id = " - (".$value->booking->lead_id.")";
|
||||
$value->lead_title_id = " - (".$value->booking->inquiry_id.")";
|
||||
|
||||
|
||||
$tmp = [];
|
||||
|
|
@ -342,7 +344,7 @@ class CustomerMailRepository extends BaseRepository {
|
|||
$value->booking = $booking;
|
||||
$value->show = 'single';
|
||||
$value->draft = true;
|
||||
$value->lead_title_id = " - (".$value->booking->lead_id.")";
|
||||
$value->lead_title_id = " - (".$value->booking->inquiry_id.")";
|
||||
|
||||
}else{
|
||||
//multi
|
||||
|
|
@ -379,8 +381,8 @@ class CustomerMailRepository extends BaseRepository {
|
|||
$value->draft = false;
|
||||
$value->booking = $booking;
|
||||
$value->message = "";
|
||||
$value->subject = " - (".$value->booking->lead_id.")";
|
||||
$value->lead_title_id = " - (".$value->booking->lead_id.")";
|
||||
$value->subject = " - (".$value->booking->inquiry_id.")";
|
||||
$value->lead_title_id = " - (".$value->booking->inquiry_id.")";
|
||||
$value->s_placeholder = "Betreff des Kunden";
|
||||
$value->m_placeholder = "Nachricht des Kunden";
|
||||
if(isset($data['customer_mail_id']) && $customer_mail = CustomerMail::find($data['customer_mail_id'])){
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ class LeadRepository extends BaseRepository {
|
|||
$data = [
|
||||
'booking_date' => date('Y-m-d'), //now
|
||||
'customer_id' => $this->model->customer->id,
|
||||
'lead_id' => $this->model->id,
|
||||
'inquiry_id' => $this->model->id,
|
||||
'new_drafts' => 1,
|
||||
'sf_guard_user_id' => $this->model->sf_guard_user_id,
|
||||
'branch_id' => 4,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class Booking
|
|||
->pluck('slug', 'id');
|
||||
}
|
||||
|
||||
public static function setOutputDirs(string $dir, string $subdir): void
|
||||
public static function setOutputDirs(string $dir, ?string $subdir): void
|
||||
{
|
||||
MailDirService::setOutputDir($dir, $subdir);
|
||||
}
|
||||
|
|
@ -39,12 +39,12 @@ class Booking
|
|||
return MailDirService::getCustomerMailDir($id);
|
||||
}
|
||||
|
||||
public static function getCustomerMailName(\App\Models\CMSContent $mailDir, int $mailDirId): string
|
||||
public static function getCustomerMailName(\App\Models\CMSContent $mailDir, ?int $mailDirId): string
|
||||
{
|
||||
return MailDirService::getCustomerMailName($mailDir, $mailDirId);
|
||||
}
|
||||
|
||||
public static function getCustomerMailEmails(\App\Models\CMSContent $mailDir, int $mailDirId): array|string
|
||||
public static function getCustomerMailEmails(\App\Models\CMSContent $mailDir, ?int $mailDirId): array|string
|
||||
{
|
||||
return MailDirService::getCustomerMailEmails($mailDir, $mailDirId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class BookingImport
|
|||
$data = [
|
||||
'booking_date' => $travel_booking->created->format('Y-m-d'),
|
||||
'customer_id' => $customer->id,
|
||||
'lead_id' => $lead->id,
|
||||
'inquiry_id' => $lead->id,
|
||||
'new_drafts' => $travel_booking->drafts === null ? 0 : 1,
|
||||
'sf_guard_user_id' => 15,
|
||||
'branch_id' => 4,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class Lead
|
|||
->pluck('slug', 'id');
|
||||
}
|
||||
|
||||
public static function setOutputDirs(string $dir, string $subdir): void
|
||||
public static function setOutputDirs(string $dir, ?string $subdir): void
|
||||
{
|
||||
MailDirService::setOutputDir($dir, $subdir);
|
||||
}
|
||||
|
|
@ -39,12 +39,12 @@ class Lead
|
|||
return MailDirService::getCustomerMailDir($id);
|
||||
}
|
||||
|
||||
public static function getCustomerMailName(\App\Models\CMSContent $mailDir, int $mailDirId): string
|
||||
public static function getCustomerMailName(\App\Models\CMSContent $mailDir, ?int $mailDirId): string
|
||||
{
|
||||
return MailDirService::getCustomerMailName($mailDir, $mailDirId);
|
||||
}
|
||||
|
||||
public static function getCustomerMailEmails(\App\Models\CMSContent $mailDir, int $mailDirId): array|string
|
||||
public static function getCustomerMailEmails(\App\Models\CMSContent $mailDir, ?int $mailDirId): array|string
|
||||
{
|
||||
return MailDirService::getCustomerMailEmails($mailDir, $mailDirId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class MailDirService
|
|||
{
|
||||
private static array $outputDirs = [];
|
||||
|
||||
public static function setOutputDir(string $dir, string $subdir): void
|
||||
public static function setOutputDir(string $dir, ?string $subdir): void
|
||||
{
|
||||
self::$outputDirs[$dir][] = $subdir;
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ class MailDirService
|
|||
return CMSContent::where('identifier', '=', 'customer-mail-dirs')->where('pos', '=', $id)->first();
|
||||
}
|
||||
|
||||
public static function getCustomerMailName(CMSContent $mailDir, int $mailDirId): string
|
||||
public static function getCustomerMailName(CMSContent $mailDir, ?int $mailDirId): string
|
||||
{
|
||||
$model = self::resolveModel($mailDir, $mailDirId);
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ class MailDirService
|
|||
/**
|
||||
* @return array<string>|string
|
||||
*/
|
||||
public static function getCustomerMailEmails(CMSContent $mailDir, int $mailDirId): array|string
|
||||
public static function getCustomerMailEmails(CMSContent $mailDir, ?int $mailDirId): array|string
|
||||
{
|
||||
$model = self::resolveModel($mailDir, $mailDirId);
|
||||
|
||||
|
|
@ -79,8 +79,11 @@ class MailDirService
|
|||
return $result;
|
||||
}
|
||||
|
||||
private static function resolveModel(CMSContent $mailDir, int $mailDirId): mixed
|
||||
private static function resolveModel(CMSContent $mailDir, ?int $mailDirId): mixed
|
||||
{
|
||||
if ($mailDirId === null) {
|
||||
return null;
|
||||
}
|
||||
return match ($mailDir->getArrayContent('model')) {
|
||||
'TravelCountry' => \App\Models\Sym\TravelCountry::find($mailDirId),
|
||||
'Airline' => Airline::find($mailDirId),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue