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:
253
db.php
253
db.php
@@ -502,6 +502,65 @@ function initDatabase(): void {
|
||||
FOREIGN KEY (todo_id) REFERENCES todos(id) ON DELETE CASCADE,
|
||||
INDEX idx_todo (todo_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS documents (
|
||||
id VARCHAR(20) PRIMARY KEY,
|
||||
company_id VARCHAR(50) NOT NULL,
|
||||
customer_id VARCHAR(20) DEFAULT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT DEFAULT '',
|
||||
category VARCHAR(50) DEFAULT 'muu',
|
||||
current_version INT DEFAULT 0,
|
||||
created_by VARCHAR(100) DEFAULT '',
|
||||
luotu DATETIME,
|
||||
muokattu DATETIME,
|
||||
muokkaaja VARCHAR(100) DEFAULT '',
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
|
||||
INDEX idx_company (company_id),
|
||||
INDEX idx_customer (customer_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS document_versions (
|
||||
id VARCHAR(20) PRIMARY KEY,
|
||||
document_id VARCHAR(20) NOT NULL,
|
||||
version_number INT NOT NULL,
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
original_name VARCHAR(255) NOT NULL,
|
||||
file_size INT DEFAULT 0,
|
||||
mime_type VARCHAR(100) DEFAULT '',
|
||||
change_notes TEXT DEFAULT '',
|
||||
created_by VARCHAR(100) DEFAULT '',
|
||||
luotu DATETIME,
|
||||
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE,
|
||||
INDEX idx_document (document_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS laitetilat (
|
||||
id VARCHAR(20) PRIMARY KEY,
|
||||
company_id VARCHAR(50) NOT NULL,
|
||||
nimi VARCHAR(255) NOT NULL,
|
||||
kuvaus TEXT DEFAULT '',
|
||||
osoite VARCHAR(255) DEFAULT '',
|
||||
luotu DATETIME,
|
||||
muokattu DATETIME,
|
||||
muokkaaja VARCHAR(100) DEFAULT '',
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
|
||||
INDEX idx_company (company_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS laitetila_files (
|
||||
id VARCHAR(20) PRIMARY KEY,
|
||||
laitetila_id VARCHAR(20) NOT NULL,
|
||||
filename VARCHAR(255) NOT NULL,
|
||||
original_name VARCHAR(255) NOT NULL,
|
||||
file_size INT DEFAULT 0,
|
||||
mime_type VARCHAR(100) DEFAULT '',
|
||||
description VARCHAR(500) DEFAULT '',
|
||||
created_by VARCHAR(100) DEFAULT '',
|
||||
luotu DATETIME,
|
||||
FOREIGN KEY (laitetila_id) REFERENCES laitetilat(id) ON DELETE CASCADE,
|
||||
INDEX idx_laitetila (laitetila_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
|
||||
];
|
||||
|
||||
foreach ($tables as $i => $sql) {
|
||||
@@ -1625,3 +1684,197 @@ function dbToggleTodoSubtask(string $subtaskId): bool {
|
||||
function dbDeleteTodoSubtask(string $subtaskId): void {
|
||||
_dbExecute("DELETE FROM todo_subtasks WHERE id = ?", [$subtaskId]);
|
||||
}
|
||||
|
||||
// ==================== DOKUMENTIT ====================
|
||||
|
||||
function dbLoadDocuments(string $companyId, ?string $customerId = null): array {
|
||||
$sql = "SELECT d.*,
|
||||
dv.original_name AS current_file, dv.file_size AS current_size, dv.created_by AS version_author, dv.luotu AS version_date
|
||||
FROM documents d
|
||||
LEFT JOIN document_versions dv ON dv.document_id = d.id AND dv.version_number = d.current_version
|
||||
WHERE d.company_id = :companyId";
|
||||
$params = ['companyId' => $companyId];
|
||||
if ($customerId !== null) {
|
||||
$sql .= " AND d.customer_id = :customerId";
|
||||
$params['customerId'] = $customerId;
|
||||
}
|
||||
$sql .= " ORDER BY d.muokattu DESC, d.luotu DESC";
|
||||
return _dbFetchAll($sql, $params);
|
||||
}
|
||||
|
||||
function dbLoadDocument(string $documentId): ?array {
|
||||
$doc = _dbFetchOne("SELECT * FROM documents WHERE id = ?", [$documentId]);
|
||||
if (!$doc) return null;
|
||||
$doc['versions'] = _dbFetchAll(
|
||||
"SELECT * FROM document_versions WHERE document_id = ? ORDER BY version_number DESC",
|
||||
[$documentId]
|
||||
);
|
||||
return $doc;
|
||||
}
|
||||
|
||||
function dbSaveDocument(string $companyId, array $doc): string {
|
||||
$id = $doc['id'] ?? generateId();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
_dbExecute("
|
||||
INSERT INTO documents (id, company_id, customer_id, title, description, category, current_version, created_by, luotu, muokattu, muokkaaja)
|
||||
VALUES (:id, :companyId, :customerId, :title, :description, :category, :currentVersion, :createdBy, :luotu, :muokattu, :muokkaaja)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
title = VALUES(title),
|
||||
description = VALUES(description),
|
||||
category = VALUES(category),
|
||||
customer_id = VALUES(customer_id),
|
||||
muokattu = VALUES(muokattu),
|
||||
muokkaaja = VALUES(muokkaaja)
|
||||
", [
|
||||
'id' => $id,
|
||||
'companyId' => $companyId,
|
||||
'customerId' => !empty($doc['customer_id']) ? $doc['customer_id'] : null,
|
||||
'title' => $doc['title'] ?? '',
|
||||
'description' => $doc['description'] ?? '',
|
||||
'category' => $doc['category'] ?? 'muu',
|
||||
'currentVersion' => (int)($doc['current_version'] ?? 0),
|
||||
'createdBy' => $doc['created_by'] ?? '',
|
||||
'luotu' => $doc['luotu'] ?? $now,
|
||||
'muokattu' => $now,
|
||||
'muokkaaja' => $doc['muokkaaja'] ?? ''
|
||||
]);
|
||||
return $id;
|
||||
}
|
||||
|
||||
function dbDeleteDocument(string $documentId): ?array {
|
||||
// Palauta dokumentin tiedot tiedostojen poistoa varten
|
||||
$doc = _dbFetchOne("SELECT id, company_id FROM documents WHERE id = ?", [$documentId]);
|
||||
if ($doc) {
|
||||
_dbExecute("DELETE FROM documents WHERE id = ?", [$documentId]); // CASCADE poistaa versiot
|
||||
}
|
||||
return $doc;
|
||||
}
|
||||
|
||||
function dbAddDocumentVersion(string $documentId, array $version): void {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
// Hae seuraava versionumero
|
||||
$maxVersion = _dbFetchScalar("SELECT COALESCE(MAX(version_number), 0) FROM document_versions WHERE document_id = ?", [$documentId]);
|
||||
$nextVersion = (int)$maxVersion + 1;
|
||||
|
||||
_dbExecute("INSERT INTO document_versions (id, document_id, version_number, filename, original_name, file_size, mime_type, change_notes, created_by, luotu) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
$version['id'] ?? generateId(),
|
||||
$documentId,
|
||||
$nextVersion,
|
||||
$version['filename'] ?? '',
|
||||
$version['original_name'] ?? '',
|
||||
$version['file_size'] ?? 0,
|
||||
$version['mime_type'] ?? '',
|
||||
$version['change_notes'] ?? '',
|
||||
$version['created_by'] ?? '',
|
||||
$now
|
||||
]);
|
||||
// Päivitä dokumentin current_version
|
||||
_dbExecute("UPDATE documents SET current_version = ?, muokattu = ?, muokkaaja = ? WHERE id = ?", [
|
||||
$nextVersion, $now, $version['created_by'] ?? '', $documentId
|
||||
]);
|
||||
}
|
||||
|
||||
function dbRestoreDocumentVersion(string $documentId, string $versionId, string $user): ?int {
|
||||
// Hae palautettava versio
|
||||
$oldVersion = _dbFetchOne("SELECT * FROM document_versions WHERE id = ? AND document_id = ?", [$versionId, $documentId]);
|
||||
if (!$oldVersion) return null;
|
||||
|
||||
// Lisää uutena versiona (kopioi tiedostotiedot)
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$maxVersion = _dbFetchScalar("SELECT COALESCE(MAX(version_number), 0) FROM document_versions WHERE document_id = ?", [$documentId]);
|
||||
$nextVersion = (int)$maxVersion + 1;
|
||||
$newId = generateId();
|
||||
|
||||
_dbExecute("INSERT INTO document_versions (id, document_id, version_number, filename, original_name, file_size, mime_type, change_notes, created_by, luotu) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
$newId,
|
||||
$documentId,
|
||||
$nextVersion,
|
||||
$oldVersion['filename'],
|
||||
$oldVersion['original_name'],
|
||||
$oldVersion['file_size'],
|
||||
$oldVersion['mime_type'],
|
||||
'Palautettu versiosta ' . $oldVersion['version_number'],
|
||||
$user,
|
||||
$now
|
||||
]);
|
||||
_dbExecute("UPDATE documents SET current_version = ?, muokattu = ?, muokkaaja = ? WHERE id = ?", [
|
||||
$nextVersion, $now, $user, $documentId
|
||||
]);
|
||||
return $nextVersion;
|
||||
}
|
||||
|
||||
function dbGetDocumentVersion(string $documentId, int $versionNumber): ?array {
|
||||
return _dbFetchOne("SELECT * FROM document_versions WHERE document_id = ? AND version_number = ?", [$documentId, $versionNumber]);
|
||||
}
|
||||
|
||||
// ==================== LAITETILAT ====================
|
||||
|
||||
function dbLoadLaitetilat(string $companyId): array {
|
||||
$tilat = _dbFetchAll("SELECT * FROM laitetilat WHERE company_id = ? ORDER BY nimi", [$companyId]);
|
||||
foreach ($tilat as &$t) {
|
||||
$t['file_count'] = (int)_dbFetchScalar("SELECT COUNT(*) FROM laitetila_files WHERE laitetila_id = ?", [$t['id']]);
|
||||
}
|
||||
return $tilat;
|
||||
}
|
||||
|
||||
function dbLoadLaitetila(string $laitetilaId): ?array {
|
||||
$tila = _dbFetchOne("SELECT * FROM laitetilat WHERE id = ?", [$laitetilaId]);
|
||||
if (!$tila) return null;
|
||||
$tila['files'] = _dbFetchAll("SELECT * FROM laitetila_files WHERE laitetila_id = ? ORDER BY luotu DESC", [$laitetilaId]);
|
||||
return $tila;
|
||||
}
|
||||
|
||||
function dbSaveLaitetila(string $companyId, array $tila): string {
|
||||
$id = $tila['id'] ?? generateId();
|
||||
$now = date('Y-m-d H:i:s');
|
||||
_dbExecute("
|
||||
INSERT INTO laitetilat (id, company_id, nimi, kuvaus, osoite, luotu, muokattu, muokkaaja)
|
||||
VALUES (:id, :companyId, :nimi, :kuvaus, :osoite, :luotu, :muokattu, :muokkaaja)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
nimi = VALUES(nimi),
|
||||
kuvaus = VALUES(kuvaus),
|
||||
osoite = VALUES(osoite),
|
||||
muokattu = VALUES(muokattu),
|
||||
muokkaaja = VALUES(muokkaaja)
|
||||
", [
|
||||
'id' => $id,
|
||||
'companyId' => $companyId,
|
||||
'nimi' => $tila['nimi'] ?? '',
|
||||
'kuvaus' => $tila['kuvaus'] ?? '',
|
||||
'osoite' => $tila['osoite'] ?? '',
|
||||
'luotu' => $tila['luotu'] ?? $now,
|
||||
'muokattu' => $now,
|
||||
'muokkaaja' => $tila['muokkaaja'] ?? ''
|
||||
]);
|
||||
return $id;
|
||||
}
|
||||
|
||||
function dbDeleteLaitetila(string $laitetilaId): ?array {
|
||||
$tila = _dbFetchOne("SELECT id, company_id FROM laitetilat WHERE id = ?", [$laitetilaId]);
|
||||
if ($tila) {
|
||||
_dbExecute("DELETE FROM laitetilat WHERE id = ?", [$laitetilaId]); // CASCADE poistaa tiedostot
|
||||
}
|
||||
return $tila;
|
||||
}
|
||||
|
||||
function dbAddLaitetilaFile(string $laitetilaId, array $file): void {
|
||||
_dbExecute("INSERT INTO laitetila_files (id, laitetila_id, filename, original_name, file_size, mime_type, description, created_by, luotu) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [
|
||||
$file['id'] ?? generateId(),
|
||||
$laitetilaId,
|
||||
$file['filename'] ?? '',
|
||||
$file['original_name'] ?? '',
|
||||
$file['file_size'] ?? 0,
|
||||
$file['mime_type'] ?? '',
|
||||
$file['description'] ?? '',
|
||||
$file['created_by'] ?? '',
|
||||
date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
|
||||
function dbDeleteLaitetilaFile(string $fileId): ?array {
|
||||
$file = _dbFetchOne("SELECT lf.*, lt.company_id FROM laitetila_files lf JOIN laitetilat lt ON lt.id = lf.laitetila_id WHERE lf.id = ?", [$fileId]);
|
||||
if ($file) {
|
||||
_dbExecute("DELETE FROM laitetila_files WHERE id = ?", [$fileId]);
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user