Resolve all non-false-positive PHPStan 2 findings, reducing the baseline from 486 to 382 entries (only unused-public false positives remain). Categories fixed: - Remove 39 redundant type checks (is_string, is_array, is_object, method_exists) - Remove 12 no-op array_values() on already-sequential lists - Simplify 13 null-coalesce on guaranteed offsets - Remove 8 always-true instanceof checks - Replace 12 redundant test assertions (assertTrue(true) → addToAssertionCount) - Simplify 6 unnecessary nullsafe operators (?-> → ->) - Fix 6 always-true !== '' comparisons - Fix remaining: unset.offset, return.unusedType, staticMethod narrowing 53 files changed across core/, modules/, pages/, and tests/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
199 lines
6.4 KiB
PHP
199 lines
6.4 KiB
PHP
<?php
|
|
|
|
use MintyPHP\Http\SessionStoreInterface;
|
|
use MintyPHP\Module\Helpdesk\HelpdeskAuthorizationPolicy;
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorCacheControl;
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorDetailService;
|
|
use MintyPHP\Router;
|
|
use MintyPHP\Support\Guard;
|
|
|
|
Guard::requireLogin();
|
|
Guard::requireAbilityOrForbidden(HelpdeskAuthorizationPolicy::ABILITY_ACCESS);
|
|
|
|
gridRequireGetRequest();
|
|
|
|
$filters = gridParseFiltersFromSchemaFile(__DIR__ . '/debitor-contacts-filter-schema.php');
|
|
$request = requestInput();
|
|
|
|
$customerNo = (string) ($filters['customerNo'] ?? '');
|
|
$customerName = (string) ($filters['customerName'] ?? '');
|
|
$refreshRequested = DebitorCacheControl::parseRefreshFlag($request->query('refresh', ''));
|
|
|
|
if ($customerNo === '' || $customerName === '') {
|
|
Router::json([
|
|
'data' => [],
|
|
'total' => 0,
|
|
'type_options' => [],
|
|
'meta' => [
|
|
'cache_used' => false,
|
|
'cache_bypassed' => $refreshRequested,
|
|
'cache_refreshed' => false,
|
|
],
|
|
]);
|
|
|
|
return;
|
|
}
|
|
|
|
// --- Session cache (lazy TTL = 300s) ---
|
|
|
|
$sessionStore = app(SessionStoreInterface::class);
|
|
$session = $sessionStore->all();
|
|
$tenantScope = DebitorCacheControl::resolveTenantScope($session);
|
|
if ($refreshRequested) {
|
|
DebitorCacheControl::invalidateDebitorCaches($sessionStore, $tenantScope, $customerNo, true);
|
|
}
|
|
|
|
$cacheKey = DebitorCacheControl::contactsKey($tenantScope, $customerNo);
|
|
$cacheTtl = DebitorCacheControl::TTL_STANDARD_SECONDS;
|
|
$cached = $sessionStore->get($cacheKey);
|
|
$allContacts = null;
|
|
$cacheUsed = false;
|
|
$serviceMeta = [
|
|
'source' => 'primary.company_name',
|
|
'fallback_used' => false,
|
|
'fallback_reason' => '',
|
|
];
|
|
|
|
if (!$refreshRequested && is_array($cached) && isset($cached['fetched_at']) && (time() - (int) $cached['fetched_at']) < $cacheTtl) {
|
|
$allContacts = $cached['contacts'] ?? [];
|
|
$cacheUsed = true;
|
|
if (is_array($cached['service_meta'] ?? null)) {
|
|
$serviceMeta = [
|
|
'source' => (string) ($cached['service_meta']['source'] ?? $serviceMeta['source']),
|
|
'fallback_used' => (bool) ($cached['service_meta']['fallback_used'] ?? false),
|
|
'fallback_reason' => (string) ($cached['service_meta']['fallback_reason'] ?? ''),
|
|
];
|
|
}
|
|
}
|
|
|
|
if ($allContacts === null) {
|
|
$service = app(DebitorDetailService::class);
|
|
$result = $service->loadContacts($customerNo, $customerName);
|
|
|
|
if (!($result['ok'] ?? false)) {
|
|
$resultMeta = is_array($result['meta'] ?? null) ? $result['meta'] : [];
|
|
Router::json([
|
|
'data' => [],
|
|
'total' => 0,
|
|
'type_options' => [],
|
|
'meta' => [
|
|
'cache_used' => $cacheUsed,
|
|
'cache_bypassed' => $refreshRequested,
|
|
'cache_refreshed' => $refreshRequested,
|
|
'source' => (string) ($resultMeta['source'] ?? $serviceMeta['source']),
|
|
'fallback_used' => (bool) ($resultMeta['fallback_used'] ?? false),
|
|
'fallback_reason' => (string) ($resultMeta['fallback_reason'] ?? ''),
|
|
],
|
|
]);
|
|
|
|
return;
|
|
}
|
|
|
|
$allContacts = $result['contacts'] ?? [];
|
|
$resultMeta = is_array($result['meta'] ?? null) ? $result['meta'] : [];
|
|
$serviceMeta = [
|
|
'source' => (string) ($resultMeta['source'] ?? $serviceMeta['source']),
|
|
'fallback_used' => (bool) ($resultMeta['fallback_used'] ?? false),
|
|
'fallback_reason' => (string) ($resultMeta['fallback_reason'] ?? ''),
|
|
];
|
|
|
|
$sessionStore->set($cacheKey, [
|
|
'contacts' => $allContacts,
|
|
'service_meta' => $serviceMeta,
|
|
'fetched_at' => time(),
|
|
]);
|
|
}
|
|
|
|
// --- Collect unique types for filter options ---
|
|
|
|
$typeSet = [];
|
|
foreach ($allContacts as $contact) {
|
|
$type = trim((string) ($contact['Type'] ?? ''));
|
|
if ($type !== '' && !isset($typeSet[$type])) {
|
|
$typeSet[$type] = true;
|
|
}
|
|
}
|
|
ksort($typeSet);
|
|
$typeOptions = [];
|
|
foreach (array_keys($typeSet) as $type) {
|
|
$typeOptions[] = ['id' => $type, 'description' => $type];
|
|
}
|
|
|
|
// --- PHP-side filtering ---
|
|
|
|
$search = trim((string) ($filters['search'] ?? ''));
|
|
$typeFilter = trim((string) ($filters['type'] ?? ''));
|
|
$order = (string) ($filters['order'] ?? 'Name');
|
|
$dir = (string) ($filters['dir'] ?? 'asc');
|
|
$limit = (int) ($filters['limit'] ?? 10);
|
|
$offset = (int) ($filters['offset'] ?? 0);
|
|
|
|
$filtered = $allContacts;
|
|
|
|
// Search: stripos on Name + Job_Title + E_Mail + Phone_No + Mobile_Phone_No
|
|
if ($search !== '') {
|
|
$searchLower = mb_strtolower($search);
|
|
$filtered = array_values(array_filter($filtered, static function (array $contact) use ($searchLower): bool {
|
|
$haystack = mb_strtolower(
|
|
($contact['Name'] ?? '') . ' '
|
|
. ($contact['Job_Title'] ?? '') . ' '
|
|
. ($contact['E_Mail'] ?? '') . ' '
|
|
. ($contact['Phone_No'] ?? '') . ' '
|
|
. ($contact['Mobile_Phone_No'] ?? '')
|
|
);
|
|
|
|
return str_contains($haystack, $searchLower);
|
|
}));
|
|
}
|
|
|
|
// Type filter
|
|
if ($typeFilter !== '') {
|
|
$filtered = array_values(array_filter($filtered, static function (array $contact) use ($typeFilter): bool {
|
|
return trim((string) ($contact['Type'] ?? '')) === $typeFilter;
|
|
}));
|
|
}
|
|
|
|
// --- Sorting ---
|
|
|
|
$total = count($filtered);
|
|
|
|
usort($filtered, static function (array $a, array $b) use ($order, $dir): int {
|
|
$va = (string) ($a[$order] ?? '');
|
|
$vb = (string) ($b[$order] ?? '');
|
|
$cmp = strnatcasecmp($va, $vb);
|
|
|
|
return $dir === 'desc' ? -$cmp : $cmp;
|
|
});
|
|
|
|
// --- Pagination ---
|
|
|
|
$rows = array_slice($filtered, $offset, $limit);
|
|
|
|
// --- Row preparation ---
|
|
|
|
$preparedRows = [];
|
|
foreach ($rows as $contact) {
|
|
$preparedRows[] = [
|
|
'Name' => (string) ($contact['Name'] ?? ''),
|
|
'Type' => (string) ($contact['Type'] ?? ''),
|
|
'Job_Title' => (string) ($contact['Job_Title'] ?? ''),
|
|
'E_Mail' => (string) ($contact['E_Mail'] ?? ''),
|
|
'Phone_No' => (string) ($contact['Phone_No'] ?? ''),
|
|
'Mobile_Phone_No' => (string) ($contact['Mobile_Phone_No'] ?? ''),
|
|
];
|
|
}
|
|
|
|
Router::json([
|
|
'data' => $preparedRows,
|
|
'total' => max(0, $total),
|
|
'type_options' => $typeOptions,
|
|
'meta' => [
|
|
'cache_used' => $cacheUsed,
|
|
'cache_bypassed' => $refreshRequested,
|
|
'cache_refreshed' => $refreshRequested,
|
|
'source' => $serviceMeta['source'],
|
|
'fallback_used' => $serviceMeta['fallback_used'],
|
|
'fallback_reason' => $serviceMeta['fallback_reason'],
|
|
],
|
|
]);
|