174 lines
5.6 KiB
PHP
174 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Recipients;
|
|
|
|
use App\Models\Contact;
|
|
use App\Models\ContactGroup;
|
|
use App\Models\ContactPhoneNumber;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class RecipientResolver
|
|
{
|
|
/**
|
|
* @param array<int, int|string> $contactIds
|
|
* @param array<int, int|string> $groupIds
|
|
*/
|
|
public function resolve(array $contactIds, array $groupIds): RecipientResolutionResult
|
|
{
|
|
$directContactIds = $this->cleanIds($contactIds);
|
|
$selectedGroupIds = $this->cleanIds($groupIds);
|
|
|
|
$groups = ContactGroup::query()
|
|
->with(['contacts' => fn ($query) => $query->select('contacts.id')])
|
|
->whereKey($selectedGroupIds)
|
|
->orderBy('name')
|
|
->get();
|
|
|
|
/** @var array<int, array<int, string>> $groupNamesByContactId */
|
|
$groupNamesByContactId = [];
|
|
|
|
foreach ($groups as $group) {
|
|
foreach ($group->contacts as $contact) {
|
|
$groupNamesByContactId[$contact->id][] = $group->name;
|
|
}
|
|
}
|
|
|
|
$selectedContactIds = $directContactIds
|
|
->merge($groups->flatMap(fn (ContactGroup $group): Collection => $group->contacts->pluck('id')))
|
|
->unique()
|
|
->values();
|
|
|
|
$contacts = Contact::query()
|
|
->with([
|
|
'groups',
|
|
'phoneNumbers' => fn ($query) => $query
|
|
->orderByDesc('is_primary')
|
|
->orderBy('id'),
|
|
])
|
|
->whereKey($selectedContactIds)
|
|
->orderBy('last_name')
|
|
->orderBy('first_name')
|
|
->get();
|
|
|
|
$recipients = [];
|
|
$excluded = [];
|
|
$seenNumbers = [];
|
|
|
|
foreach ($contacts as $contact) {
|
|
$selection = $this->selectionLabels($contact, $directContactIds, $groupNamesByContactId[$contact->id] ?? []);
|
|
|
|
if ($contact->sms_opt_out) {
|
|
$excluded[] = $this->excludedEntry($contact, 'opt_out', null, $selection);
|
|
|
|
continue;
|
|
}
|
|
|
|
if ($contact->phoneNumbers->isEmpty()) {
|
|
$excluded[] = $this->excludedEntry($contact, 'missing_phone', null, $selection);
|
|
|
|
continue;
|
|
}
|
|
|
|
foreach ($contact->phoneNumbers as $phoneNumber) {
|
|
if (! $phoneNumber->is_valid || $phoneNumber->normalized_number === null) {
|
|
$excluded[] = $this->excludedEntry($contact, 'invalid', $phoneNumber, $selection);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (! $phoneNumber->hasDetectedMobileNumber()) {
|
|
$excluded[] = $this->excludedEntry($contact, 'non_mobile', $phoneNumber, $selection);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (isset($seenNumbers[$phoneNumber->normalized_number])) {
|
|
$excluded[] = $this->excludedEntry(
|
|
$contact,
|
|
'duplicate',
|
|
$phoneNumber,
|
|
$selection,
|
|
['duplicate_of' => $seenNumbers[$phoneNumber->normalized_number]],
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
$entry = [
|
|
'contact_id' => $contact->id,
|
|
'contact_name' => $contact->displayName(),
|
|
'phone_number_id' => $phoneNumber->id,
|
|
'number' => $phoneNumber->normalized_number,
|
|
'raw_number' => $phoneNumber->raw_number,
|
|
'label' => $phoneNumber->label,
|
|
'selection' => $selection,
|
|
];
|
|
|
|
$recipients[] = $entry;
|
|
$seenNumbers[$phoneNumber->normalized_number] = $entry['contact_name'];
|
|
}
|
|
}
|
|
|
|
return new RecipientResolutionResult(
|
|
recipients: $recipients,
|
|
excluded: $excluded,
|
|
selectedContactCount: $contacts->count(),
|
|
selectedGroupCount: $groups->count(),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, int|string> $ids
|
|
* @return Collection<int, int>
|
|
*/
|
|
private function cleanIds(array $ids): Collection
|
|
{
|
|
return collect($ids)
|
|
->map(fn (int|string $id): int => (int) $id)
|
|
->filter(fn (int $id): bool => $id > 0)
|
|
->unique()
|
|
->values();
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, int> $directContactIds
|
|
* @param array<int, string> $groupNames
|
|
* @return array<int, string>
|
|
*/
|
|
private function selectionLabels(Contact $contact, Collection $directContactIds, array $groupNames): array
|
|
{
|
|
$selection = $groupNames;
|
|
|
|
if ($directContactIds->contains($contact->id)) {
|
|
array_unshift($selection, 'Selection directe');
|
|
}
|
|
|
|
return array_values(array_unique($selection));
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $selection
|
|
* @param array<string, mixed> $extra
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function excludedEntry(
|
|
Contact $contact,
|
|
string $reason,
|
|
?ContactPhoneNumber $phoneNumber,
|
|
array $selection,
|
|
array $extra = [],
|
|
): array {
|
|
return array_merge([
|
|
'contact_id' => $contact->id,
|
|
'contact_name' => $contact->displayName(),
|
|
'contact_source' => $contact->source,
|
|
'phone_number_id' => $phoneNumber?->id,
|
|
'reason' => $reason,
|
|
'number' => $phoneNumber?->normalized_number,
|
|
'raw_number' => $phoneNumber?->raw_number,
|
|
'label' => $phoneNumber?->label,
|
|
'selection' => $selection,
|
|
], $extra);
|
|
}
|
|
}
|