feat: add multi-city representation support and domain context resolution

- Created migration to add representation_cities table and domain, is_global fields to representations.
- Implemented SiteContextController to resolve domain to site context (city | representation | unknown).
- Developed DomainContext and DomainContextResolver services for domain mapping.
- Added tests for DomainContextResolver and commission logic based on domain ownership.
This commit is contained in:
hamed
2026-07-09 07:26:58 +03:30
parent 59559e2e31
commit 0750bc9812
56 changed files with 4297 additions and 1600 deletions
+37 -16
View File
@@ -1013,10 +1013,9 @@ class AdminApiController extends BaseController
$cityId = $request->query->get('city_id');
$qb = $this->em->createQueryBuilder()
->select('r.id, r.uuid, r.fullName, r.mobileNumber, u.mobileNumber as user_mobile, r.cityId, r.commissionPercent, r.active, r.createdAt, c.name as city_name')
->select('r.id, r.uuid, r.fullName, r.mobileNumber, u.mobileNumber as user_mobile, r.cityId, r.domain, r.isGlobal, r.commissionPercent, r.active, r.createdAt')
->from(Representation::class, 'r')
->join('r.user', 'u')
->leftJoin(City::class, 'c', 'WITH', 'c.id = r.cityId')
->orderBy('r.createdAt', 'DESC');
if ($search !== '') {
@@ -1025,7 +1024,7 @@ class AdminApiController extends BaseController
}
if ($cityId !== null && $cityId !== '') {
$qb->andWhere('r.cityId = :cityId')
$qb->andWhere(':cityId MEMBER OF r.cities')
->setParameter('cityId', (int) $cityId);
}
@@ -1034,19 +1033,41 @@ class AdminApiController extends BaseController
$rows = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
->getQuery()->getArrayResult();
$items = array_map(fn(array $r) => [
'id' => (int) $r['id'],
'uuid' => $r['uuid'],
'domain' => $r['fullName'],
'full_name' => $r['fullName'],
'mobile_number' => $r['mobileNumber'] ?: ($r['user_mobile'] ?? null),
'city_id' => $r['cityId'],
'city' => $r['city_name'] ?? null,
'commission_percent' => (float) $r['commissionPercent'],
'wallet_balance' => 0,
'is_active' => (bool) $r['active'],
'created_at' => date('c', (int) $r['createdAt']),
], $rows);
// نام شهرهای هر نماینده (چند-شهری) در یک کوئری برای صفحه‌ی جاری.
$repIds = array_map(fn(array $r) => (int) $r['id'], $rows);
$cityNames = [];
if ($repIds !== []) {
$cityRows = $this->em->getConnection()->fetchAllAssociative(
'SELECT rc.representation_id, c.id AS city_id, c.name
FROM representation_cities rc JOIN cities c ON c.id = rc.city_id
WHERE rc.representation_id IN (?) ORDER BY c.name',
[$repIds],
[\Doctrine\DBAL\ArrayParameterType::INTEGER],
);
foreach ($cityRows as $cr) {
$cityNames[(int) $cr['representation_id']][] = ['id' => (int) $cr['city_id'], 'name' => $cr['name']];
}
}
$items = array_map(function (array $r) use ($cityNames) {
$cities = $cityNames[(int) $r['id']] ?? [];
return [
'id' => (int) $r['id'],
'uuid' => $r['uuid'],
'domain' => $r['domain'],
'is_global' => (bool) $r['isGlobal'],
'full_name' => $r['fullName'],
'mobile_number' => $r['mobileNumber'] ?: ($r['user_mobile'] ?? null),
'city_id' => $cities[0]['id'] ?? $r['cityId'],
'city_ids' => array_column($cities, 'id'),
'cities' => $cities,
'city' => $cities !== [] ? implode('، ', array_column($cities, 'name')) : null,
'commission_percent' => (float) $r['commissionPercent'],
'wallet_balance' => 0,
'is_active' => (bool) $r['active'],
'created_at' => date('c', (int) $r['createdAt']),
];
}, $rows);
return $this->paginated($items, (int) $total, $page, $limit);
}