186 lines
8.4 KiB
PHP
186 lines
8.4 KiB
PHP
<?php
|
|
|
|
namespace App\Policies;
|
|
|
|
use App\Models\ExistingBusiness;
|
|
use App\Models\User;
|
|
use App\Services\PortalProfileAccess;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class ExistingBusinessPolicy
|
|
{
|
|
public function create(User $user): bool
|
|
{
|
|
return $user->hasVerifiedEmail();
|
|
}
|
|
|
|
public function update(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return $this->owns($user, $business) && in_array($business->status, ['draft', 'needs_information'], true);
|
|
}
|
|
|
|
public function view(User $user, ExistingBusiness $business): bool
|
|
{
|
|
if ($this->owns($user, $business) || $this->review($user, $business) || $this->triage($user, $business)
|
|
|| $this->approveLevelOne($user, $business) || $this->approveLevelTwo($user, $business) || $this->publish($user, $business)) {
|
|
return true;
|
|
}
|
|
|
|
$reviewedStates = ['rejected', 'published', 'suspended', 'cancelled', 'archived'];
|
|
if (app(PortalProfileAccess::class)->available()) {
|
|
$reviewedStates[] = 'approved';
|
|
}
|
|
|
|
return ! $business->trashed() && $business->owner_user_id !== $user->id
|
|
&& in_array($business->status, $reviewedStates, true)
|
|
&& $this->designatedAny($user, $business);
|
|
}
|
|
|
|
public function delete(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return $this->update($user, $business);
|
|
}
|
|
|
|
public function owns(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return $user->hasVerifiedEmail() && $business->owner_user_id === $user->id && ! $business->trashed();
|
|
}
|
|
|
|
public function review(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return ! $business->trashed() && $business->owner_user_id !== $user->id
|
|
&& in_array($business->status, ['triage', 'in_review', 'needs_information'], true)
|
|
&& $this->designated($user, 'reviewers', $business);
|
|
}
|
|
|
|
public function triage(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return ! $business->trashed() && $business->owner_user_id !== $user->id
|
|
&& in_array($business->status, ['submitted', 'triage'], true)
|
|
&& $this->designated($user, 'triagers', $business);
|
|
}
|
|
|
|
public function approveLevelOne(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return ! $business->trashed() && $business->owner_user_id !== $user->id
|
|
&& $business->status === 'approval_l1'
|
|
&& $this->designated($user, 'approvers_l1', $business);
|
|
}
|
|
|
|
public function approveLevelTwo(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return ! $business->trashed() && $business->owner_user_id !== $user->id
|
|
&& $business->status === 'approval_l2'
|
|
&& $this->designated($user, 'approvers_l2', $business);
|
|
}
|
|
|
|
public function publish(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return ! $business->trashed() && $business->owner_user_id !== $user->id
|
|
&& $business->status === 'approved'
|
|
&& $this->designated($user, 'publishers', $business);
|
|
}
|
|
|
|
public function reviewQueue(User $user): bool
|
|
{
|
|
return $this->designatedAny($user);
|
|
}
|
|
|
|
/** Filter before projecting or decrypting private fields in the review queue. */
|
|
public function scopeReviewQueue(Builder $query, User $user): void
|
|
{
|
|
if (app(PortalProfileAccess::class)->available()) {
|
|
$query->whereIn('modality',array_keys(\App\Services\ExistingBusinessProfileCategory::MAP));
|
|
$ids = (clone $query)->get()->filter(fn (ExistingBusiness $business) => $this->view($user, $business))->modelKeys();
|
|
$query->whereKey($ids);
|
|
}
|
|
}
|
|
|
|
/** Supplement-only authority never changes the normal content/workflow abilities. */
|
|
public function prepareClassification(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return $this->owns($user, $business) && ! app(PortalProfileAccess::class)->has($user, 'auditor')
|
|
&& in_array($business->status, ['draft', 'needs_information', 'published'], true);
|
|
}
|
|
|
|
public function reviewClassification(User $user, ExistingBusiness $business): bool
|
|
{
|
|
return ! $business->trashed() && $business->owner_user_id !== $user->id
|
|
&& ! app(PortalProfileAccess::class)->has($user, 'auditor')
|
|
&& in_array($business->status, ['triage', 'in_review', 'needs_information', 'published'], true)
|
|
&& $this->designated($user, 'reviewers', $business);
|
|
}
|
|
|
|
/** Current responsibility check without requiring a reviewer's live browser session on public reads. */
|
|
public function classificationReviewerScope(User $user, ExistingBusiness $business, string $dimension): bool
|
|
{
|
|
if ($business->trashed() || ! $user->hasVerifiedEmail() || (int) $business->owner_user_id === (int) $user->id) {
|
|
return false;
|
|
}
|
|
$access = app(PortalProfileAccess::class);
|
|
if ($access->has($user, 'auditor')) { return false; }
|
|
if (! $access->available()) {
|
|
return $access->legacyWriteAllowed($user) && (bool) $user->two_factor_confirmed_at && (bool) $user->two_factor_secret
|
|
&& in_array(strtolower($user->email), config('existing-business.reviewers', []), true);
|
|
}
|
|
if ($business->modality !== 'company_sale') { return false; }
|
|
$responsibility = $dimension === 'regulation' ? 'legal_reviewer' : 'technical_reviewer';
|
|
return collect($access->grants($user, 'validator'))->contains(fn ($g) => $g->organization_id === null
|
|
&& in_array('business_sale', json_decode($g->opportunity_types, true), true)
|
|
&& in_array($responsibility, json_decode($g->responsibilities, true), true));
|
|
}
|
|
|
|
public function discloseClassification(User $user, ExistingBusiness $business): bool
|
|
{
|
|
if ($business->trashed() || $business->status !== 'published' || (int) $business->owner_user_id === (int) $user->id
|
|
|| app(PortalProfileAccess::class)->has($user, 'auditor') || ! $this->designated($user, 'publishers', $business)) {
|
|
return false;
|
|
}
|
|
return ! DB::table('business_workflow_transitions')->where('existing_business_id', $business->id)
|
|
->whereIn('to_status', ['approval_l2', 'approved'])->where('actor_id', $user->id)->exists();
|
|
}
|
|
|
|
private function designatedAny(User $user, ?ExistingBusiness $business = null): bool
|
|
{
|
|
foreach (['reviewers', 'triagers', 'approvers_l1', 'approvers_l2', 'publishers'] as $designation) {
|
|
if ($this->designated($user, $designation, $business)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function designated(User $user, string $designation, ?ExistingBusiness $business = null): bool
|
|
{
|
|
$access = app(PortalProfileAccess::class);
|
|
if ($access->available()) {
|
|
$category=$business ? \App\Services\ExistingBusinessProfileCategory::category($business) : null;
|
|
if (($business !== null && $category === null) || ! $access->mfa($user) || $access->has($user,'auditor')) {
|
|
return false;
|
|
}
|
|
$role = $designation === 'publishers' ? 'publisher' : 'validator';
|
|
if (! $access->operational($user, $role === 'publisher' ? 'publish' : 'validate')) {
|
|
return false;
|
|
}
|
|
foreach ($access->grants($user, $role) as $grant) {
|
|
$organizationMatches = $grant->organization_id === null
|
|
|| ($business !== null && $grant->organization_id === $business->organization_id);
|
|
if ($organizationMatches
|
|
&& ($category !== null ? in_array($category,json_decode($grant->opportunity_types,true,512,JSON_THROW_ON_ERROR),true) : array_intersect(array_values(\App\Services\ExistingBusinessProfileCategory::MAP),json_decode($grant->opportunity_types,true,512,JSON_THROW_ON_ERROR)) !== [])) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Legacy-only environments retain their explicit lists; a revoked/scoped v1
|
|
// assignment must never regain wider permissions through that allowlist.
|
|
return $user->hasVerifiedEmail() && $access->legacyWriteAllowed($user)
|
|
&& (bool) $user->two_factor_confirmed_at && (bool) $user->two_factor_secret
|
|
&& in_array(strtolower($user->email), config('existing-business.'.$designation, []), true);
|
|
}
|
|
}
|