Carregar ficheiros para "/"

This commit is contained in:
2024-12-19 09:04:00 -08:00
parent 39dbb95672
commit faa63ae8e5
5 changed files with 627 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Gestão de Documentos Automotivos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

21
init_database.py Normal file
View File

@ -0,0 +1,21 @@
import os
from database import init_db, get_session
from models import Folder
def initialize_database():
# Remove existing database if it exists
if os.path.exists("automotive_docs.db"):
os.remove("automotive_docs.db")
# Create new database
engine = init_db()
# Create initial root folder
session = get_session()
root = Folder(name="Root")
session.add(root)
session.commit()
if __name__ == "__main__":
initialize_database()
print("Database initialized successfully!")

298
main.py Normal file
View File

@ -0,0 +1,298 @@
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QMenuBar, QStackedWidget, QSplitter, QMessageBox, QTableWidgetItem, QTabWidget
from PySide6.QtCore import Qt, QMimeData
from PySide6.QtGui import QDragEnterEvent, QDropEvent
from database import init_db
from folder_view import FolderTreeView
from document_form import DocumentForm
from search_view import SearchView
from document_viewer import DocumentViewer
from batch_upload import BatchUploadWidget
from document_list import DocumentListView
from version_control import VersionControl
from backup_manager import BackupManager
from tag_manager import TagManager, DocumentTags
from statistics_view import StatisticsView
from preferences import PreferencesDialog, Preferences, RecentDocuments
from document_compare import DocumentCompare
from document_collections import CollectionsWidget, CollectionDocuments
import os
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Informação Técnica Automóvel")
self.setMinimumSize(1200, 800)
self.setAcceptDrops(True)
self.current_folder_id = None
# Create central widget and layout
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# Create splitter for main layout
main_splitter = QSplitter(Qt.Horizontal)
main_layout.addWidget(main_splitter)
# Left panel with folder tree and collections
left_panel = QTabWidget()
# Folder tree
self.folder_view = FolderTreeView()
self.folder_view.folder_selected.connect(self.on_folder_selected)
left_panel.addTab(self.folder_view, "Pastas")
# Collections
self.collections_widget = CollectionsWidget()
self.collections_widget.collection_selected.connect(self.show_collection)
left_panel.addTab(self.collections_widget, "Coleções")
main_splitter.addWidget(left_panel)
# Center panel with document list and collection documents
self.center_panel = QStackedWidget()
# Document list
self.doc_list = DocumentListView()
self.doc_list.document_selected.connect(self.show_document)
self.center_panel.addWidget(self.doc_list)
# Collection documents
self.collection_docs = CollectionDocuments()
self.collection_docs.document_selected.connect(self.show_document)
self.center_panel.addWidget(self.collection_docs)
main_splitter.addWidget(self.center_panel)
# Right panel with operations
self.right_panel = QStackedWidget()
# Add document form
self.document_form = DocumentForm()
self.right_panel.addWidget(self.document_form)
# Add batch upload widget
self.batch_upload = BatchUploadWidget()
self.batch_upload.upload_completed.connect(self.refresh_views)
self.right_panel.addWidget(self.batch_upload)
# Add search view
self.search_view = SearchView()
self.search_view.document_selected.connect(self.show_document)
self.right_panel.addWidget(self.search_view)
# Add document viewer with tags
viewer_widget = QWidget()
viewer_layout = QVBoxLayout(viewer_widget)
self.document_viewer = DocumentViewer()
viewer_layout.addWidget(self.document_viewer)
self.document_tags = DocumentTags()
viewer_layout.addWidget(self.document_tags)
self.right_panel.addWidget(viewer_widget)
# Add version control
self.version_control = VersionControl()
self.right_panel.addWidget(self.version_control)
# Add backup manager
self.backup_manager = BackupManager()
self.right_panel.addWidget(self.backup_manager)
# Add tag manager
self.tag_manager = TagManager()
self.tag_manager.tags_updated.connect(self.refresh_views)
self.right_panel.addWidget(self.tag_manager)
# Add statistics view
self.statistics_view = StatisticsView()
self.right_panel.addWidget(self.statistics_view)
# Add document compare
self.document_compare = DocumentCompare()
self.right_panel.addWidget(self.document_compare)
main_splitter.addWidget(self.right_panel)
# Set splitter proportions
main_splitter.setSizes([
int(self.width() * 0.2), # Left panel
int(self.width() * 0.3), # Center panel
int(self.width() * 0.5) # Right panel
])
# Create menu bar
self.create_menu_bar()
def create_menu_bar(self):
menubar = self.menuBar()
# File menu
file_menu = menubar.addMenu("Arquivo")
new_folder_action = file_menu.addAction("Nova Pasta")
new_folder_action.triggered.connect(self.folder_view.add_folder)
upload_action = file_menu.addAction("Upload Documento")
upload_action.triggered.connect(self.show_upload_form)
batch_upload_action = file_menu.addAction("Upload em Lote")
batch_upload_action.triggered.connect(self.show_batch_upload)
file_menu.addSeparator()
# Recent documents submenu
self.recent_menu = file_menu.addMenu("Documentos Recentes")
self.recent_menu.aboutToShow.connect(self.update_recent_menu)
file_menu.addSeparator()
backup_menu = file_menu.addMenu("Backup")
backup_menu.addAction("Exportar").triggered.connect(
lambda: self.right_panel.setCurrentWidget(self.backup_manager))
file_menu.addSeparator()
exit_action = file_menu.addAction("Sair")
exit_action.triggered.connect(self.close)
# Search menu
search_menu = menubar.addMenu("Pesquisar")
advanced_search_action = search_menu.addAction("Busca Avançada")
advanced_search_action.triggered.connect(self.show_search)
# Tools menu
tools_menu = menubar.addMenu("Ferramentas")
tools_menu.addAction("Gerenciar Tags").triggered.connect(
lambda: self.right_panel.setCurrentWidget(self.tag_manager))
tools_menu.addAction("Comparar Documentos").triggered.connect(
lambda: self.right_panel.setCurrentWidget(self.document_compare))
tools_menu.addAction("Estatísticas").triggered.connect(
lambda: self.right_panel.setCurrentWidget(self.statistics_view))
# Settings menu
settings_menu = menubar.addMenu("Configurações")
preferences_action = settings_menu.addAction("Preferências")
preferences_action.triggered.connect(self.show_preferences)
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
files = [url.toLocalFile() for url in event.mimeData().urls()]
if files:
current_index = self.folder_view.currentIndex()
if current_index.isValid():
folder_id = self.folder_view.model.itemFromIndex(current_index).data()
self.batch_upload.folder_id = folder_id
# Add files to batch upload
for file in files:
row = self.batch_upload.files_table.rowCount()
self.batch_upload.files_table.insertRow(row)
self.batch_upload.files_table.setItem(
row, 0, QTableWidgetItem(os.path.basename(file)))
self.batch_upload.files_table.setItem(
row, 1, QTableWidgetItem(file))
self.right_panel.setCurrentWidget(self.batch_upload)
else:
QMessageBox.warning(
self,
"Aviso",
"Selecione uma pasta antes de arrastar arquivos."
)
def show_upload_form(self):
print(f"Showing document form for folder ID: {self.current_folder_id}")
self.document_form = DocumentForm(folder_id=self.current_folder_id)
self.right_panel.addWidget(self.document_form)
self.right_panel.setCurrentWidget(self.document_form)
def show_batch_upload(self):
current_index = self.folder_view.currentIndex()
folder_id = None
if current_index.isValid():
folder_id = self.folder_view.model.itemFromIndex(current_index).data()
self.batch_upload.folder_id = folder_id
self.right_panel.setCurrentWidget(self.batch_upload)
def show_search(self):
self.right_panel.setCurrentWidget(self.search_view)
def show_document(self, doc_id):
self.document_viewer.load_document(doc_id)
self.document_tags.load_document(doc_id)
self.version_control.load_document(doc_id)
self.right_panel.setCurrentWidget(self.version_control)
def on_folder_selected(self, folder_id):
"""Handle folder selection"""
print(f"Main window received folder selection: ID={folder_id}")
self.current_folder_id = folder_id
self.doc_list.set_folder(folder_id)
self.center_panel.setCurrentWidget(self.doc_list)
# Update document form with current folder
self.document_form.folder_id = folder_id
print(f"Updated document form folder ID to: {folder_id}")
def refresh_views(self):
self.doc_list.refresh_documents()
self.collections_widget.refresh_collections()
self.collection_docs.refresh_documents()
self.document_compare.refresh_documents()
def show_preferences(self):
dialog = PreferencesDialog(Preferences(), self)
dialog.show()
def update_recent_menu(self):
self.recent_menu.clear()
recent = RecentDocuments(Preferences())
documents = recent.get_recent_docs()
if documents:
for doc in documents:
action = self.recent_menu.addAction(doc['name'])
action.triggered.connect(
lambda checked, doc_id=doc['id']: self.show_document(doc_id)
)
self.recent_menu.addSeparator()
clear_action = self.recent_menu.addAction("Limpar Lista")
clear_action.triggered.connect(self.clear_recent_documents)
else:
no_docs_action = self.recent_menu.addAction("Nenhum documento recente")
no_docs_action.setEnabled(False)
def clear_recent_documents(self):
recent = RecentDocuments(Preferences())
recent.clear_recent_docs()
self.update_recent_menu()
def show_collection(self, collection_id):
self.collection_docs.load_collection(collection_id)
self.center_panel.setCurrentWidget(self.collection_docs)
def main():
# Initialize database
engine = init_db()
# Start application
app = QApplication(sys.argv)
# Ensure preferences are initialized
preferences = Preferences()
if not preferences.get_save_path():
# Ask for initial save path if not set
default_docs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "documentos")
preferences.set("default_save_path", default_docs_path)
if not os.path.exists(default_docs_path):
os.makedirs(default_docs_path)
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()

