Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.83% covered (success)
97.83%
45 / 46
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
RelationMmFieldResolver
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
4 / 4
31
100.00% covered (success)
100.00%
1 / 1
 resolveForeignKeyColumn
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
8
 resolveVisibleRelationFields
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
8
 filterConfiguredFields
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 filterPrimaryModuleFields
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
11
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Engine\Application\Service\Relation;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Domain\Model\FieldMetadata;
12use App\Core\Engine\Domain\Model\ModuleMetadata;
13use App\Core\Engine\Domain\Model\RelationMmMetadata;
14
15/**
16 * Relation M:M Field Resolver.
17 *
18 * Resolves foreign key columns and determines visible columns for relation DataGrid tabs and record pickers.
19 *
20 * @package App\Core\Engine\Application\Service\Relation
21 */
22final readonly class RelationMmFieldResolver
23{
24    /**
25     * Resolves foreign key column name based on module name.
26     *
27     * @param string $moduleName Machine module name (e.g. 'contacts').
28     * @return string Column name (e.g. 'contact_id').
29     */
30    public function resolveForeignKeyColumn(string $moduleName): string
31    {
32        return match ($moduleName) {
33            'companies'     => 'company_id',
34            'contacts'      => 'contact_id',
35            'partners'      => 'partner_id',
36            'tickets'       => 'ticket_id',
37            'contracts'     => 'contract_id',
38            'opportunities' => 'opportunity_id',
39            default         => rtrim($moduleName, 's') . '_id',
40        };
41    }
42
43    /**
44     * Resolves visible fields for relation tab and picker DataGrid views.
45     *
46     * @param ModuleMetadata          $targetModule Target module metadata.
47     * @param array<FieldMetadata>    $fields       All module fields.
48     * @param RelationMmMetadata|null $relation     Optional M:M relation configuration.
49     * @return array<FieldMetadata> Filtered prioritized visible fields.
50     */
51    public function resolveVisibleRelationFields(
52        ModuleMetadata $targetModule,
53        array $fields,
54        ?RelationMmMetadata $relation = null,
55    ): array {
56        if ($relation !== null && $relation->visibleFields !== []) {
57            $configured = $this->filterConfiguredFields($fields, $relation->visibleFields);
58            if ($configured !== []) {
59                return $configured;
60            }
61        }
62
63        $primary = $this->filterPrimaryModuleFields($targetModule->name, $fields);
64        if ($primary !== []) {
65            return $primary;
66        }
67
68        $visible = array_values(array_filter(
69            $fields,
70            static fn (FieldMetadata $f): bool => !$f->isSystem && ($f->isFilterable || $f->isSortable)
71        ));
72
73        return $visible !== [] ? array_slice($visible, 0, 6) : array_slice($fields, 0, 6);
74    }
75
76    /**
77     * Filters fields based on relation configured visible field keys.
78     *
79     * @param array<FieldMetadata> $fields      Field metadata list.
80     * @param array<string>        $visibleKeys Configured field keys.
81     * @return array<FieldMetadata> Matching field metadata list.
82     */
83    public function filterConfiguredFields(array $fields, array $visibleKeys): array
84    {
85        $fieldMap = [];
86        foreach ($fields as $f) {
87            $fieldMap[$f->fieldKey] = $f;
88        }
89        $configured = [];
90        foreach ($visibleKeys as $key) {
91            if (isset($fieldMap[$key])) {
92                $configured[] = $fieldMap[$key];
93            }
94        }
95        return $configured;
96    }
97
98    /**
99     * Filters primary domain columns for standard modules.
100     *
101     * @param string               $moduleName Module machine name.
102     * @param array<FieldMetadata> $fields     All module fields.
103     * @return array<FieldMetadata> Prioritized primary fields.
104     */
105    public function filterPrimaryModuleFields(string $moduleName, array $fields): array
106    {
107        $primaryKeys = match ($moduleName) {
108            'contacts'  => ['first_name', 'last_name', 'job_title', 'email', 'phone', 'address_city'],
109            'companies' => ['name', 'tax_identifier', 'email', 'phone', 'address_city', 'status'],
110            'partners'  => ['name', 'tax_identifier', 'email', 'phone', 'address_city', 'status'],
111            'tickets'   => ['ticket_no', 'subject', 'ticket_status', 'priority', 'resolution_due'],
112            'contracts' => ['contract_no', 'subject', 'contract_status', 'start_date', 'end_date'],
113            default     => [],
114        };
115
116        if ($primaryKeys === []) {
117            return [];
118        }
119
120        $visible = [];
121        foreach ($primaryKeys as $key) {
122            foreach ($fields as $f) {
123                if ($f->fieldKey === $key) {
124                    $visible[] = $f;
125                    break;
126                }
127            }
128        }
129        return $visible;
130    }
131}