WIP: Sicherheitsnetz vor Phase-1-R\u00fcckbau
Enth\u00e4lt gemischt: Laravel-10-Upgrade + Phase 1 (Contacts-Modul, Duplicats-Commands, Soft-Delete+Merge-Fields) + Phase 2 Code-Umstellungen (inquiry_id, $table='contacts'/'inquiries') + Offers-Modul (Migrationen, Models, offer_id in Booking, offer-Disk in filesystems.php). Phase 2 + Offers werden im folgenden Commit nach dev/backups/phase2-offers-2026-04-17/ verschoben, damit der Workspace auf Phase-1-only (= Test-System-Stand) reduziert ist und direkt auf Live deploybar wird. Tarball-Backup zus\u00e4tzlich unter: ../backups-safety/workspace-pre-phase1-rollback-2026-04-17.tar.gz Made-with: Cursor
This commit is contained in:
parent
389d5d1820
commit
e3dc1afd8e
165 changed files with 21914 additions and 3516 deletions
|
|
@ -1,84 +0,0 @@
|
|||
{
|
||||
"name": "STERN TOURS (Dev Container)",
|
||||
// 1. DIES IST DER WICHTIGSTE TEIL:
|
||||
// Wir verwenden Docker Compose für alle Services
|
||||
"dockerComposeFile": [
|
||||
"../docker-compose.yml"
|
||||
],
|
||||
"service": "laravel.test",
|
||||
// 3. WIR DEFINIEREN DEN ARBEITSBEREICH:
|
||||
// Das ist der Pfad, in dem Ihr Code *innerhalb* des Containers liegt.
|
||||
"workspaceFolder": "/var/www/html",
|
||||
// 4. WIR LEGEN DEN BENUTZER FEST:
|
||||
// Laravel Sail führt Befehle standardmäßig als 'sail'-Benutzer aus, um Berechtigungsprobleme zu vermeiden.
|
||||
"remoteUser": "sail",
|
||||
// 5. ZUSÄTZLICHE ENTWICKLER-TOOLS (FEATURES):
|
||||
// Features werden über postCreateCommand installiert um Kompatibilitätsprobleme zu vermeiden
|
||||
"features": {},
|
||||
// 6. BEFEHLE NACH DEM ERSTELLEN:
|
||||
// Installiert nur die Tools die ohne Root-Rechte funktionieren
|
||||
//"postCreateCommand": "composer install --no-interaction --prefer-dist --optimize-autoloader",
|
||||
// 7. EDITOR-ANPASSUNGEN (Optional, aber sehr empfohlen):
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"bmewburn.vscode-intelephense-client",
|
||||
"onecentlin.laravel-blade",
|
||||
"shufo.vscode-blade-formatter",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
},
|
||||
// 8. ZU STARTENDE DIENSTE:
|
||||
// Legt fest, welche Dienste aus der docker-compose.yml gestartet werden sollen.
|
||||
"runServices": [
|
||||
"laravel.test",
|
||||
"mysql",
|
||||
"mysql-stern",
|
||||
"redis",
|
||||
"mailpit"
|
||||
],
|
||||
// 9. ZUSÄTZLICHE KONFIGURATION:
|
||||
// Umgebungsvariablen für den DevContainer
|
||||
"containerEnv": {
|
||||
"WWWUSER": "501",
|
||||
"WWWGROUP": "20",
|
||||
"LARAVEL_SAIL": "1"
|
||||
},
|
||||
// 10. MOUNT-KONFIGURATION:
|
||||
// Stellt sicher, dass der Code korrekt gemountet wird
|
||||
"mounts": [
|
||||
"source=${localWorkspaceFolder},target=/var/www/html,type=bind,consistency=cached"
|
||||
],
|
||||
// 11. FORWARD PORTS:
|
||||
// Ports die automatisch weitergeleitet werden sollen
|
||||
"forwardPorts": [
|
||||
33064,
|
||||
33065,
|
||||
6379,
|
||||
1030,
|
||||
8030
|
||||
],
|
||||
"portsAttributes": {
|
||||
"33064": {
|
||||
"label": "MySQL",
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
"33065": {
|
||||
"label": "MySQL Stern",
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
"6379": {
|
||||
"label": "Redis",
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
"1030": {
|
||||
"label": "Mailpit SMTP",
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
"8030": {
|
||||
"label": "Mailpit Dashboard",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
.env
10
.env
|
|
@ -15,19 +15,21 @@ APP_DOMAIN_TLD=test
|
|||
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
SUCCESS_KEY=f6077389c9ce710e554763a5de02c8ec
|
||||
|
||||
# Standard Database Connection
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_HOST=global-mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=stern_crm
|
||||
DB_USERNAME=sail
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=password
|
||||
|
||||
# STERN Database Connection
|
||||
DB_HOST_STERN=mysql-stern
|
||||
DB_HOST_STERN=global-mysql
|
||||
DB_PORT_STERN=3306
|
||||
DB_DATABASE_STERN=stern_db
|
||||
DB_USERNAME_STERN=sail
|
||||
DB_USERNAME_STERN=root
|
||||
DB_PASSWORD_STERN=password
|
||||
|
||||
# Docker Port Forwards
|
||||
|
|
|
|||
27
.mcp.json
Normal file
27
.mcp.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"laravel-boost": {
|
||||
"command": "php",
|
||||
"args": [
|
||||
"artisan",
|
||||
"boost:mcp"
|
||||
]
|
||||
},
|
||||
"context7": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@upstash/context7-mcp",
|
||||
"--api-key",
|
||||
"ctx7sk-119cd4ab-8983-4229-8702-e84c59c34fc9"
|
||||
]
|
||||
},
|
||||
"sequential-thinking": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-sequential-thinking"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
181
CLAUDE.md
Normal file
181
CLAUDE.md
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<laravel-boost-guidelines>
|
||||
=== foundation rules ===
|
||||
|
||||
# Laravel Boost Guidelines
|
||||
|
||||
The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications.
|
||||
|
||||
## Foundational Context
|
||||
This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.
|
||||
|
||||
- php - 8.3.30
|
||||
- laravel/framework (LARAVEL) - v10
|
||||
- laravel/passport (PASSPORT) - v11
|
||||
- laravel/prompts (PROMPTS) - v0
|
||||
- laravel/mcp (MCP) - v0
|
||||
- laravel/sail (SAIL) - v1
|
||||
- phpunit/phpunit (PHPUNIT) - v10
|
||||
|
||||
## Conventions
|
||||
- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming.
|
||||
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
|
||||
- Check for existing components to reuse before writing a new one.
|
||||
|
||||
## Verification Scripts
|
||||
- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important.
|
||||
|
||||
## Application Structure & Architecture
|
||||
- Stick to existing directory structure; don't create new base folders without approval.
|
||||
- Do not change the application's dependencies without approval.
|
||||
|
||||
## Frontend Bundling
|
||||
- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.
|
||||
|
||||
## Replies
|
||||
- Be concise in your explanations - focus on what's important rather than explaining obvious details.
|
||||
|
||||
## Documentation Files
|
||||
- You must only create documentation files if explicitly requested by the user.
|
||||
|
||||
=== boost rules ===
|
||||
|
||||
## Laravel Boost
|
||||
- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them.
|
||||
|
||||
## Artisan
|
||||
- Use the `list-artisan-commands` tool when you need to call an Artisan command to double-check the available parameters.
|
||||
|
||||
## URLs
|
||||
- Whenever you share a project URL with the user, you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain/IP, and port.
|
||||
|
||||
## Tinker / Debugging
|
||||
- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly.
|
||||
- Use the `database-query` tool when you only need to read from the database.
|
||||
|
||||
## Reading Browser Logs With the `browser-logs` Tool
|
||||
- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost.
|
||||
- Only recent browser logs will be useful - ignore old logs.
|
||||
|
||||
## Searching Documentation (Critically Important)
|
||||
- Boost comes with a powerful `search-docs` tool you should use before any other approaches when dealing with Laravel or Laravel ecosystem packages. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages.
|
||||
- The `search-docs` tool is perfect for all Laravel-related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc.
|
||||
- You must use this tool to search for Laravel ecosystem documentation before falling back to other approaches.
|
||||
- Search the documentation before making code changes to ensure we are taking the correct approach.
|
||||
- Use multiple, broad, simple, topic-based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`.
|
||||
- Do not add package names to queries; package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`.
|
||||
|
||||
### Available Search Syntax
|
||||
- You can and should pass multiple queries at once. The most relevant results will be returned first.
|
||||
|
||||
1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'.
|
||||
2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit".
|
||||
3. Quoted Phrases (Exact Position) - query="infinite scroll" - words must be adjacent and in that order.
|
||||
4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit".
|
||||
5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms.
|
||||
|
||||
=== php rules ===
|
||||
|
||||
## PHP
|
||||
|
||||
- Always use curly braces for control structures, even if it has one line.
|
||||
|
||||
### Constructors
|
||||
- Use PHP 8 constructor property promotion in `__construct()`.
|
||||
- <code-snippet>public function __construct(public GitHub $github) { }</code-snippet>
|
||||
- Do not allow empty `__construct()` methods with zero parameters unless the constructor is private.
|
||||
|
||||
### Type Declarations
|
||||
- Always use explicit return type declarations for methods and functions.
|
||||
- Use appropriate PHP type hints for method parameters.
|
||||
|
||||
<code-snippet name="Explicit Return Types and Method Params" lang="php">
|
||||
protected function isAccessible(User $user, ?string $path = null): bool
|
||||
{
|
||||
...
|
||||
}
|
||||
</code-snippet>
|
||||
|
||||
## Comments
|
||||
- Prefer PHPDoc blocks over inline comments. Never use comments within the code itself unless there is something very complex going on.
|
||||
|
||||
## PHPDoc Blocks
|
||||
- Add useful array shape type definitions for arrays when appropriate.
|
||||
|
||||
## Enums
|
||||
- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`.
|
||||
|
||||
=== laravel/core rules ===
|
||||
|
||||
## Do Things the Laravel Way
|
||||
|
||||
- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool.
|
||||
- If you're creating a generic PHP class, use `php artisan make:class`.
|
||||
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.
|
||||
|
||||
### Database
|
||||
- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins.
|
||||
- Use Eloquent models and relationships before suggesting raw database queries.
|
||||
- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them.
|
||||
- Generate code that prevents N+1 query problems by using eager loading.
|
||||
- Use Laravel's query builder for very complex database operations.
|
||||
|
||||
### Model Creation
|
||||
- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`.
|
||||
|
||||
### APIs & Eloquent Resources
|
||||
- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.
|
||||
|
||||
### Controllers & Validation
|
||||
- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages.
|
||||
- Check sibling Form Requests to see if the application uses array or string based validation rules.
|
||||
|
||||
### Queues
|
||||
- Use queued jobs for time-consuming operations with the `ShouldQueue` interface.
|
||||
|
||||
### Authentication & Authorization
|
||||
- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.).
|
||||
|
||||
### URL Generation
|
||||
- When generating links to other pages, prefer named routes and the `route()` function.
|
||||
|
||||
### Configuration
|
||||
- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`.
|
||||
|
||||
### Testing
|
||||
- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
|
||||
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
|
||||
- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.
|
||||
|
||||
### Vite Error
|
||||
- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.
|
||||
|
||||
=== laravel/v10 rules ===
|
||||
|
||||
## Laravel 10
|
||||
|
||||
- Use the `search-docs` tool to get version-specific documentation.
|
||||
- Middleware typically live in `app/Http/Middleware/` and service providers in `app/Providers/`.
|
||||
- Laravel 10 has a `bootstrap/app.php` file that creates the application instance and binds kernel contracts, but does not use it for application configuration like Laravel 11:
|
||||
- Middleware registration is in `app/Http/Kernel.php`
|
||||
- Exception handling is in `app/Exceptions/Handler.php`
|
||||
- Console commands and schedule registration is in `app/Console/Kernel.php`
|
||||
- Rate limits likely exist in `RouteServiceProvider` or `app/Http/Kernel.php`
|
||||
- When using Eloquent model casts, you must use `protected $casts = [];` and not the `casts()` method. The `casts()` method isn't available on models in Laravel 10.
|
||||
|
||||
=== phpunit/core rules ===
|
||||
|
||||
## PHPUnit
|
||||
|
||||
- This application uses PHPUnit for testing. All tests must be written as PHPUnit classes. Use `php artisan make:test --phpunit {name}` to create a new test.
|
||||
- If you see a test using "Pest", convert it to PHPUnit.
|
||||
- Every time a test has been updated, run that singular test.
|
||||
- When the tests relating to your feature are passing, ask the user if they would like to also run the entire test suite to make sure everything is still passing.
|
||||
- Tests should test all of the happy paths, failure paths, and weird paths.
|
||||
- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files; these are core to the application.
|
||||
|
||||
### Running Tests
|
||||
- Run the minimal number of tests, using an appropriate filter, before finalizing.
|
||||
- To run all tests: `php artisan test --compact`.
|
||||
- To run all tests in a file: `php artisan test --compact tests/Feature/ExampleTest.php`.
|
||||
- To filter on a particular test name: `php artisan test --compact --filter=testName` (recommended after making a change to a related file).
|
||||
</laravel-boost-guidelines>
|
||||
File diff suppressed because it is too large
Load diff
160
app/Console/Commands/ContactsFindDuplicates.php
Normal file
160
app/Console/Commands/ContactsFindDuplicates.php
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 1 — Schritt 1: Duplikate identifizieren
|
||||
*
|
||||
* Sucht Kunden-Datensätze, die vermutlich dieselbe Person repräsentieren.
|
||||
* Drei Erkennungs-Stufen (absteigend nach Konfidenz):
|
||||
*
|
||||
* HIGH — gleiche E-Mail (nicht leer)
|
||||
* MEDIUM — gleicher Name + Vorname + Geburtsdatum
|
||||
* LOW — gleicher Name + Vorname + PLZ
|
||||
*
|
||||
* Verwendung:
|
||||
* php artisan contacts:find-duplicates
|
||||
* php artisan contacts:find-duplicates --export=duplicates.csv
|
||||
* php artisan contacts:find-duplicates --confidence=HIGH
|
||||
*/
|
||||
class ContactsFindDuplicates extends Command
|
||||
{
|
||||
protected $signature = 'contacts:find-duplicates
|
||||
{--export= : Pfad zur CSV-Ausgabedatei}
|
||||
{--confidence= : Nur diese Konfidenz-Stufe ausgeben (HIGH|MEDIUM|LOW)}';
|
||||
|
||||
protected $description = 'Identifiziert doppelte Customer-Datensätze anhand von E-Mail, Name/Geburtsdatum oder Name/PLZ';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('Suche nach Duplikaten in der customer-Tabelle...');
|
||||
$this->newLine();
|
||||
|
||||
$groups = collect();
|
||||
|
||||
// ── HIGH: gleiche E-Mail ──────────────────────────────────────────
|
||||
if ($this->shouldCheck('HIGH')) {
|
||||
$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', '!=', '')
|
||||
->whereNull('merged_into_id')
|
||||
->groupBy('email')
|
||||
->having('cnt', '>', 1)
|
||||
->get();
|
||||
|
||||
foreach ($emailDupes as $row) {
|
||||
$groups->push([
|
||||
'confidence' => 'HIGH',
|
||||
'reason' => 'E-Mail: ' . $row->email,
|
||||
'ids' => $row->ids,
|
||||
'count' => $row->cnt,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->line(sprintf('<fg=green>HIGH</> (gleiche E-Mail): %d Gruppen', $emailDupes->count()));
|
||||
}
|
||||
|
||||
// ── MEDIUM: Name + Vorname + Geburtsdatum ────────────────────────
|
||||
if ($this->shouldCheck('MEDIUM')) {
|
||||
$nameBdDupes = DB::table('contacts')
|
||||
->select(
|
||||
'name', 'firstname', 'birthdate',
|
||||
DB::raw('COUNT(*) as cnt'),
|
||||
DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC) as ids')
|
||||
)
|
||||
->whereNotNull('name')
|
||||
->whereNotNull('firstname')
|
||||
->whereNotNull('birthdate')
|
||||
->whereNull('merged_into_id')
|
||||
->groupBy('name', 'firstname', 'birthdate')
|
||||
->having('cnt', '>', 1)
|
||||
->get();
|
||||
|
||||
foreach ($nameBdDupes as $row) {
|
||||
$groups->push([
|
||||
'confidence' => 'MEDIUM',
|
||||
'reason' => "Name: {$row->firstname} {$row->name}, GD: {$row->birthdate}",
|
||||
'ids' => $row->ids,
|
||||
'count' => $row->cnt,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->line(sprintf('<fg=yellow>MEDIUM</> (Name+GD): %d Gruppen', $nameBdDupes->count()));
|
||||
}
|
||||
|
||||
// ── LOW: Name + Vorname + PLZ ─────────────────────────────────────
|
||||
if ($this->shouldCheck('LOW')) {
|
||||
$nameZipDupes = DB::table('contacts')
|
||||
->select(
|
||||
'name', 'firstname', 'zip',
|
||||
DB::raw('COUNT(*) as cnt'),
|
||||
DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC) as ids')
|
||||
)
|
||||
->whereNotNull('name')
|
||||
->whereNotNull('firstname')
|
||||
->whereNotNull('zip')
|
||||
->where('zip', '!=', '')
|
||||
->whereNull('merged_into_id')
|
||||
->groupBy('name', 'firstname', 'zip')
|
||||
->having('cnt', '>', 1)
|
||||
->get();
|
||||
|
||||
foreach ($nameZipDupes as $row) {
|
||||
$groups->push([
|
||||
'confidence' => 'LOW',
|
||||
'reason' => "Name: {$row->firstname} {$row->name}, PLZ: {$row->zip}",
|
||||
'ids' => $row->ids,
|
||||
'count' => $row->cnt,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->line(sprintf('<fg=red>LOW</> (Name+PLZ): %d Gruppen', $nameZipDupes->count()));
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info(sprintf('Gesamt: %d Duplikat-Gruppen gefunden', $groups->count()));
|
||||
|
||||
if ($groups->isEmpty()) {
|
||||
$this->info('Keine Duplikate — nichts zu tun.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
// Tabellen-Ausgabe
|
||||
$this->table(
|
||||
['Konfidenz', 'Grund', 'IDs (neueste zuerst)', 'Anzahl'],
|
||||
$groups->map(fn ($g) => [$g['confidence'], $g['reason'], $g['ids'], $g['count']])->all()
|
||||
);
|
||||
|
||||
// CSV-Export
|
||||
if ($export = $this->option('export')) {
|
||||
$this->exportCsv($groups->all(), $export);
|
||||
$this->info("CSV gespeichert: {$export}");
|
||||
} else {
|
||||
$this->newLine();
|
||||
$this->line('Tipp: --export=duplicates.csv für CSV-Export');
|
||||
$this->line('Tipp: php artisan contacts:merge-duplicates --dry-run zum Prüfen');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function shouldCheck(string $level): bool
|
||||
{
|
||||
$filter = strtoupper((string) $this->option('confidence'));
|
||||
return $filter === '' || $filter === $level;
|
||||
}
|
||||
|
||||
private function exportCsv(array $groups, string $path): void
|
||||
{
|
||||
$handle = fopen($path, 'w');
|
||||
fputcsv($handle, ['Konfidenz', 'Grund', 'IDs (neueste zuerst)', 'Anzahl']);
|
||||
foreach ($groups as $group) {
|
||||
fputcsv($handle, [$group['confidence'], $group['reason'], $group['ids'], $group['count']]);
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
233
app/Console/Commands/ContactsMergeDuplicates.php
Normal file
233
app/Console/Commands/ContactsMergeDuplicates.php
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 1 — Schritt 2: Duplikate zusammenführen
|
||||
*
|
||||
* Strategie: Der neueste Datensatz (höchstes updated_at, dann höchste id)
|
||||
* wird Master. Alle anderen Datensätze derselben Gruppe erhalten
|
||||
* merged_into_id = master_id und werden nicht mehr zurückgegeben.
|
||||
*
|
||||
* Alle FK-Referenzen in lead, booking, customer_mails, lead_mails
|
||||
* werden auf den Master umgestellt.
|
||||
*
|
||||
* Verwendung:
|
||||
* php artisan contacts:merge-duplicates --dry-run # Vorschau, keine Änderung
|
||||
* php artisan contacts:merge-duplicates --confidence=HIGH # Nur sichere Duplikate
|
||||
* php artisan contacts:merge-duplicates # Ausführen
|
||||
*/
|
||||
class ContactsMergeDuplicates extends Command
|
||||
{
|
||||
protected $signature = 'contacts:merge-duplicates
|
||||
{--dry-run : Zeigt was passieren würde, ohne Daten zu ändern}
|
||||
{--confidence= : Nur diese Konfidenz-Stufe verarbeiten (HIGH|MEDIUM|LOW)}
|
||||
{--force : Überspringt Sicherheitsabfrage}';
|
||||
|
||||
protected $description = 'Führt doppelte Customer-Datensätze zusammen (neuester wird Master)';
|
||||
|
||||
private bool $dryRun = false;
|
||||
private int $mergedCount = 0;
|
||||
private int $updatedLeads = 0;
|
||||
private int $updatedBookings = 0;
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->warn('DRY-RUN Modus — keine Daten werden verändert');
|
||||
} else {
|
||||
$this->warn('ACHTUNG: Diese Operation verändert Produktionsdaten.');
|
||||
if (!$this->option('force') && !$this->confirm('Fortfahren?')) {
|
||||
$this->info('Abgebrochen.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
|
||||
DB::transaction(function () {
|
||||
$this->processLevel(
|
||||
'HIGH',
|
||||
fn () => $this->findByEmail()
|
||||
);
|
||||
|
||||
$this->processLevel(
|
||||
'MEDIUM',
|
||||
fn () => $this->findByNameBirthdate()
|
||||
);
|
||||
|
||||
$this->processLevel(
|
||||
'LOW',
|
||||
fn () => $this->findByNameZip()
|
||||
);
|
||||
});
|
||||
|
||||
$this->newLine();
|
||||
$this->info(sprintf(
|
||||
'%s %d Duplikate zusammengeführt | %d leads aktualisiert | %d bookings aktualisiert',
|
||||
$this->dryRun ? '[DRY-RUN]' : '',
|
||||
$this->mergedCount,
|
||||
$this->updatedLeads,
|
||||
$this->updatedBookings
|
||||
));
|
||||
|
||||
if ($this->dryRun) {
|
||||
$this->newLine();
|
||||
$this->line('Zum Ausführen: php artisan contacts:merge-duplicates');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Duplikat-Gruppen ermitteln
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private function findByEmail(): array
|
||||
{
|
||||
return DB::table('contacts')
|
||||
->select('email', DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids'))
|
||||
->whereNotNull('email')
|
||||
->where('email', '!=', '')
|
||||
->whereNull('merged_into_id')
|
||||
->groupBy('email')
|
||||
->having(DB::raw('COUNT(*)'), '>', 1)
|
||||
->pluck('ids')
|
||||
->map(fn ($ids) => explode(',', $ids))
|
||||
->all();
|
||||
}
|
||||
|
||||
private function findByNameBirthdate(): array
|
||||
{
|
||||
return DB::table('contacts')
|
||||
->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids'))
|
||||
->whereNotNull('name')
|
||||
->whereNotNull('firstname')
|
||||
->whereNotNull('birthdate')
|
||||
->whereNull('merged_into_id')
|
||||
->groupBy('name', 'firstname', 'birthdate')
|
||||
->having(DB::raw('COUNT(*)'), '>', 1)
|
||||
->pluck('ids')
|
||||
->map(fn ($ids) => explode(',', $ids))
|
||||
->all();
|
||||
}
|
||||
|
||||
private function findByNameZip(): array
|
||||
{
|
||||
return DB::table('contacts')
|
||||
->select(DB::raw('GROUP_CONCAT(id ORDER BY updated_at DESC, id DESC) as ids'))
|
||||
->whereNotNull('name')
|
||||
->whereNotNull('firstname')
|
||||
->whereNotNull('zip')
|
||||
->where('zip', '!=', '')
|
||||
->whereNull('merged_into_id')
|
||||
->groupBy('name', 'firstname', 'zip')
|
||||
->having(DB::raw('COUNT(*)'), '>', 1)
|
||||
->pluck('ids')
|
||||
->map(fn ($ids) => explode(',', $ids))
|
||||
->all();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Verarbeitung
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private function processLevel(string $level, callable $finder): void
|
||||
{
|
||||
if ($filter = $this->option('confidence')) {
|
||||
if (strtoupper($filter) !== $level) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$groups = $finder();
|
||||
|
||||
if (empty($groups)) {
|
||||
$this->line("[{$level}] Keine Duplikate.");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->line(sprintf('[%s] %d Gruppe(n) gefunden', $level, count($groups)));
|
||||
|
||||
foreach ($groups as $ids) {
|
||||
$masterId = (int) $ids[0]; // erster = neuester
|
||||
$duplicateIds = array_map('intval', array_slice($ids, 1));
|
||||
|
||||
$this->line(sprintf(
|
||||
' Master: #%d ← Duplikate: %s',
|
||||
$masterId,
|
||||
implode(', ', array_map(fn ($id) => '#' . $id, $duplicateIds))
|
||||
));
|
||||
|
||||
foreach ($duplicateIds as $dupeId) {
|
||||
$this->mergeInto($masterId, $dupeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeInto(int $masterId, int $dupeId): void
|
||||
{
|
||||
// 1. Leads umhängen
|
||||
$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('inquiries')
|
||||
->where('customer_id', $dupeId)
|
||||
->update(['customer_id' => $masterId]);
|
||||
}
|
||||
$this->updatedLeads += $leadCount;
|
||||
}
|
||||
|
||||
// 2. Bookings umhängen
|
||||
$bookingCount = DB::table('booking')->where('customer_id', $dupeId)->count();
|
||||
if ($bookingCount > 0) {
|
||||
$this->line(" booking.customer_id: {$bookingCount} Zeile(n) → #{$masterId}");
|
||||
if (!$this->dryRun) {
|
||||
DB::table('booking')
|
||||
->where('customer_id', $dupeId)
|
||||
->update(['customer_id' => $masterId]);
|
||||
}
|
||||
$this->updatedBookings += $bookingCount;
|
||||
}
|
||||
|
||||
// 3. customer_mails umhängen
|
||||
$mailCount = DB::table('customer_mails')->where('customer_id', $dupeId)->count();
|
||||
if ($mailCount > 0) {
|
||||
$this->line(" customer_mails.customer_id: {$mailCount} Zeile(n) → #{$masterId}");
|
||||
if (!$this->dryRun) {
|
||||
DB::table('customer_mails')
|
||||
->where('customer_id', $dupeId)
|
||||
->update(['customer_id' => $masterId]);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. lead_mails umhängen
|
||||
$leadMailCount = DB::table('lead_mails')->where('customer_id', $dupeId)->count();
|
||||
if ($leadMailCount > 0) {
|
||||
$this->line(" lead_mails.customer_id: {$leadMailCount} Zeile(n) → #{$masterId}");
|
||||
if (!$this->dryRun) {
|
||||
DB::table('lead_mails')
|
||||
->where('customer_id', $dupeId)
|
||||
->update(['customer_id' => $masterId]);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Duplikat als zusammengeführt markieren
|
||||
if (!$this->dryRun) {
|
||||
DB::table('contacts')
|
||||
->where('id', $dupeId)
|
||||
->update([
|
||||
'merged_into_id' => $masterId,
|
||||
'merged_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->mergedCount++;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,14 @@ class Kernel extends ConsoleKernel
|
|||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// $schedule->command('inspire')
|
||||
// ->hourly();
|
||||
// Duplikate täglich um 02:00 Uhr automatisch zusammenführen (nur HIGH-Konfidenz = exakte E-Mail-Treffer)
|
||||
$schedule->command('contacts:merge-duplicates --confidence=HIGH --force')
|
||||
->dailyAt('02:00')
|
||||
->withoutOverlapping()
|
||||
->runInBackground()
|
||||
->appendOutputTo(storage_path('logs/contacts-merge.log'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -35,7 +39,7 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,42 +8,37 @@ use App\Services\BookingImport;
|
|||
|
||||
class BookingController extends Controller
|
||||
{
|
||||
private $successStatus = 200;
|
||||
private $successKey = 'f6077389c9ce710e554763a5de02c8ec';
|
||||
|
||||
protected $draftRepo;
|
||||
private int $successStatus = 200;
|
||||
private string $successKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
|
||||
$this->successKey = config('app.success_key');
|
||||
}
|
||||
|
||||
public function import()
|
||||
{
|
||||
|
||||
$request = \Request::all();
|
||||
if(!isset($request['key']) || $request['key'] !== $this->successKey){
|
||||
if (!isset($request['key']) || $request['key'] !== $this->successKey) {
|
||||
return response()->json(['error' => "key"], 401);
|
||||
}
|
||||
$travel_booking = TravelBooking::find($request['travel_booking_id']);
|
||||
|
||||
//# vor testing
|
||||
//$travel_booking = TravelBooking::find(2922);
|
||||
if(!isset($travel_booking) || !$travel_booking){
|
||||
if (!$travel_booking) {
|
||||
return response()->json(['error' => 'no-booking-found'], $this->successStatus);
|
||||
}
|
||||
|
||||
$booking = BookingImport::importFrom($travel_booking);
|
||||
|
||||
$ret= [
|
||||
'url_v1' => make_old_url('/index.php/booking/'.$booking->id.'/edit'),
|
||||
$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);
|
||||
//return response()->json(['error' => 'no-node'], $this->successStatus);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 : "",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() : "",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Request;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Booking;
|
||||
use App\Models\BookingFile;
|
||||
use App\Models\Participant;
|
||||
|
|
@ -60,11 +60,9 @@ class BookingController extends Controller
|
|||
return view('booking.detail', $data);
|
||||
}
|
||||
|
||||
public function store($id)
|
||||
public function store(Request $request, $id)
|
||||
{
|
||||
// \Session()->flash('alert-save', '1');
|
||||
|
||||
$data = Request::all();
|
||||
$data = $request->all();
|
||||
|
||||
if ($id === "new") {
|
||||
$booking = new Booking();
|
||||
|
|
@ -308,11 +306,11 @@ class BookingController extends Controller
|
|||
return $redirect;
|
||||
}
|
||||
|
||||
public function loadModal()
|
||||
public function loadModal(Request $request)
|
||||
{
|
||||
$data = Request::all();
|
||||
$data = $request->all();
|
||||
$ret = "";
|
||||
if (Request::ajax()) {
|
||||
if ($request->ajax()) {
|
||||
if ($data['action'] === "new-customer-mail" || $data['action'] === "reply-customer-mail" || $data['action'] === "show-customer-mail" || $data['action'] === "edit-customer-mail") {
|
||||
$data['customers'] = [];
|
||||
if ($data['action'] === "new-customer-mail" && isset($data['booking_id']) && $booking = Booking::find($data['booking_id'])) {
|
||||
|
|
@ -374,7 +372,7 @@ class BookingController extends Controller
|
|||
$bookingFileRepo->_set('booking_id', $data['booking_id']);
|
||||
$bookingFileRepo->_set('dir', '/files/' . date('Y/m') . '/');
|
||||
$bookingFileRepo->_set('identifier', 'booking');
|
||||
return $bookingFileRepo->uploadFile(Request::all());
|
||||
return $bookingFileRepo->uploadFile($request->all());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -391,7 +389,7 @@ class BookingController extends Controller
|
|||
return redirect(route('booking_detail', [$booking->id]) . "#collapseBookingOrganisation");
|
||||
}
|
||||
|
||||
public function action($action, $id = false)
|
||||
public function action(Request $request, $action, $id = false)
|
||||
{
|
||||
|
||||
if (!$booking = Booking::find($id)) {
|
||||
|
|
@ -400,7 +398,7 @@ class BookingController extends Controller
|
|||
|
||||
if ($action === 'change_travel_dates') {
|
||||
$draftRepo = new DraftRepository($booking);
|
||||
$draftRepo->change_dates_drafts_from_booking(Request::get('change_travel_start_date'));
|
||||
$draftRepo->change_dates_drafts_from_booking($request->get('change_travel_start_date'));
|
||||
\Session()->flash('alert-success', __('Datum der Reise wurde geändert'));
|
||||
return redirect(route('booking_detail', [$booking->id]) . "#collapseBookingOrganisation");
|
||||
}
|
||||
|
|
|
|||
312
app/Http/Controllers/ContactController.php
Normal file
312
app/Http/Controllers/ContactController.php
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Contact;
|
||||
use App\Repositories\ContactRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Yajra\DataTables\Facades\DataTables;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
public function __construct(private readonly ContactRepository $contactRepo)
|
||||
{
|
||||
$this->middleware(['admin', '2fa']);
|
||||
}
|
||||
|
||||
public function index(): \Illuminate\View\View
|
||||
{
|
||||
return view('contact.index');
|
||||
}
|
||||
|
||||
public function detail(string $id): \Illuminate\View\View
|
||||
{
|
||||
if ($id === 'new') {
|
||||
$contact = new Contact();
|
||||
} else {
|
||||
$contact = Contact::with(['salutation', 'leads', 'bookings', 'mergedContacts'])
|
||||
->findOrFail((int) $id);
|
||||
}
|
||||
|
||||
return view('contact.detail', [
|
||||
'contact' => $contact,
|
||||
'id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, string $id): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$data = $request->except('_token');
|
||||
|
||||
if (!isset($data['action'])) {
|
||||
abort(403, 'keine Action');
|
||||
}
|
||||
|
||||
if ($id === 'new') {
|
||||
$contact = $this->contactRepo->createContact($data);
|
||||
\Session()->flash('alert-save', '1');
|
||||
return redirect(route('contact_detail', [$contact->id]) . '#collapseContactDetail');
|
||||
}
|
||||
|
||||
$this->contactRepo->updateContact($id, $data);
|
||||
\Session()->flash('alert-save', '1');
|
||||
|
||||
return redirect(route('contact_detail', [$id]) . '#collapseContactDetail');
|
||||
}
|
||||
|
||||
public function history(int $id): \Illuminate\View\View
|
||||
{
|
||||
$contact = Contact::with([
|
||||
'leads.sf_guard_user',
|
||||
'bookings.travel_country',
|
||||
'bookings.travel_agenda',
|
||||
'bookings.sf_guard_user',
|
||||
'bookings.lead',
|
||||
])->findOrFail($id);
|
||||
|
||||
return view('contact._detail_history', [
|
||||
'contact' => $contact,
|
||||
'modal' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $id): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$contact = Contact::withoutGlobalScope('not_merged')->findOrFail($id);
|
||||
|
||||
$leadsCount = $contact->leads()->count();
|
||||
$bookingCount = $contact->bookings()->count();
|
||||
|
||||
if ($leadsCount > 0 || $bookingCount > 0) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => sprintf(
|
||||
'Kontakt kann nicht gelöscht werden: %s%s vorhanden.',
|
||||
$leadsCount > 0 ? $leadsCount . ' Anfrage(n)' : '',
|
||||
$bookingCount > 0 ? ($leadsCount > 0 ? ' und ' : '') . $bookingCount . ' Buchung(en)' : ''
|
||||
),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$contact->delete();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function duplicates(): \Illuminate\View\View
|
||||
{
|
||||
$counts = [
|
||||
'HIGH' => $this->countDuplicateGroups('email'),
|
||||
'MEDIUM' => $this->countDuplicateGroups('name_birthdate'),
|
||||
'LOW' => $this->countDuplicateGroups('name_zip'),
|
||||
];
|
||||
|
||||
return view('contact.duplicates', compact('counts'));
|
||||
}
|
||||
|
||||
public function getDuplicateGroups(Request $request): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$confidence = strtoupper($request->input('confidence', 'HIGH'));
|
||||
|
||||
$groups = match ($confidence) {
|
||||
'HIGH' => $this->findByEmail(),
|
||||
'MEDIUM' => $this->findByNameBirthdate(),
|
||||
'LOW' => $this->findByNameZip(),
|
||||
default => [],
|
||||
};
|
||||
|
||||
// Für jede Gruppe die vollständigen Kontakt-Daten laden
|
||||
$result = [];
|
||||
foreach ($groups as $ids) {
|
||||
$contacts = Contact::withoutGlobalScopes()
|
||||
->withCount(['leads', 'bookings'])
|
||||
->whereIn('id', $ids)
|
||||
->whereNull('merged_into_id')
|
||||
->whereNull('deleted_at')
|
||||
->orderByRaw('FIELD(id, ' . implode(',', $ids) . ')')
|
||||
->get(['id', 'firstname', 'name', 'email', 'zip', 'city', 'phone', 'phonemobile', 'birthdate', 'created_at', 'updated_at']);
|
||||
|
||||
if ($contacts->count() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'master' => $contacts->first(),
|
||||
'duplicates' => $contacts->skip(1)->values(),
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
public function merge(Request $request): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$masterId = (int) $request->input('master_id');
|
||||
$dupeId = (int) $request->input('duplicate_id');
|
||||
|
||||
if (!$masterId || !$dupeId || $masterId === $dupeId) {
|
||||
return response()->json(['success' => false, 'message' => 'Ungültige IDs.'], 422);
|
||||
}
|
||||
|
||||
$master = Contact::withoutGlobalScopes()->find($masterId);
|
||||
$dupe = Contact::withoutGlobalScopes()->find($dupeId);
|
||||
|
||||
if (!$master || !$dupe) {
|
||||
return response()->json(['success' => false, 'message' => 'Kontakt nicht gefunden.'], 404);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($masterId, $dupeId) {
|
||||
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('contacts')->where('id', $dupeId)->update([
|
||||
'merged_into_id' => $masterId,
|
||||
'merged_at' => now(),
|
||||
]);
|
||||
});
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
// ── Duplikat-Hilfs-Queries ────────────────────────────────────────────────
|
||||
|
||||
private function countDuplicateGroups(string $type): int
|
||||
{
|
||||
return match ($type) {
|
||||
'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('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')
|
||||
->groupBy('email')->havingRaw('COUNT(*) > 1')
|
||||
->pluck('ids')->map(fn ($s) => array_map('intval', explode(',', $s)))->all();
|
||||
}
|
||||
|
||||
private function findByNameBirthdate(): array
|
||||
{
|
||||
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')
|
||||
->groupBy('name', 'firstname', 'birthdate')->havingRaw('COUNT(*) > 1')
|
||||
->pluck('ids')->map(fn ($s) => array_map('intval', explode(',', $s)))->all();
|
||||
}
|
||||
|
||||
private function findByNameZip(): array
|
||||
{
|
||||
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', '!=', '')
|
||||
->whereNull('merged_into_id')->whereNull('deleted_at')
|
||||
->groupBy('name', 'firstname', 'zip')->havingRaw('COUNT(*) > 1')
|
||||
->pluck('ids')->map(fn ($s) => array_map('intval', explode(',', $s)))->all();
|
||||
}
|
||||
|
||||
public function restore(int $id): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$contact = Contact::onlyTrashed()->withoutGlobalScope('not_merged')->findOrFail($id);
|
||||
$contact->restore();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
public function getContacts(Request $request): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$showDeleted = $request->filled('filter_deleted');
|
||||
|
||||
// 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('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')) {
|
||||
$query->has('leads');
|
||||
}
|
||||
if ($request->filled('filter_has_bookings')) {
|
||||
$query->has('bookings');
|
||||
}
|
||||
if ($request->filled('filter_has_email')) {
|
||||
$query->whereNotNull('email')->where('email', '!=', '');
|
||||
}
|
||||
|
||||
return DataTables::eloquent($query)
|
||||
->addColumn('action_edit', function (Contact $contact) use ($showDeleted) {
|
||||
if ($showDeleted) {
|
||||
return '';
|
||||
}
|
||||
return '<a href="' . route('contact_detail', [$contact->id]) . '" class="btn icon-btn btn-sm btn-primary" title="Öffnen"><span class="fa fa-edit"></span></a>';
|
||||
})
|
||||
->addColumn('action_delete', function (Contact $contact) use ($showDeleted) {
|
||||
if ($showDeleted) {
|
||||
return '<button class="btn icon-btn btn-xs btn-success ml-1 btn-contact-restore" '
|
||||
. 'data-id="' . $contact->id . '" '
|
||||
. 'data-name="' . e($contact->fullName()) . '" '
|
||||
. 'title="Wiederherstellen"><span class="fa fa-undo"></span></button>';
|
||||
}
|
||||
return '<button class="btn icon-btn btn-xs btn-danger ml-1 btn-contact-delete" '
|
||||
. 'data-id="' . $contact->id . '" '
|
||||
. 'data-name="' . e($contact->fullName()) . '" '
|
||||
. 'title="Löschen"><span class="fa fa-trash"></span></button>';
|
||||
})
|
||||
->addColumn('id', function (Contact $contact) use ($showDeleted) {
|
||||
if ($showDeleted) {
|
||||
return '<span data-order="' . $contact->id . '">' . $contact->id . '</span>';
|
||||
}
|
||||
return '<a data-order="' . $contact->id . '" href="' . route('contact_detail', [$contact->id]) . '">' . $contact->id . '</a>';
|
||||
})
|
||||
->addColumn('raw_id', fn(Contact $contact) => $contact->id)
|
||||
->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) {
|
||||
if ($keyword !== '') {
|
||||
$query->where('contacts.id', 'LIKE', '%' . $keyword . '%');
|
||||
}
|
||||
})
|
||||
->filterColumn('name', function ($query, $keyword) {
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$q->where('name', 'LIKE', '%' . $keyword . '%')
|
||||
->orWhere('firstname', 'LIKE', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
})
|
||||
->filter(function ($query) use ($request) {
|
||||
$location = $request->input('filter_location');
|
||||
if ($location && $location !== '') {
|
||||
$query->where(function ($q) use ($location) {
|
||||
$q->where('zip', 'LIKE', '%' . $location . '%')
|
||||
->orWhere('city', 'LIKE', '%' . $location . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$search = $request->input('search.value');
|
||||
if ($search && $search !== '') {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'LIKE', '%' . $search . '%')
|
||||
->orWhere('firstname', 'LIKE', '%' . $search . '%')
|
||||
->orWhere('email', 'LIKE', '%' . $search . '%')
|
||||
->orWhere('phone', 'LIKE', '%' . $search . '%')
|
||||
->orWhere('phonemobile', 'LIKE', '%' . $search . '%');
|
||||
});
|
||||
}
|
||||
}, true)
|
||||
->rawColumns(['action_edit', 'action_delete', 'id'])
|
||||
->make(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -14,15 +14,6 @@ use Request;
|
|||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the application dashboard.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Kernel extends HttpKernel
|
|||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
|
|
@ -39,19 +39,17 @@ class Kernel extends HttpKernel
|
|||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
'bindings',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'throttle:api',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
* The application's route middleware aliases.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'2fa' => \App\Http\Middleware\MiddleGoogle2FA::class,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
use Fideloper\Proxy\TrustProxies as Middleware;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
|
|
@ -19,5 +19,11 @@ class TrustProxies extends Middleware
|
|||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
Request::HEADER_X_FORWARDED_PREFIX |
|
||||
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class CreatePDF{
|
|||
{
|
||||
$this->view = $view;
|
||||
$this->disk = $disk;
|
||||
$this->prepath = Storage::disk($disk)->getAdapter()->getPathPrefix();
|
||||
$this->prepath = Storage::disk($disk)->path('');
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ class Account extends Model
|
|||
|
||||
|
||||
use SoftDeletes;
|
||||
protected $dates = ['deleted_at'];
|
||||
protected $casts = [
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
|
||||
|
||||
public function user()
|
||||
|
|
|
|||
|
|
@ -55,14 +55,12 @@ class Arrangement extends Model
|
|||
'view_position' => 'int',
|
||||
'booking_id' => 'int',
|
||||
'type_id' => 'int',
|
||||
'in_pdf' => 'bool'
|
||||
'in_pdf' => 'bool',
|
||||
'state' => 'datetime',
|
||||
'begin' => 'datetime',
|
||||
'end' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'state',
|
||||
'begin',
|
||||
'end'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'template_id',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace App\Models;
|
|||
use Carbon\Carbon;
|
||||
use App\Services\Util;
|
||||
use App\Services\Passolution;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
|
|
@ -18,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
|
||||
|
|
@ -203,13 +204,16 @@ use Illuminate\Database\Eloquent\Collection;
|
|||
*/
|
||||
class Booking extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $connection = 'mysql';
|
||||
|
||||
protected $table = 'booking';
|
||||
|
||||
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',
|
||||
|
|
@ -237,25 +241,24 @@ class Booking extends Model
|
|||
'is_rail_fly' => 'bool',
|
||||
'comfort' => 'bool',
|
||||
'airline_ids' => 'array',
|
||||
'participant_pass' => 'bool'
|
||||
'participant_pass' => 'bool',
|
||||
'booking_date' => 'datetime',
|
||||
'start_date' => 'datetime',
|
||||
'end_date' => 'datetime',
|
||||
'participant_birthdate' => 'datetime',
|
||||
'final_payment_date' => 'datetime',
|
||||
'refund_date' => 'datetime',
|
||||
'lawyer_date' => 'datetime',
|
||||
'xx_tkt_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'booking_date',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'participant_birthdate',
|
||||
'final_payment_date',
|
||||
'refund_date',
|
||||
'lawyer_date',
|
||||
'xx_tkt_date'
|
||||
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_date',
|
||||
'customer_id',
|
||||
'lead_id',
|
||||
'inquiry_id',
|
||||
'offer_id',
|
||||
'new_drafts',
|
||||
'sf_guard_user_id',
|
||||
'branch_id',
|
||||
|
|
@ -392,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()
|
||||
|
|
|
|||
|
|
@ -48,12 +48,10 @@ class BookingConfirmation extends Model
|
|||
'total' => 'float',
|
||||
'deposit' => 'float',
|
||||
'final_payment' => 'float',
|
||||
'deposit_payment_date' => 'datetime',
|
||||
'final_payment_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'deposit_payment_date',
|
||||
'final_payment_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -71,11 +71,9 @@ class BookingDocument extends Model
|
|||
'status' => 'int',
|
||||
'booking_storno_id' => 'int',
|
||||
'coupon_id' => 'int',
|
||||
'date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -48,12 +48,10 @@ class BookingInvoice extends Model
|
|||
'total' => 'float',
|
||||
'deposit' => 'float',
|
||||
'final_payment' => 'float',
|
||||
'deposit_payment_date' => 'datetime',
|
||||
'final_payment_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'deposit_payment_date',
|
||||
'final_payment_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -52,12 +52,10 @@ class BookingNotice extends Model
|
|||
'from_user_id' => 'int',
|
||||
'to_user_id' => 'int',
|
||||
'show' => 'bool',
|
||||
'important' => 'bool'
|
||||
'important' => 'bool',
|
||||
'edit_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'edit_at',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -55,12 +55,10 @@ class BookingServiceItem extends Model
|
|||
'service_price' => 'float',
|
||||
'service_price_refund' => 'float',
|
||||
'commission' => 'float',
|
||||
'is_commission_locked' => 'bool'
|
||||
'is_commission_locked' => 'bool',
|
||||
'travel_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'travel_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -49,13 +49,11 @@ class BookingStorno extends Model
|
|||
'booking_id' => 'int',
|
||||
'total' => 'float',
|
||||
'storno' => 'float',
|
||||
'done' => 'bool'
|
||||
'done' => 'bool',
|
||||
'storno_date' => 'datetime',
|
||||
'storno_print' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'storno_date',
|
||||
'storno_print'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
164
app/Models/Contact.php
Normal file
164
app/Models/Contact.php
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Sym\TravelCountry;
|
||||
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\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Kontakt-Modell — saubere Neuimplementierung auf Basis der customer-Tabelle.
|
||||
*
|
||||
* Unterschiede zum alten Customer-Modell:
|
||||
* - Global Scope schließt zusammengeführte Duplikate (merged_into_id IS NOT NULL) aus
|
||||
* - merged_into_id + merged_at in $fillable
|
||||
* - mergedInto() / mergedContacts() Beziehungen
|
||||
*
|
||||
* Tabellen-Name: 'contacts' (nach Phase 2 — RENAME TABLE customer → contacts).
|
||||
*
|
||||
* @property int $id
|
||||
* @property int|null $salutation_id
|
||||
* @property string|null $title
|
||||
* @property string|null $name
|
||||
* @property string|null $firstname
|
||||
* @property Carbon|null $birthdate
|
||||
* @property string|null $company
|
||||
* @property string|null $street
|
||||
* @property string|null $zip
|
||||
* @property string|null $city
|
||||
* @property string|null $email
|
||||
* @property string|null $phone
|
||||
* @property string|null $phonebusiness
|
||||
* @property string|null $phonemobile
|
||||
* @property string|null $fax
|
||||
* @property int|null $merged_into_id
|
||||
* @property Carbon|null $merged_at
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property-read Contact|null $mergedInto
|
||||
* @property-read Collection|Contact[] $mergedContacts
|
||||
* @property-read Collection|Lead[] $leads
|
||||
* @property-read Collection|Booking[] $bookings
|
||||
*/
|
||||
class Contact extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $connection = 'mysql';
|
||||
|
||||
protected $table = 'contacts';
|
||||
|
||||
protected $casts = [
|
||||
'salutation_id' => 'int',
|
||||
'credit_card_type_id' => 'int',
|
||||
'country_id' => 'int',
|
||||
'merged_into_id' => 'int',
|
||||
'birthdate' => 'datetime',
|
||||
'credit_card_expiration_date' => 'datetime',
|
||||
'merged_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'salutation_id',
|
||||
'title',
|
||||
'name',
|
||||
'firstname',
|
||||
'birthdate',
|
||||
'company',
|
||||
'street',
|
||||
'zip',
|
||||
'city',
|
||||
'email',
|
||||
'phone',
|
||||
'phonebusiness',
|
||||
'phonemobile',
|
||||
'fax',
|
||||
'bank',
|
||||
'bank_code',
|
||||
'bank_account_number',
|
||||
'credit_card_type_id',
|
||||
'credit_card_number',
|
||||
'credit_card_expiration_date',
|
||||
'participants_remarks',
|
||||
'miscellaneous_remarks',
|
||||
'country_id',
|
||||
'merged_into_id',
|
||||
'merged_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Globaler Scope: zusammengeführte Duplikate werden standardmäßig ausgeblendet.
|
||||
* Für Zugriff auf alle inkl. Duplikate: Contact::withoutGlobalScope('not_merged')
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::addGlobalScope('not_merged', function (Builder $query) {
|
||||
$query->whereNull('merged_into_id');
|
||||
});
|
||||
}
|
||||
|
||||
// ── Beziehungen ──────────────────────────────────────────────────────────
|
||||
|
||||
public function mergedInto(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Contact::class, 'merged_into_id')
|
||||
->withoutGlobalScope('not_merged');
|
||||
}
|
||||
|
||||
public function mergedContacts(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Contact::class, 'merged_into_id')
|
||||
->withoutGlobalScope('not_merged');
|
||||
}
|
||||
|
||||
public function leads(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Lead::class, 'customer_id')->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function bookings(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Booking::class, 'customer_id')->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
public function salutation(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Salutation::class);
|
||||
}
|
||||
|
||||
public function travel_country(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(TravelCountry::class, 'country_id');
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ────────────────────────────────────────────────────────
|
||||
|
||||
public function fullName(): string
|
||||
{
|
||||
if ($this->firstname) {
|
||||
return $this->firstname . ' ' . $this->name;
|
||||
}
|
||||
return (string) $this->name;
|
||||
}
|
||||
|
||||
public function isMerged(): bool
|
||||
{
|
||||
return $this->merged_into_id !== null;
|
||||
}
|
||||
|
||||
public static function getCountriesArray(): \Illuminate\Support\Collection
|
||||
{
|
||||
return TravelCountry::where('is_customer_country', 1)->get()->pluck('name', 'id');
|
||||
}
|
||||
|
||||
public static $salutationType = [
|
||||
1 => 'Herr',
|
||||
2 => 'Frau',
|
||||
3 => 'Divers/keine Anrede',
|
||||
4 => 'Firma',
|
||||
];
|
||||
}
|
||||
|
|
@ -59,14 +59,12 @@ class Coupon extends Model
|
|||
'customer_id' => 'int',
|
||||
'booking_id' => 'int',
|
||||
'value' => 'float',
|
||||
'is_redeemed' => 'bool'
|
||||
'is_redeemed' => 'bool',
|
||||
'issue_date' => 'datetime',
|
||||
'valid_date' => 'datetime',
|
||||
'redeem_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'issue_date',
|
||||
'valid_date',
|
||||
'redeem_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'number',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace App\Models;
|
|||
use App\Models\Sym\TravelCountry;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
|
|
@ -83,20 +84,26 @@ use Illuminate\Database\Eloquent\Model;
|
|||
*/
|
||||
class Customer extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
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',
|
||||
'credit_card_type_id' => 'int',
|
||||
'country_id' => 'int'
|
||||
'country_id' => 'int',
|
||||
'birthdate' => 'datetime',
|
||||
'credit_card_expiration_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'birthdate',
|
||||
'credit_card_expiration_date'
|
||||
];
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'salutation_id',
|
||||
|
|
|
|||
|
|
@ -60,10 +60,11 @@ class CustomerFewoFile extends Model
|
|||
protected $casts = [
|
||||
'travel_user_id' => 'int',
|
||||
'customer_fewo_mail_id' => 'int',
|
||||
'size' => 'int'
|
||||
'size' => 'int',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
|
||||
protected $fillable = [
|
||||
'travel_user_id',
|
||||
|
|
|
|||
|
|
@ -106,15 +106,13 @@ class CustomerFewoMail extends Model
|
|||
'recipient' => 'array',
|
||||
'forward' => 'array',
|
||||
'cc' => 'array',
|
||||
'bcc' => 'array'
|
||||
'bcc' => 'array',
|
||||
'sent_at' => 'datetime',
|
||||
'scheduled_at' => 'datetime',
|
||||
'delivered_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'sent_at',
|
||||
'scheduled_at',
|
||||
'delivered_at',
|
||||
'deleted_at'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'travel_user_booking_fewo_id',
|
||||
|
|
|
|||
|
|
@ -109,14 +109,12 @@ class CustomerMail extends Model
|
|||
'recipient' => 'array',
|
||||
'forward' => 'array',
|
||||
'cc' => 'array',
|
||||
'bcc' => 'array'
|
||||
'bcc' => 'array',
|
||||
'sent_at' => 'datetime',
|
||||
'scheduled_at' => 'datetime',
|
||||
'delivered_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'sent_at',
|
||||
'scheduled_at',
|
||||
'delivered_at'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -41,13 +41,11 @@ class FewoReservation extends Model
|
|||
protected $casts = [
|
||||
'lodging_id' => 'int',
|
||||
'status' => 'int',
|
||||
'type' => 'int'
|
||||
'type' => 'int',
|
||||
'from_date' => 'datetime',
|
||||
'to_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'from_date',
|
||||
'to_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'lodging_id',
|
||||
|
|
|
|||
|
|
@ -43,13 +43,11 @@ class FewoSeason extends Model
|
|||
|
||||
protected $casts = [
|
||||
'minimum_stay' => 'int',
|
||||
'only_weekday' => 'int'
|
||||
'only_weekday' => 'int',
|
||||
'from_date' => 'datetime',
|
||||
'to_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'from_date',
|
||||
'to_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class IQContentTree extends Model
|
|||
|
||||
protected $connection = 'mysql_stern';
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
|
||||
protected $table = 'i_q_content_trees';
|
||||
|
||||
|
|
@ -65,7 +65,9 @@ class IQContentTree extends Model
|
|||
'name', 'identifier', 'description', 'settings', 'pos', 'active',
|
||||
];
|
||||
|
||||
protected $casts = ['settings' => 'array'];
|
||||
protected $casts = ['settings' => 'array',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function sluggable(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class IQContentTreeNode extends Model
|
|||
|
||||
protected $connection = 'mysql_stern';
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
|
||||
|
||||
protected $table = 'i_q_content_tree_nodes';
|
||||
|
||||
|
|
@ -80,7 +80,9 @@ class IQContentTreeNode extends Model
|
|||
'tree_id', 'parent_id', 'lvl', 'name', 'identifier', 'title', 'description', 'settings', 'image', 'pos', 'active',
|
||||
];
|
||||
|
||||
protected $casts = ['settings' => 'array', 'image' => 'array'];
|
||||
protected $casts = ['settings' => 'array', 'image' => 'array',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function sluggable(): array
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,13 +53,11 @@ class Inquiry extends Model
|
|||
'template_id' => 'int',
|
||||
'in_pdf' => 'bool',
|
||||
'type_id' => 'int',
|
||||
'view_position' => 'int'
|
||||
'view_position' => 'int',
|
||||
'begin' => 'datetime',
|
||||
'end' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'begin',
|
||||
'end'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'lead_id',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace App\Models;
|
|||
use Carbon\Carbon;
|
||||
use App\Services\Passolution;
|
||||
use App\Models\Lead as ModelsLead;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
|
|
@ -107,9 +108,16 @@ use Illuminate\Database\Eloquent\Collection;
|
|||
*/
|
||||
class Lead extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
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',
|
||||
|
|
@ -126,16 +134,14 @@ class Lead extends Model
|
|||
'travelcategory_id' => 'int',
|
||||
'price' => 'float',
|
||||
'pax' => 'int',
|
||||
'participant_salutation_id' => 'int'
|
||||
'participant_salutation_id' => 'int',
|
||||
'request_date' => 'datetime',
|
||||
'travelperiod_start' => 'datetime',
|
||||
'travelperiod_end' => 'datetime',
|
||||
'next_due_date' => 'datetime',
|
||||
'participant_birthdate' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'request_date',
|
||||
'travelperiod_start',
|
||||
'travelperiod_end',
|
||||
'next_due_date',
|
||||
'participant_birthdate'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
|
|
|
|||
|
|
@ -91,14 +91,12 @@ class LeadMail extends Model
|
|||
'recipient' => 'array',
|
||||
'forward' => 'array',
|
||||
'cc' => 'array',
|
||||
'bcc' => 'array'
|
||||
'bcc' => 'array',
|
||||
'sent_at' => 'datetime',
|
||||
'scheduled_at' => 'datetime',
|
||||
'delivered_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'sent_at',
|
||||
'scheduled_at',
|
||||
'delivered_at'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'lead_id',
|
||||
|
|
|
|||
|
|
@ -53,12 +53,10 @@ class LeadNotice extends Model
|
|||
'from_user_id' => 'int',
|
||||
'to_user_id' => 'int',
|
||||
'show' => 'bool',
|
||||
'important' => 'bool'
|
||||
'important' => 'bool',
|
||||
'edit_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'edit_at'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'lead_id',
|
||||
|
|
|
|||
|
|
@ -46,12 +46,10 @@ class LeadParticipant extends Model
|
|||
|
||||
protected $casts = [
|
||||
'lead_id' => 'int',
|
||||
'participant_salutation_id' => 'int'
|
||||
'participant_salutation_id' => 'int',
|
||||
'participant_birthdate' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'participant_birthdate'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'lead_id',
|
||||
|
|
|
|||
|
|
@ -36,6 +36,49 @@ use Illuminate\Database\Eloquent\Collection;
|
|||
* @property-read TravelUser|null $travel_user
|
||||
* @property-read Collection|NewsletterLog[] $logs
|
||||
* @package App\Models
|
||||
* @property-read mixed $full_name
|
||||
* @property-read mixed $groups
|
||||
* @property-read mixed $groups_string
|
||||
* @property-read mixed $source_label
|
||||
* @property-read mixed $status_badge
|
||||
* @property-read mixed $status_color
|
||||
* @property-read mixed $status_label
|
||||
* @property-read mixed $total_bookings
|
||||
* @property-read int|null $logs_count
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact active()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact ferienwohnungen()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact kulturreisen()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact multipleBookers()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact onlyTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereCustomerId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereDeletedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereEmail($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereFirstname($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereGroupFerienwohnungen($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereGroupKulturreisen($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereLastBookingAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereLastSyncedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereLastTravelEndDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereLastname($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereNotes($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereSource($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereSubscribedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereSyncHash($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereTotalBookingsFerienwohnungen($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereTotalBookingsKulturreisen($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereTravelUserId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereUnsubscribedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact withBookings()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact withTrashed()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterContact withoutTrashed()
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class NewsletterContact extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,19 @@ use Illuminate\Database\Eloquent\Model;
|
|||
* @property-read NewsletterContact $newsletter_contact
|
||||
* @property-read SfGuardUser|null $user
|
||||
* @package App\Models
|
||||
* @property-read mixed $action_label
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereAction($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereMetadata($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereNewsletterContactId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|NewsletterLog whereUserId($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class NewsletterLog extends Model
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 $table = 'offers';
|
||||
|
||||
protected $fillable = [
|
||||
'lead_id',
|
||||
'total',
|
||||
'binary_data'
|
||||
'offer_number',
|
||||
'contact_id',
|
||||
'inquiry_id',
|
||||
'booking_id',
|
||||
'status',
|
||||
'current_version_id',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
public function lead()
|
||||
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(Lead::class);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -152,12 +152,10 @@ class Page extends Model
|
|||
'tree_root' => 'int',
|
||||
'parent_id' => 'int',
|
||||
'travel_guide_content_id' => 'int',
|
||||
'fewo_lodging' => 'int'
|
||||
'fewo_lodging' => 'int',
|
||||
'date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'owner',
|
||||
|
|
|
|||
|
|
@ -53,13 +53,10 @@ class Participant extends Model
|
|||
'participant_salutation_id' => 'int',
|
||||
'participant_child' => 'bool',
|
||||
'participant_pass' => 'bool',
|
||||
'participant_storno' => 'bool'
|
||||
|
||||
'participant_storno' => 'bool',
|
||||
'participant_birthdate' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'participant_birthdate'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -55,12 +55,10 @@ class ServiceProviderEntry extends Model
|
|||
'amount' => 'float',
|
||||
'amount_eur' => 'float',
|
||||
'factor' => 'float',
|
||||
'is_cleared' => 'bool'
|
||||
'is_cleared' => 'bool',
|
||||
'payment_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'payment_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'booking_id',
|
||||
|
|
|
|||
|
|
@ -47,14 +47,12 @@ class StatusHistory extends Model
|
|||
protected $casts = [
|
||||
'status_id' => 'int',
|
||||
'lead_id' => 'int',
|
||||
'sf_guard_user_id' => 'int'
|
||||
'sf_guard_user_id' => 'int',
|
||||
'date' => 'datetime',
|
||||
'target_date' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'date',
|
||||
'target_date',
|
||||
'created_at'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'status_id',
|
||||
|
|
|
|||
|
|
@ -137,15 +137,13 @@ class TravelBooking extends Model
|
|||
'options' => 'array',
|
||||
'class_options' => 'array',
|
||||
'extra_category' => 'array',
|
||||
'insurances' => 'array'
|
||||
'insurances' => 'array',
|
||||
'created' => 'datetime',
|
||||
'selected_start_date' => 'datetime',
|
||||
'selected_end_date' => 'datetime',
|
||||
'final_payment_date' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'created',
|
||||
'selected_start_date',
|
||||
'selected_end_date',
|
||||
'final_payment_date'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'crm_booking_id',
|
||||
|
|
|
|||
|
|
@ -79,12 +79,10 @@ class TravelUser extends Model
|
|||
protected $casts = [
|
||||
'salutation_id' => 'int',
|
||||
'travel_nationality_id' => 'int',
|
||||
'last_user_data' => 'array'
|
||||
'last_user_data' => 'array',
|
||||
'birthday' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'birthday'
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'password'
|
||||
|
|
|
|||
|
|
@ -159,14 +159,12 @@ class TravelUserBookingFewo extends Model
|
|||
'send_service_mail' => 'array',
|
||||
'send_info_mail' => 'array',
|
||||
'send_employee_mail' => 'array',
|
||||
'booking_date' => 'datetime',
|
||||
'from_date' => 'datetime',
|
||||
'to_date' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'booking_date',
|
||||
'from_date',
|
||||
'to_date',
|
||||
'deleted_at'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'travel_user_id',
|
||||
|
|
@ -545,7 +543,7 @@ class TravelUserBookingFewo extends Model
|
|||
if(!Storage::disk('fewo_invoices')->exists( $dir )){
|
||||
Storage::disk('fewo_invoices')->makeDirectory($dir); //creates directory
|
||||
}
|
||||
$path = Storage::disk('fewo_invoices')->getAdapter()->getPathPrefix();
|
||||
$path = Storage::disk('fewo_invoices')->path('');
|
||||
return $path.$dir;
|
||||
}
|
||||
|
||||
|
|
@ -621,7 +619,7 @@ class TravelUserBookingFewo extends Model
|
|||
if(!Storage::disk('fewo_infos')->exists( $dir )){
|
||||
Storage::disk('fewo_infos')->makeDirectory($dir); //creates directory
|
||||
}
|
||||
$path = Storage::disk('fewo_infos')->getAdapter()->getPathPrefix();
|
||||
$path = Storage::disk('fewo_infos')->path('');
|
||||
return $path.$dir;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,12 +53,10 @@ class TravelUserBookingFewoNotice extends Model
|
|||
'from_user_id' => 'int',
|
||||
'to_user_id' => 'int',
|
||||
'show' => 'bool',
|
||||
'important' => 'bool'
|
||||
'important' => 'bool',
|
||||
'edit_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
'edit_at'
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'travel_user_booking_fewo_id',
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Laravel\Passport\Passport;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -22,12 +21,8 @@ class AuthServiceProvider extends ServiceProvider
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerPolicies();
|
||||
Passport::routes();
|
||||
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,72 +2,47 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* This namespace is applied to your controller routes.
|
||||
*
|
||||
* In addition, it is set as the URL generator's root namespace.
|
||||
* The path to your application's "home" route.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const HOME = '/home';
|
||||
|
||||
/**
|
||||
* The controller namespace for the application.
|
||||
* Kept for backwards compatibility with string-based route definitions.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $namespace = 'App\Http\Controllers';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
*
|
||||
* @return void
|
||||
* Define your route model bindings, pattern filters, and other route configuration.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
|
||||
parent::boot();
|
||||
}
|
||||
$this->routes(function () {
|
||||
Route::middleware('api')
|
||||
->prefix('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api.php'));
|
||||
|
||||
/**
|
||||
* Define the routes for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function map()
|
||||
{
|
||||
$this->mapApiRoutes();
|
||||
|
||||
$this->mapWebRoutes();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "web" routes for the application.
|
||||
*
|
||||
* These routes all receive session state, CSRF protection, etc.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapWebRoutes()
|
||||
{
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "api" routes for the application.
|
||||
*
|
||||
* These routes are typically stateless.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapApiRoutes()
|
||||
{
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api.php'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class BookingPDFRepository extends BaseRepository
|
|||
public function __construct(Booking $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
$this->prepath = Storage::disk('public')->getAdapter()->getPathPrefix();
|
||||
$this->prepath = Storage::disk('public')->path('');
|
||||
}
|
||||
|
||||
public function update($data)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
28
app/Repositories/ContactRepository.php
Normal file
28
app/Repositories/ContactRepository.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Contact;
|
||||
|
||||
class ContactRepository extends BaseRepository
|
||||
{
|
||||
public function __construct(Contact $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function updateContact(int|string $id, array $data): Contact
|
||||
{
|
||||
/** @var Contact $contact */
|
||||
$contact = Contact::findOrFail($id);
|
||||
$contact->fill($data);
|
||||
$contact->save();
|
||||
|
||||
return $contact;
|
||||
}
|
||||
|
||||
public function createContact(array $data): Contact
|
||||
{
|
||||
return Contact::create($data);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,127 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Airline;
|
||||
use App\Models\Insurance;
|
||||
use App\Models\CMSContent;
|
||||
use App\Models\CustomerMail;
|
||||
use App\Models\TravelCompany;
|
||||
|
||||
class Booking
|
||||
{
|
||||
|
||||
private static $output_dirs = [];
|
||||
|
||||
public static function contentFiles(){
|
||||
$booking_email_files = CMSContent::where('identifier', '=', 'booking-email-file')->get()->sortByDesc('pos')->pluck('slug', 'id');
|
||||
return $booking_email_files;
|
||||
public static function contentFiles(): \Illuminate\Support\Collection
|
||||
{
|
||||
return CMSContent::where('identifier', '=', 'booking-email-file')
|
||||
->get()
|
||||
->sortByDesc('pos')
|
||||
->pluck('slug', 'id');
|
||||
}
|
||||
|
||||
public static function setOutputDirs($dir, $subdir){
|
||||
self::$output_dirs[$dir][] = $subdir;
|
||||
public static function setOutputDirs(string $dir, string $subdir): void
|
||||
{
|
||||
MailDirService::setOutputDir($dir, $subdir);
|
||||
}
|
||||
|
||||
public static function getMailDirNotInOutput($booking_id, $dir){
|
||||
$is_o_dirs = isset(self::$output_dirs[$dir]) ? self::$output_dirs[$dir] : [];
|
||||
$ret = [];
|
||||
$CustomerMails = CustomerMail::whereBookingId($booking_id)->whereDir($dir)->get();
|
||||
if($CustomerMails){
|
||||
foreach($CustomerMails as $CustomerMail){
|
||||
if(!in_array($CustomerMail->subdir, $is_o_dirs)){
|
||||
$ret[] = $CustomerMail->subdir;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getMailDirNotInOutput(int $bookingId, string $dir): array
|
||||
{
|
||||
$mails = CustomerMail::whereBookingId($bookingId)->whereDir($dir)->get();
|
||||
return MailDirService::getMailDirsNotInOutput($mails, $dir);
|
||||
}
|
||||
|
||||
public static function getCustomerMailDirs(){
|
||||
$customer_mail_dirs = CMSContent::where('identifier', '=', 'customer-mail-dirs')->get()->sortBy('pos');
|
||||
return $customer_mail_dirs;
|
||||
public static function getCustomerMailDirs(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return MailDirService::getCustomerMailDirs();
|
||||
}
|
||||
|
||||
|
||||
public static function getCustomerMailDir($id){
|
||||
return CMSContent::where('identifier', '=', 'customer-mail-dirs')->where('pos', '=', $id)->first();
|
||||
public static function getCustomerMailDir(int $id): ?\App\Models\CMSContent
|
||||
{
|
||||
return MailDirService::getCustomerMailDir($id);
|
||||
}
|
||||
|
||||
public static function getCustomerMailName($customer_mail_dir, $mail_dir_id){
|
||||
|
||||
switch ($customer_mail_dir->getArrayContent('model')){
|
||||
case 'TravelCountry':
|
||||
$model = \App\Models\Sym\TravelCountry::find($mail_dir_id);
|
||||
break;
|
||||
case 'Airline':
|
||||
$model = Airline::find($mail_dir_id);
|
||||
break;
|
||||
case 'Insurance':
|
||||
$model = Insurance::find($mail_dir_id);
|
||||
break;
|
||||
case 'TravelCompany':
|
||||
$model = TravelCompany::find($mail_dir_id);
|
||||
break;
|
||||
default:
|
||||
return '';
|
||||
public static function getCustomerMailName(\App\Models\CMSContent $mailDir, int $mailDirId): string
|
||||
{
|
||||
return MailDirService::getCustomerMailName($mailDir, $mailDirId);
|
||||
}
|
||||
|
||||
if($model){
|
||||
if($customer_mail_dir->getArrayContent('model') === 'TravelCountry'){
|
||||
return $model->mail_dir_name;
|
||||
}
|
||||
return $model->name;
|
||||
}
|
||||
return "";
|
||||
public static function getCustomerMailEmails(\App\Models\CMSContent $mailDir, int $mailDirId): array|string
|
||||
{
|
||||
return MailDirService::getCustomerMailEmails($mailDir, $mailDirId);
|
||||
}
|
||||
|
||||
public static function getCustomerMailEmails($customer_mail_dir, $mail_dir_id){
|
||||
|
||||
switch ($customer_mail_dir->getArrayContent('model')){
|
||||
case 'TravelCountry':
|
||||
$model = \App\Models\Sym\TravelCountry::find($mail_dir_id);
|
||||
break;
|
||||
case 'Airline':
|
||||
$model = Airline::find($mail_dir_id);
|
||||
break;
|
||||
case 'Insurance':
|
||||
$model = Insurance::find($mail_dir_id);
|
||||
break;
|
||||
case 'TravelCompany':
|
||||
$model = TravelCompany::find($mail_dir_id);
|
||||
break;
|
||||
default:
|
||||
//direkt from CMSContent
|
||||
return $customer_mail_dir->getArrayContent('emails');
|
||||
public static function getBookingInstructionPDFName(\App\Models\FewoLodging $fewo): string
|
||||
{
|
||||
return "HINWEISE-FERIENWOHNUNG-" . $fewo->pdf_name . ".pdf";
|
||||
}
|
||||
|
||||
if($model){
|
||||
return $model->contact_emails;
|
||||
}
|
||||
return [];
|
||||
public static function getBookingCMSContent(\App\Models\CMSContent $content, string $identifier): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return CMSContent::where('identifier', '=', $identifier)
|
||||
->where('integer', $content->id)
|
||||
->get()
|
||||
->sortBy('pos');
|
||||
}
|
||||
|
||||
public static function getBookingInstructionPDFName($fewo){
|
||||
return "HINWEISE-FERIENWOHNUNG-".$fewo->pdf_name.".pdf";
|
||||
public static function getBookingCMSContentForPDF(string $identifierContent, string $identifier): array
|
||||
{
|
||||
$pdfContent = [];
|
||||
$contents = CMSContent::where('identifier', '=', $identifierContent)->get()->sortBy('pos');
|
||||
|
||||
foreach ($contents as $content) {
|
||||
if ($content->decimal > 0) {
|
||||
$pdfContent[] = $content;
|
||||
}
|
||||
|
||||
public static function getBookingCMSContent($content, $identifier){
|
||||
return CMSContent::where('identifier', '=', $identifier)->where('integer', $content->id)->get()->sortBy('pos');
|
||||
foreach (self::getBookingCMSContent($content, $identifier) as $fewoContent) {
|
||||
if ($fewoContent->decimal > 0) {
|
||||
$pdfContent[] = $fewoContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getBookingCMSContentForPDF($identifier_content, $identifier){
|
||||
$pdf_content = [];
|
||||
$contents = CMSContent::where('identifier', '=', $identifier_content)->get()->sortBy('pos');
|
||||
foreach ($contents as $content){
|
||||
if($content->decimal > 0){ //in_pdf
|
||||
$pdf_content[] = $content;
|
||||
}
|
||||
if($fewo_contents = self::getBookingCMSContent($content, $identifier)){
|
||||
foreach ($fewo_contents as $fewo_content){
|
||||
if($fewo_content->decimal > 0){ //in_pdf
|
||||
$pdf_content[] = $fewo_content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pdf_content;
|
||||
return $pdfContent;
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,124 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Airline;
|
||||
use App\Models\CMSContent;
|
||||
use App\Models\Insurance;
|
||||
use App\Models\LeadMail;
|
||||
use App\Models\TravelCompany;
|
||||
|
||||
class Lead
|
||||
{
|
||||
private static $output_dirs = [];
|
||||
|
||||
public static function contentFiles(){
|
||||
$lead_files = CMSContent::where('identifier', '=', 'lead-email-file')->get()->sortByDesc('pos')->pluck('slug', 'id');
|
||||
return $lead_files;
|
||||
public static function contentFiles(): \Illuminate\Support\Collection
|
||||
{
|
||||
return CMSContent::where('identifier', '=', 'lead-email-file')
|
||||
->get()
|
||||
->sortByDesc('pos')
|
||||
->pluck('slug', 'id');
|
||||
}
|
||||
|
||||
public static function setOutputDirs($dir, $subdir){
|
||||
self::$output_dirs[$dir][] = $subdir;
|
||||
public static function setOutputDirs(string $dir, string $subdir): void
|
||||
{
|
||||
MailDirService::setOutputDir($dir, $subdir);
|
||||
}
|
||||
|
||||
public static function getMailDirNotInOutput($lead_id, $dir){
|
||||
$is_o_dirs = isset(self::$output_dirs[$dir]) ? self::$output_dirs[$dir] : [];
|
||||
$ret = [];
|
||||
$LeadMails = LeadMail::whereLeadId($lead_id)->whereDir($dir)->get();
|
||||
if($LeadMails){
|
||||
foreach($LeadMails as $LeadMail){
|
||||
if(!in_array($LeadMail->subdir, $is_o_dirs)){
|
||||
$ret[] = $LeadMail->subdir;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getMailDirNotInOutput(int $leadId, string $dir): array
|
||||
{
|
||||
$mails = LeadMail::whereLeadId($leadId)->whereDir($dir)->get();
|
||||
return MailDirService::getMailDirsNotInOutput($mails, $dir);
|
||||
}
|
||||
|
||||
public static function getCustomerMailDirs(){
|
||||
$customer_mail_dirs = CMSContent::where('identifier', '=', 'customer-mail-dirs')->get()->sortBy('pos');
|
||||
return $customer_mail_dirs;
|
||||
public static function getCustomerMailDirs(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return MailDirService::getCustomerMailDirs();
|
||||
}
|
||||
|
||||
public static function getCustomerMailDir($id){
|
||||
return CMSContent::where('identifier', '=', 'customer-mail-dirs')->where('pos', '=', $id)->first();
|
||||
public static function getCustomerMailDir(int $id): ?\App\Models\CMSContent
|
||||
{
|
||||
return MailDirService::getCustomerMailDir($id);
|
||||
}
|
||||
|
||||
public static function getCustomerMailName($lead_mail_dir, $mail_dir_id){
|
||||
|
||||
switch ($lead_mail_dir->getArrayContent('model')){
|
||||
case 'TravelCountry':
|
||||
$model = \App\Models\Sym\TravelCountry::find($mail_dir_id);
|
||||
break;
|
||||
case 'Airline':
|
||||
$model = Airline::find($mail_dir_id);
|
||||
break;
|
||||
case 'Insurance':
|
||||
$model = Insurance::find($mail_dir_id);
|
||||
break;
|
||||
case 'TravelCompany':
|
||||
$model = TravelCompany::find($mail_dir_id);
|
||||
break;
|
||||
default:
|
||||
return '';
|
||||
public static function getCustomerMailName(\App\Models\CMSContent $mailDir, int $mailDirId): string
|
||||
{
|
||||
return MailDirService::getCustomerMailName($mailDir, $mailDirId);
|
||||
}
|
||||
|
||||
if($model){
|
||||
if($lead_mail_dir->getArrayContent('model') === 'TravelCountry'){
|
||||
return $model->mail_dir_name;
|
||||
public static function getCustomerMailEmails(\App\Models\CMSContent $mailDir, int $mailDirId): array|string
|
||||
{
|
||||
return MailDirService::getCustomerMailEmails($mailDir, $mailDirId);
|
||||
}
|
||||
return $model->name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static function getCustomerMailEmails($lead_mail_dir, $mail_dir_id){
|
||||
|
||||
switch ($lead_mail_dir->getArrayContent('model')){
|
||||
case 'TravelCountry':
|
||||
$model = \App\Models\Sym\TravelCountry::find($mail_dir_id);
|
||||
break;
|
||||
case 'Airline':
|
||||
$model = Airline::find($mail_dir_id);
|
||||
break;
|
||||
case 'Insurance':
|
||||
$model = Insurance::find($mail_dir_id);
|
||||
break;
|
||||
case 'TravelCompany':
|
||||
$model = TravelCompany::find($mail_dir_id);
|
||||
break;
|
||||
default:
|
||||
//direkt from CMSContent
|
||||
return $lead_mail_dir->getArrayContent('emails');
|
||||
}
|
||||
|
||||
if($model){
|
||||
return $model->contact_emails;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/*public static function getFeWoInstructionPDFName($fewo){
|
||||
return "HINWEISE-FERIENWOHNUNG-".$fewo->pdf_name.".pdf";
|
||||
}
|
||||
public static function getFeWoCMSContent($content, $identifier_fewo){
|
||||
return CMSContent::where('identifier', '=', $identifier_fewo)->where('integer', $content->id)->get()->sortBy('pos');
|
||||
}
|
||||
|
||||
public static function getFeWoCMSContentForPDF($identifier_content, $identifier_fewo){
|
||||
$pdf_content = [];
|
||||
$contents = CMSContent::where('identifier', '=', $identifier_content)->get()->sortBy('pos');
|
||||
foreach ($contents as $content){
|
||||
if($content->decimal > 0){ //in_pdf
|
||||
$pdf_content[] = $content;
|
||||
}
|
||||
if($fewo_contents = BookingFewo::getFeWoCMSContent($content, $identifier_fewo)){
|
||||
foreach ($fewo_contents as $fewo_content){
|
||||
if($fewo_content->decimal > 0){ //in_pdf
|
||||
$pdf_content[] = $fewo_content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pdf_content;
|
||||
}*/
|
||||
}
|
||||
92
app/Services/MailDirService.php
Normal file
92
app/Services/MailDirService.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Airline;
|
||||
use App\Models\CMSContent;
|
||||
use App\Models\Insurance;
|
||||
use App\Models\TravelCompany;
|
||||
|
||||
class MailDirService
|
||||
{
|
||||
private static array $outputDirs = [];
|
||||
|
||||
public static function setOutputDir(string $dir, string $subdir): void
|
||||
{
|
||||
self::$outputDirs[$dir][] = $subdir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getCustomerMailDirs(): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
return CMSContent::where('identifier', '=', 'customer-mail-dirs')->get()->sortBy('pos');
|
||||
}
|
||||
|
||||
public static function getCustomerMailDir(int $id): ?CMSContent
|
||||
{
|
||||
return CMSContent::where('identifier', '=', 'customer-mail-dirs')->where('pos', '=', $id)->first();
|
||||
}
|
||||
|
||||
public static function getCustomerMailName(CMSContent $mailDir, int $mailDirId): string
|
||||
{
|
||||
$model = self::resolveModel($mailDir, $mailDirId);
|
||||
|
||||
if ($model === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($mailDir->getArrayContent('model') === 'TravelCountry') {
|
||||
return $model->mail_dir_name ?? '';
|
||||
}
|
||||
|
||||
return $model->name ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>|string
|
||||
*/
|
||||
public static function getCustomerMailEmails(CMSContent $mailDir, int $mailDirId): array|string
|
||||
{
|
||||
$model = self::resolveModel($mailDir, $mailDirId);
|
||||
|
||||
if ($model === null) {
|
||||
// Default: emails come directly from CMSContent
|
||||
return $mailDir->getArrayContent('emails') ?? [];
|
||||
}
|
||||
|
||||
return $model->contact_emails ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subdirs from a mail collection that were not already in the output.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection $mails Collection of CustomerMail or LeadMail
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getMailDirsNotInOutput(iterable $mails, string $dir): array
|
||||
{
|
||||
$processed = self::$outputDirs[$dir] ?? [];
|
||||
$result = [];
|
||||
|
||||
foreach ($mails as $mail) {
|
||||
if (!in_array($mail->subdir, $processed, true)) {
|
||||
$result[] = $mail->subdir;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function resolveModel(CMSContent $mailDir, int $mailDirId): mixed
|
||||
{
|
||||
return match ($mailDir->getArrayContent('model')) {
|
||||
'TravelCountry' => \App\Models\Sym\TravelCountry::find($mailDirId),
|
||||
'Airline' => Airline::find($mailDirId),
|
||||
'Insurance' => Insurance::find($mailDirId),
|
||||
'TravelCompany' => TravelCompany::find($mailDirId),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -221,7 +221,7 @@ class Passolution
|
|||
if(!Storage::disk('public')->exists( $this->pdf_dir )){
|
||||
Storage::disk('public')->makeDirectory($this->pdf_dir); //creates directory
|
||||
}
|
||||
$this->pdf_path = Storage::disk('public')->getAdapter()->getPathPrefix();
|
||||
$this->pdf_path = Storage::disk('public')->path('');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class Util
|
||||
{
|
||||
|
||||
|
|
@ -51,18 +53,17 @@ class Util
|
|||
public static function _format_date($date, $to = 'date')
|
||||
{
|
||||
if ($to === 'datetime') {
|
||||
return \Carbon::parse($date)->format(\Util::formatDateTimeDB());
|
||||
return Carbon::parse($date)->format(self::formatDateTimeDB());
|
||||
}
|
||||
//date
|
||||
return \Carbon::parse($date)->format(\Util::formatDateDB());
|
||||
return Carbon::parse($date)->format(self::formatDateDB());
|
||||
}
|
||||
|
||||
public static function _reformat_date($date, $to = 'date')
|
||||
{
|
||||
if ($to === 'datetime') {
|
||||
return \Carbon::parse($date)->format('Y-m-d - H:i');
|
||||
return Carbon::parse($date)->format('Y-m-d - H:i');
|
||||
}
|
||||
return \Carbon::parse($date)->format('Y-m-d');
|
||||
return Carbon::parse($date)->format('Y-m-d');
|
||||
}
|
||||
|
||||
public static function _format_number($value)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use Illuminate\Support\Facades\Crypt;
|
|||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\F;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
/**
|
||||
* App\User
|
||||
|
|
@ -86,9 +86,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Distributions\F;
|
|||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, Notifiable;
|
||||
|
||||
use SoftDeletes;
|
||||
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
|
||||
|
||||
protected $connection = 'mysql';
|
||||
|
||||
|
|
|
|||
11
boost.json
Normal file
11
boost.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"agents": [
|
||||
"claude_code",
|
||||
"cursor"
|
||||
],
|
||||
"editors": [
|
||||
"claude_code",
|
||||
"cursor"
|
||||
],
|
||||
"guidelines": []
|
||||
}
|
||||
1729
bootstrap/cache/config.php
vendored
Normal file
1729
bootstrap/cache/config.php
vendored
Normal file
File diff suppressed because it is too large
Load diff
63
bootstrap/cache/packages.php
vendored
63
bootstrap/cache/packages.php
vendored
|
|
@ -38,31 +38,13 @@
|
|||
),
|
||||
'digital-bird/shoppingcart' =>
|
||||
array (
|
||||
'aliases' =>
|
||||
array (
|
||||
'Cart' => 'Gloudemans\\Shoppingcart\\Facades\\Cart',
|
||||
),
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Gloudemans\\Shoppingcart\\ShoppingcartServiceProvider',
|
||||
),
|
||||
),
|
||||
'facade/ignition' =>
|
||||
array (
|
||||
'aliases' =>
|
||||
array (
|
||||
'Flare' => 'Facade\\Ignition\\Facades\\Flare',
|
||||
),
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Facade\\Ignition\\IgnitionServiceProvider',
|
||||
),
|
||||
),
|
||||
'fideloper/proxy' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Fideloper\\Proxy\\TrustedProxyServiceProvider',
|
||||
'Cart' => 'Gloudemans\\Shoppingcart\\Facades\\Cart',
|
||||
),
|
||||
),
|
||||
'intervention/image' =>
|
||||
|
|
@ -108,6 +90,24 @@
|
|||
0 => 'Laracasts\\Flash\\FlashServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/boost' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Boost\\BoostServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/mcp' =>
|
||||
array (
|
||||
'aliases' =>
|
||||
array (
|
||||
'Mcp' => 'Laravel\\Mcp\\Server\\Facades\\Mcp',
|
||||
),
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Mcp\\Server\\McpServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/passport' =>
|
||||
array (
|
||||
'providers' =>
|
||||
|
|
@ -115,6 +115,13 @@
|
|||
0 => 'Laravel\\Passport\\PassportServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/roster' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Laravel\\Roster\\RosterServiceProvider',
|
||||
),
|
||||
),
|
||||
'laravel/sail' =>
|
||||
array (
|
||||
'providers' =>
|
||||
|
|
@ -173,6 +180,13 @@
|
|||
0 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
),
|
||||
),
|
||||
'nunomaduro/termwind' =>
|
||||
array (
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
),
|
||||
),
|
||||
'pragmarx/google2fa-laravel' =>
|
||||
array (
|
||||
'aliases' =>
|
||||
|
|
@ -191,6 +205,17 @@
|
|||
0 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
),
|
||||
),
|
||||
'spatie/laravel-ignition' =>
|
||||
array (
|
||||
'aliases' =>
|
||||
array (
|
||||
'Flare' => 'Spatie\\LaravelIgnition\\Facades\\Flare',
|
||||
),
|
||||
'providers' =>
|
||||
array (
|
||||
0 => 'Spatie\\LaravelIgnition\\IgnitionServiceProvider',
|
||||
),
|
||||
),
|
||||
'yajra/laravel-datatables-oracle' =>
|
||||
array (
|
||||
'aliases' =>
|
||||
|
|
|
|||
289
bootstrap/cache/services.php
vendored
289
bootstrap/cache/services.php
vendored
|
|
@ -28,37 +28,40 @@
|
|||
24 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
25 => 'Cviebrock\\EloquentSluggable\\ServiceProvider',
|
||||
26 => 'Gloudemans\\Shoppingcart\\ShoppingcartServiceProvider',
|
||||
27 => 'Facade\\Ignition\\IgnitionServiceProvider',
|
||||
28 => 'Fideloper\\Proxy\\TrustedProxyServiceProvider',
|
||||
29 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
30 => 'IqContent\\LaravelFilemanager\\LaravelFilemanagerServiceProvider',
|
||||
31 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
32 => 'Laracasts\\Flash\\FlashServiceProvider',
|
||||
27 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
28 => 'IqContent\\LaravelFilemanager\\LaravelFilemanagerServiceProvider',
|
||||
29 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
30 => 'Laracasts\\Flash\\FlashServiceProvider',
|
||||
31 => 'Laravel\\Boost\\BoostServiceProvider',
|
||||
32 => 'Laravel\\Mcp\\Server\\McpServiceProvider',
|
||||
33 => 'Laravel\\Passport\\PassportServiceProvider',
|
||||
34 => 'Laravel\\Sail\\SailServiceProvider',
|
||||
35 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
36 => 'Laravel\\Ui\\UiServiceProvider',
|
||||
37 => 'Collective\\Html\\HtmlServiceProvider',
|
||||
38 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
39 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
40 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
41 => 'PragmaRX\\Google2FALaravel\\ServiceProvider',
|
||||
42 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
43 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
44 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
45 => 'Laravel\\Passport\\PassportServiceProvider',
|
||||
46 => 'App\\Providers\\AppServiceProvider',
|
||||
47 => 'App\\Providers\\AuthServiceProvider',
|
||||
48 => 'App\\Providers\\EventServiceProvider',
|
||||
49 => 'App\\Providers\\RouteServiceProvider',
|
||||
50 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
51 => 'Barryvdh\\DomPDF\\ServiceProvider',
|
||||
52 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
53 => 'Collective\\Html\\HtmlServiceProvider',
|
||||
54 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
55 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
56 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
57 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
34 => 'Laravel\\Roster\\RosterServiceProvider',
|
||||
35 => 'Laravel\\Sail\\SailServiceProvider',
|
||||
36 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
37 => 'Laravel\\Ui\\UiServiceProvider',
|
||||
38 => 'Collective\\Html\\HtmlServiceProvider',
|
||||
39 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
40 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
41 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
42 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
43 => 'PragmaRX\\Google2FALaravel\\ServiceProvider',
|
||||
44 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
45 => 'Spatie\\LaravelIgnition\\IgnitionServiceProvider',
|
||||
46 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
47 => 'Laravel\\Tinker\\TinkerServiceProvider',
|
||||
48 => 'Laravel\\Passport\\PassportServiceProvider',
|
||||
49 => 'App\\Providers\\AppServiceProvider',
|
||||
50 => 'App\\Providers\\AuthServiceProvider',
|
||||
51 => 'App\\Providers\\EventServiceProvider',
|
||||
52 => 'App\\Providers\\RouteServiceProvider',
|
||||
53 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
54 => 'Barryvdh\\DomPDF\\ServiceProvider',
|
||||
55 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
56 => 'Collective\\Html\\HtmlServiceProvider',
|
||||
57 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
58 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
59 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
60 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
),
|
||||
'eager' =>
|
||||
array (
|
||||
|
|
@ -76,31 +79,34 @@
|
|||
11 => 'Barryvdh\\DomPDF\\ServiceProvider',
|
||||
12 => 'Cviebrock\\EloquentSluggable\\ServiceProvider',
|
||||
13 => 'Gloudemans\\Shoppingcart\\ShoppingcartServiceProvider',
|
||||
14 => 'Facade\\Ignition\\IgnitionServiceProvider',
|
||||
15 => 'Fideloper\\Proxy\\TrustedProxyServiceProvider',
|
||||
16 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
17 => 'IqContent\\LaravelFilemanager\\LaravelFilemanagerServiceProvider',
|
||||
18 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
19 => 'Laracasts\\Flash\\FlashServiceProvider',
|
||||
14 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
15 => 'IqContent\\LaravelFilemanager\\LaravelFilemanagerServiceProvider',
|
||||
16 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
17 => 'Laracasts\\Flash\\FlashServiceProvider',
|
||||
18 => 'Laravel\\Boost\\BoostServiceProvider',
|
||||
19 => 'Laravel\\Mcp\\Server\\McpServiceProvider',
|
||||
20 => 'Laravel\\Passport\\PassportServiceProvider',
|
||||
21 => 'Laravel\\Ui\\UiServiceProvider',
|
||||
22 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
23 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
24 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
25 => 'PragmaRX\\Google2FALaravel\\ServiceProvider',
|
||||
26 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
27 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
28 => 'Laravel\\Passport\\PassportServiceProvider',
|
||||
29 => 'App\\Providers\\AppServiceProvider',
|
||||
30 => 'App\\Providers\\AuthServiceProvider',
|
||||
31 => 'App\\Providers\\EventServiceProvider',
|
||||
32 => 'App\\Providers\\RouteServiceProvider',
|
||||
33 => 'Barryvdh\\DomPDF\\ServiceProvider',
|
||||
34 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
35 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
36 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
37 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
38 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
21 => 'Laravel\\Roster\\RosterServiceProvider',
|
||||
22 => 'Laravel\\Ui\\UiServiceProvider',
|
||||
23 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
24 => 'Carbon\\Laravel\\ServiceProvider',
|
||||
25 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
|
||||
26 => 'Termwind\\Laravel\\TermwindServiceProvider',
|
||||
27 => 'PragmaRX\\Google2FALaravel\\ServiceProvider',
|
||||
28 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
29 => 'Spatie\\LaravelIgnition\\IgnitionServiceProvider',
|
||||
30 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
31 => 'Laravel\\Passport\\PassportServiceProvider',
|
||||
32 => 'App\\Providers\\AppServiceProvider',
|
||||
33 => 'App\\Providers\\AuthServiceProvider',
|
||||
34 => 'App\\Providers\\EventServiceProvider',
|
||||
35 => 'App\\Providers\\RouteServiceProvider',
|
||||
36 => 'Barryvdh\\DomPDF\\ServiceProvider',
|
||||
37 => 'Jenssegers\\Date\\DateServiceProvider',
|
||||
38 => 'Intervention\\Image\\ImageServiceProvider',
|
||||
39 => 'Maatwebsite\\Excel\\ExcelServiceProvider',
|
||||
40 => 'Yajra\\DataTables\\DataTablesServiceProvider',
|
||||
41 => 'Reliese\\Coders\\CodersServiceProvider',
|
||||
),
|
||||
'deferred' =>
|
||||
array (
|
||||
|
|
@ -111,99 +117,116 @@
|
|||
'Illuminate\\Contracts\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'Illuminate\\Bus\\BatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'Illuminate\\Bus\\DatabaseBatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
|
||||
'cache' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'cache.store' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'cache.psr6' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'memcached.connector' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'Illuminate\\Cache\\RateLimiter' => 'Illuminate\\Cache\\CacheServiceProvider',
|
||||
'command.cache.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.cache.forget' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.clear-compiled' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.auth.resets.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.config.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.config.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\AboutCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\ForgetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Auth\\Console\\ClearResetsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConfigShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\DbCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.db.prune' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.db.wipe' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.down' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.environment' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.event.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.event.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.event.list' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.key.generate' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.optimize' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.optimize.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.package.discover' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.failed' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.flush' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.forget' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.listen' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.monitor' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.prune-batches' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.prune-failed-jobs' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.restart' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.retry' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.retry-batch' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.work' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.route.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.route.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.route.list' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.schema.dump' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.seed' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\PruneCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\ShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\WipeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\DownCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EnvironmentCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EnvironmentDecryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EnvironmentEncryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\KeyGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\OptimizeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\OptimizeClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\PackageDiscoverCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\PruneStaleTagsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ListFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\FlushFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RestartCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\WorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\DumpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Seeds\\SeedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleFinishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleRunCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleClearCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleTestCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleWorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.storage.link' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.up' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.view.cache' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.view.clear' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.cache.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.cast.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.channel.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.component.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.console.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.controller.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.event.generate' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.event.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.exception.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.factory.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.job.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.listener.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.mail.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.middleware.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.model.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.notification.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.notification.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.observer.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.policy.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.provider.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.failed-table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.queue.batches-table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.request.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.resource.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.rule.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.seeder.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.session.table' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.serve' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.stub.publish' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.test.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.vendor.publish' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Console\\Scheduling\\ScheduleInterruptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\ShowModelCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\StorageLinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\StorageUnlinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\UpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ViewCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ViewClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Cache\\Console\\CacheTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\CastMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ChannelListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ComponentMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ConsoleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Routing\\Console\\ControllerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\DocsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\EventMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ExceptionMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Factories\\FactoryMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\JobMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\LangPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ListenerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\MailMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Routing\\Console\\MiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ModelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\NotificationMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Notifications\\Console\\NotificationTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ObserverMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\PolicyMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ProviderMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\FailedTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\BatchesTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RequestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ResourceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RuleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ScopeMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Seeds\\SeederMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Session\\Console\\SessionTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ServeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\StubPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\TestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\VendorPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ViewMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'migration.repository' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'migration.creator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate.fresh' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate.install' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate.refresh' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate.reset' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate.rollback' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate.status' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'command.migrate.make' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\MigrateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\FreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\InstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\RefreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\ResetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\RollbackCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\StatusCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Database\\Console\\Migrations\\MigrateMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'composer' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'hash' => 'Illuminate\\Hashing\\HashServiceProvider',
|
||||
'hash.driver' => 'Illuminate\\Hashing\\HashServiceProvider',
|
||||
|
|
@ -211,6 +234,7 @@
|
|||
'mailer' => 'Illuminate\\Mail\\MailServiceProvider',
|
||||
'Illuminate\\Mail\\Markdown' => 'Illuminate\\Mail\\MailServiceProvider',
|
||||
'Illuminate\\Contracts\\Pipeline\\Hub' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
|
||||
'pipeline' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
|
||||
'queue' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
'queue.connection' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
'queue.failer' => 'Illuminate\\Queue\\QueueServiceProvider',
|
||||
|
|
@ -224,6 +248,7 @@
|
|||
'translation.loader' => 'Illuminate\\Translation\\TranslationServiceProvider',
|
||||
'validator' => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
'validation.presence' => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => 'Illuminate\\Validation\\ValidationServiceProvider',
|
||||
'command.ide-helper.generate' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'command.ide-helper.models' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
|
||||
'Laravel\\Sail\\Console\\InstallCommand' => 'Laravel\\Sail\\SailServiceProvider',
|
||||
|
|
|
|||
103
composer.json
103
composer.json
|
|
@ -14,55 +14,90 @@
|
|||
"options": {
|
||||
"symlink": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "laravelcollective/html",
|
||||
"version": "6.5.0",
|
||||
"description": "HTML and Form Builders for the Laravel Framework (L10+ patch)",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.0",
|
||||
"illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||
"illuminate/routing": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||
"illuminate/session": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||
"illuminate/view": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||
"spatie/once": "^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Collective\\Html\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": ["Collective\\Html\\HtmlServiceProvider"],
|
||||
"aliases": {
|
||||
"Form": "Collective\\Html\\FormFacade",
|
||||
"Html": "Collective\\Html\\HtmlFacade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/LaravelCollective/html.git",
|
||||
"reference": "v6.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.0|^8.2|^8.3",
|
||||
"php": "^8.1|^8.2|^8.3",
|
||||
"bacon/bacon-qr-code": "^3.0",
|
||||
"barryvdh/laravel-dompdf": "*",
|
||||
"cviebrock/eloquent-sluggable": "*",
|
||||
"barryvdh/laravel-dompdf": "^2.2",
|
||||
"cviebrock/eloquent-sluggable": "^10.0",
|
||||
"digital-bird/shoppingcart": "^3.0",
|
||||
"doctrine/dbal": "*",
|
||||
"fideloper/proxy": "^4.4",
|
||||
"guzzlehttp/guzzle": "^7.0.1",
|
||||
"intervention/image": "*",
|
||||
"doctrine/dbal": "^3.0",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"intervention/image": "^2.7",
|
||||
"iqcontent/laravel-filemanager": "*",
|
||||
"jenssegers/date": "*",
|
||||
"laracasts/flash": "*",
|
||||
"laravel/framework": "^8.12",
|
||||
"laravel/framework": "^10.49",
|
||||
"laravel/helpers": "*",
|
||||
"laravel/passport": "*",
|
||||
"laravel/tinker": "^2.5",
|
||||
"laravel/ui": "^3.1",
|
||||
"laravelcollective/html": "*",
|
||||
"laravel/passport": "^11.0",
|
||||
"laravel/tinker": "^2.9",
|
||||
"laravel/ui": "^4.0",
|
||||
"laravelcollective/html": "^6.5",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"pragmarx/google2fa-laravel": "^2.2",
|
||||
"pragmarx/google2fa-laravel": "*",
|
||||
"reliese/laravel": "*",
|
||||
"rguedes/pdfmerger": "^1.0",
|
||||
"setasign/fpdf": "*",
|
||||
"setasign/fpdi": "*",
|
||||
"yajra/laravel-datatables-oracle": "*"
|
||||
"yajra/laravel-datatables-oracle": "^10.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"facade/ignition": "^2.5",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"laravel/sail": "^1.0.1",
|
||||
"mockery/mockery": "^1.4.2",
|
||||
"nunomaduro/collision": "^5.0",
|
||||
"phpunit/phpunit": "^9.3.3",
|
||||
"barryvdh/laravel-debugbar": "*",
|
||||
"barryvdh/laravel-ide-helper": "*"
|
||||
"barryvdh/laravel-debugbar": "^3.13",
|
||||
"barryvdh/laravel-ide-helper": "^3.1",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/boost": "^1.8",
|
||||
"laravel/sail": "^1.29",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"spatie/laravel-ignition": "^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"app/helpers.php"
|
||||
],
|
||||
"classmap": [
|
||||
"database/seeds",
|
||||
"database/factories"
|
||||
],
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeds/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
|
|
@ -77,11 +112,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"post-update-cmd": [
|
||||
"php artisan clear-compiled",
|
||||
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
|
||||
"php artisan ide-helper:generate",
|
||||
"php artisan ide-helper:meta",
|
||||
"php artisan ide-helper:models"
|
||||
"php artisan clear-compiled"
|
||||
],
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
|
|
@ -90,14 +121,16 @@
|
|||
"@php artisan key:generate"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover"
|
||||
"@php artisan package:discover --ansi"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
|
|
|
|||
4788
composer.lock
generated
4788
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -116,6 +116,7 @@ return [
|
|||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
'success_key' => env('SUCCESS_KEY'),
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Autoloaded Service Providers
|
||||
|
|
|
|||
|
|
@ -72,6 +72,12 @@ return [
|
|||
'url' => env('APP_URL').'/storage/booking',
|
||||
'visibility' => 'public',
|
||||
],
|
||||
'offer' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/offer'),
|
||||
'url' => env('APP_URL').'/storage/offer',
|
||||
'visibility' => 'public',
|
||||
],
|
||||
'general' => [
|
||||
'driver' => 'local',
|
||||
'root' => storage_path('app/general'),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,12 @@ return [
|
|||
*
|
||||
* @link https://symfony.com/doc/current/deployment/proxies.html
|
||||
*/
|
||||
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL,
|
||||
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_FOR |
|
||||
Illuminate\Http\Request::HEADER_X_FORWARDED_HOST |
|
||||
Illuminate\Http\Request::HEADER_X_FORWARDED_PORT |
|
||||
Illuminate\Http\Request::HEADER_X_FORWARDED_PROTO |
|
||||
Illuminate\Http\Request::HEADER_X_FORWARDED_PREFIX |
|
||||
Illuminate\Http\Request::HEADER_X_FORWARDED_AWS_ELB,
|
||||
|
||||
|
||||
];
|
||||
|
|
|
|||
69
database/factories/BookingFactory.php
Normal file
69
database/factories/BookingFactory.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Booking;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Booking>
|
||||
*/
|
||||
class BookingFactory extends Factory
|
||||
{
|
||||
protected $model = Booking::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$start = fake()->dateTimeBetween('+1 month', '+6 months');
|
||||
$end = fake()->dateTimeBetween($start, '+12 months');
|
||||
|
||||
return [
|
||||
'customer_id' => CustomerFactory::new(),
|
||||
'lead_id' => LeadFactory::new(),
|
||||
'booking_date' => now()->format('Y-m-d'),
|
||||
'start_date' => $start->format('Y-m-d'),
|
||||
'end_date' => $end->format('Y-m-d'),
|
||||
'sf_guard_user_id' => null,
|
||||
'branch_id' => null,
|
||||
'service_fee' => 0.0,
|
||||
'travel_country_id' => null,
|
||||
'travel_category_id' => null,
|
||||
'pax' => 2,
|
||||
'coupon_id' => null,
|
||||
'title' => fake()->sentence(4),
|
||||
'travel_number' => strtoupper(fake()->bothify('??####')),
|
||||
'participant_name' => fake()->lastName(),
|
||||
'participant_firstname' => fake()->firstName(),
|
||||
'participant_birthdate' => fake()->dateTimeBetween('-60 years', '-18 years')->format('Y-m-d'),
|
||||
'participant_salutation_id' => 1,
|
||||
'ev_number' => '',
|
||||
'merlin_knr' => '',
|
||||
'merlin_order_number' => '',
|
||||
'travel_company_id' => null,
|
||||
'travel_documents' => false,
|
||||
'price' => fake()->randomFloat(2, 500, 5000),
|
||||
'price_total' => 0.0,
|
||||
'deposit_total' => 0.0,
|
||||
'final_payment' => 0.0,
|
||||
'final_payment_date' => null,
|
||||
'travelagenda_id' => null,
|
||||
'website_id' => null,
|
||||
'new_drafts' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public function canceled(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'canceled' => 1.0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function withPrice(float $price): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'price' => $price,
|
||||
'price_total' => $price,
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
database/factories/CustomerFactory.php
Normal file
50
database/factories/CustomerFactory.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Customer;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Customer>
|
||||
*/
|
||||
class CustomerFactory extends Factory
|
||||
{
|
||||
protected $model = Customer::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'salutation_id' => 1,
|
||||
'title' => '',
|
||||
'name' => fake()->lastName(),
|
||||
'firstname' => fake()->firstName(),
|
||||
'birthdate' => fake()->dateTimeBetween('-80 years', '-18 years')->format('Y-m-d'),
|
||||
'company' => '',
|
||||
'street' => fake()->streetAddress(),
|
||||
'zip' => fake()->postcode(),
|
||||
'city' => fake()->city(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'phone' => fake()->phoneNumber(),
|
||||
'phonebusiness' => '',
|
||||
'phonemobile' => fake()->phoneNumber(),
|
||||
'fax' => '',
|
||||
'bank' => '',
|
||||
'bank_code' => '',
|
||||
'bank_account_number' => '',
|
||||
'credit_card_type_id' => null,
|
||||
'credit_card_number' => '',
|
||||
'credit_card_expiration_date'=> null,
|
||||
'participants_remarks' => '',
|
||||
'miscellaneous_remarks' => '',
|
||||
'country_id' => null,
|
||||
];
|
||||
}
|
||||
|
||||
public function company(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'company' => fake()->company(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
60
database/factories/LeadFactory.php
Normal file
60
database/factories/LeadFactory.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Lead;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Lead>
|
||||
*/
|
||||
class LeadFactory extends Factory
|
||||
{
|
||||
protected $model = Lead::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$start = fake()->dateTimeBetween('+1 month', '+6 months');
|
||||
$end = fake()->dateTimeBetween($start, '+12 months');
|
||||
|
||||
return [
|
||||
'customer_id' => CustomerFactory::new(),
|
||||
'request_date' => now()->format('Y-m-d'),
|
||||
'travelperiod_start' => $start->format('Y-m-d'),
|
||||
'travelperiod_end' => $end->format('Y-m-d'),
|
||||
'travelperiod_length' => (int) $start->diff($end)->days,
|
||||
'travelcountry_id' => null,
|
||||
'travelagenda_id' => null,
|
||||
'remarks' => '',
|
||||
'sf_guard_user_id' => null,
|
||||
'is_closed' => false,
|
||||
'initialcontacttype_id' => null,
|
||||
'searchengine_id' => null,
|
||||
'searchengine_keywords' => '',
|
||||
'status_id' => 1,
|
||||
'next_due_date' => now()->addDays(7)->format('Y-m-d'),
|
||||
'website_id' => null,
|
||||
'travelcategory_id' => null,
|
||||
'price' => 0.0,
|
||||
'pax' => 2,
|
||||
'participant_name' => fake()->lastName(),
|
||||
'participant_firstname' => fake()->firstName(),
|
||||
'participant_birthdate' => fake()->dateTimeBetween('-60 years', '-18 years')->format('Y-m-d'),
|
||||
'participant_salutation_id' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
public function closed(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'is_closed' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
public function withPrice(float $price): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'price' => $price,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,43 @@
|
|||
<?php
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
namespace Database\Factories;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Model Factories
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This directory should contain each of the model factory definitions for
|
||||
| your application. Factories provide a convenient way to generate new
|
||||
| model instances for testing / seeding your application's database.
|
||||
|
|
||||
*/
|
||||
use App\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
$factory->define(App\User::class, function (Faker $faker) {
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
protected $model = User::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => $faker->name,
|
||||
'email' => $faker->unique()->safeEmail,
|
||||
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
|
||||
'remember_token' => str_random(10),
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'password' => Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
'active' => 1,
|
||||
'admin' => 0,
|
||||
'confirmed' => 1,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function admin(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'admin' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function inactive(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'active' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 1 — Contact-Deduplizierung
|
||||
*
|
||||
* Fügt der customer-Tabelle zwei Felder hinzu:
|
||||
* - merged_into_id : zeigt auf den Master-Datensatz bei Duplikaten
|
||||
* - merged_at : Zeitstempel der Zusammenführung
|
||||
*
|
||||
* Rollback: Felder werden wieder entfernt. Keine Datenverluste,
|
||||
* da keine bestehenden Daten verändert werden.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('customer', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('merged_into_id')->nullable()->after('id');
|
||||
$table->dateTime('merged_at')->nullable()->after('merged_into_id');
|
||||
|
||||
$table->index('merged_into_id', 'customer_merged_into_id_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('customer', function (Blueprint $table) {
|
||||
$table->dropIndex('customer_merged_into_id_idx');
|
||||
$table->dropColumn(['merged_into_id', 'merged_at']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 1 — Schritt 3: Soft Delete für customer-Tabelle
|
||||
*
|
||||
* Fügt deleted_at hinzu. Gelöschte Kontakte bleiben in der DB erhalten
|
||||
* und können wiederhergestellt werden.
|
||||
*
|
||||
* Das alte Customer-Model nutzt kein SoftDeletes → unberührt.
|
||||
* Nur der neue Contact-Model (mit SoftDeletes-Trait) berücksichtigt deleted_at.
|
||||
*
|
||||
* Rollback: entfernt deleted_at (alle soft-deleted Datensätze werden damit
|
||||
* dauerhaft ausgeblendet — erst prüfen!).
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('customer', function (Blueprint $table) {
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('customer', function (Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 2 — Schritt 1: customer → contacts
|
||||
*
|
||||
* Benennt die Tabelle um. Alle Foreign Keys, die auf `customer` zeigen,
|
||||
* werden von MySQL automatisch mitgezogen (ON DELETE / ON UPDATE bleiben).
|
||||
*
|
||||
* Rollback: benennt `contacts` zurück in `customer`.
|
||||
*
|
||||
* HINWEIS: Vor dem Deployment sicherstellen, dass die App-Models
|
||||
* bereits $table = 'contacts' verwenden, ODER die Migration vor dem
|
||||
* Code-Deployment ausführen und im Notfall zurückrollen.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// MySQL: RENAME TABLE ist ein atomarer DDL-Befehl
|
||||
DB::statement('RENAME TABLE `customer` TO `contacts`');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement('RENAME TABLE `contacts` TO `customer`');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 2 — Schritt 2: lead → inquiries
|
||||
*
|
||||
* Benennt die Tabelle um. Abhängige Tabellen:
|
||||
* - lead_mails (lead_id FK → wird mitgezogen)
|
||||
* - lead_files (lead_id FK → wird mitgezogen)
|
||||
* - lead_notices (lead_id FK → wird mitgezogen)
|
||||
* - lead_participant (lead_id FK → wird mitgezogen)
|
||||
* - booking (lead_id FK → wird mitgezogen)
|
||||
* - customer_mails (lead_id FK → wird mitgezogen)
|
||||
*
|
||||
* Rollback: benennt `inquiries` zurück in `lead`.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::statement('RENAME TABLE `lead` TO `inquiries`');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement('RENAME TABLE `inquiries` TO `lead`');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 2 — Schritt 3: booking.lead_id → booking.inquiry_id
|
||||
*
|
||||
* Benennt die Spalte und den Foreign Key um.
|
||||
* inquiry_id bleibt nullable (Direktbuchungen ohne Anfrage sind erlaubt).
|
||||
*
|
||||
* Rollback: benennt inquiry_id zurück in lead_id.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Foreign Key muss zuerst gedroppt werden, bevor die Spalte umbenannt wird
|
||||
Schema::table('booking', function (Blueprint $table) {
|
||||
// Drop existing FK (Name aus DB-Schema)
|
||||
$table->dropForeign('booking_lead_id_lead_id');
|
||||
});
|
||||
|
||||
DB::statement('ALTER TABLE `booking` RENAME COLUMN `lead_id` TO `inquiry_id`');
|
||||
|
||||
Schema::table('booking', function (Blueprint $table) {
|
||||
// Neuen FK auf umbenannte Tabelle setzen
|
||||
$table->foreign('inquiry_id', 'booking_inquiry_id_inquiries_id')
|
||||
->references('id')->on('inquiries')
|
||||
->onDelete('no action')
|
||||
->onUpdate('no action');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('booking', function (Blueprint $table) {
|
||||
$table->dropForeign('booking_inquiry_id_inquiries_id');
|
||||
});
|
||||
|
||||
DB::statement('ALTER TABLE `booking` RENAME COLUMN `inquiry_id` TO `lead_id`');
|
||||
|
||||
Schema::table('booking', function (Blueprint $table) {
|
||||
$table->foreign('lead_id', 'booking_lead_id_lead_id')
|
||||
->references('id')->on('lead')
|
||||
->onDelete('no action')
|
||||
->onUpdate('no action');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 3 — Schritt 1: Neue participants-Tabelle erstellen und Daten migrieren
|
||||
*
|
||||
* Konsolidiert lead_participant + participant in eine einheitliche Tabelle.
|
||||
* Die alten Tabellen bleiben zunächst erhalten (werden in Phase 3 Schritt 2
|
||||
* nach Abschluss der Tests gedroppt).
|
||||
*
|
||||
* Neue Felder:
|
||||
* - inquiry_id : FK auf inquiries (war: lead_participant.lead_id)
|
||||
* - booking_id : FK auf booking (war: participant.booking_id)
|
||||
* - participant_pass : nur bei Buchungs-Teilnehmern relevant
|
||||
* - participant_storno: nur bei Buchungs-Teilnehmern relevant
|
||||
* - is_lead_contact : markiert den Hauptkontakt aus lead.participant_name
|
||||
*
|
||||
* Rollback: droppt die neue Tabelle (alte Tabellen bleiben unverändert).
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('participants_unified', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Kontext-FKs — genau einer ist gesetzt, der andere NULL
|
||||
$table->unsignedBigInteger('inquiry_id')->nullable();
|
||||
$table->unsignedBigInteger('booking_id')->nullable();
|
||||
|
||||
// Teilnehmerdaten
|
||||
$table->string('participant_name')->nullable();
|
||||
$table->string('participant_firstname')->nullable();
|
||||
$table->date('participant_birthdate')->nullable();
|
||||
$table->unsignedBigInteger('participant_salutation_id')->nullable();
|
||||
$table->boolean('participant_child')->default(false);
|
||||
$table->integer('nationality_id')->nullable();
|
||||
|
||||
// Nur bei Buchungs-Teilnehmern
|
||||
$table->boolean('participant_pass')->default(false);
|
||||
$table->boolean('participant_storno')->default(false);
|
||||
|
||||
// Markiert den Hauptreisenden aus lead.participant_name
|
||||
$table->boolean('is_lead_contact')->default(false);
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('inquiry_id');
|
||||
$table->index('booking_id');
|
||||
|
||||
$table->foreign('inquiry_id', 'pu_inquiry_id_fk')
|
||||
->references('id')->on('inquiries')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('booking_id', 'pu_booking_id_fk')
|
||||
->references('id')->on('booking')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('participant_salutation_id', 'pu_salutation_id_fk')
|
||||
->references('id')->on('salutation')
|
||||
->onDelete('set null');
|
||||
});
|
||||
|
||||
// ── Daten migrieren ─────────────────────────────────────────────────
|
||||
|
||||
// 1. Aus lead_participant → inquiry_id gesetzt
|
||||
DB::statement("
|
||||
INSERT INTO participants_unified
|
||||
(inquiry_id, booking_id, participant_name, participant_firstname,
|
||||
participant_birthdate, participant_salutation_id, participant_child,
|
||||
nationality_id, is_lead_contact, created_at, updated_at)
|
||||
SELECT
|
||||
lead_id, NULL,
|
||||
participant_name, participant_firstname,
|
||||
participant_birthdate, participant_salutation_id, participant_child,
|
||||
nationality_id, 0,
|
||||
NOW(), NOW()
|
||||
FROM lead_participant
|
||||
");
|
||||
|
||||
// 2. Aus lead.participant_name → Hauptreisende-Datensätze (is_lead_contact = 1)
|
||||
DB::statement("
|
||||
INSERT INTO participants_unified
|
||||
(inquiry_id, booking_id, participant_name, participant_firstname,
|
||||
participant_birthdate, participant_salutation_id, participant_child,
|
||||
nationality_id, is_lead_contact, created_at, updated_at)
|
||||
SELECT
|
||||
id, NULL,
|
||||
participant_name, participant_firstname,
|
||||
participant_birthdate, participant_salutation_id, 0,
|
||||
NULL, 1,
|
||||
NOW(), NOW()
|
||||
FROM inquiries
|
||||
WHERE participant_name IS NOT NULL
|
||||
AND participant_name != ''
|
||||
");
|
||||
|
||||
// 3. Aus participant → booking_id gesetzt
|
||||
DB::statement("
|
||||
INSERT INTO participants_unified
|
||||
(inquiry_id, booking_id, participant_name, participant_firstname,
|
||||
participant_birthdate, participant_salutation_id, participant_child,
|
||||
nationality_id, participant_pass, participant_storno,
|
||||
is_lead_contact, created_at, updated_at)
|
||||
SELECT
|
||||
NULL, booking_id,
|
||||
participant_name, participant_firstname,
|
||||
participant_birthdate, participant_salutation_id, participant_child,
|
||||
nationality_id, participant_pass, participant_storno,
|
||||
0, NOW(), NOW()
|
||||
FROM participant
|
||||
");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('participants_unified');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 3 — Schritt 2: Alte Participant-Tabellen droppen
|
||||
*
|
||||
* Voraussetzungen (vor Ausführung prüfen!):
|
||||
* 1. Phase 3 Schritt 1 (participants_unified) läuft stabil in Produktion
|
||||
* 2. Alle Queries / Repositories auf participants_unified umgestellt
|
||||
* 3. Datenmigration durch Vergleich der Zeilenzahlen geprüft:
|
||||
* SELECT COUNT(*) FROM lead_participant;
|
||||
* SELECT COUNT(*) FROM participant;
|
||||
* SELECT COUNT(*) FROM participants_unified WHERE inquiry_id IS NOT NULL;
|
||||
* SELECT COUNT(*) FROM participants_unified WHERE booking_id IS NOT NULL;
|
||||
*
|
||||
* Rollback: NICHT möglich ohne Datenbank-Backup — erst ausführen wenn sicher!
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// FKs in lead_participant zeigen auf lead (jetzt inquiries) — werden mitgedroppt
|
||||
Schema::dropIfExists('lead_participant');
|
||||
|
||||
// FKs in participant zeigen auf booking — werden mitgedroppt
|
||||
Schema::dropIfExists('participant');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Bewusst leer: das Droppen von Produktionsdaten ist irreversibel.
|
||||
// Für einen vollständigen Rollback bitte Datenbank-Backup einspielen.
|
||||
throw new \RuntimeException(
|
||||
'Phase 3 Schritt 2 kann nicht automatisch zurückgerollt werden. ' .
|
||||
'Bitte Datenbank-Backup einspielen.'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 4 — Schritt 1a: communications-Tabelle erstellen und Daten migrieren
|
||||
*
|
||||
* Konsolidiert lead_mails + customer_mails in eine einheitliche Tabelle.
|
||||
* Die alten Tabellen bleiben zunächst erhalten (werden in Phase 4 Schritt 2 gedroppt).
|
||||
*
|
||||
* Voraussetzung: Phase 2 muss bereits ausgeführt worden sein
|
||||
* (lead → inquiries, customer → contacts).
|
||||
*
|
||||
* Besonderheiten:
|
||||
* - reply_id ist selbst-referenziell; nach beiden INSERTs wird ein Remapping
|
||||
* der alten IDs auf die neuen IDs durchgeführt (via legacy_source + legacy_id).
|
||||
* - travel_country_id existiert nur in customer_mails → nullable, NULL für lead_mails.
|
||||
* - customer_mails hat sowohl lead_id als auch booking_id → beide werden übernommen.
|
||||
*
|
||||
* Rollback: droppt die neue Tabelle (alte Tabellen bleiben unverändert).
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('communications', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Kontext-FKs — bei customer_mails können beide gesetzt sein
|
||||
$table->unsignedBigInteger('inquiry_id')->nullable();
|
||||
$table->unsignedBigInteger('booking_id')->nullable();
|
||||
$table->unsignedBigInteger('contact_id')->nullable();
|
||||
|
||||
// Reply-Chain — kein FK-Constraint (cross-table Remapping)
|
||||
$table->unsignedBigInteger('reply_id')->nullable();
|
||||
|
||||
// Herkunft für Remapping und spätere Analyse
|
||||
$table->enum('legacy_source', ['lead_mail', 'customer_mail']);
|
||||
$table->unsignedBigInteger('legacy_id');
|
||||
|
||||
// Mail-Felder (identisch in beiden Quell-Tabellen)
|
||||
$table->boolean('is_answer')->default(false);
|
||||
$table->string('email')->nullable();
|
||||
$table->text('recipient')->nullable();
|
||||
$table->text('cc')->nullable();
|
||||
$table->text('bcc')->nullable();
|
||||
$table->string('subject')->nullable();
|
||||
$table->text('message')->nullable();
|
||||
$table->boolean('dir')->default(false);
|
||||
$table->unsignedBigInteger('subdir')->nullable();
|
||||
|
||||
// Nur in customer_mails vorhanden
|
||||
$table->unsignedBigInteger('travel_country_id')->nullable();
|
||||
|
||||
$table->boolean('draft')->default(false);
|
||||
$table->boolean('important')->default(false);
|
||||
$table->boolean('send')->default(false);
|
||||
$table->boolean('fail')->default(false);
|
||||
$table->text('error')->nullable();
|
||||
$table->text('forward')->nullable();
|
||||
|
||||
$table->dateTime('sent_at')->nullable();
|
||||
$table->dateTime('scheduled_at')->nullable();
|
||||
$table->dateTime('delivered_at')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('inquiry_id');
|
||||
$table->index('booking_id');
|
||||
$table->index('contact_id');
|
||||
$table->index('reply_id');
|
||||
$table->index(['legacy_source', 'legacy_id'], 'comm_legacy_idx');
|
||||
|
||||
$table->foreign('inquiry_id', 'comm_inquiry_id_fk')
|
||||
->references('id')->on('inquiries')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('booking_id', 'comm_booking_id_fk')
|
||||
->references('id')->on('booking')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('contact_id', 'comm_contact_id_fk')
|
||||
->references('id')->on('contacts')
|
||||
->onDelete('set null');
|
||||
});
|
||||
|
||||
// ── Daten migrieren ─────────────────────────────────────────────────
|
||||
|
||||
// 1. Aus lead_mails → inquiry_id gesetzt
|
||||
DB::statement("
|
||||
INSERT INTO communications
|
||||
(inquiry_id, booking_id, contact_id, is_answer, reply_id,
|
||||
legacy_source, legacy_id,
|
||||
email, recipient, cc, bcc, subject, message,
|
||||
dir, subdir, travel_country_id,
|
||||
draft, important, send, fail, error, forward,
|
||||
sent_at, scheduled_at, delivered_at, created_at, updated_at)
|
||||
SELECT
|
||||
lead_id, NULL, customer_id, is_answer, reply_id,
|
||||
'lead_mail', id,
|
||||
email, recipient, cc, bcc, subject, message,
|
||||
dir, subdir, NULL,
|
||||
draft, important, send, fail, error, forward,
|
||||
sent_at, scheduled_at, delivered_at, created_at, updated_at
|
||||
FROM lead_mails
|
||||
");
|
||||
|
||||
// 2. Aus customer_mails → booking_id gesetzt (kann zusätzlich lead_id haben)
|
||||
DB::statement("
|
||||
INSERT INTO communications
|
||||
(inquiry_id, booking_id, contact_id, is_answer, reply_id,
|
||||
legacy_source, legacy_id,
|
||||
email, recipient, cc, bcc, subject, message,
|
||||
dir, subdir, travel_country_id,
|
||||
draft, important, send, fail, error, forward,
|
||||
sent_at, scheduled_at, delivered_at, created_at, updated_at)
|
||||
SELECT
|
||||
lead_id, booking_id, customer_id, is_answer, reply_id,
|
||||
'customer_mail', id,
|
||||
email, recipient, cc, bcc, subject, message,
|
||||
dir, subdir, travel_country_id,
|
||||
draft, important, send, fail, error, forward,
|
||||
sent_at, scheduled_at, delivered_at, created_at, updated_at
|
||||
FROM customer_mails
|
||||
");
|
||||
|
||||
// 3. Reply-IDs remappen: alte source-table-IDs auf neue communications-IDs umstellen
|
||||
// Funktioniert weil reply_id immer auf eine Mail derselben Quell-Tabelle zeigt.
|
||||
DB::statement("
|
||||
UPDATE communications c1
|
||||
INNER JOIN communications c2
|
||||
ON c1.legacy_source = c2.legacy_source
|
||||
AND c2.legacy_id = c1.reply_id
|
||||
SET c1.reply_id = c2.id
|
||||
WHERE c1.reply_id IS NOT NULL
|
||||
");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('communications');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 4 — Schritt 1b: notices-Tabelle erstellen und Daten migrieren
|
||||
*
|
||||
* Konsolidiert lead_notices + booking_notices in eine einheitliche Tabelle.
|
||||
* Die alten Tabellen bleiben zunächst erhalten (werden in Phase 4 Schritt 2 gedroppt).
|
||||
*
|
||||
* Voraussetzung: Phase 2 muss bereits ausgeführt worden sein
|
||||
* (lead → inquiries, customer → contacts).
|
||||
*
|
||||
* Struktur: identisch in beiden Quell-Tabellen — nur inquiry_id vs. booking_id.
|
||||
*
|
||||
* Rollback: droppt die neue Tabelle (alte Tabellen bleiben unverändert).
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('notices', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Kontext-FKs — genau einer ist gesetzt
|
||||
$table->unsignedBigInteger('inquiry_id')->nullable();
|
||||
$table->unsignedBigInteger('booking_id')->nullable();
|
||||
|
||||
// Benutzer-Referenzen (integer wie in den Quell-Tabellen)
|
||||
$table->unsignedInteger('from_user_id');
|
||||
$table->unsignedInteger('to_user_id')->nullable();
|
||||
|
||||
$table->text('message')->nullable();
|
||||
$table->boolean('show')->default(false);
|
||||
$table->boolean('important')->default(false);
|
||||
$table->dateTime('edit_at')->nullable();
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('inquiry_id');
|
||||
$table->index('booking_id');
|
||||
$table->index('from_user_id');
|
||||
$table->index('to_user_id');
|
||||
|
||||
$table->foreign('inquiry_id', 'notices_inquiry_id_fk')
|
||||
->references('id')->on('inquiries')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('booking_id', 'notices_booking_id_fk')
|
||||
->references('id')->on('booking')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('from_user_id', 'notices_from_user_id_fk')
|
||||
->references('id')->on('users')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('to_user_id', 'notices_to_user_id_fk')
|
||||
->references('id')->on('users')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
|
||||
// ── Daten migrieren ─────────────────────────────────────────────────
|
||||
|
||||
// 1. Aus lead_notices → inquiry_id gesetzt
|
||||
DB::statement("
|
||||
INSERT INTO notices
|
||||
(inquiry_id, booking_id, from_user_id, to_user_id,
|
||||
message, show, important, edit_at, created_at, updated_at)
|
||||
SELECT
|
||||
lead_id, NULL, from_user_id, to_user_id,
|
||||
message, show, important, edit_at, created_at, updated_at
|
||||
FROM lead_notices
|
||||
");
|
||||
|
||||
// 2. Aus booking_notices → booking_id gesetzt
|
||||
DB::statement("
|
||||
INSERT INTO notices
|
||||
(inquiry_id, booking_id, from_user_id, to_user_id,
|
||||
message, show, important, edit_at, created_at, updated_at)
|
||||
SELECT
|
||||
NULL, booking_id, from_user_id, to_user_id,
|
||||
message, show, important, edit_at, created_at, updated_at
|
||||
FROM booking_notices
|
||||
");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('notices');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Phase 4 — Schritt 1c: attachments-Tabelle erstellen und Daten migrieren
|
||||
*
|
||||
* Konsolidiert lead_files + booking_files in eine einheitliche Tabelle.
|
||||
* Die alten Tabellen bleiben zunächst erhalten (werden in Phase 4 Schritt 2 gedroppt).
|
||||
*
|
||||
* Voraussetzung: Phase 2 + Phase 4a (communications) müssen bereits ausgeführt sein,
|
||||
* da communication_id auf die neue communications-Tabelle verweist.
|
||||
*
|
||||
* Besonderheiten:
|
||||
* - lead_files hat lead_mail_id (FK auf lead_mails) → wird auf communication_id gemappt
|
||||
* - booking_files hat keinen Mail-Bezug → communication_id bleibt NULL
|
||||
* - Spalte `mine` in den Quell-Tabellen ist ein Tippfehler für mime → hier: mime_type
|
||||
*
|
||||
* Rollback: droppt die neue Tabelle (alte Tabellen bleiben unverändert).
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('attachments', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
// Kontext-FKs
|
||||
$table->unsignedBigInteger('inquiry_id')->nullable();
|
||||
$table->unsignedBigInteger('booking_id')->nullable();
|
||||
|
||||
// Verknüpfung zur dazugehörigen Mail (nur bei Anfrage-Anhängen)
|
||||
$table->unsignedBigInteger('communication_id')->nullable();
|
||||
|
||||
// Datei-Metadaten
|
||||
$table->string('identifier')->nullable();
|
||||
$table->string('filename');
|
||||
$table->string('dir')->nullable();
|
||||
$table->string('original_name')->nullable();
|
||||
$table->string('ext')->nullable();
|
||||
$table->string('mime_type')->nullable();
|
||||
$table->unsignedInteger('size')->nullable();
|
||||
|
||||
// Herkunft für spätere Analyse / Rollback
|
||||
$table->enum('legacy_source', ['lead_file', 'booking_file']);
|
||||
$table->unsignedBigInteger('legacy_id');
|
||||
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('inquiry_id');
|
||||
$table->index('booking_id');
|
||||
$table->index('communication_id');
|
||||
$table->index('identifier', 'attachments_identifier_idx');
|
||||
$table->index(['legacy_source', 'legacy_id'], 'attachments_legacy_idx');
|
||||
|
||||
$table->foreign('inquiry_id', 'att_inquiry_id_fk')
|
||||
->references('id')->on('inquiries')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('booking_id', 'att_booking_id_fk')
|
||||
->references('id')->on('booking')
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->foreign('communication_id', 'att_communication_id_fk')
|
||||
->references('id')->on('communications')
|
||||
->onDelete('set null');
|
||||
});
|
||||
|
||||
// ── Daten migrieren ─────────────────────────────────────────────────
|
||||
|
||||
// 1. Aus lead_files → inquiry_id gesetzt; communication_id wird unten gesetzt
|
||||
DB::statement("
|
||||
INSERT INTO attachments
|
||||
(inquiry_id, booking_id, communication_id,
|
||||
identifier, filename, dir, original_name, ext, mime_type, size,
|
||||
legacy_source, legacy_id, created_at, updated_at)
|
||||
SELECT
|
||||
lead_id, NULL, NULL,
|
||||
identifier, filename, dir, original_name, ext, mine, size,
|
||||
'lead_file', id, created_at, updated_at
|
||||
FROM lead_files
|
||||
");
|
||||
|
||||
// 2. Aus booking_files → booking_id gesetzt
|
||||
DB::statement("
|
||||
INSERT INTO attachments
|
||||
(inquiry_id, booking_id, communication_id,
|
||||
identifier, filename, dir, original_name, ext, mime_type, size,
|
||||
legacy_source, legacy_id, created_at, updated_at)
|
||||
SELECT
|
||||
NULL, booking_id, NULL,
|
||||
identifier, filename, dir, original_name, ext, mine, size,
|
||||
'booking_file', id, created_at, updated_at
|
||||
FROM booking_files
|
||||
");
|
||||
|
||||
// 3. communication_id für lead_files setzen (sofern lead_mail_id gesetzt war)
|
||||
// Nutzt legacy_source + legacy_id der communications-Tabelle.
|
||||
DB::statement("
|
||||
UPDATE attachments a
|
||||
INNER JOIN lead_files lf ON lf.id = a.legacy_id
|
||||
INNER JOIN communications c
|
||||
ON c.legacy_source = 'lead_mail'
|
||||
AND c.legacy_id = lf.lead_mail_id
|
||||
SET a.communication_id = c.id
|
||||
WHERE a.legacy_source = 'lead_file'
|
||||
AND lf.lead_mail_id IS NOT NULL
|
||||
");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('attachments');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 4 — Schritt 2a: Alte Mail-Tabellen droppen
|
||||
*
|
||||
* Voraussetzungen (vor Ausführung prüfen!):
|
||||
* 1. Phase 4 Schritt 1a (communications) läuft stabil in Produktion
|
||||
* 2. Alle Queries / Repositories auf communications umgestellt
|
||||
* 3. Datenmigration geprüft:
|
||||
* SELECT COUNT(*) FROM lead_mails;
|
||||
* SELECT COUNT(*) FROM customer_mails;
|
||||
* SELECT COUNT(*) FROM communications WHERE legacy_source = 'lead_mail';
|
||||
* SELECT COUNT(*) FROM communications WHERE legacy_source = 'customer_mail';
|
||||
* 4. Reply-Chain korrekt: spot-check einiger Datensätze mit reply_id
|
||||
*
|
||||
* Rollback: NICHT möglich ohne Datenbank-Backup.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// lead_files referenziert lead_mails → erst lead_files droppen oder FK bereits weg
|
||||
// (lead_files wird in 400005 gedroppt — daher hier prüfen ob noch FK vorhanden)
|
||||
// Sicherheitshalber: lead_files.lead_mail_id FK zuerst entfernen falls noch vorhanden
|
||||
if (Schema::hasTable('lead_files') && Schema::hasColumn('lead_files', 'lead_mail_id')) {
|
||||
Schema::table('lead_files', function ($table) {
|
||||
$table->dropForeign('lead_files_lead_mail_id_foreign');
|
||||
});
|
||||
}
|
||||
|
||||
Schema::dropIfExists('lead_mails');
|
||||
Schema::dropIfExists('customer_mails');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
throw new \RuntimeException(
|
||||
'Phase 4 Schritt 2a kann nicht automatisch zurückgerollt werden. ' .
|
||||
'Bitte Datenbank-Backup einspielen.'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* Phase 4 — Schritt 2b: Alte Notiz-Tabellen droppen
|
||||
*
|
||||
* Voraussetzungen (vor Ausführung prüfen!):
|
||||
* 1. Phase 4 Schritt 1b (notices) läuft stabil in Produktion
|
||||
* 2. Alle Queries / Repositories auf notices umgestellt
|
||||
* 3. Datenmigration geprüft:
|
||||
* SELECT COUNT(*) FROM lead_notices;
|
||||
* SELECT COUNT(*) FROM booking_notices;
|
||||
* SELECT COUNT(*) FROM notices WHERE inquiry_id IS NOT NULL;
|
||||
* SELECT COUNT(*) FROM notices WHERE booking_id IS NOT NULL;
|
||||
*
|
||||
* Rollback: NICHT möglich ohne Datenbank-Backup.
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::dropIfExists('lead_notices');
|
||||
Schema::dropIfExists('booking_notices');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
throw new \RuntimeException(
|
||||
'Phase 4 Schritt 2b kann nicht automatisch zurückgerollt werden. ' .
|
||||
'Bitte Datenbank-Backup einspielen.'
|
||||
);
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue