Neustrukturierung Customer / Lead / Booking Phase 2
This commit is contained in:
parent
313f0dbf4e
commit
6df9c401af
69 changed files with 3809 additions and 374 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
184
app/Http/Controllers/OfferController.php
Normal file
184
app/Http/Controllers/OfferController.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
63
app/Http/Controllers/OfferFileController.php
Normal file
63
app/Http/Controllers/OfferFileController.php
Normal 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();
|
||||
}
|
||||
}
|
||||
60
app/Http/Controllers/OfferTemplateController.php
Normal file
60
app/Http/Controllers/OfferTemplateController.php
Normal 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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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']]));
|
||||
|
||||
|
|
|
|||
104
app/Http/Requests/Offer/OfferTemplateRequest.php
Normal file
104
app/Http/Requests/Offer/OfferTemplateRequest.php
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Offer;
|
||||
|
||||
use App\Models\OfferItem;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class OfferTemplateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'branch_id' => 'nullable|integer|exists:branch,id',
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string|max:5000',
|
||||
|
||||
'default_headline' => 'nullable|string|max:500',
|
||||
'default_intro' => 'nullable|string|max:65000',
|
||||
'default_itinerary' => 'nullable|string|max:65000',
|
||||
'default_closing' => 'nullable|string|max:65000',
|
||||
|
||||
'default_items' => 'nullable|array|max:200',
|
||||
'default_items.*' => 'array',
|
||||
'default_items.*.type' => ['required', Rule::in(OfferItem::TYPES)],
|
||||
'default_items.*.title' => 'required|string|max:1000',
|
||||
'default_items.*.description' => 'nullable|string|max:20000',
|
||||
'default_items.*.quantity' => 'required|integer|min:1',
|
||||
'default_items.*.price_per_unit' => 'required|numeric',
|
||||
'default_items.*.travel_program_id' => 'nullable|integer|min:0',
|
||||
'default_items.*.fewo_lodging_id' => 'nullable|integer|min:0',
|
||||
'default_items.*.metadata' => 'nullable|array',
|
||||
|
||||
'is_active' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $v) {
|
||||
if ($v->fails()) {
|
||||
return;
|
||||
}
|
||||
$rows = $this->input('default_items', []) ?? [];
|
||||
foreach (array_values($rows) as $i => $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$type = $row['type'] ?? null;
|
||||
$p = $row['price_per_unit'] ?? null;
|
||||
if ($type === null || $p === null) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($type, [OfferItem::TYPE_TRAVEL, OfferItem::TYPE_SERVICE, OfferItem::TYPE_OPTION, OfferItem::TYPE_INSURANCE, OfferItem::TYPE_CUSTOM], true)) {
|
||||
if ((float) $p < 0) {
|
||||
$v->errors()->add("default_items.$i.price_per_unit", 'Der Einzelpreis muss mindestens 0 sein (außer bei Rabatten).');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('default_headline') && is_string($this->input('default_headline'))) {
|
||||
$this->merge(['default_headline' => strip_tags($this->input('default_headline'))]);
|
||||
}
|
||||
$this->merge(
|
||||
$this->sanitizeWysiwygInput([
|
||||
'default_intro' => $this->input('default_intro'),
|
||||
'default_itinerary' => $this->input('default_itinerary'),
|
||||
'default_closing' => $this->input('default_closing'),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $fields
|
||||
* @return array<string, string|null>
|
||||
*/
|
||||
protected function sanitizeWysiwygInput(array $fields): array
|
||||
{
|
||||
$allowed = '<p><br><br/><strong><b><em><i><u><ul><ol><li><a><h1><h2><h3><h4><h5><h6><blockquote><span><div><table><thead><tbody><tr><th><td>';
|
||||
$out = [];
|
||||
foreach ($fields as $key => $val) {
|
||||
if ($val === null) {
|
||||
$out[$key] = $val;
|
||||
continue;
|
||||
}
|
||||
if (! is_string($val)) {
|
||||
$out[$key] = (string) $val;
|
||||
continue;
|
||||
}
|
||||
$out[$key] = strip_tags($val, $allowed);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
34
app/Http/Requests/Offer/SendOfferRequest.php
Normal file
34
app/Http/Requests/Offer/SendOfferRequest.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Offer;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SendOfferRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'required|string|max:500',
|
||||
'body' => 'required|string|max:100000',
|
||||
'cc' => 'nullable|string|max:2000',
|
||||
'bcc' => 'nullable|string|max:2000',
|
||||
'reply_to' => 'nullable|email|max:255',
|
||||
'expires_at' => 'nullable|date|after:now',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'subject.required' => 'Bitte einen Betreff angeben.',
|
||||
'body.required' => 'Bitte den E-Mail-Text angeben.',
|
||||
'expires_at.after' => 'Ablaufdatum muss in der Zukunft liegen.',
|
||||
];
|
||||
}
|
||||
}
|
||||
60
app/Http/Requests/Offer/StoreOfferRequest.php
Normal file
60
app/Http/Requests/Offer/StoreOfferRequest.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Offer;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class StoreOfferRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'contact_id' => [
|
||||
'nullable',
|
||||
'integer',
|
||||
Rule::exists('contacts', 'id'),
|
||||
],
|
||||
'inquiry_id' => [
|
||||
'nullable',
|
||||
'integer',
|
||||
Rule::exists('inquiries', 'id'),
|
||||
],
|
||||
'template_id' => [
|
||||
'nullable',
|
||||
'integer',
|
||||
Rule::exists('offer_templates', 'id')->whereNull('deleted_at'),
|
||||
],
|
||||
'booking_id' => [
|
||||
'nullable',
|
||||
'integer',
|
||||
Rule::exists('booking', 'id'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $v) {
|
||||
if (! $v->fails() && $this->input('contact_id') === null && $this->input('inquiry_id') === null) {
|
||||
$v->errors()->add('contact_id', 'Bitte wähle einen Kontakt oder eine Anfrage.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'contact_id.exists' => 'Der gewählte Kontakt existiert nicht.',
|
||||
'inquiry_id.exists' => 'Die gewählte Anfrage existiert nicht.',
|
||||
'template_id.exists' => 'Die gewählte Vorlage existiert (nicht) oder ist gelöscht.',
|
||||
'booking_id.exists' => 'Die gewählte Buchung existiert nicht.',
|
||||
];
|
||||
}
|
||||
}
|
||||
107
app/Http/Requests/Offer/UpdateVersionRequest.php
Normal file
107
app/Http/Requests/Offer/UpdateVersionRequest.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Offer;
|
||||
|
||||
use App\Models\OfferItem;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
class UpdateVersionRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'headline' => 'nullable|string|max:500',
|
||||
'intro_text' => 'nullable|string|max:65000',
|
||||
'itinerary_text' => 'nullable|string|max:65000',
|
||||
'closing_text' => 'nullable|string|max:65000',
|
||||
'valid_until' => 'nullable|date|after_or_equal:today',
|
||||
'template_id' => [
|
||||
'nullable',
|
||||
'integer',
|
||||
Rule::exists('offer_templates', 'id')->whereNull('deleted_at'),
|
||||
],
|
||||
'template_document_ids' => 'nullable|array',
|
||||
'template_document_ids.*' => 'integer',
|
||||
|
||||
'items' => 'nullable|array|max:200',
|
||||
'items.*' => 'array',
|
||||
'items.*.id' => 'nullable|integer|exists:offer_items,id',
|
||||
'items.*.type' => ['required', Rule::in(OfferItem::TYPES)],
|
||||
'items.*.title' => 'required|string|max:1000',
|
||||
'items.*.description' => 'nullable|string|max:20000',
|
||||
'items.*.quantity' => 'required|integer|min:1',
|
||||
'items.*.price_per_unit' => 'required|numeric',
|
||||
'items.*.travel_program_id' => 'nullable|integer|min:0',
|
||||
'items.*.fewo_lodging_id' => 'nullable|integer|min:0',
|
||||
'items.*.metadata' => 'nullable|array',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(function (Validator $v) {
|
||||
if ($v->fails()) {
|
||||
return;
|
||||
}
|
||||
$items = $this->input('items', []);
|
||||
foreach (array_values($items) as $i => $row) {
|
||||
if (! is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$type = $row['type'] ?? null;
|
||||
$p = $row['price_per_unit'] ?? null;
|
||||
if ($type === null || $p === null) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($type, [OfferItem::TYPE_TRAVEL, OfferItem::TYPE_SERVICE, OfferItem::TYPE_OPTION, OfferItem::TYPE_INSURANCE, OfferItem::TYPE_CUSTOM], true)) {
|
||||
if ((float) $p < 0) {
|
||||
$v->errors()->add("items.$i.price_per_unit", 'Der Einzelpreis muss mindestens 0 sein (außer bei Rabatten).');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('headline') && is_string($this->input('headline'))) {
|
||||
$this->merge(['headline' => strip_tags($this->input('headline'))]);
|
||||
}
|
||||
$this->merge($this->sanitizeWysiwygFields([
|
||||
'intro_text' => $this->input('intro_text'),
|
||||
'itinerary_text' => $this->input('itinerary_text'),
|
||||
'closing_text' => $this->input('closing_text'),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Einfache WYSIWYG-Whitelist (TinyMCE/TipTap): gefährliche Tags raus, Basis-Formatierung behalten.
|
||||
*
|
||||
* @param array<string, mixed> $fields
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function sanitizeWysiwygFields(array $fields): array
|
||||
{
|
||||
$allowed = '<p><br><br/><strong><b><em><i><u><ul><ol><li><a><h1><h2><h3><h4><h5><h6><blockquote><span><div><table><thead><tbody><tr><th><td>';
|
||||
$out = [];
|
||||
foreach ($fields as $key => $val) {
|
||||
if ($val === null) {
|
||||
$out[$key] = $val;
|
||||
continue;
|
||||
}
|
||||
if (! is_string($val)) {
|
||||
$out[$key] = (string) $val;
|
||||
continue;
|
||||
}
|
||||
$out[$key] = strip_tags($val, $allowed);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue