Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
RoleplayLevelsReportController
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 index
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 resolveCompanyScope
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3namespace App\Http\Controllers\v2\Admin\Report;
4
5use App\Http\Controllers\Controller;
6use App\Http\Models\Auth\Role;
7use App\Http\Requests\v2\Admin\Report\RoleplayLevelsReportRequest;
8use App\Http\Resources\v2\Admin\Report\RoleplayLevelsResource;
9use App\Http\Services\Report\RoleplayLevelsReportService;
10use Carbon\Carbon;
11
12/**
13 * Admin endpoint exposing the roleplay coach-level distribution across users.
14 *
15 * Used by:
16 *  - cmc-fe (global scope; Vengreso super-admins with `cmc=1`)
17 *  - admin-fe (company scope; company global/group/reporting admins)
18 *
19 * The endpoint returns a fixed, ordered list of 5 buckets (Needs Work →
20 * Expert) with user counts and percentages, so the client can render a
21 * distribution chart without further processing.
22 */
23class RoleplayLevelsReportController extends Controller
24{
25    public function __construct(
26        private readonly RoleplayLevelsReportService $service,
27    ) {}
28
29    /**
30     * Return the roleplay level distribution for the scoped cohort.
31     *
32     * @param  RoleplayLevelsReportRequest  $request  Validated request
33     *
34     * @response 200 {
35     *   "result": {
36     *     "status": "success",
37     *     "data": {
38     *       "total_users": 1234,
39     *       "levels": [
40     *         {"key": "needs_work", "label": "Needs Work", "count": 500, "percentage": 40.52},
41     *         {"key": "developing", "label": "Developing", "count": 300, "percentage": 24.31},
42     *         {"key": "competent",  "label": "Competent",  "count": 200, "percentage": 16.21},
43     *         {"key": "advanced",   "label": "Advanced",   "count": 150, "percentage": 12.16},
44     *         {"key": "expert",     "label": "Expert",     "count":  84, "percentage":  6.80}
45     *       ]
46     *     }
47     *   }
48     * }
49     */
50    public function index(RoleplayLevelsReportRequest $request): RoleplayLevelsResource
51    {
52        $user = $request->user();
53        $roles = $user->roles();
54        $isCmc = (bool) $request->input('cmc', false);
55
56        $companyIds = $this->resolveCompanyScope($request, $user, $roles, $isCmc);
57
58        $from = $request->filled('from') ? Carbon::parse($request->input('from')) : null;
59        $to = $request->filled('to') ? Carbon::parse($request->input('to')) : null;
60
61        $distribution = $this->service->getDistribution($companyIds, $from, $to);
62
63        return new RoleplayLevelsResource($distribution);
64    }
65
66    /**
67     * Resolve the effective company scope for the report based on the
68     * request parameters and the caller's role.
69     *
70     * Rules (mirrors the existing report filter conventions):
71     *  - If the caller is a Vengreso super-admin in CMC mode and passes no
72     *    `company_ids`, the scope is global (returns `null`).
73     *  - If `company_ids` is passed, it is used verbatim (authorization has
74     *    already verified the caller is allowed to see those companies).
75     *  - Otherwise the scope is pinned to the caller's own company.
76     *
77     * @param  \App\Http\Models\Auth\User  $user
78     * @param  array<int, string>  $roles
79     * @return array<int, string>|null List of company IDs, or null for global
80     */
81    private function resolveCompanyScope(
82        RoleplayLevelsReportRequest $request,
83        $user,
84        array $roles,
85        bool $isCmc,
86    ): ?array {
87        $requested = array_values(array_filter(
88            explode(',', (string) $request->input('company_ids', ''))
89        ));
90
91        if (! empty($requested)) {
92            return $requested;
93        }
94
95        if ($isCmc && in_array(Role::VENGRESO_ADMIN, $roles, true)) {
96            return null;
97        }
98
99        return $user->company_id ? [(string) $user->company_id] : [];
100    }
101}