Add cross-company ticket viewing and move Yritykset button to header

- tickets endpoint supports ?all=1 to fetch from all user's companies
- ticket_detail/reply/status/etc support ?company_id= for cross-company ops
- Support tab shows all companies' tickets with company badge on subject
- Yritykset button moved from tab bar to header (next to Käyttäjät)
- requireCompanyOrParam() helper for ticket endpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 11:19:35 +02:00
parent 7f89a29b94
commit f82f62877d
3 changed files with 135 additions and 76 deletions

154
api.php
View File

@@ -105,6 +105,22 @@ function requireCompany(): string {
return $companyId;
}
// Kuten requireCompany(), mutta sallii company_id:n overriden GET-parametrista
// Käytetään tiketti-endpointeissa jotta toisen yrityksen tikettejä voi avata
function requireCompanyOrParam(): string {
$paramCompany = $_GET['company_id'] ?? '';
if (!empty($paramCompany)) {
$userCompanies = $_SESSION['companies'] ?? [];
if (!in_array($paramCompany, $userCompanies)) {
http_response_code(403);
echo json_encode(['error' => 'Ei oikeutta tähän yritykseen']);
exit;
}
$_SESSION['company_id'] = $paramCompany;
}
return requireCompany();
}
function companyFile(string $filename): string {
return getCompanyDir() . '/' . $filename;
}
@@ -1663,62 +1679,86 @@ switch ($action) {
// ---------- TICKETS ----------
case 'tickets':
requireAuth();
requireCompany();
$tickets = loadTickets();
// Palauta ilman viestisisältöjä (lista-näkymä)
// Auto-close tarkistus: sulje tiketit joiden auto_close_at on ohitettu
$now = date('Y-m-d H:i:s');
$autoCloseCount = 0;
foreach ($tickets as &$tc) {
if (!empty($tc['auto_close_at']) && $tc['auto_close_at'] <= $now && !in_array($tc['status'], ['suljettu'])) {
$tc['status'] = 'suljettu';
$tc['updated'] = $now;
$autoCloseCount++;
$allCompanies = !empty($_GET['all']);
$userCompanyIds = $_SESSION['companies'] ?? [];
// Kerää yritykset joista haetaan
$companiesToQuery = [];
if ($allCompanies && count($userCompanyIds) > 1) {
$allComps = loadCompanies();
foreach ($allComps as $c) {
if (in_array($c['id'], $userCompanyIds)) {
$companiesToQuery[] = $c;
}
}
} else {
requireCompany();
$companiesToQuery[] = ['id' => $_SESSION['company_id'], 'nimi' => ''];
}
$list = [];
foreach ($companiesToQuery as $comp) {
$cDir = DATA_DIR . '/companies/' . $comp['id'];
$ticketsFile = $cDir . '/tickets.json';
if (!file_exists($ticketsFile)) continue;
$tickets = json_decode(file_get_contents($ticketsFile), true) ?: [];
// Auto-close tarkistus
$now = date('Y-m-d H:i:s');
$autoCloseCount = 0;
foreach ($tickets as &$tc) {
if (!empty($tc['auto_close_at']) && $tc['auto_close_at'] <= $now && !in_array($tc['status'], ['suljettu'])) {
$tc['status'] = 'suljettu';
$tc['updated'] = $now;
$autoCloseCount++;
}
}
unset($tc);
if ($autoCloseCount > 0) {
file_put_contents($ticketsFile, json_encode($tickets, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Resolve mailbox names for this company
$confFile = $cDir . '/config.json';
$companyConf = file_exists($confFile) ? (json_decode(file_get_contents($confFile), true) ?: []) : [];
$mailboxNames = [];
foreach ($companyConf['mailboxes'] ?? [] as $mb) {
$mailboxNames[$mb['id']] = $mb['nimi'];
}
foreach ($tickets as $t) {
$msgCount = count($t['messages'] ?? []);
$lastMsg = $msgCount > 0 ? $t['messages'][$msgCount - 1] : null;
$list[] = [
'id' => $t['id'],
'subject' => $t['subject'],
'from_email' => $t['from_email'],
'from_name' => $t['from_name'],
'status' => $t['status'],
'type' => $t['type'] ?? 'muu',
'assigned_to' => $t['assigned_to'] ?? '',
'customer_id' => $t['customer_id'] ?? '',
'customer_name' => $t['customer_name'] ?? '',
'tags' => $t['tags'] ?? [],
'auto_close_at' => $t['auto_close_at'] ?? '',
'mailbox_id' => $t['mailbox_id'] ?? '',
'mailbox_name' => $mailboxNames[$t['mailbox_id'] ?? ''] ?? '',
'company_id' => $comp['id'],
'company_name' => $comp['nimi'] ?? '',
'created' => $t['created'],
'updated' => $t['updated'],
'message_count' => $msgCount,
'last_message_type' => $lastMsg ? ($lastMsg['type'] ?? '') : '',
'last_message_time' => $lastMsg ? ($lastMsg['timestamp'] ?? '') : '',
];
}
}
unset($tc);
if ($autoCloseCount > 0) {
saveTickets($tickets);
addLog('ticket_auto_close', '', '', "Automaattisulku: $autoCloseCount tikettiä");
}
// Resolve mailbox names
$companyConf = loadCompanyConfig();
$mailboxNames = [];
foreach ($companyConf['mailboxes'] ?? [] as $mb) {
$mailboxNames[$mb['id']] = $mb['nimi'];
}
$list = array_map(function($t) use ($mailboxNames) {
$msgCount = count($t['messages'] ?? []);
$lastMsg = $msgCount > 0 ? $t['messages'][$msgCount - 1] : null;
return [
'id' => $t['id'],
'subject' => $t['subject'],
'from_email' => $t['from_email'],
'from_name' => $t['from_name'],
'status' => $t['status'],
'type' => $t['type'] ?? 'muu',
'assigned_to' => $t['assigned_to'] ?? '',
'customer_id' => $t['customer_id'] ?? '',
'customer_name' => $t['customer_name'] ?? '',
'tags' => $t['tags'] ?? [],
'auto_close_at' => $t['auto_close_at'] ?? '',
'mailbox_id' => $t['mailbox_id'] ?? '',
'mailbox_name' => $mailboxNames[$t['mailbox_id'] ?? ''] ?? '',
'created' => $t['created'],
'updated' => $t['updated'],
'message_count' => $msgCount,
'last_message_type' => $lastMsg ? ($lastMsg['type'] ?? '') : '',
'last_message_time' => $lastMsg ? ($lastMsg['timestamp'] ?? '') : '',
];
}, $tickets);
echo json_encode($list);
break;
case 'ticket_detail':
requireAuth();
requireCompany();
requireCompanyOrParam();
$id = $_GET['id'] ?? '';
$tickets = loadTickets();
$ticket = null;
@@ -1880,7 +1920,7 @@ switch ($action) {
case 'ticket_reply':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
@@ -1955,7 +1995,7 @@ switch ($action) {
case 'ticket_status':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
@@ -1990,7 +2030,7 @@ switch ($action) {
case 'ticket_type':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
@@ -2025,7 +2065,7 @@ switch ($action) {
case 'ticket_customer':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
@@ -2055,7 +2095,7 @@ switch ($action) {
case 'ticket_assign':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
@@ -2083,7 +2123,7 @@ switch ($action) {
case 'ticket_note':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
@@ -2125,7 +2165,7 @@ switch ($action) {
case 'ticket_delete':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';
@@ -2142,7 +2182,7 @@ switch ($action) {
case 'ticket_tags':
requireAuth();
requireCompany();
requireCompanyOrParam();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$id = $input['id'] ?? '';