GestGazziNet

Da GazziNet.
Vai alla navigazione Vai alla ricerca

Versione 31/5/2025 - 3.0

✅ Requisiti comuni (tutti i sistemi)
Python 3 installato

Librerie standard: tkinter, sqlite3, shutil, ecc. (inclusi con Python)

Stampante Brother QL-700 configurata (opzionale per stampa etichette)

Solo Linux: CUPS installato e il comando lp funzionante per la stampa

🔧 INSTALLAZIONE SU UBUNTU / DEBIAN
1. Installa Python e tkinter
bash
Copia
Modifica
sudo apt update
sudo apt install python3 python3-tk
2. Clona o copia i file dell'app
Salva il file Python (es. gestgn.py) in una cartella dedicata, ad esempio ~/GestGN.

3. Avvia l'applicazione
bash
Copia
Modifica
cd ~/GestGN
python3 gestgn.py
🔧 INSTALLAZIONE SU RED HAT / CENTOS / ORACLE LINUX
1. Installa Python e tkinter
bash
Copia
Modifica
sudo dnf install python3 python3-tkinter
Su Oracle Linux potresti dover abilitare EPEL per tkinter:

bash
Copia
Modifica
sudo dnf install epel-release
sudo dnf install python3-tkinter
2. Avvia l'applicazione
bash
Copia
Modifica
cd ~/GestGN
python3 gestgn.py
🪟 INSTALLAZIONE SU WINDOWS
1. Installa Python
Scarica ed esegui l’installer da https://www.python.org/downloads/

⚠️ Assicurati di selezionare “Add Python to PATH” durante l’installazione.

2. Verifica Python e tkinter
Apri il prompt dei comandi (CMD) e digita:

cmd
Copia
Modifica
python --version
Per testare tkinter:

cmd
Copia
Modifica
python -m tkinter
3. Salva il file gestgn.py e avvialo
Doppio clic sul file oppure da CMD:

cmd
Copia
Modifica
cd C:\percorso\della\cartella
python gestgn.py
🖨️ Supporto stampa con Brother QL-700 (Linux)
Installa i driver:

bash
Copia
Modifica
sudo apt install printer-driver-brlaser
Verifica che la stampante sia installata con nome Brother_QL_700 (modifica nel codice se il nome è diverso).

Per test stampa:

bash
Copia
Modifica
echo "Test stampa" > /tmp/test.txt
lp -d Brother_QL_700 /tmp/test.txt
📁 Struttura consigliata della cartella GestGN
css
Copia
Modifica
GestGN/
├── gestgn.py
├── gestgn.db          ← creato automaticamente
└── allegati_pdf/      ← usato per i file PDF







# === MIGLIORAMENTI APPLICATI A GESTGN ===
# Ristrutturazione completa con GUI completa e migliorata.

import os
import sqlite3
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from datetime import datetime
import shutil
import platform
import subprocess

DB_FILE = 'gestgn.db'
PDF_DIR = 'allegati_pdf'
FORNITORI = ['Amazon', 'Unieuro', 'Comet', 'MediaWorld']

os.makedirs(PDF_DIR, exist_ok=True)

# Utility

