Refactor subuser management payload and enhance grant deduplication logic
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -454,8 +454,8 @@ class subusersRoute
|
||||
'id' => 's.`id`',
|
||||
'created_at' => 's.`created_at`',
|
||||
'updated_at' => 'row_updated_at',
|
||||
'customer_number' => 'g.`billing_customer_number`',
|
||||
'grant_id' => 'g.`id`',
|
||||
'customer_number' => 'customer_number_sort',
|
||||
'grant_id' => 'grant_id_sort',
|
||||
'name' => 's.`name`',
|
||||
];
|
||||
|
||||
@@ -487,11 +487,10 @@ class subusersRoute
|
||||
$statement->bind_param($types, ...$refs);
|
||||
}
|
||||
|
||||
private function buildSuperuserSubuserManagementPayload(array $row): array
|
||||
private function buildSuperuserGrantPayload(array $row, bool $setupRequired): array
|
||||
{
|
||||
$grantPermissions = subuser_grants_o::normalizePermissionsValue($row['grant_permissions'] ?? null);
|
||||
$templateService = new subuser_permission_templates_service();
|
||||
$setupRequired = !is_string($row['password'] ?? null) || trim((string)$row['password']) === '';
|
||||
$grantEnabled = (bool)((int)($row['grant_enabled'] ?? 0));
|
||||
|
||||
$accessState = 'inactive';
|
||||
@@ -501,6 +500,44 @@ class subusersRoute
|
||||
$accessState = 'disabled';
|
||||
}
|
||||
|
||||
return [
|
||||
'grant_id' => (int)$row['grant_id'],
|
||||
'customer_number' => (int)$row['customer_number'],
|
||||
'customer_name' => $row['customer_name'] ?: null,
|
||||
'grant_enabled' => $grantEnabled,
|
||||
'grant_note' => $row['grant_note'] ?? null,
|
||||
'grant_permissions' => $grantPermissions,
|
||||
'permissions' => $grantPermissions,
|
||||
'permission_template_key' => $templateService->classify($grantPermissions, $grantEnabled),
|
||||
'permission_groups' => $templateService->permissionGroups($grantPermissions),
|
||||
'grant_created_at' => $row['grant_created_at'] ?? null,
|
||||
'grant_updated_at' => $row['grant_updated_at'] ?? null,
|
||||
'access_state' => $accessState,
|
||||
];
|
||||
}
|
||||
|
||||
private function buildSuperuserSubuserManagementPayload(array $row, array $grantRows = []): array
|
||||
{
|
||||
$setupRequired = !is_string($row['password'] ?? null) || trim((string)$row['password']) === '';
|
||||
if ($grantRows === [] && !empty($row['grant_id'])) {
|
||||
$grantRows = [$row];
|
||||
}
|
||||
|
||||
$grants = array_map(
|
||||
fn (array $grantRow): array => $this->buildSuperuserGrantPayload($grantRow, $setupRequired),
|
||||
$this->dedupeSuperuserGrantRows($grantRows)
|
||||
);
|
||||
$primaryGrant = $grants[0] ?? null;
|
||||
|
||||
$accessState = 'inactive';
|
||||
if (array_filter($grants, static fn (array $grant): bool => $grant['access_state'] === 'active') !== []) {
|
||||
$accessState = 'active';
|
||||
} elseif (array_filter($grants, static fn (array $grant): bool => $grant['access_state'] === 'pending_setup') !== []) {
|
||||
$accessState = 'pending_setup';
|
||||
} elseif ($grants !== []) {
|
||||
$accessState = 'disabled';
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'username' => $row['username'] ?? null,
|
||||
@@ -516,21 +553,71 @@ class subusersRoute
|
||||
'invite_accepted' => !$setupRequired,
|
||||
'can_resend_invite' => $setupRequired,
|
||||
'profile_editable_by_manager' => false,
|
||||
'customer_number' => (int)$row['customer_number'],
|
||||
'customer_name' => $row['customer_name'] ?: null,
|
||||
'grant_id' => (int)$row['grant_id'],
|
||||
'grant_enabled' => $grantEnabled,
|
||||
'grant_note' => $row['grant_note'] ?? null,
|
||||
'grant_permissions' => $grantPermissions,
|
||||
'permissions' => $grantPermissions,
|
||||
'permission_template_key' => $templateService->classify($grantPermissions, $grantEnabled),
|
||||
'permission_groups' => $templateService->permissionGroups($grantPermissions),
|
||||
'grant_created_at' => $row['grant_created_at'] ?? null,
|
||||
'grant_updated_at' => $row['grant_updated_at'] ?? null,
|
||||
'customer_number' => $primaryGrant['customer_number'] ?? null,
|
||||
'customer_name' => $primaryGrant['customer_name'] ?? null,
|
||||
'grant_id' => $primaryGrant['grant_id'] ?? null,
|
||||
'grant_enabled' => $primaryGrant['grant_enabled'] ?? false,
|
||||
'grant_note' => $primaryGrant['grant_note'] ?? null,
|
||||
'grant_permissions' => $primaryGrant['grant_permissions'] ?? [],
|
||||
'permissions' => $primaryGrant['permissions'] ?? [],
|
||||
'permission_template_key' => $primaryGrant['permission_template_key'] ?? subuser_permission_templates_service::TEMPLATE_DEACTIVATED,
|
||||
'permission_groups' => $primaryGrant['permission_groups'] ?? [],
|
||||
'grant_created_at' => $primaryGrant['grant_created_at'] ?? null,
|
||||
'grant_updated_at' => $primaryGrant['grant_updated_at'] ?? null,
|
||||
'grants' => $grants,
|
||||
'grant_count' => count($grants),
|
||||
'customer_numbers' => array_values(array_unique(array_map(
|
||||
static fn (array $grant): int => (int)$grant['customer_number'],
|
||||
$grants
|
||||
))),
|
||||
'access_state' => $accessState,
|
||||
];
|
||||
}
|
||||
|
||||
private function dedupeSuperuserGrantRows(array $grantRows): array
|
||||
{
|
||||
$byCustomer = [];
|
||||
foreach ($grantRows as $grantRow) {
|
||||
$customerNumber = (int)($grantRow['customer_number'] ?? 0);
|
||||
if ($customerNumber <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = $byCustomer[$customerNumber] ?? null;
|
||||
if ($existing === null || $this->compareSuperuserGrantRows($grantRow, $existing) < 0) {
|
||||
$byCustomer[$customerNumber] = $grantRow;
|
||||
}
|
||||
}
|
||||
|
||||
$deduped = array_values($byCustomer);
|
||||
usort($deduped, fn (array $left, array $right): int => $this->compareSuperuserGrantRows($left, $right));
|
||||
|
||||
return $deduped;
|
||||
}
|
||||
|
||||
private function compareSuperuserGrantRows(array $left, array $right): int
|
||||
{
|
||||
$leftEnabled = (int)($left['grant_enabled'] ?? 0);
|
||||
$rightEnabled = (int)($right['grant_enabled'] ?? 0);
|
||||
if ($leftEnabled !== $rightEnabled) {
|
||||
return $rightEnabled <=> $leftEnabled;
|
||||
}
|
||||
|
||||
$leftCustomer = (int)($left['customer_number'] ?? 0);
|
||||
$rightCustomer = (int)($right['customer_number'] ?? 0);
|
||||
if ($leftCustomer !== $rightCustomer) {
|
||||
return $leftCustomer <=> $rightCustomer;
|
||||
}
|
||||
|
||||
$leftUpdated = strtotime((string)($left['grant_updated_at'] ?? $left['grant_created_at'] ?? '')) ?: 0;
|
||||
$rightUpdated = strtotime((string)($right['grant_updated_at'] ?? $right['grant_created_at'] ?? '')) ?: 0;
|
||||
if ($leftUpdated !== $rightUpdated) {
|
||||
return $rightUpdated <=> $leftUpdated;
|
||||
}
|
||||
|
||||
return (int)($right['grant_id'] ?? 0) <=> (int)($left['grant_id'] ?? 0);
|
||||
}
|
||||
|
||||
private function routePositiveInt(string $name): int
|
||||
{
|
||||
global $response;
|
||||
@@ -645,7 +732,12 @@ class subusersRoute
|
||||
OR CAST(s.`phone` AS CHAR) LIKE ?
|
||||
OR CAST(g.`billing_customer_number` AS CHAR) LIKE ?
|
||||
OR g.`note` LIKE ?
|
||||
OR u.`display_name` LIKE ?
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM `users` search_u
|
||||
WHERE search_u.`customer_number` = g.`billing_customer_number`
|
||||
AND search_u.`display_name` LIKE ?
|
||||
)
|
||||
)";
|
||||
$search = '%' . $pagination['search'] . '%';
|
||||
for ($i = 0; $i < 9; $i++) {
|
||||
@@ -658,10 +750,9 @@ class subusersRoute
|
||||
$fromSql = "
|
||||
FROM `subuser_grants` g
|
||||
INNER JOIN `subusers` s ON s.`id` = g.`subuser`
|
||||
LEFT JOIN `users` u ON u.`customer_number` = g.`billing_customer_number`
|
||||
";
|
||||
|
||||
$countSql = "SELECT COUNT(*) AS `count` $fromSql $whereSql";
|
||||
$countSql = "SELECT COUNT(DISTINCT s.`id`) AS `count` $fromSql $whereSql";
|
||||
$countStatement = $db->conn->prepare($countSql);
|
||||
if ($countStatement === false) {
|
||||
throw new Exception('Failed to prepare subuser count query: ' . $db->conn->error);
|
||||
@@ -672,7 +763,7 @@ class subusersRoute
|
||||
$total = (int)($countResult->fetch_assoc()['count'] ?? 0);
|
||||
$countStatement->close();
|
||||
|
||||
$dataSql = "
|
||||
$pageSql = "
|
||||
SELECT
|
||||
s.`id`,
|
||||
s.`username`,
|
||||
@@ -685,31 +776,100 @@ class subusersRoute
|
||||
s.`created_at`,
|
||||
s.`updated_at`,
|
||||
s.`suspended_at`,
|
||||
g.`id` AS `grant_id`,
|
||||
g.`billing_customer_number` AS `customer_number`,
|
||||
g.`enabled` AS `grant_enabled`,
|
||||
g.`note` AS `grant_note`,
|
||||
g.`permissions` AS `grant_permissions`,
|
||||
g.`created_at` AS `grant_created_at`,
|
||||
g.`updated_at` AS `grant_updated_at`,
|
||||
COALESCE(g.`updated_at`, s.`updated_at`) AS `row_updated_at`,
|
||||
u.`display_name` AS `customer_name`
|
||||
MAX(COALESCE(g.`updated_at`, s.`updated_at`)) AS `row_updated_at`,
|
||||
MIN(g.`billing_customer_number`) AS `customer_number_sort`,
|
||||
MAX(g.`id`) AS `grant_id_sort`
|
||||
$fromSql
|
||||
$whereSql
|
||||
GROUP BY
|
||||
s.`id`,
|
||||
s.`username`,
|
||||
s.`password`,
|
||||
s.`name`,
|
||||
s.`email`,
|
||||
s.`phone_country_code`,
|
||||
s.`phone`,
|
||||
s.`two_factor_enabled`,
|
||||
s.`created_at`,
|
||||
s.`updated_at`,
|
||||
s.`suspended_at`
|
||||
ORDER BY {$pagination['order_sql']} {$pagination['order_direction']}
|
||||
LIMIT ? OFFSET ?
|
||||
";
|
||||
|
||||
$dataStatement = $db->conn->prepare($dataSql);
|
||||
if ($dataStatement === false) {
|
||||
$pageStatement = $db->conn->prepare($pageSql);
|
||||
if ($pageStatement === false) {
|
||||
throw new Exception('Failed to prepare subuser list query: ' . $db->conn->error);
|
||||
}
|
||||
$dataParams = [...$params, (int)$pagination['limit'], $offset];
|
||||
$this->bindStatementParameters($dataStatement, $types . 'ii', $dataParams);
|
||||
$dataStatement->execute();
|
||||
$result = $dataStatement->get_result();
|
||||
$pageParams = [...$params, (int)$pagination['limit'], $offset];
|
||||
$this->bindStatementParameters($pageStatement, $types . 'ii', $pageParams);
|
||||
$pageStatement->execute();
|
||||
$result = $pageStatement->get_result();
|
||||
$rows = $result->fetch_all(MYSQLI_ASSOC);
|
||||
$dataStatement->close();
|
||||
$pageStatement->close();
|
||||
|
||||
$subuserIds = array_map(static fn (array $row): int => (int)$row['id'], $rows);
|
||||
$grantRowsBySubuserId = [];
|
||||
if ($subuserIds !== []) {
|
||||
$placeholders = implode(',', array_fill(0, count($subuserIds), '?'));
|
||||
$grantWhere = [
|
||||
'g.`deleted_at` IS NULL',
|
||||
'g.`subuser` IN (' . $placeholders . ')',
|
||||
];
|
||||
$grantParams = $subuserIds;
|
||||
$grantTypes = str_repeat('i', count($subuserIds));
|
||||
|
||||
if (!$includeNonEnabled) {
|
||||
$grantWhere[] = 'g.`enabled` = 1';
|
||||
}
|
||||
if ($customerNumber !== null) {
|
||||
$grantWhere[] = 'g.`billing_customer_number` = ?';
|
||||
$grantParams[] = $customerNumber;
|
||||
$grantTypes .= 'i';
|
||||
}
|
||||
|
||||
$grantSql = "
|
||||
SELECT
|
||||
g.`subuser` AS `subuser_id`,
|
||||
g.`id` AS `grant_id`,
|
||||
g.`billing_customer_number` AS `customer_number`,
|
||||
g.`enabled` AS `grant_enabled`,
|
||||
g.`note` AS `grant_note`,
|
||||
g.`permissions` AS `grant_permissions`,
|
||||
g.`created_at` AS `grant_created_at`,
|
||||
g.`updated_at` AS `grant_updated_at`
|
||||
FROM `subuser_grants` g
|
||||
WHERE " . implode(' AND ', $grantWhere) . "
|
||||
ORDER BY
|
||||
g.`subuser` ASC,
|
||||
g.`enabled` DESC,
|
||||
g.`billing_customer_number` ASC,
|
||||
COALESCE(g.`updated_at`, g.`created_at`) DESC,
|
||||
g.`id` DESC
|
||||
";
|
||||
$grantStatement = $db->conn->prepare($grantSql);
|
||||
if ($grantStatement === false) {
|
||||
throw new Exception('Failed to prepare subuser grant list query: ' . $db->conn->error);
|
||||
}
|
||||
$this->bindStatementParameters($grantStatement, $grantTypes, $grantParams);
|
||||
$grantStatement->execute();
|
||||
$grantResult = $grantStatement->get_result();
|
||||
$grantRows = $grantResult->fetch_all(MYSQLI_ASSOC);
|
||||
$grantStatement->close();
|
||||
|
||||
$customerNames = $this->resolveCustomerNames(array_map(
|
||||
static fn (array $grantRow): int => (int)($grantRow['customer_number'] ?? 0),
|
||||
$grantRows
|
||||
));
|
||||
|
||||
foreach ($grantRows as $grantRow) {
|
||||
$subuserId = (int)$grantRow['subuser_id'];
|
||||
$customerNumberForGrant = (int)$grantRow['customer_number'];
|
||||
$grantRow['customer_name'] = $this->resolveCustomerName($customerNumberForGrant, $customerNames);
|
||||
$grantRowsBySubuserId[$subuserId] ??= [];
|
||||
$grantRowsBySubuserId[$subuserId][] = $grantRow;
|
||||
}
|
||||
}
|
||||
|
||||
$response->paginate(
|
||||
(int)$pagination['page'],
|
||||
@@ -720,7 +880,10 @@ class subusersRoute
|
||||
[$pagination['order_field'] => $pagination['order_direction']]
|
||||
);
|
||||
|
||||
return array_map(fn(array $row): array => $this->buildSuperuserSubuserManagementPayload($row), $rows);
|
||||
return array_map(
|
||||
fn(array $row): array => $this->buildSuperuserSubuserManagementPayload($row, $grantRowsBySubuserId[(int)$row['id']] ?? []),
|
||||
$rows
|
||||
);
|
||||
}
|
||||
|
||||
private function getGrantForScopedUserOrFail(int $grantId, int $customerNumber): subuser_grants_o
|
||||
|
||||
Reference in New Issue
Block a user