Skip to content

Commit

Permalink
Merge pull request #415 from canyongbs/advapp-185-186
Browse files Browse the repository at this point in the history
[ADVAPP-185] & [ADVAPP-186]: Enforce license relationship for Retention CRM with respect to students / prospects
  • Loading branch information
Orrison authored Jan 4, 2024
2 parents f89fbc5 + 1a581ab commit faef579
Show file tree
Hide file tree
Showing 116 changed files with 1,436 additions and 266 deletions.
11 changes: 11 additions & 0 deletions _ide_helper_models.php
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ class IdeHelperSystemUser {}
* @method static \Illuminate\Database\Eloquent\Builder|User admins()
* @method static \Illuminate\Database\Eloquent\Builder|User advancedFilter($data)
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|User hasAnyLicense(\AdvisingApp\Authorization\Enums\LicenseType|array|string|null $type)
* @method static \Illuminate\Database\Eloquent\Builder|User hasLicense(\AdvisingApp\Authorization\Enums\LicenseType|array|string|null $type)
* @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|User onlyTrashed()
Expand Down Expand Up @@ -434,6 +436,7 @@ class IdeHelperUser {}
* @property-read \Illuminate\Database\Eloquent\Collection<int, \AdvisingApp\Audit\Models\Audit> $audits
* @property-read int|null $audits_count
* @method static \AdvisingApp\Alert\Database\Factories\AlertFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|Alert licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|Alert newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Alert newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Alert onlyTrashed()
Expand Down Expand Up @@ -613,6 +616,7 @@ class IdeHelperApplicationStep {}
* @property-read \AdvisingApp\Application\Models\ApplicationSubmissionState $state
* @property-read \AdvisingApp\Application\Models\Application $submissible
* @method static \AdvisingApp\Application\Database\Factories\ApplicationSubmissionFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|Submission licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|ApplicationSubmission newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ApplicationSubmission newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ApplicationSubmission query()
Expand Down Expand Up @@ -1347,6 +1351,7 @@ class IdeHelperEmailTemplate {}
* @method static \Illuminate\Database\Eloquent\Builder|Engagement isAwaitingDelivery()
* @method static \Illuminate\Database\Eloquent\Builder|Engagement isNotPartOfABatch()
* @method static \Illuminate\Database\Eloquent\Builder|Engagement isScheduled()
* @method static \Illuminate\Database\Eloquent\Builder|Engagement licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|Engagement newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Engagement newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Engagement query()
Expand Down Expand Up @@ -1721,6 +1726,7 @@ class IdeHelperFormStep {}
* @property-read \AdvisingApp\Form\Models\Form $submissible
* @method static \Illuminate\Database\Eloquent\Builder|FormSubmission canceled()
* @method static \AdvisingApp\Form\Database\Factories\FormSubmissionFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|Submission licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|FormSubmission newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|FormSubmission newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|FormSubmission notCanceled()
Expand Down Expand Up @@ -1804,6 +1810,7 @@ class IdeHelperTwilioConversation {}
* @property-read \AdvisingApp\Interaction\Models\InteractionType|null $type
* @property-read \App\Models\User|null $user
* @method static \AdvisingApp\Interaction\Database\Factories\InteractionFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|Interaction licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|Interaction newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Interaction newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Interaction query()
Expand Down Expand Up @@ -2619,6 +2626,7 @@ class IdeHelperOutboundDeliverable {}
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $subscribable
* @property-read \App\Models\User $user
* @method static \Illuminate\Database\Eloquent\Builder|Subscription licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|Subscription newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Subscription newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Subscription query()
Expand Down Expand Up @@ -2837,6 +2845,7 @@ class IdeHelperProspectStatus {}
* @property-read \AdvisingApp\ServiceManagement\Models\ServiceRequestStatus|null $status
* @property-read \AdvisingApp\ServiceManagement\Models\ServiceRequestType|null $type
* @method static \AdvisingApp\ServiceManagement\Database\Factories\ServiceRequestFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|ServiceRequest licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|ServiceRequest newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ServiceRequest newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|ServiceRequest onlyTrashed()
Expand Down Expand Up @@ -3322,6 +3331,7 @@ class IdeHelperSurveyStep {}
* @property-read \AdvisingApp\Survey\Models\Survey $submissible
* @method static \Illuminate\Database\Eloquent\Builder|SurveySubmission canceled()
* @method static \AdvisingApp\Survey\Database\Factories\SurveySubmissionFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|Submission licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|SurveySubmission newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|SurveySubmission newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|SurveySubmission notCanceled()
Expand Down Expand Up @@ -3370,6 +3380,7 @@ class IdeHelperSurveySubmission {}
* @property-read \App\Models\User|null $createdBy
* @method static \Illuminate\Database\Eloquent\Builder|Task byNextDue()
* @method static \AdvisingApp\Task\Database\Factories\TaskFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder|Task licensedToEducatable(string $relationship)
* @method static \Illuminate\Database\Eloquent\Builder|Task newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Task newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Task onlyTrashed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,15 @@
use Illuminate\Database\Eloquent\Model;
use AdvisingApp\Alert\Enums\AlertStatus;
use AdvisingApp\Prospect\Models\Prospect;
use App\Filament\Fields\EducatableSelect;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Filters\SelectFilter;
use Illuminate\Database\Eloquent\Builder;
use AdvisingApp\Alert\Enums\AlertSeverity;
use Filament\Forms\Components\MorphToSelect;
use Filament\Infolists\Components\TextEntry;
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\DeleteBulkAction;
use AdvisingApp\StudentDataModel\Models\Student;
use Filament\Forms\Components\MorphToSelect\Type;
use AdvisingApp\CaseloadManagement\Models\Caseload;
use AdvisingApp\Alert\Filament\Resources\AlertResource;
use AdvisingApp\StudentDataModel\Models\Scopes\EducatableSearch;
Expand Down Expand Up @@ -174,15 +173,8 @@ protected function getHeaderActions(): array
return [
CreateAction::make()
->form([
MorphToSelect::make('concern')
EducatableSelect::make('concern')
->label('Related To')
->types([
Type::make(Student::class)
->titleAttribute(Student::displayNameKey()),
Type::make(Prospect::class)
->titleAttribute(Prospect::displayNameKey()),
])
->searchable()
->required(),
Group::make()
->schema([
Expand Down
10 changes: 10 additions & 0 deletions app-modules/alert/src/Models/Alert.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
use AdvisingApp\Notification\Models\Contracts\Subscribable;
use AdvisingApp\StudentDataModel\Models\Contracts\Educatable;
use AdvisingApp\Audit\Models\Concerns\Auditable as AuditableTrait;
use AdvisingApp\StudentDataModel\Models\Scopes\LicensedToEducatable;
use AdvisingApp\StudentDataModel\Models\Concerns\BelongsToEducatable;
use AdvisingApp\Campaign\Models\Contracts\ExecutableFromACampaignAction;
use AdvisingApp\Notification\Models\Contracts\CanTriggerAutoSubscription;

Expand All @@ -62,6 +64,7 @@ class Alert extends BaseModel implements Auditable, CanTriggerAutoSubscription,
{
use SoftDeletes;
use AuditableTrait;
use BelongsToEducatable;

protected $fillable = [
'concern_id',
Expand Down Expand Up @@ -113,4 +116,11 @@ public static function executeFromCampaignAction(CampaignAction $action): bool|s

// Do we need to be able to relate campaigns/actions to the RESULT of their actions?
}

protected static function booted(): void
{
static::addGlobalScope('licensed', function (Builder $builder) {
$builder->tap(new LicensedToEducatable('concern'));
});
}
}
31 changes: 31 additions & 0 deletions app-modules/alert/src/Policies/AlertPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,20 @@
use App\Models\Authenticatable;
use AdvisingApp\Alert\Models\Alert;
use Illuminate\Auth\Access\Response;
use AdvisingApp\Prospect\Models\Prospect;
use AdvisingApp\StudentDataModel\Models\Student;

class AlertPolicy
{
public function before(Authenticatable $authenticatable): ?Response
{
if (! $authenticatable->hasAnyLicense([Student::getLicenseType(), Prospect::getLicenseType()])) {
return Response::deny('You are not licensed for the Retention or Recruitment CRM.');
}

return null;
}

public function viewAny(Authenticatable $authenticatable): Response
{
return $authenticatable->canOrElse(
Expand All @@ -52,6 +63,10 @@ public function viewAny(Authenticatable $authenticatable): Response

public function view(Authenticatable $authenticatable, Alert $alert): Response
{
if (! $authenticatable->hasLicense($alert->concern?->getLicenseType())) {
return Response::deny('You do not have permission to view this alert.');
}

return $authenticatable->canOrElse(
abilities: ['alert.*.view', "alert.{$alert->id}.view"],
denyResponse: 'You do not have permission to view this alert.'
Expand All @@ -68,6 +83,10 @@ public function create(Authenticatable $authenticatable): Response

public function update(Authenticatable $authenticatable, Alert $alert): Response
{
if (! $authenticatable->hasLicense($alert->concern?->getLicenseType())) {
return Response::deny('You do not have permission to update this alert.');
}

return $authenticatable->canOrElse(
abilities: ['alert.*.update', "alert.{$alert->id}.update"],
denyResponse: 'You do not have permission to update this alert.'
Expand All @@ -76,6 +95,10 @@ public function update(Authenticatable $authenticatable, Alert $alert): Response

public function delete(Authenticatable $authenticatable, Alert $alert): Response
{
if (! $authenticatable->hasLicense($alert->concern?->getLicenseType())) {
return Response::deny('You do not have permission to delete this alert.');
}

return $authenticatable->canOrElse(
abilities: ['alert.*.delete', "alert.{$alert->id}.delete"],
denyResponse: 'You do not have permission to delete this alert.'
Expand All @@ -84,6 +107,10 @@ public function delete(Authenticatable $authenticatable, Alert $alert): Response

public function restore(Authenticatable $authenticatable, Alert $alert): Response
{
if (! $authenticatable->hasLicense($alert->concern?->getLicenseType())) {
return Response::deny('You do not have permission to restore this alert.');
}

return $authenticatable->canOrElse(
abilities: ['alert.*.restore', "alert.{$alert->id}.restore"],
denyResponse: 'You do not have permission to restore this alert.'
Expand All @@ -92,6 +119,10 @@ public function restore(Authenticatable $authenticatable, Alert $alert): Respons

public function forceDelete(Authenticatable $authenticatable, Alert $alert): Response
{
if (! $authenticatable->hasLicense($alert->concern?->getLicenseType())) {
return Response::deny('You do not have permission to permanently delete this alert.');
}

return $authenticatable->canOrElse(
abilities: ['alert.*.force-delete', "alert.{$alert->id}.force-delete"],
denyResponse: 'You do not have permission to permanently delete this alert.'
Expand Down
5 changes: 3 additions & 2 deletions app-modules/alert/tests/AlertCreateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@
use function Pest\Laravel\actingAs;

use Illuminate\Support\Facades\Notification;
use AdvisingApp\Authorization\Enums\LicenseType;
use AdvisingApp\StudentDataModel\Models\Student;
use AdvisingApp\Alert\Notifications\AlertCreatedNotification;

it('creates a subscription for the user that created the Alert', function () {
$user = User::factory()->create();
$user = User::factory()->licensed(LicenseType::cases())->create();

actingAs($user);

Expand All @@ -60,7 +61,7 @@
it('dispatches the proper notifications to subscribers on created', function () {
Notification::fake();

$users = User::factory()->count(5)->create();
$users = User::factory()->licensed(LicenseType::cases())->count(5)->create();

/** @var Student $student */
$student = Student::factory()->create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
use Filament\Forms\Components\Grid;
use AdvisingApp\Form\Enums\Rounding;
use AdvisingApp\Form\Rules\IsDomain;
use App\Forms\Components\ColorSelect;
use App\Filament\Fields\ColorSelect;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\Section;
Expand Down
14 changes: 11 additions & 3 deletions app-modules/authorization/src/Enums/LicenseType.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,22 @@ public function hasAvailableLicenses(): bool
{
$totalLicensesInUse = License::query()->where('type', $this)->count();

return $totalLicensesInUse < $this->getSeats();
}

public function isLicenseable(): bool
{
return $this->getSeats() > 0;
}

public function getSeats(): int
{
$licenseSettings = app(LicenseSettings::class);

$licenseLimit = match ($this) {
return match ($this) {
LicenseType::ConversationalAi => $licenseSettings->data->limits->conversationalAiSeats,
LicenseType::RetentionCrm => $licenseSettings->data->limits->retentionCrmSeats,
LicenseType::RecruitmentCrm => $licenseSettings->data->limits->recruitmentCrmSeats,
};

return $totalLicensesInUse < $licenseLimit;
}
}
11 changes: 11 additions & 0 deletions app-modules/campaign/src/Models/Campaign.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,15 @@ public function hasBeenExecuted(): bool
{
return $this->actions->contains(fn (CampaignAction $action) => $action->hasBeenExecuted());
}

protected static function booted(): void
{
static::addGlobalScope('licensed', function (Builder $builder) {
if (! auth()->check()) {
return;
}

$builder->whereHas('caseload');
});
}
}
20 changes: 20 additions & 0 deletions app-modules/campaign/src/Policies/CampaignPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public function viewAny(Authenticatable $authenticatable): Response

public function view(Authenticatable $authenticatable, Campaign $campaign): Response
{
if ($authenticatable->cannot('view', $campaign->caseload)) {
return Response::deny('You do not have permission to view this campaign.');
}

return $authenticatable->canOrElse(
abilities: ['campaign.*.view', "campaign.{$campaign->id}.view"],
denyResponse: 'You do not have permission to view this campaign.'
Expand All @@ -68,6 +72,10 @@ public function create(Authenticatable $authenticatable): Response

public function update(Authenticatable $authenticatable, Campaign $campaign): Response
{
if ($authenticatable->cannot('view', $campaign->caseload)) {
return Response::deny('You do not have permission to update this campaign.');
}

return $authenticatable->canOrElse(
abilities: ['campaign.*.update', "campaign.{$campaign->id}.update"],
denyResponse: 'You do not have permission to update this campaign.'
Expand All @@ -76,6 +84,10 @@ public function update(Authenticatable $authenticatable, Campaign $campaign): Re

public function delete(Authenticatable $authenticatable, Campaign $campaign): Response
{
if ($authenticatable->cannot('view', $campaign->caseload)) {
return Response::deny('You do not have permission to delete this campaign.');
}

return $authenticatable->canOrElse(
abilities: ['campaign.*.delete', "campaign.{$campaign->id}.delete"],
denyResponse: 'You do not have permission to delete this campaign.'
Expand All @@ -84,6 +96,10 @@ public function delete(Authenticatable $authenticatable, Campaign $campaign): Re

public function restore(Authenticatable $authenticatable, Campaign $campaign): Response
{
if ($authenticatable->cannot('view', $campaign->caseload)) {
return Response::deny('You do not have permission to restore this campaign.');
}

return $authenticatable->canOrElse(
abilities: ['campaign.*.restore', "campaign.{$campaign->id}.restore"],
denyResponse: 'You do not have permission to restore this campaign.'
Expand All @@ -92,6 +108,10 @@ public function restore(Authenticatable $authenticatable, Campaign $campaign): R

public function forceDelete(Authenticatable $authenticatable, Campaign $campaign): Response
{
if ($authenticatable->cannot('view', $campaign->caseload)) {
return Response::deny('You do not have permission to permanently delete this campaign.');
}

return $authenticatable->canOrElse(
abilities: ['campaign.*.force-delete', "campaign.{$campaign->id}.force-delete"],
denyResponse: 'You do not have permission to permanently delete this campaign.'
Expand Down
Loading

0 comments on commit faef579

Please sign in to comment.