@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\CandidateAccount;
|
||||
use App\Models\PeopleProcess;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Services\CandidateAccess;
|
||||
use App\Services\CandidateProfiles;
|
||||
use App\Services\CandidateSubmissions;
|
||||
use App\Services\CandidateTriage;
|
||||
use App\Services\ConflictGuard;
|
||||
use App\Services\CurrentActor;
|
||||
use App\Services\OrganizationConfiguration;
|
||||
use App\Services\RequisitionWorkflow;
|
||||
use App\Services\SubmissionForms;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class CandidatePortalController extends Controller
|
||||
{
|
||||
public function settings(Request $request, CandidateProfiles $profiles, OrganizationConfiguration $configuration): Response
|
||||
{
|
||||
$actor = $configuration->administrator($this->staff($request));
|
||||
$portal = $profiles->portal($actor->tenant_id);
|
||||
|
||||
return $this->page($request, 'company/candidate-portal', ['portal' => $portal === null ? ['version' => 0, 'registration_open' => false, 'notice' => ''] : (array) $portal,
|
||||
'canManage' => app(SubmissionForms::class)->canManage($actor), 'portalUrl' => '/candidaturas/'.$actor->tenant_id, 'emailDeliveryAvailable' => app(CandidateAccess::class)->deliveryAvailable()]);
|
||||
}
|
||||
|
||||
public function configure(Request $request, CandidateProfiles $profiles): Response
|
||||
{
|
||||
$profiles->configure($this->staff($request), $request->all());
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function entry(Request $request, int $company, CandidateProfiles $profiles): Response
|
||||
{
|
||||
$portal = $profiles->portal($company);
|
||||
abort_unless($portal !== null, 404);
|
||||
$tenant = Tenant::query()->findOrFail($company);
|
||||
|
||||
return $this->page($request, 'candidate/entry', ['company' => ['id' => $company, 'name' => $tenant->name],
|
||||
'portal' => ['version' => $portal->version, 'registration_open' => (bool) $portal->registration_open, 'notice' => $portal->notice]]);
|
||||
}
|
||||
|
||||
public function register(Request $request, int $company, CandidateProfiles $profiles): Response
|
||||
{
|
||||
$account = $profiles->register($company, $request->all());
|
||||
Auth::guard('candidate')->login($account, false);
|
||||
$request->session()->put('candidate.session_version', $account->session_version);
|
||||
$request->session()->regenerate();
|
||||
Inertia::clearHistory();
|
||||
|
||||
return redirect('/candidaturas/'.$company.'/perfil');
|
||||
}
|
||||
|
||||
public function login(Request $request, int $company, CandidateProfiles $profiles): Response
|
||||
{
|
||||
abort_unless($profiles->portal($company) !== null, 404);
|
||||
$data = $request->validate(['email' => 'required|string|max:254', 'password' => 'required|string|max:128']);
|
||||
$account = CandidateAccount::query()->where('tenant_id', $company)->where('email', mb_strtolower(trim($data['email'])))->first();
|
||||
$dummy = Hash::make('candidate-unavailable-account');
|
||||
$valid = Hash::check($data['password'], $account->password ?? $dummy);
|
||||
if ($account === null || ! $valid) {
|
||||
throw ValidationException::withMessages(['email' => 'Não foi possível entrar com os dados indicados.']);
|
||||
}
|
||||
$profiles->event($account, 'account.login', []);
|
||||
Auth::guard('candidate')->login($account, false);
|
||||
$request->session()->put('candidate.session_version', $account->session_version);
|
||||
$request->session()->regenerate();
|
||||
Inertia::clearHistory();
|
||||
|
||||
return redirect('/candidaturas/'.$company.'/perfil');
|
||||
}
|
||||
|
||||
public function show(Request $request, int $company, CandidateProfiles $profiles): Response
|
||||
{
|
||||
$account = $this->account($request, $company);
|
||||
if ($account === null) {
|
||||
return redirect('/candidaturas/'.$company);
|
||||
}
|
||||
$profiles->event($account, 'profile.consulted', []);
|
||||
|
||||
return $this->page($request, 'candidate/profile', ['companyId' => $company, 'email' => $account->email, 'profile' => $profiles->profile($account),
|
||||
'emailVerified' => $account->email_verified_at !== null, 'accessStatus' => $request->session()->get('candidate_access_status'), 'emailDeliveryAvailable' => app(CandidateAccess::class)->deliveryAvailable(),
|
||||
'opportunities' => Schema::hasTable('candidate_opportunity_publications') ? DB::table('candidate_opportunity_publications as p')->join('people_processes as r', 'r.id', '=', 'p.people_process_id')
|
||||
->where('p.tenant_id', $company)->where('r.tenant_id', $company)->where('p.active', true)->where('r.status', 'open')->where('r.closes_on', '>=', today()->toDateString())
|
||||
->whereNotExists(fn ($q) => $q->selectRaw('1')->from('candidate_opportunity_publications as newer')->whereColumn('newer.people_process_id', 'p.people_process_id')->whereColumn('newer.version', '>', 'p.version'))
|
||||
->orderBy('r.closes_on')->limit(20)->get(['r.id', 'r.title', 'r.closes_on']) : [],
|
||||
'submissions' => Schema::hasTable('candidate_submissions') ? DB::table('candidate_submissions')->where('tenant_id', $company)->where('candidate_account_id', $account->id)->latest('id')->limit(20)->get(['id', 'reference', 'created_at']) : []]);
|
||||
}
|
||||
|
||||
public function save(Request $request, int $company, CandidateProfiles $profiles): Response
|
||||
{
|
||||
$account = $this->account($request, $company);
|
||||
abort_unless($account !== null, 401);
|
||||
$profiles->save($account, $request->all());
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function logout(Request $request, int $company, CandidateProfiles $profiles): Response
|
||||
{
|
||||
$account = $this->account($request, $company);
|
||||
abort_unless($account !== null, 401);
|
||||
$profiles->event($account, 'account.logout', []);
|
||||
Auth::guard('candidate')->logout();
|
||||
$request->session()->forget('candidate.session_version');
|
||||
$request->session()->regenerate();
|
||||
$request->session()->regenerateToken();
|
||||
Inertia::clearHistory();
|
||||
|
||||
return redirect('/candidaturas/'.$company);
|
||||
}
|
||||
|
||||
private function account(Request $request, int $company): ?CandidateAccount
|
||||
{
|
||||
$account = Auth::guard('candidate')->user();
|
||||
if ($account instanceof CandidateAccount) {
|
||||
$account = CandidateAccount::query()->where('id', $account->id)->where('tenant_id', $company)->first();
|
||||
}
|
||||
|
||||
return $account instanceof CandidateAccount && $account->tenant_id === $company && $request->session()->get('candidate.session_version') === $account->session_version ? $account : null;
|
||||
}
|
||||
|
||||
public function accessPage(Request $request, int $company, string $purpose, CandidateProfiles $profiles): Response
|
||||
{
|
||||
abort_unless($profiles->portal($company) !== null, 404);
|
||||
|
||||
return $this->page($request, 'candidate/access', ['companyId' => $company, 'purpose' => $purpose,
|
||||
'accessStatus' => $request->session()->get('candidate_access_status'), 'emailDeliveryAvailable' => app(CandidateAccess::class)->deliveryAvailable()]);
|
||||
}
|
||||
|
||||
public function requestAccess(Request $request, int $company, CandidateAccess $access, CandidateProfiles $profiles): Response
|
||||
{
|
||||
abort_unless($profiles->portal($company) !== null, 404);
|
||||
$data = $request->validate(['email' => 'required|string|email|max:254']);
|
||||
$access->request($company, $data['email'], 'reset');
|
||||
|
||||
return back()->with('candidate_access_status', 'requested');
|
||||
}
|
||||
|
||||
public function requestVerification(Request $request, int $company, CandidateAccess $access): Response
|
||||
{
|
||||
$account = $this->account($request, $company);
|
||||
abort_unless($account !== null, 401);
|
||||
$access->request($company, $account->email, 'verify');
|
||||
|
||||
return back()->with('candidate_access_status', 'requested');
|
||||
}
|
||||
|
||||
public function confirmAccess(Request $request, int $company, string $purpose, CandidateAccess $access): Response
|
||||
{
|
||||
$access->consume($company, $purpose === 'confirmar-email' ? 'verify' : 'reset', $request->all());
|
||||
Inertia::clearHistory();
|
||||
|
||||
return redirect('/candidaturas/'.$company.'/'.$purpose)->with('candidate_access_status', 'completed');
|
||||
}
|
||||
|
||||
private function staff(Request $request): User
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
abort_unless($actor instanceof User, 403);
|
||||
|
||||
return $actor;
|
||||
}
|
||||
|
||||
public function opportunity(Request $request, int $company, int $process, CandidateSubmissions $submissions): Response
|
||||
{
|
||||
$account = $this->account($request, $company);
|
||||
abort_unless($account !== null, 401);
|
||||
|
||||
return $this->page($request, 'candidate/opportunity', ['companyId' => $company, 'opportunity' => $submissions->opportunity($company, $process),
|
||||
'profile' => app(CandidateProfiles::class)->profile($account), 'email' => $account->email, 'emailVerified' => $account->email_verified_at !== null]);
|
||||
}
|
||||
|
||||
public function submit(Request $request, int $company, int $process, CandidateSubmissions $submissions): Response
|
||||
{
|
||||
$account = $this->account($request, $company);
|
||||
abort_unless($account !== null, 401);
|
||||
$id = $submissions->submit($account, $process, $request->all());
|
||||
|
||||
return redirect('/candidaturas/'.$company.'/submissoes/'.$id);
|
||||
}
|
||||
|
||||
public function receipt(Request $request, int $company, int $submission): Response
|
||||
{
|
||||
$account = $this->account($request, $company);
|
||||
abort_unless($account !== null, 401);
|
||||
$row = DB::table('candidate_submissions')->where('tenant_id', $company)->where('candidate_account_id', $account->id)->where('id', $submission)->first();
|
||||
abort_unless($row !== null, 404);
|
||||
app(CandidateProfiles::class)->event($account, 'submission.consulted', ['submission_id' => $row->id]);
|
||||
|
||||
return $this->page($request, 'candidate/receipt', ['companyId' => $company, 'decision' => app(CandidateTriage::class)->publicDecision($company, $submission), 'receipt' => ['reference' => $row->reference, 'received_at' => $row->created_at, 'snapshot' => json_decode($row->snapshot, true, 32, JSON_THROW_ON_ERROR)]]);
|
||||
}
|
||||
|
||||
public function inbox(Request $request, int $process, CandidateSubmissions $submissions): Response
|
||||
{
|
||||
return DB::transaction(function () use ($request, $process, $submissions) {
|
||||
$actor = $this->staff($request);
|
||||
app(ConflictGuard::class)->lockCompany($actor);
|
||||
$actor = app(CurrentActor::class)->lock($actor);
|
||||
$record = PeopleProcess::query()->where('tenant_id', $actor->tenant_id)->where('id', $process)->firstOrFail();
|
||||
$triage = app(CandidateTriage::class);
|
||||
abort_unless($triage->canRead($actor, $record), 403);
|
||||
$filter = $request->validate(['submission' => 'nullable|integer|min:1']);
|
||||
$rows = DB::table('candidate_submissions')->where('tenant_id', $actor->tenant_id)->where('people_process_id', $process)->orderBy('id')->paginate(20);
|
||||
if (isset($filter['submission'])) {
|
||||
$rows = DB::table('candidate_submissions')->where('tenant_id', $actor->tenant_id)->where('people_process_id', $process)->where('id', $filter['submission'])->paginate(20)->withQueryString();
|
||||
}
|
||||
$decisions = Schema::hasTable('candidate_triage') ? DB::table('candidate_triage')->where('tenant_id', $actor->tenant_id)->where('people_process_id', $process)->whereIn('candidate_submission_id', $rows->pluck('id'))->get(['candidate_submission_id', 'outcome', 'reason', 'public_message', 'created_at', 'people_application_id'])->keyBy('candidate_submission_id') : collect();
|
||||
$rows->through(fn ($row) => ['id' => $row->id, 'reference' => $row->reference, 'received_at' => $row->created_at, 'snapshot' => json_decode($row->snapshot, true, 32, JSON_THROW_ON_ERROR), 'decision' => $decisions->get($row->id)]);
|
||||
app(RequisitionWorkflow::class)->audit($actor, 'candidate_submissions.consulted', null, null, ['process_id' => $process]);
|
||||
|
||||
return $this->page($request, 'people/submissions', ['process' => ['id' => $record->id, 'title' => $record->title, 'version' => $record->version], 'publication' => $submissions->publication($record), 'submissions' => $rows,
|
||||
'canPrepare' => $triage->canPrepare($actor, $record), 'canTriage' => Schema::hasTable('candidate_triage') && $triage->canPrepare($actor, $record) && in_array($record->status, ['open', 'assessment'], true),
|
||||
'publicUrl' => '/candidaturas/'.$actor->tenant_id.'/oportunidades/'.$record->id]);
|
||||
});
|
||||
}
|
||||
|
||||
public function publish(Request $request, int $process, CandidateSubmissions $submissions): Response
|
||||
{
|
||||
$submissions->publish($this->staff($request), $process, $request->all());
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
public function triage(Request $request, int $process, int $submission, CandidateTriage $triage): Response
|
||||
{
|
||||
$triage->decide($this->staff($request), $process, $submission, $request->all());
|
||||
|
||||
return back();
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $props */
|
||||
private function page(Request $request, string $component, array $props): Response
|
||||
{
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render($component, $props)->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
$response->headers->set('X-Robots-Tag', 'noindex, nofollow');
|
||||
$response->headers->set('Referrer-Policy', 'no-referrer');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use App\Services\AiPreparationPlan;
|
||||
use App\Services\ComparisonPolicy;
|
||||
use App\Services\IntelligencePolicy;
|
||||
use App\Services\OrganizationAccess;
|
||||
use App\Services\OrganizationConfiguration;
|
||||
use Brick\Math\BigDecimal;
|
||||
@@ -23,6 +26,11 @@ class OrganizationConfigurationController extends Controller
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('company/parameters', ['ready' => $ready, 'active' => (bool) ($state->active ?? false), 'version' => $state->version ?? 1,
|
||||
'companyRules' => $this->companyRules((int) $actor->tenant_id),
|
||||
'comparisonPolicy' => app(ComparisonPolicy::class)->company(Tenant::query()->findOrFail($actor->tenant_id)),
|
||||
'intelligencePolicy' => app(IntelligencePolicy::class)->company(Tenant::query()->findOrFail($actor->tenant_id)),
|
||||
'intelligenceReady' => Schema::hasColumn('tenants', 'intelligence_policy'),
|
||||
'aiPlan' => app(AiPreparationPlan::class)->company(Tenant::query()->findOrFail($actor->tenant_id)),
|
||||
'aiDomains' => AiPreparationPlan::DOMAINS, 'aiDataScopes' => AiPreparationPlan::SCOPES,
|
||||
'permissions' => OrganizationAccess::PERMISSIONS,
|
||||
'profiles' => $ready ? DB::table('organization_profiles')->where('tenant_id', $actor->tenant_id)->orderBy('name')->get()->map(fn ($p) => ['id' => $p->id, 'name' => $p->name, 'permissions' => json_decode($p->permissions, true, 32, JSON_THROW_ON_ERROR)]) : [],
|
||||
'memberships' => $ready ? DB::table('organization_memberships')->where('tenant_id', $actor->tenant_id)->get(['user_id', 'profile_id', 'maintenance', 'expires_at', 'reason']) : [],
|
||||
@@ -51,6 +59,19 @@ class OrganizationConfigurationController extends Controller
|
||||
'expires_at' => 'nullable|date|after:now', 'reason' => 'required|string|min:10|max:4000'];
|
||||
} elseif ($command === 'rules') {
|
||||
$rules += ['first_limit' => ['required', 'string', 'regex:/^\d{1,12}(\.\d{1,2})?$/D'], 'second_limit' => ['required', 'string', 'regex:/^\d{1,12}(\.\d{1,2})?$/D'], 'includes_tax' => 'required|boolean', 'reason' => 'required|string|min:10|max:4000'];
|
||||
} elseif ($command === 'comparison') {
|
||||
$rules += ['minimum_proposals' => 'required|integer|between:1,100', 'minimum_evaluators' => 'required|integer|between:1,100',
|
||||
'weights' => 'required|array:price,quality,delivery,history,payment', 'reason' => 'required|string|min:10|max:4000'];
|
||||
foreach (array_keys(ComparisonPolicy::DEFAULTS['weights']) as $criterion) {
|
||||
$rules['weights.'.$criterion] = 'required|integer|between:0,100';
|
||||
}
|
||||
} elseif ($command === 'ai_plan') {
|
||||
$rules += app(AiPreparationPlan::class)->rules() + ['reason' => 'required|string|min:10|max:4000'];
|
||||
} elseif ($command === 'intelligence') {
|
||||
$rules += ['procurement_brief' => 'required|boolean', 'people_brief' => 'required|boolean', 'reason' => 'required|string|min:10|max:4000',
|
||||
'api_key' => 'prohibited', 'external_processing_enabled' => 'prohibited'];
|
||||
} elseif ($command === 'tax') {
|
||||
$rules += ['default_tax_rate' => 'required|numeric|decimal:0,2|between:0,100', 'reason' => 'required|string|min:10|max:4000'];
|
||||
} else {
|
||||
abort_unless($command === 'activate', 404);
|
||||
$rules += ['reason' => 'required|string|min:10|max:4000', 'confirm' => 'required|accepted'];
|
||||
@@ -67,6 +88,7 @@ class OrganizationConfigurationController extends Controller
|
||||
|
||||
return ['first_limit' => (string) BigDecimal::of((string) $company->first_limit_minor)->dividedBy(100, 2),
|
||||
'second_limit' => (string) BigDecimal::of((string) $company->second_limit_minor)->dividedBy(100, 2),
|
||||
'includes_tax' => $company->approval_includes_tax, 'provisional' => $company->rules_provisional];
|
||||
'includes_tax' => $company->approval_includes_tax, 'provisional' => $company->rules_provisional,
|
||||
'default_tax_rate' => (string) ($company->default_tax_rate ?? '16.00')];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AuditLog;
|
||||
use App\Models\PeopleProcess;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Services\AiPreparationPlan;
|
||||
use App\Services\CandidateTriage;
|
||||
use App\Services\IntelligencePolicy;
|
||||
use App\Services\PeopleAccess;
|
||||
use App\Services\PeopleClarifications;
|
||||
use App\Services\PeopleEvidenceBrief;
|
||||
use App\Services\PeopleForms;
|
||||
use App\Services\PeopleHiring;
|
||||
use App\Services\PeopleOperationalInsights;
|
||||
use App\Services\PeopleRegistry;
|
||||
use App\Services\PeopleWorkdesk;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PeopleHiringController extends Controller
|
||||
{
|
||||
public function index(Request $request, PeopleHiring $workflow): Response
|
||||
{
|
||||
return DB::transaction(function () use ($request, $workflow) {
|
||||
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
||||
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
}
|
||||
$kinds = $workflow->kinds($this->actor($request));
|
||||
abort_unless($kinds !== [], 403);
|
||||
$availableKinds = $kinds;
|
||||
$filter = $request->validate(['kind' => 'nullable|in:service,staff', 'search' => 'nullable|string|max:100', 'status' => ['nullable', Rule::in(PeopleWorkdesk::STATES)],
|
||||
'from' => 'nullable|date_format:Y-m-d', 'to' => ['nullable', 'date_format:Y-m-d', Rule::when($request->filled('from'), 'after_or_equal:from')]]);
|
||||
if (isset($filter['kind'])) {
|
||||
abort_unless(in_array($filter['kind'], $kinds, true), 403);
|
||||
$kinds = [$filter['kind']];
|
||||
}
|
||||
$ready = Schema::hasTable('people_processes') && Schema::hasTable('people_process_access');
|
||||
$query = app(PeopleAccess::class)->visible(PeopleProcess::query()->whereIn('kind', $kinds), $this->actor($request));
|
||||
if (($filter['search'] ?? '') !== '') {
|
||||
$search = $filter['search'];
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->whereLike('title', '%'.$search.'%');
|
||||
if (preg_match('/^(?:PES-)?([1-9][0-9]*)$/D', $search, $matches)) {
|
||||
$reference = filter_var($matches[1], FILTER_VALIDATE_INT, ['options' => ['min_range' => 1]]);
|
||||
if ($reference !== false) {
|
||||
$q->orWhere('id', $reference);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (! empty($filter['status'])) {
|
||||
$query->where('status', $filter['status']);
|
||||
}
|
||||
if (! empty($filter['from'])) {
|
||||
$query->whereDate('created_at', '>=', $filter['from']);
|
||||
}
|
||||
if (! empty($filter['to'])) {
|
||||
$query->whereDate('created_at', '<=', $filter['to']);
|
||||
}
|
||||
$workdesk = $ready ? app(PeopleWorkdesk::class)->build(clone $query, $this->actor($request)) : null;
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('people/index', ['ready' => $ready, 'kinds' => $kinds, 'availableKinds' => $availableKinds,
|
||||
'filters' => $filter, 'workdesk' => $workdesk, 'insights' => $workdesk === null ? null : app(PeopleOperationalInsights::class)->build($workdesk),
|
||||
'company' => Tenant::query()->findOrFail($this->actor($request)->tenant_id)->only(['name']),
|
||||
'canPrepare' => array_values(array_filter($availableKinds, fn ($kind) => $workflow->allowed($this->actor($request), $kind, 'prepare'))),
|
||||
'formFields' => ['staff' => PeopleForms::STAFF, 'service' => PeopleForms::SERVICE],
|
||||
'administration' => $ready && $this->actor($request)->hasFunctions(['admin']) ? PeopleProcess::query()->whereIn('kind', $kinds)->orderByDesc('id')->get(['id', 'kind', 'status']) : [],
|
||||
'processes' => $ready ? (clone $query)->select(['id', 'title', 'kind', 'status', 'closes_on', 'created_at'])->latest('id')->paginate(15)->withQueryString() : null])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
public function show(Request $request, int $process, PeopleHiring $workflow): Response
|
||||
{
|
||||
return DB::transaction(function () use ($request, $process, $workflow) {
|
||||
if (DB::getDriverName() === 'pgsql' && DB::transactionLevel() === 1) {
|
||||
DB::statement('SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
}
|
||||
$record = PeopleProcess::query()->findOrFail($process);
|
||||
$access = app(PeopleAccess::class);
|
||||
abort_unless($access->can($this->actor($request), $record, 'view'), 403);
|
||||
$registrySearch = $request->validate(['cadastro' => 'nullable|string|max:100'])['cadastro'] ?? '';
|
||||
$snapshot = $workflow->snapshot($record);
|
||||
$rows = $workflow->matrix($record);
|
||||
if (! $access->can($this->actor($request), $record, 'financial')) {
|
||||
unset($snapshot['rules']['annex']['fields']['financial']);
|
||||
unset($snapshot['selection']['rules']['annex']['fields']['financial']);
|
||||
}
|
||||
if (! $access->can($this->actor($request), $record, 'interviews')) {
|
||||
$rows = array_map(fn ($row) => [...$row, 'reviews' => []], $rows);
|
||||
if (isset($snapshot['selection']['rows'])) {
|
||||
$snapshot['selection']['rows'] = array_map(fn ($row) => [...$row, 'reviews' => []], $snapshot['selection']['rows']);
|
||||
}
|
||||
}
|
||||
if (! $access->can($this->actor($request), $record, 'contract') && ! $access->can($this->actor($request), $record, 'contract_view')) {
|
||||
$snapshot['contract'] = null;
|
||||
}
|
||||
$clarifications = app(PeopleClarifications::class)->page($this->actor($request), $record);
|
||||
$company = Tenant::query()->findOrFail($this->actor($request)->tenant_id);
|
||||
$policy = app(IntelligencePolicy::class)->company($company);
|
||||
$brief = app(PeopleEvidenceBrief::class)->build($snapshot, $rows, $clarifications, $access->can($this->actor($request), $record, 'financial'),
|
||||
$access->can($this->actor($request), $record, 'contract') || $access->can($this->actor($request), $record, 'contract_view'));
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('people/show', ['process' => $snapshot, 'candidates' => $rows, 'formFields' => $record->kind === 'staff' ? PeopleForms::STAFF : PeopleForms::SERVICE,
|
||||
'clarifications' => $clarifications, 'brief' => $policy['people_brief'] ? $brief : null,
|
||||
'registry' => app(PeopleRegistry::class)->lookup($this->actor($request), $record, $registrySearch),
|
||||
'aiPreparation' => app(AiPreparationPlan::class)->preview($company, 'people', $brief),
|
||||
'canPrepare' => $workflow->allowed($this->actor($request), $record->kind, 'prepare') && $access->can($this->actor($request), $record, 'prepare'),
|
||||
'canViewElectronicSources' => app(CandidateTriage::class)->canRead($this->actor($request), $record),
|
||||
'canReview' => $workflow->allowed($this->actor($request), $record->kind, 'review') && $access->can($this->actor($request), $record, 'review') && $this->actor($request)->id !== $record->created_by,
|
||||
'canApprove' => $workflow->allowed($this->actor($request), $record->kind, 'review') && $access->can($this->actor($request), $record, 'approve') && ! in_array($this->actor($request)->id, $workflow->decisionContributors($record), true),
|
||||
'canContract' => $workflow->allowed($this->actor($request), $record->kind, 'prepare') && $access->can($this->actor($request), $record, 'contract'),
|
||||
'canManage' => $access->manages($this->actor($request), $record),
|
||||
'history' => AuditLog::query()->where('action', 'like', 'people.%')->where('after->id', $record->id)->orderBy('id')->get(['id', 'action', 'user_id', 'created_at'])])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
public function access(Request $request, int $process, PeopleAccess $access): Response
|
||||
{
|
||||
$record = PeopleProcess::query()->findOrFail($process);
|
||||
abort_unless($access->manages($this->actor($request), $record), 403);
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('people/access', ['process' => $record->only(['id', 'kind', 'status']),
|
||||
'version' => DB::table('people_access_states')->where('people_process_id', $record->id)->value('version'), 'scopes' => PeopleAccess::SCOPES,
|
||||
'users' => User::query()->where('tenant_id', $record->tenant_id)->orderBy('name')->get(['id', 'name', 'tenant_id', 'purchasing_role'])
|
||||
->filter(fn ($user) => app(PeopleHiring::class)->allowed($user, $record->kind) || DB::table('people_process_access')->where('people_process_id', $record->id)->where('user_id', $user->id)->exists())
|
||||
->map(fn ($user) => $user->only(['id', 'name']))->values(),
|
||||
'assignments' => DB::table('people_process_access')->where('tenant_id', $record->tenant_id)->where('people_process_id', $record->id)->get()->map(fn ($row) => ['user_id' => $row->user_id, 'scopes' => json_decode($row->scopes, true, 16, JSON_THROW_ON_ERROR), 'expires_at' => $row->expires_at]),
|
||||
'canView' => $access->can($this->actor($request), $record, 'view')])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function assign(Request $request, int $process, PeopleAccess $access): Response
|
||||
{
|
||||
$access->assign($this->actor($request), $process, $request->all());
|
||||
|
||||
return redirect('/pessoas/'.$process.'/acessos');
|
||||
}
|
||||
|
||||
public function command(Request $request, int $process, string $command, PeopleHiring $workflow): Response
|
||||
{
|
||||
abort_unless(Schema::hasTable('people_processes'), 503, 'Instalação de Pessoas e contratação pendente.');
|
||||
$record = $workflow->execute($this->actor($request), $process, $command, $request->all());
|
||||
|
||||
return redirect('/pessoas/'.$record->id);
|
||||
}
|
||||
|
||||
public function store(Request $request, PeopleHiring $workflow): Response
|
||||
{
|
||||
abort_unless(Schema::hasTable('people_processes'), 503);
|
||||
$record = $workflow->execute($this->actor($request), null, 'create', $request->all());
|
||||
|
||||
return redirect('/pessoas/'.$record->id);
|
||||
}
|
||||
|
||||
public function clarification(Request $request, int $process, string $command, PeopleClarifications $workflow): Response
|
||||
{
|
||||
abort_unless(Schema::hasTable('people_clarifications'), 503, 'Instalação dos esclarecimentos pendente.');
|
||||
$data = $request->validate(['clarification_id' => 'nullable|integer|min:1']);
|
||||
$workflow->execute($this->actor($request), $process, $data['clarification_id'] ?? null, $command, $request->all());
|
||||
|
||||
return redirect('/pessoas/'.$process.'#people-clarifications');
|
||||
}
|
||||
|
||||
private function actor(Request $request): User
|
||||
{
|
||||
$actor = $request->user('web');
|
||||
abort_unless($actor instanceof User, 403);
|
||||
|
||||
return $actor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\PeopleRegistry;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class PeopleRegistryController extends Controller
|
||||
{
|
||||
public function index(Request $request, PeopleRegistry $registry): Response
|
||||
{
|
||||
abort_unless($registry->canRead($request->user()), 403);
|
||||
$filter = $request->validate(['search' => 'nullable|string|max:100', 'status' => 'nullable|in:active,archived,all']);
|
||||
$ready = Schema::hasTable('people_registry');
|
||||
$records = null;
|
||||
if ($ready) {
|
||||
$query = $registry->visible($request->user());
|
||||
if (($filter['search'] ?? '') !== '') {
|
||||
$query->where(fn ($q) => $q->whereLike('name', '%'.$filter['search'].'%')->orWhereLike('reference', '%'.$filter['search'].'%'));
|
||||
}
|
||||
if (($filter['status'] ?? 'active') !== 'all') {
|
||||
$query->where('active', ($filter['status'] ?? 'active') === 'active');
|
||||
}
|
||||
$records = $query->orderBy('name')->orderBy('id')->paginate(15)->withQueryString()->through(fn ($row) => $registry->item($request->user(), $row));
|
||||
}
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('people/registry', ['ready' => $ready, 'canCreate' => $registry->canCreate($request->user()), 'records' => $records, 'filters' => $filter])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function save(Request $request, PeopleRegistry $registry, ?int $person = null): Response
|
||||
{
|
||||
abort_unless(Schema::hasTable('people_registry'), 503, 'Instalação do cadastro de pessoas pendente.');
|
||||
$record = $registry->execute($request->user(), $person, $request->all());
|
||||
|
||||
return redirect('/pessoas/cadastro?'.http_build_query(['search' => $record->reference, 'status' => 'all']));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ use App\Models\PurchaseOrder;
|
||||
use App\Models\Quotation;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\User;
|
||||
use App\Services\ProcurementOperationalInsights;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -110,7 +112,10 @@ class ProcurementReportController extends Controller
|
||||
$records = $this->records(clone $query)->orderByDesc('id')->paginate(25)->withQueryString()->through(fn (Requisition $record) => $this->row($record));
|
||||
$departments = Department::query()->when($request->user()->departmentScoped(), fn ($q) => $q->whereKey($request->user()->department_id))->orderBy('name')->get(['id', 'name']);
|
||||
|
||||
return ['records' => $records, 'counts' => $counts, 'filters' => $filters, 'departments' => $departments, 'generatedAt' => now()->toIso8601String()];
|
||||
$instant = CarbonImmutable::now();
|
||||
$insights = app(ProcurementOperationalInsights::class)->build($this->records(clone $query), $instant);
|
||||
|
||||
return ['records' => $records, 'counts' => $counts, 'filters' => $filters, 'departments' => $departments, 'generatedAt' => $instant->toIso8601String(), 'insights' => $insights];
|
||||
});
|
||||
Inertia::encryptHistory();
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Requisition;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\AiPreparationPlan;
|
||||
use App\Services\IntelligencePolicy;
|
||||
use App\Services\ProcurementEvidenceBrief;
|
||||
use App\Services\ProcurementReviewReport;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
@@ -14,7 +18,11 @@ class ProcurementReviewReportController extends Controller
|
||||
{
|
||||
$report = $builder->build($request->user(), $requisition->id);
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('purchases/review-report', ['report' => $report])->toResponse($request);
|
||||
$company = Tenant::query()->findOrFail($request->user()->tenant_id);
|
||||
$policy = app(IntelligencePolicy::class)->company($company);
|
||||
$brief = app(ProcurementEvidenceBrief::class)->build($report);
|
||||
$response = Inertia::render('purchases/review-report', ['report' => $report, 'brief' => $policy['procurement_brief'] ? $brief : null,
|
||||
'aiPreparation' => app(AiPreparationPlan::class)->preview($company, 'procurement', $brief)])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
|
||||
@@ -8,8 +8,10 @@ use App\Models\PurchaseAward;
|
||||
use App\Models\Quotation;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\Supplier;
|
||||
use App\Services\ComparisonPolicy;
|
||||
use App\Services\ConflictGuard;
|
||||
use App\Services\ProposalComparison;
|
||||
use App\Services\QuotationPricing;
|
||||
use App\Services\RequisitionWorkflow;
|
||||
use Brick\Math\BigDecimal;
|
||||
use Brick\Math\RoundingMode;
|
||||
@@ -49,6 +51,8 @@ class QuotationController extends Controller
|
||||
'canCreate' => ! $closed && $request->user()->hasFunctions([PurchasingRole::Procurement]) && $requisition->status === 'approved' && ! $guard->participationBlocked($request->user(), $requisition),
|
||||
'canEvaluate' => ! $closed && $request->user()->hasFunctions([PurchasingRole::Procurement, PurchasingRole::Finance, PurchasingRole::DepartmentHead]) && $request->user()->id !== $requisition->requester_id && $requisition->status === 'approved' && ! $guard->participationBlocked($request->user(), $requisition),
|
||||
'ready' => $result['ready'],
|
||||
'topIds' => $result['top_ids'],
|
||||
'comparisonPolicy' => $result['policy'],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -61,6 +65,8 @@ class QuotationController extends Controller
|
||||
'payment_terms' => 'required|string|min:3|max:500', 'no_conflict' => 'accepted',
|
||||
'items' => 'required|array|min:1|max:100', 'items.*.requisition_item_id' => 'required|integer|distinct',
|
||||
'items.*.unit_price' => ['required', 'numeric', 'decimal:0,2', 'min:0.01', 'max:1000000000'],
|
||||
'items.*.discount_type' => 'sometimes|required|in:none,percent,amount',
|
||||
'items.*.discount_value' => 'required_with:items.*.discount_type|numeric|decimal:0,2|min:0|max:1000000000',
|
||||
]);
|
||||
DB::transaction(function () use ($request, $requisition, $data, $workflow) {
|
||||
$guard = app(ConflictGuard::class);
|
||||
@@ -84,6 +90,7 @@ class QuotationController extends Controller
|
||||
throw ValidationException::withMessages(['items' => 'A proposta deve conter todas as linhas da requisição.']);
|
||||
}
|
||||
$items = [];
|
||||
$terms = [];
|
||||
$subtotal = 0;
|
||||
$tax = 0;
|
||||
foreach ($data['items'] as $input) {
|
||||
@@ -91,7 +98,9 @@ class QuotationController extends Controller
|
||||
if (! $line) {
|
||||
throw ValidationException::withMessages(['items' => 'Artigo não pertence a esta requisição.']);
|
||||
}
|
||||
$price = BigDecimal::of($input['unit_price'])->multipliedBy(100)->toScale(0)->toInt();
|
||||
$pricing = app(QuotationPricing::class)->unit((string) $input['unit_price'], $input['discount_type'] ?? 'none', (string) ($input['discount_value'] ?? '0'));
|
||||
$terms[] = ['requisition_item_id' => $line->id, ...$pricing];
|
||||
$price = $pricing['net_unit_minor'];
|
||||
$amount = BigDecimal::of($line->quantity)->multipliedBy($price)->toScale(0, RoundingMode::HalfUp)->toInt();
|
||||
$vat = BigDecimal::of($amount)->multipliedBy($line->tax_rate)->dividedBy(100, 0, RoundingMode::HalfUp)->toInt();
|
||||
$subtotal += $amount;
|
||||
@@ -108,7 +117,8 @@ class QuotationController extends Controller
|
||||
'tenant_id' => $request->user()->tenant_id, 'requisition_id' => $requisition->id, 'supplier_id' => $supplier->id, 'created_by' => $request->user()->id,
|
||||
'reference' => $data['reference'], 'delivery_days' => $data['delivery_days'], 'valid_until' => $data['valid_until'], 'payment_terms' => $data['payment_terms'],
|
||||
'subtotal_minor' => $subtotal, 'tax_minor' => $tax, 'total_minor' => $subtotal + $tax, 'currency' => 'MZN',
|
||||
'weights' => ['price' => 40, 'quality' => 20, 'delivery' => 15, 'history' => 15, 'payment' => 10],
|
||||
'weights' => app(ComparisonPolicy::class)->forRequest($requisition)['weights'],
|
||||
'commercial_terms' => array_filter($terms, fn ($term) => $term['discount_type'] !== 'none') === [] ? null : ['version' => 1, 'discount_basis' => 'unit_before_vat', 'rounding' => 'net_unit_half_up_cent', 'lines' => $terms],
|
||||
]);
|
||||
$quote->items()->createMany($items);
|
||||
$workflow->audit($request->user(), 'quotation.created', $requisition, null, [...$quote->load('items')->toArray(), 'no_conflict' => true]);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\StockItem;
|
||||
use App\Services\RequisitionCatalogue;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RequisitionCatalogueController extends Controller
|
||||
{
|
||||
public function index(Request $request, RequisitionCatalogue $catalogue): Response
|
||||
{
|
||||
abort_unless($request->user()->hasFunctions(['admin', 'warehouse']), 403);
|
||||
$filter = $request->validate(['search' => 'nullable|string|max:100']);
|
||||
$ready = Schema::hasTable('requisition_catalogue');
|
||||
$query = StockItem::query()->where('tenant_id', $request->user()->tenant_id);
|
||||
if (($filter['search'] ?? '') !== '') {
|
||||
$query->where(fn ($q) => $q->whereLike('name', '%'.$filter['search'].'%')->orWhereLike('code', '%'.$filter['search'].'%'));
|
||||
}
|
||||
$records = $query->orderBy('code')->paginate(15)->withQueryString();
|
||||
$entries = $ready ? DB::table('requisition_catalogue')->where('tenant_id', $request->user()->tenant_id)->whereIn('stock_item_id', $records->pluck('id'))->get()->keyBy('stock_item_id') : collect();
|
||||
$records->through(function (StockItem $stock) use ($entries, $catalogue) {
|
||||
$entry = $entries->get($stock->id);
|
||||
|
||||
return [...$catalogue->source($stock), 'stock_basis' => $catalogue->basis($stock), 'version' => $entry->version ?? 0,
|
||||
'category' => $entry->category ?? '', 'purchase_type' => $entry->purchase_type ?? 'consumable', 'published' => (bool) ($entry->published ?? false),
|
||||
'source_current' => $entry === null || json_decode($entry->source_snapshot, true, 16, JSON_THROW_ON_ERROR) === $catalogue->source($stock)];
|
||||
});
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('stock/catalogue', ['records' => $records, 'ready' => $ready, 'canManage' => $catalogue->canManage($request->user()), 'filters' => $filter])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function save(Request $request, int $stock, RequisitionCatalogue $catalogue): Response
|
||||
{
|
||||
abort_unless(Schema::hasTable('requisition_catalogue'), 503, 'Instalação do catálogo pendente.');
|
||||
$catalogue->save($request->user(), $stock, $request->all());
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,11 @@ use App\Models\SparePart;
|
||||
use App\Models\StockItem;
|
||||
use App\Models\TechnicalReview;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\ComparisonPolicy;
|
||||
use App\Services\ConflictGuard;
|
||||
use App\Services\EquipmentWorkflow;
|
||||
use App\Services\OrganizationAccess;
|
||||
use App\Services\RequisitionCatalogue;
|
||||
use App\Services\RequisitionWorkflow;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -57,7 +59,9 @@ class RequisitionController extends Controller
|
||||
return Inertia::render('requisitions/form', [
|
||||
'costCenters' => CostCenter::query()->where('department_id', $request->user()->department_id)->orderBy('code')->get(),
|
||||
'requisition' => null,
|
||||
'defaultTaxRate' => (string) (Tenant::query()->findOrFail($request->user()->tenant_id)->default_tax_rate ?? '16.00'),
|
||||
...$this->equipmentOptions($request),
|
||||
'catalogue' => $this->catalogueOptions($request),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -75,6 +79,7 @@ class RequisitionController extends Controller
|
||||
|
||||
return Inertia::render('requisitions/show', [
|
||||
'requisition' => $requisition,
|
||||
'comparisonPolicy' => app(ComparisonPolicy::class)->forRequest($requisition),
|
||||
'canEdit' => app(OrganizationAccess::class)->canRequest($request->user()) && $requisition->requester_id === $request->user()->id && $requisition->status === 'draft',
|
||||
'canDecide' => $requisition->canDecide($request->user()),
|
||||
'conflictBlocked' => app(ConflictGuard::class)->participationBlocked($request->user(), $requisition),
|
||||
@@ -94,8 +99,10 @@ class RequisitionController extends Controller
|
||||
|
||||
return Inertia::render('requisitions/form', [
|
||||
'requisition' => $requisition->load('items'),
|
||||
'defaultTaxRate' => (string) (Tenant::query()->findOrFail($request->user()->tenant_id)->default_tax_rate ?? '16.00'),
|
||||
'costCenters' => CostCenter::query()->where('department_id', $request->user()->department_id)->orderBy('code')->get(),
|
||||
...$this->equipmentOptions($request),
|
||||
'catalogue' => $this->catalogueOptions($request),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -108,6 +115,14 @@ class RequisitionController extends Controller
|
||||
return ['equipment' => $equipment, 'spareParts' => $parts, 'stockItems' => StockItem::query()->whereIn('id', $parts->pluck('stock_item_id'))->get(['id', 'code', 'name', 'unit'])];
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private function catalogueOptions(Request $request): array
|
||||
{
|
||||
$search = $request->validate(['catalogo' => 'nullable|string|max:100'])['catalogo'] ?? '';
|
||||
|
||||
return app(RequisitionCatalogue::class)->options($request->user(), $search);
|
||||
}
|
||||
|
||||
public function technical(Request $request, Requisition $requisition): RedirectResponse
|
||||
{
|
||||
$data = $request->validate(['basis_hash' => 'required|string|size:64', 'decision' => ['required', Rule::in(['approved', 'rejected'])], 'reason' => 'required|string|max:4000']);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\OrganizationConfiguration;
|
||||
use App\Services\SubmissionForms;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Inertia\Inertia;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SubmissionFormsController extends Controller
|
||||
{
|
||||
public function index(Request $request, SubmissionForms $forms, OrganizationConfiguration $configuration): Response
|
||||
{
|
||||
$actor = $configuration->administrator($request->user());
|
||||
Inertia::encryptHistory();
|
||||
$response = Inertia::render('company/submission-forms', ['forms' => $forms->listing($actor), 'canManage' => $forms->canManage($actor),
|
||||
'ready' => Schema::hasTable('submission_forms'), 'externalPortalAvailable' => false])->toResponse($request);
|
||||
$response->headers->set('Cache-Control', 'private, no-store, max-age=0');
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function save(Request $request, SubmissionForms $forms): Response
|
||||
{
|
||||
$forms->save($request->user(), $request->all());
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user