Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
99.20% covered (success)
99.20%
124 / 125
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
RolePlayOptionsController
99.20% covered (success)
99.20%
124 / 125
50.00% covered (danger)
50.00%
1 / 2
14
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 index
99.19% covered (success)
99.19%
123 / 124
0.00% covered (danger)
0.00%
0 / 1
13
1<?php
2
3namespace App\Http\Controllers\v2\RolePlay;
4
5use App\Http\Controllers\Controller;
6use App\Http\Models\Parameter;
7use App\Http\Models\RolePlayConfig;
8use App\Http\Models\RolePlayObjections;
9use App\Http\Models\RolePlayProjects;
10use App\Http\Models\RolePlaySkillProgressions;
11use App\Http\Services\RolePlay\ObjectionNormalizer;
12use App\Traits\SubscriptionTrait;
13use Illuminate\Http\JsonResponse;
14use Illuminate\Http\Request;
15
16class RolePlayOptionsController extends Controller
17{
18    use SubscriptionTrait;
19
20    public function __construct() {}
21
22    public function index(Request $request): JsonResponse
23    {
24        $objections = RolePlayObjections::where('status', 'active')->orderBy('category', 'asc')->get();
25        $skillProgression = RolePlaySkillProgressions::where('status', 'active')->get();
26        $callTypes = [
27            [
28                'value' => RolePlayProjects::$COLD_CALL,
29                'label' => 'Cold Call',
30            ],
31            [
32                'value' => RolePlayProjects::$DISCOVERY_CALL,
33                'label' => 'Discovery Call',
34            ],
35        ];
36        $companySizes = [
37            [
38                'value' => RolePlayProjects::$COMPANY_SIZE_SMALL,
39                'label' => RolePlayProjects::$COMPANY_SIZE_SMALL,
40            ],
41            [
42                'value' => RolePlayProjects::$COMPANY_SIZE_MEDIUM,
43                'label' => RolePlayProjects::$COMPANY_SIZE_MEDIUM,
44            ],
45            [
46                'value' => RolePlayProjects::$COMPANY_SIZE_LARGE,
47                'label' => RolePlayProjects::$COMPANY_SIZE_LARGE,
48            ],
49        ];
50        $levels = [
51            [
52                'value' => RolePlayProjects::$LEVEL_LOW,
53                'label' => RolePlayProjects::$LEVEL_LOW,
54            ],
55            [
56                'value' => RolePlayProjects::$LEVEL_MODERATE,
57                'label' => RolePlayProjects::$LEVEL_MODERATE,
58            ],
59            [
60                'value' => RolePlayProjects::$LEVEL_HIGH,
61                'label' => RolePlayProjects::$LEVEL_HIGH,
62            ],
63            [
64                'value' => RolePlayProjects::$LEVEL_CRITICAL,
65                'label' => RolePlayProjects::$LEVEL_CRITICAL,
66            ],
67        ];
68        $timeFilters = [
69            [
70                'value' => 'all_time',
71                'label' => 'All Time',
72            ],
73            [
74                'value' => 'this_week',
75                'label' => 'This Week',
76            ],
77            [
78                'value' => 'this_month',
79                'label' => 'This Month',
80            ],
81        ];
82        // Load personalities and images from RolePlayConfig or fallback to Parameters
83        $config = RolePlayConfig::getGlobal();
84
85        if ($config) {
86            $personalitiesRaw = $config->personalities ?? [];
87            $maleImages = $config->images['male'] ?? [];
88            $femaleImages = $config->images['female'] ?? [];
89        } else {
90            $personalitiesParam = Parameter::where('name', 'role_play_personalities')->first();
91            $personalitiesRaw = $personalitiesParam->value ?? [];
92            $maleImages = Parameter::where('name', 'role_play_male_images')->first()?->value ?? [];
93            $femaleImages = Parameter::where('name', 'role_play_female_images')->first()?->value ?? [];
94        }
95
96        $personalities = collect($personalitiesRaw)->map(function ($param) {
97            return [
98                'value' => $param['type'],
99                'label' => $param['name'],
100                'description' => $param['description'] ?? '',
101            ];
102        })->toArray();
103
104        // Load default scorecards per call type
105        $defaultScorecards = [];
106        if ($config && ! empty($config->default_scorecards)) {
107            $defaultScorecards = $config->default_scorecards;
108        } else {
109            $defaultScorecards = [
110                'cold-call' => RolePlaySkillProgressions::$COLD_CALL_SCORECARD,
111                'discovery-call' => RolePlaySkillProgressions::$DISCOVERY_CALL,
112            ];
113        }
114
115        // Load default objections per call type (keyed by call type slug).
116        //
117        // Each seed row in `roleplay_objections` targets a single company
118        // size (small|medium|large|all). The persona form wants ONE merged
119        // category per name with per-option `company_sizes` toggles, so we
120        // group by (call_type, category) and tag each option with the seed
121        // row's size. RolePlayConfig may also ship a pre-built copy of
122        // this map â€” we still run everything through the normalizer so
123        // legacy documents end up in the canonical shape too.
124        $defaultObjections = [];
125
126        if ($config && ! empty($config->default_objections)) {
127            foreach ($config->default_objections as $callType => $objectionsList) {
128                $defaultObjections[$callType] = ObjectionNormalizer::normalize(
129                    is_array($objectionsList) ? $objectionsList : [],
130                );
131            }
132        } else {
133            $grouped = $objections->groupBy(function ($obj) {
134                return $obj->call_type ?? RolePlayProjects::$COLD_CALL;
135            });
136
137            foreach ($grouped as $callType => $items) {
138                $intermediate = [];
139
140                foreach ($items as $row) {
141                    $seedSize = strtolower((string) ($row->company_size ?? 'all'));
142                    $sizesForRow = $seedSize === 'all'
143                        ? RolePlayProjects::COMPANY_SIZE_KEYS
144                        : (in_array($seedSize, RolePlayProjects::COMPANY_SIZE_KEYS, true) ? [$seedSize] : RolePlayProjects::COMPANY_SIZE_KEYS);
145
146                    $rowOptions = is_array($row->options) ? $row->options : [];
147                    $intermediate[] = [
148                        'category' => (string) $row->category,
149                        'options' => array_map(
150                            fn ($text) => ['text' => (string) $text, 'company_sizes' => $sizesForRow],
151                            $rowOptions,
152                        ),
153                    ];
154                }
155
156                $defaultObjections[$callType] = ObjectionNormalizer::normalize($intermediate);
157            }
158        }
159
160        return response()->json([
161            'data' => [
162                'objections' => $objections,
163                'skill_progression' => $skillProgression,
164                'call_types' => $callTypes,
165                'company_sizes' => $companySizes,
166                'levels' => $levels,
167                'time_filters' => $timeFilters,
168                'personalities' => $personalities,
169                'personalities_full' => array_values($personalitiesRaw),
170                'images' => [
171                    'male' => $maleImages,
172                    'female' => $femaleImages,
173                ],
174                'default_scorecards' => $defaultScorecards,
175                'default_objections' => $defaultObjections,
176            ],
177        ]);
178    }
179}