Versioiva dokumentinhallinta + Laitetilat-moduuli
Dokumentit: versioiva tiedostonhallinta asiakkaille (sopimukset, laskut, ohjeet). Sisältää versiohistorian, tiedostojen latauksen/palautuksen ja asiakas-suodatuksen. Laitetilat: laitetilojen hallinta kuvagallerialla ja tiedostolistauksella. Sisältää korttipohjaisen listanäkymän, kuvien esikatselun ja tiedostojen hallinnan. Molemmat moduulit: 4 DB-taulua, 14 API-endpointtia, täysi CRUD, tiedostoupload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
447
api.php
447
api.php
@@ -3994,6 +3994,453 @@ switch ($action) {
|
||||
echo json_encode(['success' => true]);
|
||||
break;
|
||||
|
||||
// ==================== DOKUMENTIT ====================
|
||||
|
||||
case 'documents':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
try {
|
||||
$customerId = $_GET['customer_id'] ?? null;
|
||||
$docs = dbLoadDocuments($companyId, $customerId ?: null);
|
||||
echo json_encode($docs);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Dokumenttien haku epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'document':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
try {
|
||||
$docId = $_GET['id'] ?? '';
|
||||
if (empty($docId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Dokumentin ID puuttuu']);
|
||||
break;
|
||||
}
|
||||
$doc = dbLoadDocument($docId);
|
||||
if (!$doc || $doc['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Dokumenttia ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
echo json_encode($doc);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Dokumentin haku epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'document_save':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$input['created_by'] = $input['created_by'] ?? currentUser();
|
||||
$input['muokkaaja'] = currentUser();
|
||||
$id = dbSaveDocument($companyId, $input);
|
||||
$doc = dbLoadDocument($id);
|
||||
echo json_encode($doc);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Dokumentin tallennus epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'document_upload':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$docId = $_POST['document_id'] ?? '';
|
||||
if (empty($docId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Dokumentin ID puuttuu']);
|
||||
break;
|
||||
}
|
||||
// Tarkista dokumentin olemassaolo
|
||||
$doc = dbLoadDocument($docId);
|
||||
if (!$doc || $doc['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Dokumenttia ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tiedosto puuttuu tai lähetys epäonnistui']);
|
||||
break;
|
||||
}
|
||||
$file = $_FILES['file'];
|
||||
// Max 50MB
|
||||
if ($file['size'] > 50 * 1024 * 1024) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tiedosto on liian suuri (max 50MB)']);
|
||||
break;
|
||||
}
|
||||
// Luo hakemisto
|
||||
$docDir = DATA_DIR . '/companies/' . $companyId . '/documents/' . $docId;
|
||||
if (!file_exists($docDir)) mkdir($docDir, 0755, true);
|
||||
|
||||
$nextVersion = (int)$doc['current_version'] + 1;
|
||||
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
$safeFilename = $nextVersion . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '_', $file['name']);
|
||||
|
||||
move_uploaded_file($file['tmp_name'], $docDir . '/' . $safeFilename);
|
||||
|
||||
$mimeType = '';
|
||||
if (function_exists('finfo_open')) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $docDir . '/' . $safeFilename);
|
||||
finfo_close($finfo);
|
||||
}
|
||||
|
||||
dbAddDocumentVersion($docId, [
|
||||
'filename' => $safeFilename,
|
||||
'original_name' => $file['name'],
|
||||
'file_size' => $file['size'],
|
||||
'mime_type' => $mimeType ?: ($file['type'] ?? ''),
|
||||
'change_notes' => $_POST['change_notes'] ?? '',
|
||||
'created_by' => currentUser()
|
||||
]);
|
||||
|
||||
$updatedDoc = dbLoadDocument($docId);
|
||||
echo json_encode($updatedDoc);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Tiedoston lataus epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'document_download':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
try {
|
||||
$docId = $_GET['id'] ?? '';
|
||||
$versionNum = (int)($_GET['version'] ?? 0);
|
||||
if (empty($docId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Dokumentin ID puuttuu']);
|
||||
break;
|
||||
}
|
||||
$doc = dbLoadDocument($docId);
|
||||
if (!$doc || $doc['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Dokumenttia ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
// Jos versiota ei annettu, käytä nykyistä
|
||||
if ($versionNum <= 0) $versionNum = (int)$doc['current_version'];
|
||||
$version = dbGetDocumentVersion($docId, $versionNum);
|
||||
if (!$version) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Versiota ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
$filePath = DATA_DIR . '/companies/' . $companyId . '/documents/' . $docId . '/' . $version['filename'];
|
||||
if (!file_exists($filePath)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Tiedostoa ei löytynyt palvelimelta']);
|
||||
break;
|
||||
}
|
||||
header('Content-Type: ' . ($version['mime_type'] ?: 'application/octet-stream'));
|
||||
header('Content-Disposition: attachment; filename="' . $version['original_name'] . '"');
|
||||
header('Content-Length: ' . filesize($filePath));
|
||||
readfile($filePath);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Lataus epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'document_restore':
|
||||
requireAdmin();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$docId = $input['document_id'] ?? '';
|
||||
$versionId = $input['version_id'] ?? '';
|
||||
if (empty($docId) || empty($versionId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Parametrit puuttuvat']);
|
||||
break;
|
||||
}
|
||||
$doc = dbLoadDocument($docId);
|
||||
if (!$doc || $doc['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Dokumenttia ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
// Hae vanha versio ja kopioi tiedosto
|
||||
$oldVersion = null;
|
||||
foreach ($doc['versions'] as $v) {
|
||||
if ($v['id'] === $versionId) { $oldVersion = $v; break; }
|
||||
}
|
||||
if (!$oldVersion) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Versiota ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
// Kopioi tiedosto uudella nimellä
|
||||
$docDir = DATA_DIR . '/companies/' . $companyId . '/documents/' . $docId;
|
||||
$oldFilePath = $docDir . '/' . $oldVersion['filename'];
|
||||
if (file_exists($oldFilePath)) {
|
||||
$nextVersion = (int)$doc['current_version'] + 1;
|
||||
$newFilename = $nextVersion . '_' . $oldVersion['original_name'];
|
||||
$newFilename = preg_replace('/[^a-zA-Z0-9._-]/', '_', $newFilename);
|
||||
copy($oldFilePath, $docDir . '/' . $newFilename);
|
||||
}
|
||||
$result = dbRestoreDocumentVersion($docId, $versionId, currentUser());
|
||||
if ($result === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Palautus epäonnistui']);
|
||||
break;
|
||||
}
|
||||
$updatedDoc = dbLoadDocument($docId);
|
||||
echo json_encode($updatedDoc);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Palautus epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'document_delete':
|
||||
requireAdmin();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$docId = $input['id'] ?? '';
|
||||
$doc = dbLoadDocument($docId);
|
||||
if (!$doc || $doc['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Dokumenttia ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
// Poista tiedostot levyltä
|
||||
$docDir = DATA_DIR . '/companies/' . $companyId . '/documents/' . $docId;
|
||||
if (is_dir($docDir)) {
|
||||
$files = glob($docDir . '/*');
|
||||
foreach ($files as $f) { if (is_file($f)) unlink($f); }
|
||||
rmdir($docDir);
|
||||
}
|
||||
dbDeleteDocument($docId);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Poisto epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
// ==================== LAITETILAT ====================
|
||||
|
||||
case 'laitetilat':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
try {
|
||||
$tilat = dbLoadLaitetilat($companyId);
|
||||
echo json_encode($tilat);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Laitetilojen haku epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'laitetila':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
try {
|
||||
$tilaId = $_GET['id'] ?? '';
|
||||
if (empty($tilaId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Laitetilan ID puuttuu']);
|
||||
break;
|
||||
}
|
||||
$tila = dbLoadLaitetila($tilaId);
|
||||
if (!$tila || $tila['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Laitetilaa ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
echo json_encode($tila);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Laitetilan haku epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'laitetila_save':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
if (empty($input['nimi'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Laitetilan nimi puuttuu']);
|
||||
break;
|
||||
}
|
||||
$input['muokkaaja'] = currentUser();
|
||||
$id = dbSaveLaitetila($companyId, $input);
|
||||
$tila = dbLoadLaitetila($id);
|
||||
echo json_encode($tila);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Laitetilan tallennus epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'laitetila_delete':
|
||||
requireAdmin();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$tilaId = $input['id'] ?? '';
|
||||
$tila = dbLoadLaitetila($tilaId);
|
||||
if (!$tila || $tila['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Laitetilaa ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
// Poista tiedostot levyltä
|
||||
$tilaDir = DATA_DIR . '/companies/' . $companyId . '/laitetilat/' . $tilaId;
|
||||
if (is_dir($tilaDir)) {
|
||||
$files = glob($tilaDir . '/*');
|
||||
foreach ($files as $f) { if (is_file($f)) unlink($f); }
|
||||
rmdir($tilaDir);
|
||||
}
|
||||
dbDeleteLaitetila($tilaId);
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Poisto epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'laitetila_file_upload':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$tilaId = $_POST['laitetila_id'] ?? '';
|
||||
if (empty($tilaId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Laitetilan ID puuttuu']);
|
||||
break;
|
||||
}
|
||||
$tila = dbLoadLaitetila($tilaId);
|
||||
if (!$tila || $tila['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Laitetilaa ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tiedosto puuttuu tai lähetys epäonnistui']);
|
||||
break;
|
||||
}
|
||||
$file = $_FILES['file'];
|
||||
if ($file['size'] > 50 * 1024 * 1024) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tiedosto on liian suuri (max 50MB)']);
|
||||
break;
|
||||
}
|
||||
$tilaDir = DATA_DIR . '/companies/' . $companyId . '/laitetilat/' . $tilaId;
|
||||
if (!file_exists($tilaDir)) mkdir($tilaDir, 0755, true);
|
||||
|
||||
$safeFilename = time() . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '_', $file['name']);
|
||||
move_uploaded_file($file['tmp_name'], $tilaDir . '/' . $safeFilename);
|
||||
|
||||
$mimeType = '';
|
||||
if (function_exists('finfo_open')) {
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mimeType = finfo_file($finfo, $tilaDir . '/' . $safeFilename);
|
||||
finfo_close($finfo);
|
||||
}
|
||||
|
||||
dbAddLaitetilaFile($tilaId, [
|
||||
'filename' => $safeFilename,
|
||||
'original_name' => $file['name'],
|
||||
'file_size' => $file['size'],
|
||||
'mime_type' => $mimeType ?: ($file['type'] ?? ''),
|
||||
'description' => $_POST['description'] ?? '',
|
||||
'created_by' => currentUser()
|
||||
]);
|
||||
|
||||
$updatedTila = dbLoadLaitetila($tilaId);
|
||||
echo json_encode($updatedTila);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Tiedoston lataus epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'laitetila_file_delete':
|
||||
requireAdmin();
|
||||
$companyId = requireCompany();
|
||||
if ($method !== 'POST') break;
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$fileId = $input['id'] ?? '';
|
||||
$file = dbDeleteLaitetilaFile($fileId);
|
||||
if ($file) {
|
||||
$filePath = DATA_DIR . '/companies/' . $file['company_id'] . '/laitetilat/' . $file['laitetila_id'] . '/' . $file['filename'];
|
||||
if (file_exists($filePath)) unlink($filePath);
|
||||
}
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Tiedoston poisto epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'laitetila_file_download':
|
||||
requireAuth();
|
||||
$companyId = requireCompany();
|
||||
try {
|
||||
$tilaId = $_GET['laitetila_id'] ?? '';
|
||||
$fileId = $_GET['file_id'] ?? '';
|
||||
if (empty($tilaId) || empty($fileId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Parametrit puuttuvat']);
|
||||
break;
|
||||
}
|
||||
$tila = dbLoadLaitetila($tilaId);
|
||||
if (!$tila || $tila['company_id'] !== $companyId) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Laitetilaa ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
$targetFile = null;
|
||||
foreach ($tila['files'] as $f) {
|
||||
if ($f['id'] === $fileId) { $targetFile = $f; break; }
|
||||
}
|
||||
if (!$targetFile) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Tiedostoa ei löytynyt']);
|
||||
break;
|
||||
}
|
||||
$filePath = DATA_DIR . '/companies/' . $companyId . '/laitetilat/' . $tilaId . '/' . $targetFile['filename'];
|
||||
if (!file_exists($filePath)) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Tiedostoa ei löytynyt palvelimelta']);
|
||||
break;
|
||||
}
|
||||
header('Content-Type: ' . ($targetFile['mime_type'] ?: 'application/octet-stream'));
|
||||
header('Content-Disposition: attachment; filename="' . $targetFile['original_name'] . '"');
|
||||
header('Content-Length: ' . filesize($filePath));
|
||||
readfile($filePath);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Lataus epäonnistui: ' . $e->getMessage()]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Tuntematon toiminto']);
|
||||
|
||||
Reference in New Issue
Block a user