- Refactor data model: each customer now has a liittymat array (auto-migration from old format) - Add sopimuskausi (1/12/24/36 kk) and alkupvm fields per connection - Form supports adding/removing multiple connection rows per company - Add "use same as installation address" checkbox for billing address - Move stat cards to compact sidebar on the right - Place search bar above customer table Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
341 lines
13 KiB
PHP
341 lines
13 KiB
PHP
<?php
|
|
session_start();
|
|
header('Content-Type: application/json');
|
|
|
|
define('ADMIN_PASSWORD', 'cuitunet2024');
|
|
define('DATA_FILE', __DIR__ . '/data/customers.json');
|
|
|
|
// Varmista data-kansio
|
|
if (!file_exists(__DIR__ . '/data')) {
|
|
mkdir(__DIR__ . '/data', 0755, true);
|
|
}
|
|
if (!file_exists(DATA_FILE)) {
|
|
file_put_contents(DATA_FILE, '[]');
|
|
}
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
$action = $_GET['action'] ?? '';
|
|
|
|
// Auth-tarkistus (paitsi login)
|
|
function requireAuth() {
|
|
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
|
http_response_code(401);
|
|
echo json_encode(['error' => 'Kirjaudu sisään']);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function loadCustomers(): array {
|
|
$data = file_get_contents(DATA_FILE);
|
|
$customers = json_decode($data, true) ?: [];
|
|
// Migroi vanha data: jos asiakkaalla ei ole liittymat-arrayta, luo se
|
|
$migrated = false;
|
|
foreach ($customers as &$c) {
|
|
if (!isset($c['liittymat'])) {
|
|
$c['liittymat'] = [[
|
|
'asennusosoite' => $c['asennusosoite'] ?? '',
|
|
'postinumero' => $c['postinumero'] ?? '',
|
|
'kaupunki' => $c['kaupunki'] ?? '',
|
|
'liittymanopeus' => $c['liittymanopeus'] ?? '',
|
|
'hinta' => floatval($c['hinta'] ?? 0),
|
|
'sopimuskausi' => '',
|
|
'alkupvm' => '',
|
|
]];
|
|
unset($c['asennusosoite'], $c['postinumero'], $c['kaupunki'], $c['liittymanopeus'], $c['hinta']);
|
|
$migrated = true;
|
|
}
|
|
}
|
|
unset($c);
|
|
if ($migrated) {
|
|
file_put_contents(DATA_FILE, json_encode($customers, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
|
}
|
|
return $customers;
|
|
}
|
|
|
|
function saveCustomers(array $customers): void {
|
|
// Automaattinen backup ennen tallennusta
|
|
if (file_exists(DATA_FILE) && filesize(DATA_FILE) > 2) {
|
|
$backupDir = __DIR__ . '/data/backups';
|
|
if (!file_exists($backupDir)) mkdir($backupDir, 0755, true);
|
|
copy(DATA_FILE, $backupDir . '/customers_' . date('Y-m-d_His') . '.json');
|
|
// Säilytä vain 30 viimeisintä backuppia
|
|
$backups = glob($backupDir . '/customers_*.json');
|
|
if (count($backups) > 30) {
|
|
sort($backups);
|
|
array_map('unlink', array_slice($backups, 0, count($backups) - 30));
|
|
}
|
|
}
|
|
file_put_contents(DATA_FILE, json_encode($customers, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
|
}
|
|
|
|
function generateId(): string {
|
|
return bin2hex(random_bytes(8));
|
|
}
|
|
|
|
switch ($action) {
|
|
case 'login':
|
|
if ($method !== 'POST') break;
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$password = $input['password'] ?? '';
|
|
if ($password === ADMIN_PASSWORD) {
|
|
$_SESSION['authenticated'] = true;
|
|
echo json_encode(['success' => true]);
|
|
} else {
|
|
http_response_code(401);
|
|
echo json_encode(['error' => 'Väärä salasana']);
|
|
}
|
|
break;
|
|
|
|
case 'logout':
|
|
session_destroy();
|
|
echo json_encode(['success' => true]);
|
|
break;
|
|
|
|
case 'check_auth':
|
|
echo json_encode(['authenticated' => isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true]);
|
|
break;
|
|
|
|
case 'customers':
|
|
requireAuth();
|
|
if ($method === 'GET') {
|
|
$customers = loadCustomers();
|
|
echo json_encode($customers);
|
|
}
|
|
break;
|
|
|
|
case 'customer':
|
|
requireAuth();
|
|
if ($method === 'POST') {
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$customers = loadCustomers();
|
|
$liittymat = [];
|
|
foreach (($input['liittymat'] ?? []) as $l) {
|
|
$liittymat[] = [
|
|
'asennusosoite' => trim($l['asennusosoite'] ?? ''),
|
|
'postinumero' => trim($l['postinumero'] ?? ''),
|
|
'kaupunki' => trim($l['kaupunki'] ?? ''),
|
|
'liittymanopeus' => trim($l['liittymanopeus'] ?? ''),
|
|
'hinta' => floatval($l['hinta'] ?? 0),
|
|
'sopimuskausi' => trim($l['sopimuskausi'] ?? ''),
|
|
'alkupvm' => trim($l['alkupvm'] ?? ''),
|
|
];
|
|
}
|
|
if (empty($liittymat)) {
|
|
$liittymat[] = ['asennusosoite' => '', 'postinumero' => '', 'kaupunki' => '', 'liittymanopeus' => '', 'hinta' => 0, 'sopimuskausi' => '', 'alkupvm' => ''];
|
|
}
|
|
$customer = [
|
|
'id' => generateId(),
|
|
'yritys' => trim($input['yritys'] ?? ''),
|
|
'yhteyshenkilö' => trim($input['yhteyshenkilö'] ?? ''),
|
|
'puhelin' => trim($input['puhelin'] ?? ''),
|
|
'sahkoposti' => trim($input['sahkoposti'] ?? ''),
|
|
'laskutusosoite' => trim($input['laskutusosoite'] ?? ''),
|
|
'laskutuspostinumero' => trim($input['laskutuspostinumero'] ?? ''),
|
|
'laskutuskaupunki' => trim($input['laskutuskaupunki'] ?? ''),
|
|
'laskutussahkoposti' => trim($input['laskutussahkoposti'] ?? ''),
|
|
'elaskuosoite' => trim($input['elaskuosoite'] ?? ''),
|
|
'elaskuvalittaja' => trim($input['elaskuvalittaja'] ?? ''),
|
|
'ytunnus' => trim($input['ytunnus'] ?? ''),
|
|
'lisatiedot' => trim($input['lisatiedot'] ?? ''),
|
|
'liittymat' => $liittymat,
|
|
'luotu' => date('Y-m-d H:i:s'),
|
|
];
|
|
if (empty($customer['yritys'])) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Yrityksen nimi vaaditaan']);
|
|
break;
|
|
}
|
|
$customers[] = $customer;
|
|
saveCustomers($customers);
|
|
echo json_encode($customer);
|
|
}
|
|
break;
|
|
|
|
case 'customer_update':
|
|
requireAuth();
|
|
if ($method !== 'POST') break;
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$id = $input['id'] ?? '';
|
|
$customers = loadCustomers();
|
|
$found = false;
|
|
foreach ($customers as &$c) {
|
|
if ($c['id'] === $id) {
|
|
$c['yritys'] = trim($input['yritys'] ?? $c['yritys']);
|
|
$c['yhteyshenkilö'] = trim($input['yhteyshenkilö'] ?? $c['yhteyshenkilö']);
|
|
$c['puhelin'] = trim($input['puhelin'] ?? $c['puhelin']);
|
|
$c['sahkoposti'] = trim($input['sahkoposti'] ?? $c['sahkoposti']);
|
|
$c['laskutusosoite'] = trim($input['laskutusosoite'] ?? $c['laskutusosoite']);
|
|
$c['laskutuspostinumero'] = trim($input['laskutuspostinumero'] ?? ($c['laskutuspostinumero'] ?? ''));
|
|
$c['laskutuskaupunki'] = trim($input['laskutuskaupunki'] ?? ($c['laskutuskaupunki'] ?? ''));
|
|
$c['laskutussahkoposti'] = trim($input['laskutussahkoposti'] ?? $c['laskutussahkoposti']);
|
|
$c['elaskuosoite'] = trim($input['elaskuosoite'] ?? ($c['elaskuosoite'] ?? ''));
|
|
$c['elaskuvalittaja'] = trim($input['elaskuvalittaja'] ?? ($c['elaskuvalittaja'] ?? ''));
|
|
$c['ytunnus'] = trim($input['ytunnus'] ?? $c['ytunnus']);
|
|
$c['lisatiedot'] = trim($input['lisatiedot'] ?? $c['lisatiedot']);
|
|
if (isset($input['liittymat'])) {
|
|
$liittymat = [];
|
|
foreach ($input['liittymat'] as $l) {
|
|
$liittymat[] = [
|
|
'asennusosoite' => trim($l['asennusosoite'] ?? ''),
|
|
'postinumero' => trim($l['postinumero'] ?? ''),
|
|
'kaupunki' => trim($l['kaupunki'] ?? ''),
|
|
'liittymanopeus' => trim($l['liittymanopeus'] ?? ''),
|
|
'hinta' => floatval($l['hinta'] ?? 0),
|
|
'sopimuskausi' => trim($l['sopimuskausi'] ?? ''),
|
|
'alkupvm' => trim($l['alkupvm'] ?? ''),
|
|
];
|
|
}
|
|
$c['liittymat'] = $liittymat;
|
|
}
|
|
$c['muokattu'] = date('Y-m-d H:i:s');
|
|
$found = true;
|
|
echo json_encode($c);
|
|
break;
|
|
}
|
|
}
|
|
unset($c);
|
|
if (!$found) {
|
|
http_response_code(404);
|
|
echo json_encode(['error' => 'Asiakasta ei löydy']);
|
|
break;
|
|
}
|
|
saveCustomers($customers);
|
|
break;
|
|
|
|
case 'file_upload':
|
|
requireAuth();
|
|
if ($method !== 'POST') break;
|
|
$customerId = $_POST['customer_id'] ?? '';
|
|
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId)) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Virheellinen asiakas-ID']);
|
|
break;
|
|
}
|
|
if (empty($_FILES['file'])) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Tiedosto puuttuu']);
|
|
break;
|
|
}
|
|
$file = $_FILES['file'];
|
|
if ($file['error'] !== UPLOAD_ERR_OK) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Tiedoston lähetys epäonnistui']);
|
|
break;
|
|
}
|
|
// Max 20MB
|
|
if ($file['size'] > 20 * 1024 * 1024) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Tiedosto on liian suuri (max 20 MB)']);
|
|
break;
|
|
}
|
|
$uploadDir = __DIR__ . '/data/files/' . $customerId;
|
|
if (!file_exists($uploadDir)) mkdir($uploadDir, 0755, true);
|
|
$safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', basename($file['name']));
|
|
// Jos samanniminen tiedosto on jo olemassa, lisää aikaleima
|
|
$dest = $uploadDir . '/' . $safeName;
|
|
if (file_exists($dest)) {
|
|
$ext = pathinfo($safeName, PATHINFO_EXTENSION);
|
|
$base = pathinfo($safeName, PATHINFO_FILENAME);
|
|
$safeName = $base . '_' . date('His') . ($ext ? '.' . $ext : '');
|
|
$dest = $uploadDir . '/' . $safeName;
|
|
}
|
|
if (move_uploaded_file($file['tmp_name'], $dest)) {
|
|
echo json_encode([
|
|
'success' => true,
|
|
'filename' => $safeName,
|
|
'size' => $file['size'],
|
|
]);
|
|
} else {
|
|
http_response_code(500);
|
|
echo json_encode(['error' => 'Tallennusvirhe']);
|
|
}
|
|
break;
|
|
|
|
case 'file_list':
|
|
requireAuth();
|
|
$customerId = $_GET['customer_id'] ?? '';
|
|
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId)) {
|
|
echo json_encode([]);
|
|
break;
|
|
}
|
|
$dir = __DIR__ . '/data/files/' . $customerId;
|
|
$files = [];
|
|
if (is_dir($dir)) {
|
|
foreach (scandir($dir) as $f) {
|
|
if ($f === '.' || $f === '..') continue;
|
|
$path = $dir . '/' . $f;
|
|
$files[] = [
|
|
'filename' => $f,
|
|
'size' => filesize($path),
|
|
'modified' => date('Y-m-d H:i', filemtime($path)),
|
|
];
|
|
}
|
|
}
|
|
usort($files, fn($a, $b) => strcmp($b['modified'], $a['modified']));
|
|
echo json_encode($files);
|
|
break;
|
|
|
|
case 'file_download':
|
|
requireAuth();
|
|
$customerId = $_GET['customer_id'] ?? '';
|
|
$filename = $_GET['filename'] ?? '';
|
|
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId) || !$filename) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Virheelliset parametrit']);
|
|
break;
|
|
}
|
|
$safeName = basename($filename);
|
|
$path = __DIR__ . '/data/files/' . $customerId . '/' . $safeName;
|
|
if (!file_exists($path)) {
|
|
http_response_code(404);
|
|
echo json_encode(['error' => 'Tiedostoa ei löydy']);
|
|
break;
|
|
}
|
|
header('Content-Type: application/octet-stream');
|
|
header('Content-Disposition: attachment; filename="' . $safeName . '"');
|
|
header('Content-Length: ' . filesize($path));
|
|
readfile($path);
|
|
exit;
|
|
|
|
case 'file_delete':
|
|
requireAuth();
|
|
if ($method !== 'POST') break;
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$customerId = $input['customer_id'] ?? '';
|
|
$filename = $input['filename'] ?? '';
|
|
if (!$customerId || !preg_match('/^[a-f0-9]+$/', $customerId) || !$filename) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Virheelliset parametrit']);
|
|
break;
|
|
}
|
|
$safeName = basename($filename);
|
|
$path = __DIR__ . '/data/files/' . $customerId . '/' . $safeName;
|
|
if (file_exists($path)) {
|
|
unlink($path);
|
|
}
|
|
echo json_encode(['success' => true]);
|
|
break;
|
|
|
|
case 'customer_delete':
|
|
requireAuth();
|
|
if ($method !== 'POST') break;
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$id = $input['id'] ?? '';
|
|
$customers = loadCustomers();
|
|
$customers = array_values(array_filter($customers, fn($c) => $c['id'] !== $id));
|
|
saveCustomers($customers);
|
|
// Poista asiakkaan tiedostot
|
|
$filesDir = __DIR__ . '/data/files/' . $id;
|
|
if (is_dir($filesDir)) {
|
|
array_map('unlink', glob($filesDir . '/*'));
|
|
rmdir($filesDir);
|
|
}
|
|
echo json_encode(['success' => true]);
|
|
break;
|
|
|
|
default:
|
|
http_response_code(404);
|
|
echo json_encode(['error' => 'Tuntematon toiminto']);
|
|
break;
|
|
}
|