Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
70 / 70
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
AdminRolePlayScorecardController
100.00% covered (success)
100.00%
70 / 70
100.00% covered (success)
100.00%
7 / 7
11
100.00% covered (success)
100.00%
1 / 1
 index
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 show
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
3
 update
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
 destroy
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 find
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isSupportedCallType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 serialize
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace App\Http\Controllers\v2\Admin;
4
5use App\Http\Controllers\Controller;
6use App\Http\Models\CompanyRolePlayScorecard;
7use App\Http\Models\RolePlayProjects;
8use Illuminate\Http\JsonResponse;
9use Illuminate\Http\Request;
10use Illuminate\Validation\Rule;
11
12/**
13 * Admin controller for per-call-type scorecards at the company level.
14 *
15 * Complements the user-facing {@see \App\Http\Controllers\v2\RolePlay\RolePlayScorecardController}:
16 *  - Admin writes here persist the `is_forced` flag alongside the scorecard.
17 *  - When `is_forced = true`, the user-side controller ships the scorecard
18 *    read-only; this controller remains the only mutation path.
19 *
20 * One row per (company_id, call_type). Upserts are keyed on that pair.
21 */
22class AdminRolePlayScorecardController extends Controller
23{
24    /**
25     * List all scorecards configured for the admin's company, one per call type.
26     */
27    public function index(Request $request): JsonResponse
28    {
29        $rows = CompanyRolePlayScorecard::forCompany($request->user()->company_id)->get();
30
31        return response()->json([
32            'status' => 'success',
33            'data' => $rows->map(fn ($row) => $this->serialize($row))->values(),
34        ]);
35    }
36
37    /**
38     * Get the scorecard for a single call type. Falls back to the system
39     * default from `RolePlayConfig.default_scorecards` when no company
40     * override exists, with `is_default: true` so the frontend can hide
41     * the "Reset to default" control until the admin actually customizes.
42     */
43    public function show(Request $request, string $callType): JsonResponse
44    {
45        if (! $this->isSupportedCallType($callType)) {
46            return response()->json(['status' => 'error', 'message' => 'Unsupported call type'], 422);
47        }
48
49        $row = $this->find($request, $callType);
50        if ($row) {
51            return response()->json([
52                'status' => 'success',
53                'data' => $this->serialize($row),
54            ]);
55        }
56
57        $defaults = (array) (\App\Http\Models\RolePlayConfig::getGlobal()?->getSection('default_scorecards') ?? []);
58        $default = $defaults[$callType] ?? [];
59
60        return response()->json([
61            'status' => 'success',
62            'data' => [
63                'id' => null,
64                'company_id' => $request->user()->company_id,
65                'call_type' => $callType,
66                'scorecard' => $default,
67                'is_forced' => false,
68                'is_default' => true,
69                'created_by' => null,
70                'created_at' => null,
71                'updated_at' => null,
72            ],
73        ]);
74    }
75
76    /**
77     * Upsert the scorecard for a call type. `is_forced` is optional and
78     * defaults to false.
79     */
80    public function update(Request $request, string $callType): JsonResponse
81    {
82        if (! $this->isSupportedCallType($callType)) {
83            return response()->json(['status' => 'error', 'message' => 'Unsupported call type'], 422);
84        }
85
86        $validated = $request->validate([
87            'scorecard' => 'required|array',
88            'is_forced' => 'sometimes|boolean',
89        ]);
90
91        $row = CompanyRolePlayScorecard::updateOrCreate(
92            [
93                'company_id' => $request->user()->company_id,
94                'call_type' => $callType,
95            ],
96            [
97                'scorecard' => $validated['scorecard'],
98                'is_forced' => (bool) ($validated['is_forced'] ?? false),
99                'created_by' => $request->user()->id,
100            ],
101        );
102
103        return response()->json([
104            'status' => 'success',
105            'data' => $this->serialize($row->fresh()),
106        ]);
107    }
108
109    /**
110     * Reset a call type's scorecard to the global default by removing the
111     * company-level override.
112     */
113    public function destroy(Request $request, string $callType): JsonResponse
114    {
115        $row = $this->find($request, $callType);
116        if (! $row) {
117            return response()->json(['status' => 'success']);
118        }
119
120        $row->delete();
121
122        return response()->json(['status' => 'success']);
123    }
124
125    private function find(Request $request, string $callType): ?CompanyRolePlayScorecard
126    {
127        return CompanyRolePlayScorecard::forCompany($request->user()->company_id)
128            ->forCallType($callType)
129            ->first();
130    }
131
132    private function isSupportedCallType(string $callType): bool
133    {
134        return in_array($callType, [RolePlayProjects::$COLD_CALL, RolePlayProjects::$DISCOVERY_CALL], true);
135    }
136
137    private function serialize(CompanyRolePlayScorecard $row): array
138    {
139        return [
140            'id' => (string) $row->_id,
141            'company_id' => $row->company_id,
142            'call_type' => $row->call_type,
143            'scorecard' => $row->scorecard,
144            'is_forced' => (bool) $row->is_forced,
145            'is_default' => false,
146            'created_by' => $row->created_by,
147            'created_at' => $row->created_at?->timestamp,
148            'updated_at' => $row->updated_at?->timestamp,
149        ];
150    }
151}