Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
CompanyRolePlayProject
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
7 / 7
12
100.00% covered (success)
100.00%
1 / 1
 newFactory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 company
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 creator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 scopeForCompany
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 scopeActive
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 scopeForGroups
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 scopeAvailableToUser
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3namespace App\Http\Models;
4
5use App\Http\Models\Admin\Company;
6use App\Http\Models\Auth\User;
7use Database\Factories\Http\Models\CompanyRolePlayProjectFactory;
8use Illuminate\Database\Eloquent\Factories\HasFactory;
9
10/**
11 * CompanyRolePlayProject Model
12 *
13 * Stores company-level roleplay projects that company admins create
14 * for their users to practice with. These projects serve as templates
15 * that can be assigned to specific groups within a company.
16 *
17 * @property string $id The unique identifier
18 * @property string $company_id The ID of the company that owns this project
19 * @property string $created_by The ID of the admin user who created this project
20 * @property string $name The name of the roleplay project
21 * @property string $type The type of call (e.g., 'cold-call', 'discovery-call')
22 * @property string|null $description A description of the roleplay scenario
23 * @property string $industry The target industry for the roleplay
24 * @property array<string> $target_job_titles Array of job titles targeted in this roleplay
25 * @property array<string> $key_features Array of key product/service features
26 * @property string|null $product_description Description of the product being sold
27 * @property int $difficulty_level The difficulty level (1-5)
28 * @property array<array{id: int, company_name: string, company_size: string, budget: string, decision_making: string, urgency_level: string, openess_to_new_solutions: string, communication_style: string, pain_points: string, current_solution: string, personality: string}> $customer_profiles Array of customer profile objects
29 * @property array $training_personalities Array of training personality configurations
30 * @property array<array{name: string, is_default: bool, weight: int, criteria: array}> $scorecard_config Array of scorecard configuration objects
31 * @property array<array{category: string, options: array<string>}> $objections Array of objection objects with categories and options
32 * @property bool $allow_user_customization (Legacy) Whether users can customize the project when cloning — retained for backwards compatibility; superseded by `allow_clone`.
33 * @property bool $allow_clone Whether the persona can be cloned into a user-owned {@see \App\Http\Models\RolePlayProjects}. Defaults to the legacy `allow_user_customization` value when absent.
34 * @property bool $allow_direct_calls Whether users can initiate a session directly against the corporate persona (without cloning first). Defaults to true.
35 * @property array<string> $assigned_groups Array of group IDs this project is assigned to (empty means all users in company)
36 * @property array<string> $assigned_users Array of user IDs explicitly granted access (empty means fall back to group assignment)
37 * @property string $status The project status ('active', 'inactive', 'archived')
38 * @property \Illuminate\Support\Carbon|null $created_at
39 * @property \Illuminate\Support\Carbon|null $updated_at
40 *
41 * @property-read \App\Http\Models\Admin\Company $company
42 * @property-read \App\Http\Models\Auth\User $creator
43 */
44class CompanyRolePlayProject extends Moloquent
45{
46    use HasFactory;
47
48    /**
49     * The database table (collection) used by the model.
50     *
51     * @var string
52     */
53    protected $table = 'company_roleplay_projects';
54
55    /**
56     * The attributes that are mass assignable.
57     *
58     * @var array<int, string>
59     */
60    protected $fillable = [
61        'company_id',
62        'created_by',
63        'name',
64        'type',
65        'description',
66        'industry',
67        'target_job_titles',
68        'company_sizes',
69        'key_features',
70        'product_description',
71        'difficulty_level',
72        'customer_profiles',
73        'scorecard_config',
74        'objections',
75        'allow_user_customization',
76        'allow_clone',
77        'allow_direct_calls',
78        'assigned_groups',
79        'assigned_users',
80        'status',
81    ];
82
83    /**
84     * The attributes that should be cast.
85     *
86     * @var array<string, string>
87     */
88    protected $casts = [
89        'allow_user_customization' => 'boolean',
90        'allow_clone' => 'boolean',
91        'allow_direct_calls' => 'boolean',
92        'difficulty_level' => 'integer',
93        'created_at' => 'datetime',
94        'updated_at' => 'datetime',
95    ];
96
97    /**
98     * Create a new factory instance for the model.
99     *
100     * @return \Database\Factories\Http\Models\CompanyRolePlayProjectFactory
101     */
102    protected static function newFactory()
103    {
104        return CompanyRolePlayProjectFactory::new();
105    }
106
107    /**
108     * Get the company that owns this roleplay project.
109     *
110     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
111     */
112    public function company()
113    {
114        return $this->belongsTo(Company::class, 'company_id');
115    }
116
117    /**
118     * Get the admin user who created this project.
119     *
120     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
121     */
122    public function creator()
123    {
124        return $this->belongsTo(User::class, 'created_by');
125    }
126
127    /**
128     * Scope query to projects belonging to a specific company.
129     *
130     * @param \Illuminate\Database\Eloquent\Builder $query
131     * @param string $companyId The company ID to filter by
132     * @return \Illuminate\Database\Eloquent\Builder
133     */
134    public function scopeForCompany($query, $companyId)
135    {
136        return $query->where('company_id', $companyId);
137    }
138
139    /**
140     * Scope query to only active projects.
141     *
142     * @param \Illuminate\Database\Eloquent\Builder $query
143     * @return \Illuminate\Database\Eloquent\Builder
144     */
145    public function scopeActive($query)
146    {
147        return $query->where('status', 'active');
148    }
149
150    /**
151     * Scope query to projects assigned to specific groups.
152     *
153     * Projects with empty or null assigned_groups are available to all users,
154     * so they are always included in the results.
155     *
156     * @param \Illuminate\Database\Eloquent\Builder $query
157     * @param array<string> $groupIds The group IDs to filter by
158     * @return \Illuminate\Database\Eloquent\Builder
159     */
160    public function scopeForGroups($query, array $groupIds)
161    {
162        return $query->where(function ($q) use ($groupIds) {
163            // Match projects assigned to any of the given groups
164            if (! empty($groupIds)) {
165                $q->whereIn('assigned_groups', $groupIds);
166            }
167            // Also include projects with no group restriction (available to all)
168            $q->orWhereNull('assigned_groups')
169                ->orWhere('assigned_groups', []);
170        });
171    }
172
173    /**
174     * Scope to the corporate personas that a given user is allowed to see/use.
175     *
176     * A persona is "available" to a user when ALL of the following hold:
177     *  - status is "active"
178     *  - persona.company_id matches the user's company_id
179     *  - assigned_groups is empty, OR at least one entry intersects the user's
180     *    group ids (direct `company_group_id` + its parent group, when nested)
181     *  - assigned_users is empty, OR explicitly contains the user's id
182     *
183     * Empty arrays behave as "no restriction" so admins can target broadly
184     * (groups) or narrowly (explicit user pins) without double-gating.
185     *
186     * @param  \Illuminate\Database\Eloquent\Builder  $query
187     * @param  \App\Http\Models\Auth\User  $user
188     * @return \Illuminate\Database\Eloquent\Builder
189     */
190    public function scopeAvailableToUser($query, User $user)
191    {
192        $groupIds = [];
193        if (! empty($user->company_group_id)) {
194            $groupIds[] = (string) $user->company_group_id;
195
196            $group = \App\Http\Models\Admin\CompanyGroup::find($user->company_group_id);
197            if ($group && $group->parent_id) {
198                $groupIds[] = (string) $group->parent_id;
199            }
200        }
201
202        return $query
203            ->active()
204            ->forCompany($user->company_id)
205            ->where(function ($q) use ($groupIds) {
206                if (! empty($groupIds)) {
207                    $q->whereIn('assigned_groups', $groupIds);
208                }
209                $q->orWhereNull('assigned_groups')
210                    ->orWhere('assigned_groups', []);
211            })
212            ->where(function ($q) use ($user) {
213                $q->whereNull('assigned_users')
214                    ->orWhere('assigned_users', [])
215                    ->orWhere('assigned_users', (string) $user->id);
216            });
217    }
218}