111
models.py Normal file
View File

@ -0,0 +1,111 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Table, Text
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
import datetime
Base = declarative_base()
# Association table for document tags
document_tags = Table('document_tags', Base.metadata,
Column('document_id', Integer, ForeignKey('documents.id')),
Column('tag_id', Integer, ForeignKey('tags.id'))
)
# Association table for document collections
document_collections = Table('document_collections', Base.metadata,
Column('document_id', Integer, ForeignKey('documents.id')),
Column('collection_id', Integer, ForeignKey('collections.id'))
)
class Document(Base):
__tablename__ = 'documents'
id = Column(Integer, primary_key=True)
file_path = Column(String(500), nullable=False)
file_name = Column(String(255), nullable=False)
marca = Column(String(100), nullable=False)
modelo = Column(String(100), nullable=False)
ano = Column(Integer, nullable=False)
cilindrada = Column(String(50))
codigo_motor = Column(String(100))
tipo_documento = Column(String(50), nullable=False) # Elétrico/Mecânico
variante = Column(String(50), nullable=False) # Esquema, Esquema OEM, etc.
observacoes = Column(Text)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
folder_id = Column(Integer, ForeignKey('folders.id'))
folder = relationship("Folder", back_populates="documents")
versions = relationship("DocumentVersion", back_populates="document", cascade="all, delete-orphan")
tags = relationship("Tag", secondary=document_tags, back_populates="documents")
comments = relationship("Comment", back_populates="document", cascade="all, delete-orphan")
collections = relationship("Collection", secondary=document_collections, back_populates="documents")
class DocumentVersion(Base):
__tablename__ = 'document_versions'
id = Column(Integer, primary_key=True)
document_id = Column(Integer, ForeignKey('documents.id'))
version_number = Column(Integer, nullable=False)
file_path = Column(String(500), nullable=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
changes = Column(Text)
document = relationship("Document", back_populates="versions")
class Folder(Base):
__tablename__ = 'folders'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
parent_id = Column(Integer, ForeignKey('folders.id'))
created_at = Column(DateTime, default=datetime.datetime.utcnow)
parent = relationship("Folder", remote_side=[id], back_populates="children")
children = relationship("Folder", back_populates="parent")
documents = relationship("Document", back_populates="folder")
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False, unique=True)
color = Column(String(7), default="#808080") # Hex color code
created_at = Column(DateTime, default=datetime.datetime.utcnow)
documents = relationship("Document", secondary=document_tags, back_populates="tags")
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
document_id = Column(Integer, ForeignKey('documents.id'))
text = Column(Text, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
page_number = Column(Integer) # For PDF comments
x_coord = Column(Integer) # For position-specific comments
y_coord = Column(Integer)
document = relationship("Document", back_populates="comments")
class Collection(Base):
__tablename__ = 'collections'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False, unique=True)
description = Column(Text)
color = Column(String(7), default="#808080") # Hex color code
created_at = Column(DateTime, default=datetime.datetime.utcnow)
documents = relationship("Document", secondary=document_collections, back_populates="collections")
class Statistics(Base):
__tablename__ = 'statistics'
id = Column(Integer, primary_key=True)
date = Column(DateTime, nullable=False)
total_documents = Column(Integer, default=0)
total_folders = Column(Integer, default=0)
documents_by_type = Column(Text) # JSON string
documents_by_brand = Column(Text) # JSON string
storage_used = Column(Integer, default=0) # in bytes

