184 lines
6.1 KiB
PHP
184 lines
6.1 KiB
PHP
<?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',
|
|
]);
|
|
}
|
|
}
|