Neustrukturierung Customer / Lead / Booking Phase 2

This commit is contained in:
Kevin Adametz 2026-05-28 17:10:37 +02:00
parent 313f0dbf4e
commit 6df9c401af
69 changed files with 3809 additions and 374 deletions

View file

@ -4,9 +4,9 @@ namespace App\Http\Controllers\Admin;
use Request;
use Carbon\Carbon;
use App\Models\Status;
use App\Services\Util;
use App\Services\ReportDateFilterService;
use App\Models\Booking;
use App\Models\TravelCompany;
use App\Http\Controllers\Controller;
@ -63,22 +63,22 @@ class ReportBookingController extends Controller
$query->whereIn("travel_company_id", Request::get('filter_travel_company_id'));
}
if(Request::get('filter_travel_date_from') != ""){
$travel_date_from = Carbon::parse(Request::get('filter_travel_date_from'))->format("Y-m-d");
$travel_date_from = ReportDateFilterService::parseDate(Request::get('filter_travel_date_from'), 'report booking travel date from');
if($travel_date_from){
$query->where("start_date", '>=', $travel_date_from);
}
if(Request::get('filter_travel_date_to') != ""){
$travel_date_to = Carbon::parse(Request::get('filter_travel_date_to'))->format("Y-m-d");
$travel_date_to = ReportDateFilterService::parseDate(Request::get('filter_travel_date_to'), 'report booking travel date to');
if($travel_date_to){
$query->where("start_date", '<=', $travel_date_to);
}
if(Request::get('filter_booking_date_from') != ""){
$filter_booking_date_from = Carbon::parse(Request::get('filter_booking_date_from'))->format("Y-m-d");
$filter_booking_date_from = ReportDateFilterService::parseDate(Request::get('filter_booking_date_from'), 'report booking booking date from');
if($filter_booking_date_from){
$query->where("booking_date", '>=', $filter_booking_date_from);
}
if(Request::get('filter_booking_date_to') != ""){
$filter_booking_date_from = Carbon::parse(Request::get('filter_booking_date_to'))->format("Y-m-d");
$query->where("booking_date", '<=', $filter_booking_date_from);
$filter_booking_date_to = ReportDateFilterService::parseDate(Request::get('filter_booking_date_to'), 'report booking booking date to');
if($filter_booking_date_to){
$query->where("booking_date", '<=', $filter_booking_date_to);
}
return $query;

View file

@ -6,9 +6,9 @@ use Request;
use Response;
use HTMLHelper;
use Carbon\Carbon;
use App\Models\Status;
use App\Services\Util;
use App\Services\ReportDateFilterService;
use App\Models\Booking;
use App\Models\TravelAgenda;
use App\Models\TravelCompany;
@ -80,22 +80,22 @@ class ReportController extends Controller
$query->whereIn("travel_company_id", Request::get('filter_travel_company_id'));
}
if(Request::get('filter_travel_date_from') != ""){
$travel_date_from = Carbon::parse(Request::get('filter_travel_date_from'))->format("Y-m-d");
$travel_date_from = ReportDateFilterService::parseDate(Request::get('filter_travel_date_from'), 'report travel date from');
if($travel_date_from){
$query->where("start_date", '>=', $travel_date_from);
}
if(Request::get('filter_travel_date_to') != ""){
$travel_date_to = Carbon::parse(Request::get('filter_travel_date_to'))->format("Y-m-d");
$travel_date_to = ReportDateFilterService::parseDate(Request::get('filter_travel_date_to'), 'report travel date to');
if($travel_date_to){
$query->where("start_date", '<=', $travel_date_to);
}
if(Request::get('filter_booking_date_from') != ""){
$travel_date_from = Carbon::parse(Request::get('filter_booking_date_from'))->format("Y-m-d");
$query->where("booking_date", '>=', $travel_date_from);
$booking_date_from = ReportDateFilterService::parseDate(Request::get('filter_booking_date_from'), 'report booking date from');
if($booking_date_from){
$query->where("booking_date", '>=', $booking_date_from);
}
if(Request::get('filter_booking_date_to') != ""){
$travel_date_to = Carbon::parse(Request::get('filter_booking_date_to'))->format("Y-m-d");
$query->where("booking_date", '<=', $travel_date_to);
$booking_date_to = ReportDateFilterService::parseDate(Request::get('filter_booking_date_to'), 'report booking date to');
if($booking_date_to){
$query->where("booking_date", '<=', $booking_date_to);
}
return $query;
@ -371,15 +371,15 @@ class ReportController extends Controller
if(Request::get('filter_service_provider_id') != ""){
$query->where('service_provider_id', '=', Request::get('filter_service_provider_id'));
}
if(Request::get('filter_travel_date_from') != ""){
$query->whereHas('booking', function ($q) {
$travel_date_from = Carbon::parse(Request::get('filter_travel_date_from'))->format("Y-m-d");
$travel_date_from = ReportDateFilterService::parseDate(Request::get('filter_travel_date_from'), 'report provider travel date from');
if($travel_date_from){
$query->whereHas('booking', function ($q) use ($travel_date_from) {
$q->where("start_date", '>=', $travel_date_from);
});
}
if(Request::get('filter_travel_date_to') != ""){
$query->whereHas('booking', function ($q) {
$travel_date_to = Carbon::parse(Request::get('filter_travel_date_to'))->format("Y-m-d");
$travel_date_to = ReportDateFilterService::parseDate(Request::get('filter_travel_date_to'), 'report provider travel date to');
if($travel_date_to){
$query->whereHas('booking', function ($q) use ($travel_date_to) {
$q->where("start_date", '<=', $travel_date_to);
});
}

View file

@ -6,9 +6,9 @@ use Request;
use Response;
use HTMLHelper;
use Carbon\Carbon;
use App\Models\Status;
use App\Services\Util;
use App\Services\ReportDateFilterService;
use App\Models\Booking;
use App\Models\FewoLodging;
use App\Models\TravelAgenda;
@ -52,21 +52,21 @@ class ReportFewoController extends Controller
$query->where('fewo_lodging_id', '=', Request::get('filter_option_fewo_id'));
}
if(Request::get('filter_date_from') != ""){
$date_from = Carbon::parse(Request::get('filter_date_from'))->format("Y-m-d");
$date_from = ReportDateFilterService::parseDate(Request::get('filter_date_from'), 'report fewo date from');
if($date_from){
$query->where("from_date", '>=', $date_from);
}
if(Request::get('filter_date_to') != ""){
$date_to = Carbon::parse(Request::get('filter_date_to'))->format("Y-m-d");
$date_to = ReportDateFilterService::parseDate(Request::get('filter_date_to'), 'report fewo date to');
if($date_to){
$query->where("from_date", '<=', $date_to);
}
if(Request::get('filter_booking_date_from') != ""){
$booking_date_from = Carbon::parse(Request::get('filter_booking_date_from'))->format("Y-m-d");
$booking_date_from = ReportDateFilterService::parseDate(Request::get('filter_booking_date_from'), 'report fewo booking date from');
if($booking_date_from){
$query->where("booking_date", '>=', $booking_date_from);
}
if(Request::get('filter_booking_date_to') != ""){
$booking_date_to = Carbon::parse(Request::get('filter_booking_date_to'))->format("Y-m-d");
$booking_date_to = ReportDateFilterService::parseDate(Request::get('filter_booking_date_to'), 'report fewo booking date to');
if($booking_date_to){
$query->where("booking_date", '<=', $booking_date_to);
}

View file

@ -10,6 +10,7 @@ use Carbon\Carbon;
use App\Models\Lead;
use App\Models\Status;
use App\Services\Util;
use App\Services\ReportDateFilterService;
use App\Models\Booking;
use App\Models\LeadMail;
use App\Models\FewoLodging;
@ -57,12 +58,12 @@ class ReportLeadsController extends Controller
$query->where('...?', '=', Request::get('filter_option_leads_id'));
}
*/
if(Request::get('filter_date_from') != ""){
$date_from = Carbon::parse(Request::get('filter_date_from'))->format("Y-m-d");
$date_from = ReportDateFilterService::parseDate(Request::get('filter_date_from'), 'report leads date from');
if($date_from){
$query->where("request_date", '>=', $date_from);
}
if(Request::get('filter_date_to') != ""){
$date_to = Carbon::parse(Request::get('filter_date_to'))->format("Y-m-d");
$date_to = ReportDateFilterService::parseDate(Request::get('filter_date_to'), 'report leads date to');
if($date_to){
$query->where("request_date", '<=', $date_to);
}
return $query;

View file

@ -4,9 +4,9 @@ namespace App\Http\Controllers\Admin;
use Request;
use Carbon\Carbon;
use App\Models\Status;
use App\Services\Util;
use App\Services\ReportDateFilterService;
use App\Models\TravelCompany;
use App\Models\ServiceProvider;
use App\Http\Controllers\Controller;
@ -63,28 +63,28 @@ class ReportProviderController extends Controller
if(Request::get('filter_service_provider_id') != ""){
$query->where('service_provider_id', '=', Request::get('filter_service_provider_id'));
}
if(Request::get('filter_travel_date_from') != ""){
$query->whereHas('booking', function ($q) {
$travel_date_from = Carbon::parse(Request::get('filter_travel_date_from'))->format("Y-m-d");
$travel_date_from = ReportDateFilterService::parseDate(Request::get('filter_travel_date_from'), 'report provider travel date from');
if($travel_date_from){
$query->whereHas('booking', function ($q) use ($travel_date_from) {
$q->where("start_date", '>=', $travel_date_from);
});
}
if(Request::get('filter_travel_date_to') != ""){
$query->whereHas('booking', function ($q) {
$travel_date_to = Carbon::parse(Request::get('filter_travel_date_to'))->format("Y-m-d");
$travel_date_to = ReportDateFilterService::parseDate(Request::get('filter_travel_date_to'), 'report provider travel date to');
if($travel_date_to){
$query->whereHas('booking', function ($q) use ($travel_date_to) {
$q->where("start_date", '<=', $travel_date_to);
});
}
if(Request::get('filter_booking_date_from') != ""){
$query->whereHas('booking', function ($q) {
$filter_booking_date_from = Carbon::parse(Request::get('filter_booking_date_from'))->format("Y-m-d");
$filter_booking_date_from = ReportDateFilterService::parseDate(Request::get('filter_booking_date_from'), 'report provider booking date from');
if($filter_booking_date_from){
$query->whereHas('booking', function ($q) use ($filter_booking_date_from) {
$q->where("booking_date", '>=', $filter_booking_date_from);
});
}
if(Request::get('filter_booking_date_to') != ""){
$query->whereHas('booking', function ($q) {
$filter_booking_date_to = Carbon::parse(Request::get('filter_booking_date_to'))->format("Y-m-d");
$filter_booking_date_to = ReportDateFilterService::parseDate(Request::get('filter_booking_date_to'), 'report provider booking date to');
if($filter_booking_date_to){
$query->whereHas('booking', function ($q) use ($filter_booking_date_to) {
$q->where("booking_date", '<=', $filter_booking_date_to);
});
}

View file

@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers;
use App\Models\Offer;
use App\Models\OfferVersion;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class OfferController extends Controller
{
public function __construct()
{
$this->middleware(['admin', '2fa']);
}
// ── Permissions: offers-r = Routing (Lesen), fein: offers-w, offers-send, offers-accept
public function hasOfferPermission(string $key): bool
{
$user = auth()->user();
if (! $user) {
return false;
}
return $user->isPermission($key);
}
public function assertOfferPermission(string $key): void
{
if (! $this->hasOfferPermission($key)) {
abort(Response::HTTP_FORBIDDEN, 'Fehlende Berechtigung: ' . $key);
}
}
public function index($step = false)
{
return view('offer.index', [
'step' => $step,
]);
}
public function getOffers()
{
$query = Offer::query()
->with(['createdBy', 'contact'])
->select('offers.*');
return \DataTables::eloquent($query)
->addColumn('action_edit', function (Offer $offer) {
return '<a href="' . e(route('offer_detail', [$offer->id])) . '" class="btn icon-btn btn-sm btn-primary" title="Öffnen">'
. '<span class="fa fa-edit"></span></a>';
})
->addColumn('id', function (Offer $offer) {
return '<a data-order="' . (int) $offer->id . '" href="'
. e(route('offer_detail', [$offer->id])) . '" data-id="' . (int) $offer->id . '">'
. (int) $offer->id . '</a>';
})
->addColumn('offer_number', function (Offer $offer) {
return e($offer->offer_number);
})
->addColumn('contact_name', function (Offer $offer) {
$c = $offer->contact;
if (! $c) {
return '';
}
return e(trim(($c->firstname ?? '') . ' ' . ($c->name ?? '')));
})
->addColumn('status_badge', function (Offer $offer) {
return '<span class="badge badge-secondary">' . e($offer->status) . '</span>';
})
->addColumn('created_name', function (Offer $offer) {
return e($offer->createdBy->name ?? '');
})
->addColumn('created_at_fmt', function (Offer $offer) {
if (! $offer->created_at) {
return '';
}
return e($offer->created_at->format(\Util::formatDateTimeDB()));
})
->orderColumn('id', 'offers.id $1')
->orderColumn('offer_number', 'offers.offer_number $1')
->rawColumns(['action_edit', 'id', 'status_badge'])
->make(true);
}
public function detail($id)
{
if ($id === 'new') {
abort(404, 'Anlage über Modal folgt in B2.');
}
$offer = Offer::query()
->with(['currentVersion.items', 'contact', 'inquiry', 'createdBy'])
->findOrFail($id);
return view('offer.detail', [
'offer' => $offer,
'id' => $offer->id,
]);
}
public function store(Request $request, $id)
{
$this->assertOfferPermission('offers-w');
if (! $request->has('action')) {
abort(403, 'keine Action');
}
// B3: Angebots-Editor, Autosave, …
\Session::flash('alert-info', 'Speichern (Stub A6) — B3 liefert die Logik.');
return redirect()->to(route('offer_detail', [$id]) . (string) $request->get('fragment', ''));
}
public function loadModal(Request $request)
{
$data = $request->all();
$html = '';
if (($data['action'] ?? null) === 'modal-new-offer') {
$html = view('offer.modal_new_offer_stub', ['data' => $data])->render();
}
if ($request->ajax() || $request->wantsJson()) {
return response()->json(['html' => $html, 'response' => $data]);
}
return response($html);
}
public function action(Request $request, $action, $id = null)
{
$wActions = [
'create', 'update', 'supersede', 'duplicate', 'delete-file',
];
$sendActions = ['send', 'resend'];
$acceptActions = ['markAccepted', 'markDeclined', 'withdraw', 'mark_accepted', 'mark_declined'];
if (in_array($action, $wActions, true)) {
$this->assertOfferPermission('offers-w');
}
if (in_array($action, $sendActions, true)) {
$this->assertOfferPermission('offers-send');
}
if (in_array($action, $acceptActions, true)) {
$this->assertOfferPermission('offers-accept');
}
$any = array_merge($wActions, $sendActions, $acceptActions);
if (! in_array($action, $any, true)) {
$this->assertOfferPermission('offers-w');
}
if ($id === null) {
abort(404);
}
if ($id !== 'new') {
$offer = Offer::find($id);
if (! $offer) {
abort(404);
}
} else {
$offer = null;
}
\Session::flash('alert-info', 'Aktion «' . e($action) . '» (Stub A6) — siehe B2/B3.');
if ($offer) {
return redirect()->route('offer_detail', [$offer->id]);
}
return redirect()->route('offers');
}
public function delete($id, $del = null)
{
$this->assertOfferPermission('offers-w');
\Session::flash('alert-info', 'Löschen (Stub) — B9 liefert Soft-Delete-Logik.');
return redirect()->route('offers');
}
/**
* PDF-Generierung: Stub (Ticket B5 liefert den Stream).
*/
public function pdf($versionId, $do = null)
{
$version = OfferVersion::query()->with('offer')->findOrFail($versionId);
return response('PDF-Generator folgt in B5 (OfferVersion #' . (int) $version->id . ').', 200, [
'Content-Type' => 'text/plain; charset=UTF-8',
]);
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers;
use App\Models\OfferFile;
use App\Models\OfferVersion;
use App\Repositories\OfferFileRepository;
use Illuminate\Http\Request;
class OfferFileController extends Controller
{
public function __construct()
{
$this->middleware(['admin', '2fa']);
}
public function upload(Request $request, $versionId)
{
if (! auth()->user()->isPermission('offers-w')) {
abort(403, 'Fehlende Berechtigung: offers-w');
}
$version = OfferVersion::findOrFail($versionId);
if (! $version->isEditable()) {
return response()->json([
'error' => true,
'message' => 'Diese Version ist nicht mehr bearbeitbar.',
'code' => 403,
], 403);
}
$repo = new OfferFileRepository(new OfferFile());
$repo->_set('disk', 'offer');
$repo->_set('offer_version_id', (int) $versionId);
$repo->_set('dir', '/files/' . date('Y/m') . '/');
$repo->_set('identifier', 'offer');
if ($request->has('include_in_pdf')) {
$repo->_set('include_in_pdf', filter_var($request->input('include_in_pdf'), FILTER_VALIDATE_BOOL));
}
return $repo->uploadFile($request->all());
}
public function delete($id)
{
if (! auth()->user()->isPermission('offers-w')) {
abort(403, 'Fehlende Berechtigung: offers-w');
}
$file = OfferFile::query()->with('offerVersion')->findOrFail($id);
if ($file->offerVersion && ! $file->offerVersion->isEditable()) {
\Session::flash('alert-warning', 'Datei gehört zu einer gesperrten Version.');
return redirect()->back();
}
$repo = new OfferFileRepository($file);
$repo->_set('disk', 'offer');
if ($repo->delete()) {
\Session::flash('alert-success', 'Datei gelöscht');
} else {
\Session::flash('alert-warning', 'Löschen fehlgeschlagen');
}
return redirect()->back();
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers;
use App\Models\OfferTemplate;
use Illuminate\Http\Request;
class OfferTemplateController extends Controller
{
public function __construct()
{
$this->middleware(['admin', '2fa']);
}
public function index()
{
return view('offer_template.index', [
'templates' => OfferTemplate::query()->orderBy('name')->get(),
]);
}
public function detail($id)
{
if ($id === 'new') {
$template = new OfferTemplate();
$id = 'new';
} else {
$template = OfferTemplate::findOrFail($id);
$id = $template->id;
}
return view('offer_template.detail', [
'template' => $template,
'id' => $id,
]);
}
public function store(Request $request, $id = null)
{
if (! $request->has('action')) {
abort(403, 'keine Action');
}
\Session::flash('alert-info', 'Vorlagen speichern (Stub A6) — C1 liefert die Logik.');
if ($id === 'new' || $id === null) {
return redirect()->route('offer_template_detail', ['id' => 'new']);
}
$template = OfferTemplate::findOrFail($id);
return redirect()->route('offer_template_detail', ['id' => $template->id]);
}
public function delete($id)
{
$template = OfferTemplate::findOrFail($id);
$template->delete();
\Session::flash('alert-success', 'Vorlage gelöscht.');
return redirect()->route('offer_templates');
}
}

View file

@ -128,7 +128,11 @@ class TravelUserBookingFewoController extends Controller
return back()->withRequest(Request::all())->withErrors($ret['error']);
}
if($ret['success'] == true){
$this->userBookingFewoRepo->createTravelInfoPDF($id, $data['info_mail_text']);
$createResult = $this->userBookingFewoRepo->createTravelInfoPDF($id, $data['info_mail_text']);
if($createResult === false){
\Session()->flash('alert-error', __('Anreiseinfo konnte nicht erstellt werden: Es fehlt eine Rechnungsnummer.'));
return redirect(route('travel_user_booking_fewo_detail', [$ret['id']]));
}
\Session()->flash('alert-success', __('Anreiseinfo wurde erstellt/gespeichert.'));
return redirect(route('travel_user_booking_fewo_detail', [$ret['id']]));