def centra_finestra(finestra, padre):
    finestra.update_idletasks()
    w, h = finestra.winfo_width(), finestra.winfo_height()
    x = padre.winfo_x() + (padre.winfo_width() // 2) - (w // 2)
    y = padre.winfo_y() + (padre.winfo_height() // 2) - (h // 2)
    finestra.geometry(f"{w}x{h}+{x}+{y}")

def apri_pdf(nome_file):
    path = os.path.join(PDF_DIR, nome_file)
    if not os.path.exists(path):
        messagebox.showerror("Errore", "File PDF non trovato")
        return
    try:
        if platform.system() == 'Windows':
            os.startfile(path)
        elif platform.system() == 'Darwin':
            subprocess.run(['open', path])
        else:
            subprocess.run(['xdg-open', path])
    except Exception as e:
        messagebox.showerror("Errore apertura PDF", str(e))

# Database

def inizializza_db():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS inventory (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        id_code TEXT UNIQUE,
        riferimento TEXT,
        fornitore TEXT,
        descrizione TEXT,
        pdf_file TEXT,
        created_at TEXT,
        active INTEGER DEFAULT 1
    )''')
    conn.commit()
    conn.close()

def genera_codice():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("SELECT MAX(id) FROM inventory")
    max_id = c.fetchone()[0]
    conn.close()
    return f"INV-{(max_id or 0) + 1:04d}"

def aggiungi_articolo(rif, fornitore, descrizione, pdf_path):
    codice = genera_codice()
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    nome_pdf = ""
    if pdf_path:
        estensione = os.path.splitext(pdf_path)[1]
        nome_pdf = f"{codice}_{datetime.now().strftime('%Y%m%d_%H%M%S')}{estensione}"
        try:
            shutil.copy(pdf_path, os.path.join(PDF_DIR, nome_pdf))
        except Exception as e:
            messagebox.showerror("Errore copia PDF", str(e))
            return
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('''INSERT INTO inventory (id_code, riferimento, fornitore, descrizione, pdf_file, created_at)
                 VALUES (?, ?, ?, ?, ?, ?)''',
              (codice, rif.strip(), fornitore.strip(), descrizione.strip(), nome_pdf, now))
    conn.commit()
    conn.close()

def carica_articoli(attivi=True):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("SELECT * FROM inventory WHERE active=? ORDER BY created_at DESC", (1 if attivi else 0,))
    risultati = c.fetchall()
    conn.close()
    return risultati

def modifica_articolo(id, riferimento, fornitore, descrizione):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("UPDATE inventory SET riferimento=?, fornitore=?, descrizione=? WHERE id=?",
              (riferimento.strip(), fornitore.strip(), descrizione.strip(), id))
    conn.commit()
    conn.close()

def elimina_logicamente(id):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("UPDATE inventory SET active=0 WHERE id=?", (id,))
    conn.commit()
    conn.close()

def recupera_articolo(id):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("UPDATE inventory SET active=1 WHERE id=?", (id,))
    conn.commit()
    conn.close()

# GUI (completa con tutte le funzioni)
class GestGNApp:
    def __init__(self, root):
        self.root = root
        root.title("GestGN - Gestione Inventario IA Docet")

        self.refresh_attivo = tk.BooleanVar(value=False)
        self.tempo_refresh = tk.IntVar(value=30)

        frame = ttk.Frame(root, padding=10)
        frame.pack(fill=tk.BOTH, expand=True)

        style = ttk.Style()
        style.theme_use('clam')

        ttk.Button(frame, text="Aggiungi", command=self.finestra_aggiungi).grid(row=0, column=0, padx=5, pady=5)
        ttk.Button(frame, text="Modifica", command=self.finestra_modifica).grid(row=0, column=1, padx=5, pady=5)
        ttk.Button(frame, text="Elimina", command=self.elimina_articolo).grid(row=0, column=2, padx=5, pady=5)
        ttk.Button(frame, text="Apri PDF", command=self.visualizza_pdf).grid(row=0, column=3, padx=5, pady=5)
        ttk.Button(frame, text="Visualizza Disattivati", command=self.visualizza_disattivati).grid(row=0, column=4, padx=5, pady=5)
        ttk.Button(frame, text="Visualizza", command=self.visualizza_dettagli).grid(row=0, column=5, padx=5, pady=5)
        ttk.Button(frame, text="Esci", command=root.destroy).grid(row=0, column=6, padx=5, pady=5)

        ttk.Label(frame, text="Refresh (s):").grid(row=2, column=0, sticky="w", padx=5)
        spin_refresh = ttk.Spinbox(frame, from_=5, to=3600, textvariable=self.tempo_refresh, width=6)
        spin_refresh.grid(row=2, column=1, sticky="w", padx=5)
        chk_refresh = ttk.Checkbutton(frame, text="Auto-refresh", variable=self.refresh_attivo)
        chk_refresh.grid(row=2, column=2, columnspan=2, sticky="w", padx=5)

        self.tabella = ttk.Treeview(frame, columns=("id", "id_code", "riferimento", "fornitore", "descrizione", "pdf_file", "created_at"), show="headings")
        self.intestazioni = {
            "id": "ID", "id_code": "Codice", "riferimento": "Riferimento",
            "fornitore": "Fornitore", "descrizione": "Descrizione",
            "pdf_file": "PDF", "created_at": "Data Creazione"
        }
        for col in self.tabella["columns"]:
            self.tabella.heading(col, text=self.intestazioni[col])

        self.tabella.grid(row=1, column=0, columnspan=7, sticky="nsew")
        frame.rowconfigure(1, weight=1)
        frame.columnconfigure(6, weight=1)

        self.aggiorna_tabella()
        self.schedule_refresh()

    def aggiorna_tabella(self):
        for r in self.tabella.get_children():
            self.tabella.delete(r)
        for riga in carica_articoli():
            self.tabella.insert('', tk.END, values=riga)

    def schedule_refresh(self):
        if self.refresh_attivo.get():
            self.aggiorna_tabella()
        self.root.after(self.tempo_refresh.get() * 1000, self.schedule_refresh)

    def finestra_aggiungi(self):
        win = tk.Toplevel(self.root)
        win.title("Aggiungi Articolo")
        win.grab_set()
        win.transient(self.root)

        ttk.Label(win, text="Riferimento").grid(row=0, column=0)
        rif = ttk.Entry(win)
        rif.grid(row=0, column=1)

        ttk.Label(win, text="Fornitore").grid(row=1, column=0)
        fornitore = ttk.Combobox(win, values=FORNITORI, state="readonly")
        fornitore.grid(row=1, column=1)

        ttk.Label(win, text="Descrizione").grid(row=2, column=0)
        descrizione = ttk.Entry(win)
        descrizione.grid(row=2, column=1)

        pdf_path = tk.StringVar()
        ttk.Label(win, text="PDF Allegato").grid(row=3, column=0)
        ttk.Entry(win, textvariable=pdf_path).grid(row=3, column=1)
        ttk.Button(win, text="Sfoglia", command=lambda: pdf_path.set(filedialog.askopenfilename(filetypes=[("PDF", "*.pdf")], parent=win))).grid(row=3, column=2)

        def salva():
            if not rif.get() or not fornitore.get() or not descrizione.get():
                messagebox.showwarning("Attenzione", "Compila tutti i campi obbligatori", parent=win)
                return
            if not pdf_path.get():
                if not messagebox.askyesno("Nessun PDF", "Vuoi salvare il record anche senza allegato?", parent=win):
                    return
            aggiungi_articolo(rif.get(), fornitore.get(), descrizione.get(), pdf_path.get())
            messagebox.showinfo("Salvato", "Articolo aggiunto con successo", parent=win)
            win.destroy()
            self.aggiorna_tabella()

        ttk.Button(win, text="Salva", command=salva).grid(row=4, column=0, columnspan=3, pady=10)
        centra_finestra(win, self.root)

    def finestra_modifica(self):
        item = self.tabella.selection()
        if not item:
            messagebox.showinfo("Info", "Seleziona un record da modificare", parent=self.root)
            return
        valori = self.tabella.item(item[0])["values"]
        id, _, riferimento, fornitore, descrizione = valori[:5]

        win = tk.Toplevel(self.root)
        win.title("Modifica Articolo")
        win.grab_set()
        win.transient(self.root)

        ttk.Label(win, text="Riferimento").grid(row=0, column=0)
        rif = ttk.Entry(win)
        rif.insert(0, riferimento)
        rif.grid(row=0, column=1)

        ttk.Label(win, text="Fornitore").grid(row=1, column=0)
        forn = ttk.Combobox(win, values=FORNITORI, state="readonly")
        forn.set(fornitore)
        forn.grid(row=1, column=1)

        ttk.Label(win, text="Descrizione").grid(row=2, column=0)
        desc = ttk.Entry(win)
        desc.insert(0, descrizione)
        desc.grid(row=2, column=1)

        def salva():
            modifica_articolo(id, rif.get(), forn.get(), desc.get())
            messagebox.showinfo("Modificato", "Articolo modificato correttamente", parent=win)
            win.destroy()
            self.aggiorna_tabella()

        ttk.Button(win, text="Salva", command=salva).grid(row=3, column=0, columnspan=2, pady=10)
        centra_finestra(win, self.root)

    def elimina_articolo(self):
        item = self.tabella.selection()
        if not item:
            messagebox.showinfo("Info", "Seleziona un record da eliminare", parent=self.root)
            return
        valori = self.tabella.item(item[0])["values"]
        id = valori[0]
        if messagebox.askyesno("Conferma", "Vuoi davvero disattivare questo articolo?", parent=self.root):
            elimina_logicamente(id)
            messagebox.showinfo("Eliminato", "Articolo disattivato correttamente", parent=self.root)
            self.aggiorna_tabella()

    def visualizza_pdf(self):
        item = self.tabella.selection()
        if not item:
            messagebox.showinfo("Info", "Seleziona un record con PDF da aprire", parent=self.root)
            return
        valori = self.tabella.item(item[0])["values"]
        nome_file = valori[5]
        if nome_file:
            apri_pdf(nome_file)
        else:
            messagebox.showwarning("PDF mancante", "Questo articolo non ha un PDF associato", parent=self.root)

    def visualizza_disattivati(self):
        finestra = tk.Toplevel(self.root)
        finestra.title("Articoli Disattivati")
        finestra.transient(self.root)
        finestra.grab_set()

        tabella_disattivi = ttk.Treeview(finestra, columns=("id", "id_code", "riferimento", "fornitore", "descrizione", "pdf_file", "created_at"), show="headings")
        for col in tabella_disattivi["columns"]:
            tabella_disattivi.heading(col, text=self.intestazioni[col])
        tabella_disattivi.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        for riga in carica_articoli(attivi=False):
            tabella_disattivi.insert('', tk.END, values=riga)

        def elimina_definitivamente():
            item = tabella_disattivi.selection()
            if not item:
                messagebox.showinfo("Info", "Seleziona un record da eliminare definitivamente", parent=finestra)
                return
            id = tabella_disattivi.item(item[0])["values"][0]
            if messagebox.askyesno("Conferma", "Sei sicuro di voler eliminare definitivamente questo articolo?", parent=finestra):
                conn = sqlite3.connect(DB_FILE)
                c = conn.cursor()
                c.execute("DELETE FROM inventory WHERE id=?", (id,))
                conn.commit()
                conn.close()
                messagebox.showinfo("Eliminato", "Articolo eliminato definitivamente", parent=finestra)
                tabella_disattivi.delete(item[0])

        frame_bottoni = ttk.Frame(finestra)
        frame_bottoni.pack(pady=5)

        ttk.Button(frame_bottoni, text="Recupera", command=lambda: self.recupera_selezionato(tabella_disattivi, finestra)).pack(side=tk.LEFT, padx=5)
        ttk.Button(frame_bottoni, text="Elimina Definitivamente", command=elimina_definitivamente).pack(side=tk.LEFT, padx=5)
        ttk.Button(frame_bottoni, text="Chiudi", command=finestra.destroy).pack(side=tk.LEFT, padx=5)
        centra_finestra(finestra, self.root)

    def recupera_selezionato(self, tabella_disattivi, finestra):
        item = tabella_disattivi.selection()
        if not item:
            messagebox.showinfo("Info", "Seleziona un record da recuperare", parent=finestra)
            return
        id = tabella_disattivi.item(item[0])["values"][0]
        if messagebox.askyesno("Conferma", "Vuoi davvero recuperare questo articolo?", parent=finestra):
            recupera_articolo(id)
            messagebox.showinfo("Recuperato", "Articolo riattivato correttamente", parent=finestra)
            tabella_disattivi.delete(item[0])

    def stampa_etichetta(self, codice, riferimento, fornitore):
        try:
            etichetta = f"{codice} {fornitore}"
            file_path = "/tmp/etichetta.txt"
            with open(file_path, "w") as f:
                f.write(etichetta)

            # Stampa tramite lp per sistemi Linux
            subprocess.run(["lp", "-d", "Brother_QL_700", file_path], check=True)
        except Exception as e:
            messagebox.showerror("Errore Stampa", f"Errore durante la stampa dell'etichetta: {e}", parent=self.root)

    def visualizza_dettagli(self):
        item = self.tabella.selection()
        if not item:
            messagebox.showinfo("Info", "Seleziona un record da visualizzare", parent=self.root)
            return
        valori = self.tabella.item(item[0])["values"]
        if len(valori) < 7:
            messagebox.showerror("Errore", "Il record selezionato non contiene tutti i dati richiesti", parent=self.root)
            return
        id, codice, riferimento, fornitore, descrizione, pdf_file, created_at = valori[:7]

        win = tk.Toplevel(self.root)
        win.title(f"Dettagli Articolo {codice}")
        win.transient(self.root)
        win.grab_set()

        campi = [
            ("ID", id),
            ("Codice", codice),
            ("Riferimento", riferimento),
            ("Fornitore", fornitore),
            ("Descrizione", descrizione),
            ("PDF", pdf_file if pdf_file else "Nessun PDF"),
            ("Creato il", created_at)
        ]

        for i, (label, valore) in enumerate(campi):
            ttk.Label(win, text=label + ":").grid(row=i, column=0, sticky="w", padx=5, pady=2)
            ttk.Label(win, text=valore).grid(row=i, column=1, sticky="w", padx=5, pady=2)

         ttk.Button(win, text="Stampa Etichetta", command=lambda: self.stampa_etichetta(codice, riferimento, fornitore)).grid(row=len(campi), column=0, columnspan=2, pady=5)
        ttk.Button(win, text="Chiudi", command=win.destroy).grid(row=len(campi)+1, column=0, columnspan=2, pady=10)
        centra_finestra(win, self.root)

# Avvio
if __name__ == '__main__':
    inizializza_db()
    root = tk.Tk()
    app = GestGNApp(root)
    root.mainloop()