Files
api/services/nginx/app/classes/system_search_economic_customer_index.php
T

383 lines
14 KiB
PHP

<?php
namespace classes;
use objects\users_o;
use Throwable;
class system_search_economic_customer_index
{
public const TABLE = 'system_search_economic_customer_index';
private static bool $initialized = false;
public static function ensureTable(): void
{
if (self::$initialized) {
return;
}
global $db;
if (!is_object($db) || !method_exists($db, 'query')) {
return;
}
$sql = "CREATE TABLE IF NOT EXISTS `" . self::TABLE . "` (
`customer_number` INT NOT NULL,
`user_id` INT NULL,
`local_display_name` VARCHAR(255) NULL,
`local_email` VARCHAR(255) NULL,
`local_phone` VARCHAR(64) NULL,
`economic_name` VARCHAR(255) NULL,
`economic_address` VARCHAR(255) NULL,
`economic_city` VARCHAR(255) NULL,
`economic_zip` VARCHAR(64) NULL,
`economic_email` VARCHAR(255) NULL,
`economic_cvr` VARCHAR(64) NULL,
`economic_mobile_phone` VARCHAR(64) NULL,
`economic_barred` TINYINT(1) NULL,
`search_text` TEXT NULL,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`customer_number`),
INDEX `idx_system_search_econ_customer_user` (`user_id`),
INDEX `idx_system_search_econ_customer_name` (`economic_name`),
INDEX `idx_system_search_econ_customer_email` (`economic_email`),
INDEX `idx_system_search_econ_customer_cvr` (`economic_cvr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$db->query($sql);
self::ensureColumn(
'economic_barred',
"ALTER TABLE `" . self::TABLE . "` ADD COLUMN `economic_barred` TINYINT(1) NULL AFTER `economic_mobile_phone`"
);
self::$initialized = true;
}
/**
* @param array<int, int> $customerNumbers
* @return array<int, array<string, mixed>>
*/
public static function fetchContexts(array $customerNumbers): array
{
self::ensureTable();
$normalized = array_values(array_unique(array_filter(
array_map('intval', $customerNumbers),
static fn(int $value): bool => $value > 0
)));
if (empty($normalized)) {
return [];
}
global $db;
if (!is_object($db) || !method_exists($db, 'query')) {
return [];
}
$result = $db->query(
"SELECT `customer_number`, `user_id`, `local_display_name`, `local_email`, `local_phone`,"
. " `economic_name`, `economic_address`, `economic_city`, `economic_zip`, `economic_email`,"
. " `economic_cvr`, `economic_mobile_phone`, `economic_barred`"
. " FROM `" . self::TABLE . "`"
. " WHERE `customer_number` IN (" . implode(',', $normalized) . ")"
);
if (!($result instanceof \mysqli_result)) {
return [];
}
$contexts = [];
while ($row = $result->fetch_assoc()) {
$customerNumber = (int)($row['customer_number'] ?? 0);
if ($customerNumber <= 0) {
continue;
}
$barred = self::toNullableBool($row['economic_barred'] ?? null);
$contexts[$customerNumber] = [
'customer_number' => $customerNumber,
'user_id' => self::toNullableInt($row['user_id'] ?? null),
'name' => self::toNullableString($row['economic_name'] ?? null)
?? self::toNullableString($row['local_display_name'] ?? null),
'barred' => $barred,
'status' => self::barredStatus($barred),
'email' => self::toNullableString($row['economic_email'] ?? null)
?? self::toNullableString($row['local_email'] ?? null),
'phone' => self::toNullableString($row['economic_mobile_phone'] ?? null)
?? self::toNullableString($row['local_phone'] ?? null),
'cvr' => self::toNullableString($row['economic_cvr'] ?? null),
'address' => self::toNullableString($row['economic_address'] ?? null),
'city' => self::toNullableString($row['economic_city'] ?? null),
'zip' => self::toNullableString($row['economic_zip'] ?? null),
];
}
return $contexts;
}
/**
* Rebuild local e-conomic customer index from local users + cached/live e-conomic snapshots.
*
* @return array<string, int>
*/
public static function refreshIndex(bool $refreshEconomicData = false): array
{
self::ensureTable();
global $db;
if (!is_object($db) || !method_exists($db, 'query') || !property_exists($db, 'conn')) {
return [
'processed' => 0,
'upserted' => 0,
'deleted' => 0,
'errors' => 0,
];
}
$stats = [
'processed' => 0,
'upserted' => 0,
'deleted' => 0,
'errors' => 0,
];
$result = $db->query("SELECT `id`, `customer_number`, `display_name`, `email`, `phone`
FROM `users`
WHERE `customer_number` IS NOT NULL
AND `customer_number` <> 0");
if (!($result instanceof \mysqli_result)) {
return $stats;
}
$rows = $db->fetch_all($result);
$seenCustomerNumbers = [];
foreach ($rows as $row) {
$stats['processed']++;
$customerNumber = (int)($row['customer_number'] ?? 0);
if ($customerNumber <= 0) {
continue;
}
$seenCustomerNumbers[$customerNumber] = true;
$economic = [];
try {
$tmpUser = new users_o();
$tmpUser->getUserByCustomerNumber($customerNumber);
if ($refreshEconomicData) {
$tmpUser->getCustomerEcocomicData($customerNumber);
}
$cached = $tmpUser->getCached('economic_customer');
if (!$cached && !$refreshEconomicData) {
$tmpUser->getCustomerEcocomicData($customerNumber);
$cached = $tmpUser->getCached('economic_customer');
}
$economic = self::normalizeEconomicSnapshot($cached);
} catch (Throwable) {
$stats['errors']++;
}
$localDisplayName = self::toNullableString($row['display_name'] ?? null);
$localEmail = self::toNullableString($row['email'] ?? null);
$localPhone = self::toNullableString($row['phone'] ?? null);
$economicName = self::toNullableString($economic['name'] ?? null);
$economicAddress = self::toNullableString($economic['address'] ?? null);
$economicCity = self::toNullableString($economic['city'] ?? null);
$economicZip = self::toNullableString($economic['zip'] ?? null);
$economicEmail = self::toNullableString($economic['email'] ?? null);
$economicCvr = self::toNullableString($economic['corporateIdentificationNumber'] ?? null);
$economicMobilePhone = self::toNullableString($economic['mobilePhone'] ?? null);
$economicBarred = self::toNullableBool($economic['barred'] ?? null);
$searchText = trim(implode(' ', array_values(array_filter([
$customerNumber > 0 ? (string)$customerNumber : null,
$localDisplayName,
$localEmail,
$localPhone,
$economicName,
$economicAddress,
$economicCity,
$economicZip,
$economicEmail,
$economicCvr,
$economicMobilePhone,
], static fn($v) => is_string($v) && trim($v) !== ''))));
if ($searchText === '') {
$searchText = null;
}
$sql = "INSERT INTO `" . self::TABLE . "` (
`customer_number`,
`user_id`,
`local_display_name`,
`local_email`,
`local_phone`,
`economic_name`,
`economic_address`,
`economic_city`,
`economic_zip`,
`economic_email`,
`economic_cvr`,
`economic_mobile_phone`,
`economic_barred`,
`search_text`
) VALUES (
" . (int)$customerNumber . ",
" . (int)($row['id'] ?? 0) . ",
" . self::sqlNullableString($localDisplayName) . ",
" . self::sqlNullableString($localEmail) . ",
" . self::sqlNullableString($localPhone) . ",
" . self::sqlNullableString($economicName) . ",
" . self::sqlNullableString($economicAddress) . ",
" . self::sqlNullableString($economicCity) . ",
" . self::sqlNullableString($economicZip) . ",
" . self::sqlNullableString($economicEmail) . ",
" . self::sqlNullableString($economicCvr) . ",
" . self::sqlNullableString($economicMobilePhone) . ",
" . self::sqlNullableBool($economicBarred) . ",
" . self::sqlNullableString($searchText) . "
) ON DUPLICATE KEY UPDATE
`user_id` = VALUES(`user_id`),
`local_display_name` = VALUES(`local_display_name`),
`local_email` = VALUES(`local_email`),
`local_phone` = VALUES(`local_phone`),
`economic_name` = VALUES(`economic_name`),
`economic_address` = VALUES(`economic_address`),
`economic_city` = VALUES(`economic_city`),
`economic_zip` = VALUES(`economic_zip`),
`economic_email` = VALUES(`economic_email`),
`economic_cvr` = VALUES(`economic_cvr`),
`economic_mobile_phone` = VALUES(`economic_mobile_phone`),
`economic_barred` = VALUES(`economic_barred`),
`search_text` = VALUES(`search_text`),
`updated_at` = CURRENT_TIMESTAMP";
$db->query($sql);
$stats['upserted']++;
}
$seen = array_keys($seenCustomerNumbers);
if (empty($seen)) {
$db->query("DELETE FROM `" . self::TABLE . "`");
$stats['deleted'] = self::safeAffectedRows();
return $stats;
}
$in = implode(',', array_map('intval', $seen));
$db->query("DELETE FROM `" . self::TABLE . "` WHERE `customer_number` NOT IN (" . $in . ")");
$stats['deleted'] = self::safeAffectedRows();
return $stats;
}
/**
* @return array<string, mixed>
*/
private static function normalizeEconomicSnapshot(mixed $snapshot): array
{
if (is_object($snapshot)) {
return get_object_vars($snapshot);
}
if (is_array($snapshot)) {
return $snapshot;
}
return [];
}
private static function toNullableString(mixed $value): ?string
{
if ($value === null) {
return null;
}
$string = trim((string)$value);
return $string === '' ? null : $string;
}
private static function toNullableInt(mixed $value): ?int
{
if (is_int($value)) {
return $value;
}
if (is_numeric($value) && (string)(int)$value === trim((string)$value)) {
return (int)$value;
}
return null;
}
private static function toNullableBool(mixed $value): ?bool
{
if ($value === null || $value === '') {
return null;
}
if (is_bool($value)) {
return $value;
}
if (is_int($value)) {
return $value !== 0;
}
if (is_string($value)) {
$normalized = trim(mb_strtolower($value));
if ($normalized === '') {
return null;
}
if (in_array($normalized, ['1', 'true', 'yes'], true)) {
return true;
}
if (in_array($normalized, ['0', 'false', 'no'], true)) {
return false;
}
}
return null;
}
private static function sqlNullableString(?string $value): string
{
global $db;
if ($value === null) {
return 'NULL';
}
return "'" . $db->escape_string($value) . "'";
}
private static function sqlNullableBool(?bool $value): string
{
if ($value === null) {
return 'NULL';
}
return $value ? '1' : '0';
}
private static function ensureColumn(string $column, string $alterSql): void
{
global $db;
if (!is_object($db) || !method_exists($db, 'query')) {
return;
}
$result = $db->query(
"SHOW COLUMNS FROM `" . self::TABLE . "` LIKE '" . $db->escape_string($column) . "'"
);
if ($result instanceof \mysqli_result && $result->num_rows === 0) {
$db->query($alterSql);
}
}
private static function barredStatus(?bool $barred): string
{
return match ($barred) {
true => 'barred',
false => 'active',
default => 'unknown',
};
}
private static function safeAffectedRows(): int
{
global $db;
if (!is_object($db) || !property_exists($db, 'conn') || !is_object($db->conn)) {
return 0;
}
return max(0, (int)($db->conn->affected_rows ?? 0));
}
}