Add module visibility fix and configurable postal codes for saatavuus API
- Add 'hallinta' to ALL_MODULES so it appears in company settings - Change fallback from ALL_MODULES to DEFAULT_MODULES (new modules not enabled by default) - Hallinta tab requires both module enabled + superadmin role - Add per-company configurable postal codes for "todennäköinen" availability - Saatavuus API returns true/todennäköinen/false based on customer data + postal codes - Show "Todennäköinen" badge in availability queries list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
31
api.php
31
api.php
@@ -1443,6 +1443,19 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Todennäköinen saatavuus: postinumero saatavuuslistalla
|
||||||
|
$probable = false;
|
||||||
|
if (!$found) {
|
||||||
|
$saatInteg = dbGetIntegration($matchedCompany['id'], 'saatavuus_api');
|
||||||
|
$probablePostcodes = ($saatInteg && $saatInteg['config']) ? ($saatInteg['config']['probable_postcodes'] ?? []) : [];
|
||||||
|
if (in_array($queryPostinumero, $probablePostcodes)) {
|
||||||
|
$probable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tulos: true, "todennäköinen", tai false
|
||||||
|
$result = $found ? true : ($probable ? 'todennäköinen' : false);
|
||||||
|
|
||||||
// Tallenna kysely tietokantaan (ohita duplikaatit: sama osoite+postinumero+kaupunki+yritys)
|
// Tallenna kysely tietokantaan (ohita duplikaatit: sama osoite+postinumero+kaupunki+yritys)
|
||||||
try {
|
try {
|
||||||
$rawOsoite = $_GET['osoite'] ?? '';
|
$rawOsoite = $_GET['osoite'] ?? '';
|
||||||
@@ -1467,6 +1480,8 @@ switch ($action) {
|
|||||||
$org = $ipData['org'] ?? $ipData['isp'] ?? '';
|
$org = $ipData['org'] ?? $ipData['isp'] ?? '';
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) { /* IP-haku ei saa kaataa API:a */ }
|
} catch (\Throwable $e) { /* IP-haku ei saa kaataa API:a */ }
|
||||||
|
// saatavilla: 1=kyllä, 2=todennäköinen, 0=ei
|
||||||
|
$saatavillaDb = $found ? 1 : ($probable ? 2 : 0);
|
||||||
_dbExecute(
|
_dbExecute(
|
||||||
"INSERT INTO availability_queries (company_id, osoite, postinumero, kaupunki, saatavilla, ip_address, hostname, org, user_agent, referer, created_at)
|
"INSERT INTO availability_queries (company_id, osoite, postinumero, kaupunki, saatavilla, ip_address, hostname, org, user_agent, referer, created_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
@@ -1475,7 +1490,7 @@ switch ($action) {
|
|||||||
$rawOsoite,
|
$rawOsoite,
|
||||||
$rawPostinumero,
|
$rawPostinumero,
|
||||||
$rawKaupunki,
|
$rawKaupunki,
|
||||||
$found ? 1 : 0,
|
$saatavillaDb,
|
||||||
$ip,
|
$ip,
|
||||||
$hostname,
|
$hostname,
|
||||||
$org,
|
$org,
|
||||||
@@ -1487,7 +1502,7 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
} catch (\Throwable $e) { /* logitus ei saa kaataa API-vastausta */ }
|
} catch (\Throwable $e) { /* logitus ei saa kaataa API-vastausta */ }
|
||||||
|
|
||||||
echo json_encode(['saatavilla' => $found]);
|
echo json_encode(['saatavilla' => $result]);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// ---------- SAATAVUUSKYSELYT ----------
|
// ---------- SAATAVUUSKYSELYT ----------
|
||||||
@@ -1523,9 +1538,12 @@ switch ($action) {
|
|||||||
// Telegram: yrityskohtainen config (bot_token + chat_id), globaali fallback
|
// Telegram: yrityskohtainen config (bot_token + chat_id), globaali fallback
|
||||||
$teleInteg = dbGetIntegration($companyId, 'telegram');
|
$teleInteg = dbGetIntegration($companyId, 'telegram');
|
||||||
$teleConf = ($teleInteg && $teleInteg['config']) ? $teleInteg['config'] : [];
|
$teleConf = ($teleInteg && $teleInteg['config']) ? $teleInteg['config'] : [];
|
||||||
|
$saatavuusInteg = dbGetIntegration($companyId, 'saatavuus_api');
|
||||||
|
$saatavuusConf = ($saatavuusInteg && $saatavuusInteg['config']) ? $saatavuusInteg['config'] : [];
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'api_key' => dbGetCompanyApiKey($companyId),
|
'api_key' => dbGetCompanyApiKey($companyId),
|
||||||
'cors_origins' => dbGetCompanyCorsOrigins($companyId),
|
'cors_origins' => dbGetCompanyCorsOrigins($companyId),
|
||||||
|
'probable_postcodes' => $saatavuusConf['probable_postcodes'] ?? [],
|
||||||
'telegram_bot_token' => $teleConf['bot_token'] ?? ($globalConf['telegram_bot_token'] ?? ''),
|
'telegram_bot_token' => $teleConf['bot_token'] ?? ($globalConf['telegram_bot_token'] ?? ''),
|
||||||
'telegram_chat_id' => $teleConf['chat_id'] ?? '',
|
'telegram_chat_id' => $teleConf['chat_id'] ?? '',
|
||||||
]);
|
]);
|
||||||
@@ -1556,6 +1574,15 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
dbSaveIntegration($companyId, 'telegram', $teleEnabled, $teleConfig);
|
dbSaveIntegration($companyId, 'telegram', $teleEnabled, $teleConfig);
|
||||||
}
|
}
|
||||||
|
// Todennäköinen saatavuus -postinumerot
|
||||||
|
if (isset($input['probable_postcodes'])) {
|
||||||
|
$postcodes = array_filter(array_map('trim', explode("\n", $input['probable_postcodes'])));
|
||||||
|
$saatInteg = dbGetIntegration($companyId, 'saatavuus_api');
|
||||||
|
$saatConfig = ($saatInteg && $saatInteg['config']) ? $saatInteg['config'] : [];
|
||||||
|
$saatEnabled = $saatInteg ? $saatInteg['enabled'] : true;
|
||||||
|
$saatConfig['probable_postcodes'] = array_values($postcodes);
|
||||||
|
dbSaveIntegration($companyId, 'saatavuus_api', $saatEnabled, $saatConfig);
|
||||||
|
}
|
||||||
dbAddLog($companyId, currentUser(), 'config_update', '', '', 'Päivitti asetuksia');
|
dbAddLog($companyId, currentUser(), 'config_update', '', '', 'Päivitti asetuksia');
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
'api_key' => dbGetCompanyApiKey($companyId),
|
'api_key' => dbGetCompanyApiKey($companyId),
|
||||||
|
|||||||
12
index.html
12
index.html
@@ -1760,6 +1760,11 @@
|
|||||||
<label>Sallitut originit (CORS) - yksi per rivi</label>
|
<label>Sallitut originit (CORS) - yksi per rivi</label>
|
||||||
<textarea id="settings-cors" rows="3" style="font-family:monospace;font-size:0.85rem;" placeholder="https://www.yritys.fi https://yritys.fi"></textarea>
|
<textarea id="settings-cors" rows="3" style="font-family:monospace;font-size:0.85rem;" placeholder="https://www.yritys.fi https://yritys.fi"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Todennäköinen saatavuus — postinumerot (yksi per rivi)</label>
|
||||||
|
<textarea id="settings-probable-postcodes" rows="3" style="font-family:monospace;font-size:0.85rem;" placeholder="20100 20360 20500"></textarea>
|
||||||
|
<small style="color:#888;">Jos osoitetta ei löydy asiakasrekisteristä, mutta postinumero on tällä listalla, palautetaan <code>"todennäköinen"</code>.</small>
|
||||||
|
</div>
|
||||||
<div class="form-group full-width">
|
<div class="form-group full-width">
|
||||||
<button class="btn-primary" id="btn-save-settings">Tallenna asetukset</button>
|
<button class="btn-primary" id="btn-save-settings">Tallenna asetukset</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1777,7 +1782,9 @@
|
|||||||
<div style="margin-bottom:0.75rem;"><strong>Esimerkki:</strong><br>
|
<div style="margin-bottom:0.75rem;"><strong>Esimerkki:</strong><br>
|
||||||
<code id="api-example-url">api.php?action=saatavuus&key=AVAIN&osoite=Esimerkkikatu+1&postinumero=00100&kaupunki=Helsinki</code></div>
|
<code id="api-example-url">api.php?action=saatavuus&key=AVAIN&osoite=Esimerkkikatu+1&postinumero=00100&kaupunki=Helsinki</code></div>
|
||||||
<div><strong>Vastaus:</strong><br>
|
<div><strong>Vastaus:</strong><br>
|
||||||
<code>{"saatavilla":true}</code> tai <code>{"saatavilla":false}</code></div>
|
<code>{"saatavilla":true}</code> — löytyy asiakasrekisteristä<br>
|
||||||
|
<code>{"saatavilla":"todennäköinen"}</code> — postinumero saatavuusalueella<br>
|
||||||
|
<code>{"saatavilla":false}</code> — ei löydy</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 style="color:#0f3460;margin:1.5rem 0 1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Testaa API</h3>
|
<h3 style="color:#0f3460;margin:1.5rem 0 1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Testaa API</h3>
|
||||||
<div style="display:grid;grid-template-columns:1fr;gap:0.5rem;max-width:500px;">
|
<div style="display:grid;grid-template-columns:1fr;gap:0.5rem;max-width:500px;">
|
||||||
@@ -1965,6 +1972,9 @@
|
|||||||
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
|
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
|
||||||
<input type="checkbox" data-module="netadmin"> NetAdmin
|
<input type="checkbox" data-module="netadmin"> NetAdmin
|
||||||
</label>
|
</label>
|
||||||
|
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
|
||||||
|
<input type="checkbox" data-module="hallinta"> Hallinta
|
||||||
|
</label>
|
||||||
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
|
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
|
||||||
<input type="checkbox" data-module="settings" checked> Asetukset / API
|
<input type="checkbox" data-module="settings" checked> Asetukset / API
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
20
script.js
20
script.js
@@ -2744,6 +2744,7 @@ async function loadSettings() {
|
|||||||
const config = await apiCall('config');
|
const config = await apiCall('config');
|
||||||
document.getElementById('settings-api-key').value = config.api_key || '';
|
document.getElementById('settings-api-key').value = config.api_key || '';
|
||||||
document.getElementById('settings-cors').value = (config.cors_origins || []).join('\n');
|
document.getElementById('settings-cors').value = (config.cors_origins || []).join('\n');
|
||||||
|
document.getElementById('settings-probable-postcodes').value = (config.probable_postcodes || []).join('\n');
|
||||||
// Näytä yrityksen nimi API-otsikossa
|
// Näytä yrityksen nimi API-otsikossa
|
||||||
const apiTitle = document.getElementById('api-company-name');
|
const apiTitle = document.getElementById('api-company-name');
|
||||||
if (apiTitle && currentCompany) apiTitle.textContent = currentCompany.nimi + ' — ';
|
if (apiTitle && currentCompany) apiTitle.textContent = currentCompany.nimi + ' — ';
|
||||||
@@ -2831,6 +2832,7 @@ document.getElementById('btn-save-settings').addEventListener('click', async ()
|
|||||||
const config = await apiCall('config_update', 'POST', {
|
const config = await apiCall('config_update', 'POST', {
|
||||||
api_key: document.getElementById('settings-api-key').value,
|
api_key: document.getElementById('settings-api-key').value,
|
||||||
cors_origins: document.getElementById('settings-cors').value,
|
cors_origins: document.getElementById('settings-cors').value,
|
||||||
|
probable_postcodes: document.getElementById('settings-probable-postcodes').value,
|
||||||
});
|
});
|
||||||
alert('Asetukset tallennettu!');
|
alert('Asetukset tallennettu!');
|
||||||
} catch (e) { alert(e.message); }
|
} catch (e) { alert(e.message); }
|
||||||
@@ -3737,9 +3739,11 @@ async function loadAvailabilityQueries(page = 0) {
|
|||||||
} else {
|
} else {
|
||||||
tbody.innerHTML = data.queries.map(q => {
|
tbody.innerHTML = data.queries.map(q => {
|
||||||
const date = q.created_at ? q.created_at.replace('T', ' ').substring(0, 16) : '';
|
const date = q.created_at ? q.created_at.replace('T', ' ').substring(0, 16) : '';
|
||||||
const found = q.saatavilla == 1;
|
const saatVal = parseInt(q.saatavilla);
|
||||||
const badge = found
|
const badge = saatVal === 1
|
||||||
? '<span style="background:#e8f5e9;color:#2e7d32;padding:2px 8px;border-radius:10px;font-size:0.8rem;">Saatavilla</span>'
|
? '<span style="background:#e8f5e9;color:#2e7d32;padding:2px 8px;border-radius:10px;font-size:0.8rem;">Saatavilla</span>'
|
||||||
|
: saatVal === 2
|
||||||
|
? '<span style="background:#fff3e0;color:#e65100;padding:2px 8px;border-radius:10px;font-size:0.8rem;">Todennäköinen</span>'
|
||||||
: '<span style="background:#fce4ec;color:#c62828;padding:2px 8px;border-radius:10px;font-size:0.8rem;">Ei saatavilla</span>';
|
: '<span style="background:#fce4ec;color:#c62828;padding:2px 8px;border-radius:10px;font-size:0.8rem;">Ei saatavilla</span>';
|
||||||
let source = '';
|
let source = '';
|
||||||
if (q.referer) {
|
if (q.referer) {
|
||||||
@@ -6719,7 +6723,7 @@ document.getElementById('laitetila-edit-form')?.addEventListener('submit', async
|
|||||||
|
|
||||||
// ==================== MODUULIT ====================
|
// ==================== MODUULIT ====================
|
||||||
|
|
||||||
const ALL_MODULES = ['customers', 'support', 'leads', 'tekniikka', 'ohjeet', 'todo', 'documents', 'laitetilat', 'netadmin', 'archive', 'changelog', 'settings'];
|
const ALL_MODULES = ['customers', 'support', 'leads', 'tekniikka', 'ohjeet', 'todo', 'documents', 'laitetilat', 'netadmin', 'hallinta', 'archive', 'changelog', 'settings'];
|
||||||
const DEFAULT_MODULES = ['customers', 'support', 'archive', 'changelog', 'settings'];
|
const DEFAULT_MODULES = ['customers', 'support', 'archive', 'changelog', 'settings'];
|
||||||
|
|
||||||
function applyModules(modules, hasIntegrations) {
|
function applyModules(modules, hasIntegrations) {
|
||||||
@@ -6727,8 +6731,8 @@ function applyModules(modules, hasIntegrations) {
|
|||||||
if (modules && modules.includes('devices') && !modules.includes('tekniikka')) {
|
if (modules && modules.includes('devices') && !modules.includes('tekniikka')) {
|
||||||
modules = modules.map(m => m === 'devices' ? 'tekniikka' : m);
|
modules = modules.map(m => m === 'devices' ? 'tekniikka' : m);
|
||||||
}
|
}
|
||||||
// Jos tyhjä array → kaikki moduulit päällä (fallback)
|
// Jos tyhjä array → oletusmoduulit (admin aktivoi uudet erikseen)
|
||||||
const enabled = (modules && modules.length > 0) ? modules : ALL_MODULES;
|
const enabled = (modules && modules.length > 0) ? modules : DEFAULT_MODULES;
|
||||||
const isAdminUser = isCurrentUserAdmin();
|
const isAdminUser = isCurrentUserAdmin();
|
||||||
const isSuperAdmin = currentUser?.role === 'superadmin';
|
const isSuperAdmin = currentUser?.role === 'superadmin';
|
||||||
ALL_MODULES.forEach(mod => {
|
ALL_MODULES.forEach(mod => {
|
||||||
@@ -6738,14 +6742,14 @@ function applyModules(modules, hasIntegrations) {
|
|||||||
if (mod === 'settings') {
|
if (mod === 'settings') {
|
||||||
const showSettings = enabled.includes(mod) && isAdminUser && (isSuperAdmin || hasIntegrations === true);
|
const showSettings = enabled.includes(mod) && isAdminUser && (isSuperAdmin || hasIntegrations === true);
|
||||||
tabBtn.style.display = showSettings ? '' : 'none';
|
tabBtn.style.display = showSettings ? '' : 'none';
|
||||||
|
// hallinta: vain superadmineille + pitää olla moduulina päällä
|
||||||
|
} else if (mod === 'hallinta') {
|
||||||
|
tabBtn.style.display = (enabled.includes(mod) && isSuperAdmin) ? '' : 'none';
|
||||||
} else {
|
} else {
|
||||||
tabBtn.style.display = enabled.includes(mod) ? '' : 'none';
|
tabBtn.style.display = enabled.includes(mod) ? '' : 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Hallinta-tabi: aina näkyvä superadmineille, ei moduuliriippuvuutta
|
|
||||||
const hallintaTab = document.getElementById('tab-hallinta');
|
|
||||||
if (hallintaTab) hallintaTab.style.display = isSuperAdmin ? '' : 'none';
|
|
||||||
// Jos aktiivinen tabi on piilotettu → vaihda ensimmäiseen näkyvään
|
// Jos aktiivinen tabi on piilotettu → vaihda ensimmäiseen näkyvään
|
||||||
const activeTab = document.querySelector('.tab.active');
|
const activeTab = document.querySelector('.tab.active');
|
||||||
if (activeTab && activeTab.style.display === 'none') {
|
if (activeTab && activeTab.style.display === 'none') {
|
||||||
|
|||||||
Reference in New Issue
Block a user