Files
boursecu_server/app/Services/AppNotificationAudienceService.php
T
2026-07-24 22:37:48 +03:30

164 lines
5.3 KiB
PHP

<?php
namespace App\Services;
use App\Models\Customer;
use Illuminate\Support\Collection;
/**
* مخاطب اعلان داخل اپ — هم‌سبک سگمنت‌های پیامک + غیرفعال‌های اخیر.
*/
class AppNotificationAudienceService
{
public const TYPE_ALL = 'all';
public const TYPE_SEGMENT = 'segment';
public const TYPE_CUSTOMERS = 'customers';
/** @return array<int, array{key: string, name: string, description: string}> */
public function segmentOptions(): array
{
return [
[
'key' => 'all_household',
'name' => 'همه کاربران خانگی',
'description' => 'همه مشتریان نوع خانگی (آرشیو نشده)',
],
[
'key' => 'no_login',
'name' => 'هرگز وارد اپ نشده‌اند',
'description' => 'آخرین بازدید خالی است',
],
[
'key' => 'inactive_7d',
'name' => '۷ روز اخیر نیامده‌اند',
'description' => 'آخرین بازدید بیش از ۷ روز پیش یا هرگز',
],
[
'key' => 'inactive_30d',
'name' => '۳۰ روز اخیر نیامده‌اند',
'description' => 'آخرین بازدید بیش از ۳۰ روز پیش یا هرگز',
],
[
'key' => 'no_valid',
'name' => 'احراز هویت نشده',
'description' => 'is_legal = 0',
],
[
'key' => 'active_wallet',
'name' => 'کاربران فعال (دارای کیف)',
'description' => 'حداقل یک ردیف در wallets دارند',
],
];
}
public function segmentLabel(string $key): string
{
foreach ($this->segmentOptions() as $opt) {
if ($opt['key'] === $key) {
return $opt['name'];
}
}
return $key;
}
/**
* @param array<int, string> $segmentKeys
* @return Collection<int, array{id: int, name: ?string, mobile: ?string}>
*/
public function resolveSegmentCustomers(array $segmentKeys): Collection
{
$byId = [];
foreach ($segmentKeys as $key) {
foreach ($this->customersForSegment((string) $key) as $row) {
$id = (int) ($row['id'] ?? 0);
if ($id > 0) {
$byId[$id] = $row;
}
}
}
return collect(array_values($byId));
}
/**
* @param array<int, int> $customerIds
* @return Collection<int, array{id: int, name: ?string, mobile: ?string}>
*/
public function resolveCustomersByIds(array $customerIds): Collection
{
$ids = array_values(array_unique(array_filter(array_map('intval', $customerIds))));
if ($ids === []) {
return collect();
}
return Customer::query()
->whereIn('id', $ids)
->where('is_archive', false)
->get(['id', 'name', 'mobile'])
->map(fn (Customer $c) => [
'id' => (int) $c->id,
'name' => $c->name,
'mobile' => $c->mobile,
]);
}
/**
* @param array<int, string> $segmentKeys
*/
public function buildAudienceLabel(string $audienceType, array $segmentKeys = [], int $recipientsCount = 0): string
{
return match ($audienceType) {
self::TYPE_ALL => 'همه کاربران',
self::TYPE_SEGMENT => collect($segmentKeys)
->map(fn ($k) => $this->segmentLabel((string) $k))
->filter()
->implode(' + ') ?: 'گروهی',
self::TYPE_CUSTOMERS => $recipientsCount <= 1
? 'تکی'
: ('تکی / چندنفره (' . $recipientsCount . ' نفر)'),
default => $audienceType,
};
}
/**
* @return array<int, array{id: int, name: ?string, mobile: ?string}>
*/
private function customersForSegment(string $key): array
{
$base = Customer::query()
->where('type', 2)
->where('is_archive', false);
$query = match ($key) {
'all_household' => $base,
'no_login' => (clone $base)->whereNull('last_seen_at'),
'inactive_7d' => (clone $base)->where(function ($q) {
$q->whereNull('last_seen_at')
->orWhere('last_seen_at', '<', now()->subDays(7));
}),
'inactive_30d' => (clone $base)->where(function ($q) {
$q->whereNull('last_seen_at')
->orWhere('last_seen_at', '<', now()->subDays(30));
}),
'no_valid' => (clone $base)->where('is_legal', 0),
'active_wallet' => (clone $base)->whereIn('id', function ($q) {
$q->select('owner')->from('wallets');
}),
default => null,
};
if ($query === null) {
return [];
}
return $query->get(['id', 'name', 'mobile'])->map(fn (Customer $c) => [
'id' => (int) $c->id,
'name' => $c->name,
'mobile' => $c->mobile,
])->all();
}
}