176
preferences.py Normal file
View File

@ -0,0 +1,176 @@
from PySide6.QtWidgets import (QDialog, QWidget, QVBoxLayout, QHBoxLayout, QFormLayout,
QCheckBox, QPushButton, QSpinBox, QComboBox, QColorDialog,
QMessageBox, QLineEdit, QLabel, QFileDialog)
from PySide6.QtCore import QSettings, Qt
import os
import json
from datetime import datetime
class Preferences:
def __init__(self):
self.settings = QSettings("Codeium", "GestaoDocumentos")
self.load_defaults()
def load_defaults(self):
if not self.settings.contains("default_save_path"):
self.settings.setValue("default_save_path", "")
if not self.settings.contains("auto_preview"):
self.settings.setValue("auto_preview", True)
if not self.settings.contains("max_recent_docs"):
self.settings.setValue("max_recent_docs", 10)
if not self.settings.contains("theme"):
self.settings.setValue("theme", "light")
if not self.settings.contains("preview_size"):
self.settings.setValue("preview_size", "medium")
if not self.settings.contains("auto_version"):
self.settings.setValue("auto_version", True)
if not self.settings.contains("backup_reminder_days"):
self.settings.setValue("backup_reminder_days", 7)
def get(self, key, type=None):
return self.settings.value(key, type=type)
def set(self, key, value):
self.settings.setValue(key, value)
def get_save_path(self):
return self.settings.value("default_save_path", type=str)
def get_auto_save(self):
return bool(self.settings.value("auto_preview", type=bool))
def get_recent_limit(self):
return self.settings.value("max_recent_docs", type=int)
def get_theme(self):
return self.settings.value("theme", type=str)
class PreferencesDialog(QDialog):
def __init__(self, preferences, parent=None):
super().__init__(parent)
self.preferences = preferences
self.setWindowTitle("Preferências")
self.setup_ui()
def setup_ui(self):
layout = QVBoxLayout(self)
# Save path settings
save_path_layout = QHBoxLayout()
self.save_path_edit = QLineEdit(self.preferences.get_save_path())
save_path_layout.addWidget(self.save_path_edit)
browse_btn = QPushButton("Procurar...")
browse_btn.clicked.connect(self.browse_save_path)
save_path_layout.addWidget(browse_btn)
layout.addLayout(save_path_layout)
# Auto-save settings
auto_save_check = QCheckBox("Salvar automaticamente")
auto_save_check.setChecked(self.preferences.get_auto_save())
layout.addWidget(auto_save_check)
# Recent documents limit
recent_limit_layout = QHBoxLayout()
recent_limit_layout.addWidget(QLabel("Número de documentos recentes:"))
recent_limit_spin = QSpinBox()
recent_limit_spin.setRange(5, 50)
recent_limit_spin.setValue(self.preferences.get_recent_limit())
recent_limit_layout.addWidget(recent_limit_spin)
layout.addLayout(recent_limit_layout)
# Theme selection
theme_layout = QHBoxLayout()
theme_layout.addWidget(QLabel("Tema:"))
theme_combo = QComboBox()
theme_combo.addItems(["Claro", "Escuro"])
theme_combo.setCurrentText(self.preferences.get_theme())
theme_layout.addWidget(theme_combo)
layout.addLayout(theme_layout)
# Buttons
button_layout = QHBoxLayout()
save_btn = QPushButton("Salvar")
save_btn.clicked.connect(self.save_preferences)
button_layout.addWidget(save_btn)
cancel_btn = QPushButton("Cancelar")
cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout)
def browse_save_path(self):
path = QFileDialog.getExistingDirectory(
self,
"Selecionar Pasta Padrão",
self.save_path_edit.text()
)
if path:
self.save_path_edit.setText(path)
def save_preferences(self):
try:
self.preferences.set("default_save_path", self.save_path_edit.text())
# self.preferences.set("auto_save", self.auto_save_check.isChecked())
# self.preferences.set("max_recent_docs", self.recent_limit_spin.value())
# self.preferences.set("theme", self.theme_combo.currentText())
QMessageBox.information(self, "Sucesso",
"Preferências salvas com sucesso!")
self.close()
except Exception as e:
QMessageBox.critical(self, "Erro",
f"Erro ao salvar preferências: {str(e)}")
class RecentDocuments:
def __init__(self, preferences):
self.preferences = preferences
self.recent_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"recent_docs.json"
)
self.load_recent_docs()
def load_recent_docs(self):
try:
if os.path.exists(self.recent_file):
with open(self.recent_file, 'r') as f:
self.recent_docs = json.load(f)
else:
self.recent_docs = []
except Exception:
self.recent_docs = []
def save_recent_docs(self):
try:
with open(self.recent_file, 'w') as f:
json.dump(self.recent_docs, f)
except Exception as e:
print(f"Error saving recent documents: {str(e)}")
def add_document(self, doc_id, doc_name):
# Remove if already exists
self.recent_docs = [doc for doc in self.recent_docs
if doc['id'] != doc_id]
# Add to start of list
self.recent_docs.insert(0, {
'id': doc_id,
'name': doc_name,
'timestamp': datetime.now().isoformat()
})
# Trim list
max_recent = int(self.preferences.get("max_recent_docs"))
self.recent_docs = self.recent_docs[:max_recent]
self.save_recent_docs()
def get_recent_docs(self):
return self.recent_docs
def clear_recent_docs(self):
self.recent_docs = []
self.save_recent_docs()