Add wizard UI, general-assembly resolution, and test suite
Restructure Form1 into a sequential wizard (database -> import -> raw -> recursive component resolution -> completeness/STEP report), fix case-sensitivity bugs in codice/tipo_file lookups and a word-boundary gap in the assembly scanner that missed _D100-style sub-assembly references, and treat the distinta's general assembly as an ordinary CSV row (derived from the DBP_<codice>_<timestamp> filename) instead of special-casing it throughout. Also adds CadSearchGui.Tests (xUnit) covering the scanner, filename parsing, and the case-insensitive DB lookup behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
146ce78eae
commit
52483177f0
16 files changed
+1144
-249
No files matched your search
+592
-211
@@ -9,13 +9,34 @@ public partial class Form1 : Form
|
||||
private readonly Button _btnAggiorna = new();
|
||||
private readonly Button _btnCreaRaw = new();
|
||||
private readonly Button _btnTrovaPrt = new();
|
||||
private readonly Button _btnReport = new();
|
||||
private readonly Button _btnCompletezza = new();
|
||||
private readonly Button _btnPulisci = new();
|
||||
private readonly Label _lblStatus = new();
|
||||
private readonly Label _lblDb = new();
|
||||
private readonly Label _lblUltimoAggiornamento = new();
|
||||
private readonly DataGridView _gridImport = new();
|
||||
private readonly DataGridView _gridRaw = new();
|
||||
private readonly Label _lblImportStatus = new();
|
||||
private readonly ToolTip _tipImportStatus = new();
|
||||
|
||||
// Wizard: una fase alla volta occupa tutta la finestra, si avanza solo quando quella
|
||||
// corrente è "pronta" (vedi AvantiConsentito). NomiFasi/_panelFasi sono paralleli per indice.
|
||||
private static readonly string[] NomiFasi =
|
||||
{
|
||||
"1. Database",
|
||||
"2. Importa Distinta Base",
|
||||
"3. Crea cartella raw",
|
||||
"4. Trova componenti referenziati",
|
||||
"5. Verifica completezza e STEP",
|
||||
};
|
||||
private readonly Panel[] _panelFasi = new Panel[NomiFasi.Length];
|
||||
private readonly Panel _faseContainer = new() { Dock = DockStyle.Fill };
|
||||
private readonly Label _lblFase = new();
|
||||
private readonly Button _btnIndietro = new();
|
||||
private readonly Button _btnAvanti = new();
|
||||
private int _faseAttuale;
|
||||
|
||||
private static readonly string[] TipiPredefiniti = { "prt", "asm" };
|
||||
private const string RadiceDiscoDefault = @"X:\";
|
||||
|
||||
@@ -42,23 +63,210 @@ public partial class Form1 : Form
|
||||
MinimumSize = new Size(760, 480);
|
||||
Font = new Font("Segoe UI", 9.5f);
|
||||
|
||||
var contenutoTop = new TableLayoutPanel
|
||||
var headerPanel = new Panel { Dock = DockStyle.Top, Height = 40, BackColor = SystemColors.ControlLight };
|
||||
_lblFase.Dock = DockStyle.Fill;
|
||||
_lblFase.TextAlign = ContentAlignment.MiddleLeft;
|
||||
_lblFase.Padding = new Padding(12, 0, 0, 0);
|
||||
_lblFase.Font = new Font(Font, FontStyle.Bold);
|
||||
headerPanel.Controls.Add(_lblFase);
|
||||
|
||||
var navPanel = new FlowLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Bottom,
|
||||
Height = 48,
|
||||
FlowDirection = FlowDirection.RightToLeft,
|
||||
Padding = new Padding(12, 8, 12, 8),
|
||||
};
|
||||
_btnAvanti.Text = "Avanti >";
|
||||
_btnAvanti.Size = new Size(110, 32);
|
||||
_btnAvanti.FlatStyle = FlatStyle.Flat;
|
||||
_btnAvanti.FlatAppearance.BorderSize = 0;
|
||||
_btnAvanti.BackColor = ColorePrimario;
|
||||
_btnAvanti.ForeColor = Color.White;
|
||||
_btnAvanti.Font = new Font(Font, FontStyle.Bold);
|
||||
_btnAvanti.Click += (_, _) => VaiAllaFase(_faseAttuale + 1);
|
||||
navPanel.Controls.Add(_btnAvanti);
|
||||
|
||||
_btnIndietro.Text = "< Indietro";
|
||||
_btnIndietro.Size = new Size(110, 32);
|
||||
_btnIndietro.Margin = new Padding(8, 0, 0, 0);
|
||||
_btnIndietro.Click += (_, _) => VaiAllaFase(_faseAttuale - 1);
|
||||
navPanel.Controls.Add(_btnIndietro);
|
||||
|
||||
var statusPanel = new Panel { Dock = DockStyle.Bottom, Height = 28, BackColor = SystemColors.ControlLight };
|
||||
_lblStatus.Dock = DockStyle.Fill;
|
||||
_lblStatus.TextAlign = ContentAlignment.MiddleLeft;
|
||||
_lblStatus.Padding = new Padding(12, 0, 0, 0);
|
||||
_lblStatus.Text = "Pronto.";
|
||||
statusPanel.Controls.Add(_lblStatus);
|
||||
|
||||
_panelFasi[0] = BuildFaseDatabase();
|
||||
_panelFasi[1] = BuildFaseImporta();
|
||||
_panelFasi[2] = BuildFaseCreaRaw();
|
||||
_panelFasi[3] = BuildFaseTrovaComponenti();
|
||||
_panelFasi[4] = BuildFaseVerifica();
|
||||
foreach (var panel in _panelFasi)
|
||||
{
|
||||
panel.Dock = DockStyle.Fill;
|
||||
panel.Visible = false;
|
||||
_faseContainer.Controls.Add(panel);
|
||||
}
|
||||
|
||||
Controls.Add(_faseContainer);
|
||||
Controls.Add(navPanel);
|
||||
Controls.Add(statusPanel);
|
||||
Controls.Add(headerPanel);
|
||||
|
||||
DragEnter += (_, e) =>
|
||||
e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true
|
||||
? DragDropEffects.Copy
|
||||
: DragDropEffects.None;
|
||||
DragDrop += (_, e) =>
|
||||
{
|
||||
if (e.Data?.GetData(DataFormats.FileDrop) is string[] { Length: > 0 } file)
|
||||
ImportaDistinta(file[0]);
|
||||
};
|
||||
|
||||
VaiAllaFase(0);
|
||||
}
|
||||
|
||||
/// <summary>Nasconde la fase corrente e mostra quella richiesta (se nei limiti e se la fase
|
||||
/// corrente permette di avanzare); aggiorna titolo e stato dei pulsanti Indietro/Avanti.</summary>
|
||||
private void VaiAllaFase(int fase)
|
||||
{
|
||||
if (fase < 0 || fase >= NomiFasi.Length)
|
||||
return;
|
||||
if (fase > _faseAttuale && !AvantiConsentito(_faseAttuale))
|
||||
return;
|
||||
|
||||
_panelFasi[_faseAttuale].Visible = false;
|
||||
_faseAttuale = fase;
|
||||
_panelFasi[_faseAttuale].Visible = true;
|
||||
|
||||
_lblFase.Text = NomiFasi[_faseAttuale];
|
||||
_btnIndietro.Visible = _faseAttuale > 0;
|
||||
_btnAvanti.Visible = _faseAttuale < NomiFasi.Length - 1;
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
/// <summary>Condizione per cui la fase indicata è "conclusa" e si può passare alla successiva.</summary>
|
||||
private bool AvantiConsentito(int fase) => fase switch
|
||||
{
|
||||
0 => _dbPath is not null,
|
||||
1 => _righeCorrenti is { Count: > 0 },
|
||||
2 => _ultimaCartellaRaw is not null,
|
||||
3 => true, // risoluzione ricorsiva opzionale: non tutti gli assiemi hanno sotto-componenti
|
||||
_ => true,
|
||||
};
|
||||
|
||||
private void AggiornaStatoAvanti() => _btnAvanti.Enabled = AvantiConsentito(_faseAttuale);
|
||||
|
||||
private Panel BuildFaseDatabase()
|
||||
{
|
||||
var contenuto = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 1,
|
||||
Padding = new Padding(12, 12, 12, 0),
|
||||
Padding = new Padding(16),
|
||||
};
|
||||
contenutoTop.Controls.Add(BuildGruppoDatabase());
|
||||
contenutoTop.Controls.Add(BuildGruppoImporta());
|
||||
|
||||
_lblImportStatus.Dock = DockStyle.Top;
|
||||
var lblIstruzioni = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 0, 0, 12),
|
||||
Text = "L'indice CAD viene caricato automaticamente all'avvio, se già presente. "
|
||||
+ "Usa 'Apri DB...' per selezionarne un altro, oppure 'Aggiorna indice' per "
|
||||
+ "scansionare di nuovo il disco (X:\\).",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioni);
|
||||
|
||||
_lblDb.AutoSize = true;
|
||||
_lblDb.AutoEllipsis = true;
|
||||
_lblDb.MaximumSize = new Size(900, 0);
|
||||
_lblDb.ForeColor = SystemColors.GrayText;
|
||||
_lblDb.Margin = new Padding(0, 0, 0, 4);
|
||||
contenuto.Controls.Add(_lblDb);
|
||||
|
||||
_lblUltimoAggiornamento.AutoSize = true;
|
||||
_lblUltimoAggiornamento.ForeColor = SystemColors.GrayText;
|
||||
_lblUltimoAggiornamento.Margin = new Padding(0, 0, 0, 12);
|
||||
contenuto.Controls.Add(_lblUltimoAggiornamento);
|
||||
|
||||
var bottoni = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.LeftToRight };
|
||||
|
||||
_btnApriDb.Text = "Apri DB...";
|
||||
_btnApriDb.Size = new Size(120, 32);
|
||||
_btnApriDb.Margin = new Padding(0, 0, 8, 0);
|
||||
_btnApriDb.Click += (_, _) => ScegliDatabase();
|
||||
bottoni.Controls.Add(_btnApriDb);
|
||||
|
||||
_btnAggiorna.Text = "Aggiorna indice";
|
||||
_btnAggiorna.Size = new Size(120, 32);
|
||||
_btnAggiorna.Click += (_, _) => AggiornaIndice();
|
||||
bottoni.Controls.Add(_btnAggiorna);
|
||||
|
||||
contenuto.Controls.Add(bottoni);
|
||||
|
||||
var lblManutenzione = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 20, 0, 8),
|
||||
Text = "Manutenzione: svuota le cartelle 'raw' e 'step' nella root prima di un "
|
||||
+ "nuovo giro (non tocca l'indice, solo le copie locali).",
|
||||
};
|
||||
contenuto.Controls.Add(lblManutenzione);
|
||||
|
||||
_btnPulisci.Text = "Pulisci raw e step";
|
||||
_btnPulisci.Size = new Size(200, 30);
|
||||
_btnPulisci.Click += (_, _) => PulisciRawEStep();
|
||||
contenuto.Controls.Add(_btnPulisci);
|
||||
|
||||
var panel = new Panel();
|
||||
panel.Controls.Add(contenuto);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Panel BuildFaseImporta()
|
||||
{
|
||||
var contenuto = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 1,
|
||||
Padding = new Padding(16),
|
||||
};
|
||||
|
||||
var lblIstruzioni = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 0, 0, 8),
|
||||
Text = "Trascina qui il file .xls/.xml esportato dal gestionale (foglio 'Distinta "
|
||||
+ "Base'), oppure scegli il file col pulsante sotto: genera un CSV con Componente "
|
||||
+ "e Descrizione, in anteprima nella griglia.",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioni);
|
||||
|
||||
var btnSfoglia = new Button
|
||||
{
|
||||
Text = "Scegli file distinta...",
|
||||
Size = new Size(220, 32),
|
||||
Margin = new Padding(0, 0, 0, 8),
|
||||
};
|
||||
btnSfoglia.Click += (_, _) => ScegliFileDistinta();
|
||||
contenuto.Controls.Add(btnSfoglia);
|
||||
|
||||
_lblImportStatus.AutoSize = false;
|
||||
_lblImportStatus.AutoEllipsis = true;
|
||||
_lblImportStatus.Height = 28;
|
||||
_lblImportStatus.Padding = new Padding(12, 6, 12, 6);
|
||||
_lblImportStatus.Height = 24;
|
||||
_lblImportStatus.Width = 900;
|
||||
_lblImportStatus.ForeColor = SystemColors.GrayText;
|
||||
contenuto.Controls.Add(_lblImportStatus);
|
||||
|
||||
_gridImport.Dock = DockStyle.Fill;
|
||||
_gridImport.ReadOnly = true;
|
||||
@@ -80,36 +288,16 @@ public partial class Form1 : Form
|
||||
_gridImport.DefaultCellStyle.Padding = new Padding(4, 0, 4, 0);
|
||||
_gridImport.Columns.Add("componente", "Componente");
|
||||
_gridImport.Columns.Add("descrizione", "Descrizione");
|
||||
_gridImport.Columns.Add("percorso", "Percorso CAD");
|
||||
_gridImport.Columns["componente"]!.FillWeight = 20;
|
||||
_gridImport.Columns["descrizione"]!.FillWeight = 40;
|
||||
_gridImport.Columns["percorso"]!.FillWeight = 40;
|
||||
_gridImport.CellDoubleClick += (_, _) => ApriCartellaFileSelezionato();
|
||||
_gridImport.Columns["componente"]!.FillWeight = 30;
|
||||
_gridImport.Columns["descrizione"]!.FillWeight = 70;
|
||||
|
||||
var statusPanel = new Panel { Dock = DockStyle.Bottom, Height = 28, BackColor = SystemColors.ControlLight };
|
||||
_lblStatus.Dock = DockStyle.Fill;
|
||||
_lblStatus.TextAlign = ContentAlignment.MiddleLeft;
|
||||
_lblStatus.Padding = new Padding(12, 0, 0, 0);
|
||||
_lblStatus.Text = "Pronto.";
|
||||
statusPanel.Controls.Add(_lblStatus);
|
||||
|
||||
Controls.Add(_gridImport);
|
||||
Controls.Add(_lblImportStatus);
|
||||
Controls.Add(statusPanel);
|
||||
Controls.Add(contenutoTop);
|
||||
|
||||
DragEnter += (_, e) =>
|
||||
e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true
|
||||
? DragDropEffects.Copy
|
||||
: DragDropEffects.None;
|
||||
DragDrop += (_, e) =>
|
||||
{
|
||||
if (e.Data?.GetData(DataFormats.FileDrop) is string[] { Length: > 0 } file)
|
||||
ImportaDistinta(file[0]);
|
||||
};
|
||||
var panel = new Panel();
|
||||
panel.Controls.Add(_gridImport);
|
||||
panel.Controls.Add(contenuto);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private GroupBox BuildGruppoDatabase()
|
||||
private Panel BuildFaseCreaRaw()
|
||||
{
|
||||
var contenuto = new TableLayoutPanel
|
||||
{
|
||||
@@ -117,85 +305,22 @@ public partial class Form1 : Form
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 1,
|
||||
Padding = new Padding(16),
|
||||
};
|
||||
|
||||
_lblDb.AutoSize = true;
|
||||
_lblDb.AutoEllipsis = true;
|
||||
_lblDb.MaximumSize = new Size(900, 0);
|
||||
_lblDb.ForeColor = SystemColors.GrayText;
|
||||
_lblDb.Margin = new Padding(0, 0, 0, 8);
|
||||
contenuto.Controls.Add(_lblDb);
|
||||
|
||||
var bottoni = new FlowLayoutPanel { AutoSize = true, FlowDirection = FlowDirection.LeftToRight };
|
||||
|
||||
_btnApriDb.Text = "Apri DB...";
|
||||
_btnApriDb.Size = new Size(120, 32);
|
||||
_btnApriDb.Margin = new Padding(0, 0, 8, 0);
|
||||
_btnApriDb.Click += (_, _) => ScegliDatabase();
|
||||
bottoni.Controls.Add(_btnApriDb);
|
||||
|
||||
_btnAggiorna.Text = "Aggiorna indice";
|
||||
_btnAggiorna.Size = new Size(120, 32);
|
||||
_btnAggiorna.Click += (_, _) => AggiornaIndice();
|
||||
bottoni.Controls.Add(_btnAggiorna);
|
||||
|
||||
contenuto.Controls.Add(bottoni);
|
||||
|
||||
return new GroupBox
|
||||
{
|
||||
Text = "1. Indice CAD (caricato automaticamente all'avvio)",
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
Padding = new Padding(12, 8, 12, 12),
|
||||
Margin = new Padding(0, 0, 0, 10),
|
||||
Controls = { contenuto },
|
||||
};
|
||||
}
|
||||
|
||||
private GroupBox BuildGruppoImporta()
|
||||
{
|
||||
var contenuto = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 1,
|
||||
};
|
||||
|
||||
var lblIstruzioni1 = new Label
|
||||
var lblIstruzioni = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 0, 0, 0),
|
||||
Text = "2. Trascina qui il file .xls/.xml esportato dal gestionale (foglio 'Distinta "
|
||||
+ "Base'), oppure scegli il file col pulsante sotto: genera un CSV con Componente "
|
||||
+ "e Descrizione, in anteprima nella griglia.",
|
||||
Margin = new Padding(0, 0, 0, 8),
|
||||
Text = "Cerca nell'indice il percorso CAD di ogni componente importato e copia i "
|
||||
+ "file trovati in una cartella 'raw' accanto al file di distinta originale.",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioni1);
|
||||
|
||||
var btnSfoglia = new Button
|
||||
{
|
||||
Text = "Scegli file distinta...",
|
||||
Size = new Size(220, 32),
|
||||
Margin = new Padding(0, 8, 0, 16),
|
||||
};
|
||||
btnSfoglia.Click += (_, _) => ScegliFileDistinta();
|
||||
contenuto.Controls.Add(btnSfoglia);
|
||||
|
||||
var lblIstruzioni2 = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Text = "3. Premi qui sotto per cercare il percorso CAD di ogni componente "
|
||||
+ "nell'indice e copiare i file trovati in una cartella 'raw'.",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioni2);
|
||||
contenuto.Controls.Add(lblIstruzioni);
|
||||
|
||||
_btnCreaRaw.Text = "Crea cartella raw";
|
||||
_btnCreaRaw.Size = new Size(220, 36);
|
||||
_btnCreaRaw.Margin = new Padding(0, 8, 0, 0);
|
||||
_btnCreaRaw.Enabled = false;
|
||||
_btnCreaRaw.Margin = new Padding(0, 0, 0, 12);
|
||||
_btnCreaRaw.FlatStyle = FlatStyle.Flat;
|
||||
_btnCreaRaw.FlatAppearance.BorderSize = 0;
|
||||
_btnCreaRaw.BackColor = ColorePrimario;
|
||||
@@ -204,49 +329,115 @@ public partial class Form1 : Form
|
||||
_btnCreaRaw.Click += (_, _) => CreaCartellaRaw();
|
||||
contenuto.Controls.Add(_btnCreaRaw);
|
||||
|
||||
var lblIstruzioni3 = new Label
|
||||
_gridRaw.Dock = DockStyle.Fill;
|
||||
_gridRaw.ReadOnly = true;
|
||||
_gridRaw.BorderStyle = BorderStyle.None;
|
||||
_gridRaw.BackgroundColor = SystemColors.Window;
|
||||
_gridRaw.RowHeadersVisible = false;
|
||||
_gridRaw.AllowUserToAddRows = false;
|
||||
_gridRaw.AllowUserToDeleteRows = false;
|
||||
_gridRaw.AllowUserToResizeRows = false;
|
||||
_gridRaw.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
_gridRaw.MultiSelect = false;
|
||||
_gridRaw.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
|
||||
_gridRaw.ColumnHeadersHeight = 32;
|
||||
_gridRaw.ColumnHeadersDefaultCellStyle.Font = new Font(Font, FontStyle.Bold);
|
||||
_gridRaw.RowTemplate.Height = 26;
|
||||
_gridRaw.AlternatingRowsDefaultCellStyle.BackColor = Color.FromArgb(245, 247, 250);
|
||||
_gridRaw.DefaultCellStyle.SelectionBackColor = ColorePrimario;
|
||||
_gridRaw.DefaultCellStyle.Padding = new Padding(4, 0, 4, 0);
|
||||
_gridRaw.Columns.Add("componente", "Componente");
|
||||
_gridRaw.Columns.Add("percorso", "Percorso CAD");
|
||||
_gridRaw.Columns["componente"]!.FillWeight = 25;
|
||||
_gridRaw.Columns["percorso"]!.FillWeight = 75;
|
||||
_gridRaw.CellDoubleClick += (_, _) => ApriCartellaFileSelezionato();
|
||||
|
||||
var panel = new Panel();
|
||||
panel.Controls.Add(_gridRaw);
|
||||
panel.Controls.Add(contenuto);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Panel BuildFaseTrovaComponenti()
|
||||
{
|
||||
var contenuto = new TableLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 12, 0, 0),
|
||||
Text = "4. Gli assiemi referenziano altri PRT e sotto-assiemi al loro interno: premi "
|
||||
+ "qui per risolvere l'albero componenti (ricorsivamente) e copiare tutto ciò che "
|
||||
+ "viene trovato nell'indice dentro 'raw'.",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioni3);
|
||||
|
||||
_btnTrovaPrt.Text = "Trova componenti referenziati";
|
||||
_btnTrovaPrt.Size = new Size(220, 32);
|
||||
_btnTrovaPrt.Margin = new Padding(0, 8, 0, 0);
|
||||
_btnTrovaPrt.Enabled = false;
|
||||
_btnTrovaPrt.Click += (_, _) => TrovaPrtReferenziati();
|
||||
contenuto.Controls.Add(_btnTrovaPrt);
|
||||
|
||||
var lblManutenzione = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 16, 0, 0),
|
||||
Text = "Manutenzione: svuota le cartelle 'raw' e 'step' nella root prima di un "
|
||||
+ "nuovo giro (non tocca l'indice, solo le copie locali).",
|
||||
};
|
||||
contenuto.Controls.Add(lblManutenzione);
|
||||
|
||||
_btnPulisci.Text = "Pulisci raw e step";
|
||||
_btnPulisci.Size = new Size(200, 30);
|
||||
_btnPulisci.Margin = new Padding(0, 6, 0, 0);
|
||||
_btnPulisci.Click += (_, _) => PulisciRawEStep();
|
||||
contenuto.Controls.Add(_btnPulisci);
|
||||
|
||||
return new GroupBox
|
||||
{
|
||||
Text = "2-4. Importa Distinta Base, crea la cartella raw e risolvi i componenti referenziati",
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
Padding = new Padding(12, 8, 12, 12),
|
||||
Controls = { contenuto },
|
||||
ColumnCount = 1,
|
||||
Padding = new Padding(16),
|
||||
};
|
||||
|
||||
var lblIstruzioni = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 0, 0, 12),
|
||||
Text = "Gli assiemi referenziano altri PRT e sotto-assiemi al loro interno: premi "
|
||||
+ "qui per risolvere l'albero componenti (ricorsivamente) e copiare tutto ciò che "
|
||||
+ "viene trovato nell'indice dentro 'raw'. Passo opzionale: se la distinta contiene "
|
||||
+ "solo parti singole puoi anche passare direttamente alla fase successiva.",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioni);
|
||||
|
||||
_btnTrovaPrt.Text = "Trova componenti referenziati";
|
||||
_btnTrovaPrt.Size = new Size(220, 32);
|
||||
_btnTrovaPrt.Click += (_, _) => TrovaPrtReferenziati();
|
||||
contenuto.Controls.Add(_btnTrovaPrt);
|
||||
|
||||
var panel = new Panel();
|
||||
panel.Controls.Add(contenuto);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Panel BuildFaseVerifica()
|
||||
{
|
||||
var contenuto = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Top,
|
||||
AutoSize = true,
|
||||
AutoSizeMode = AutoSizeMode.GrowAndShrink,
|
||||
ColumnCount = 1,
|
||||
Padding = new Padding(16),
|
||||
};
|
||||
|
||||
var lblIstruzioniCompletezza = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 0, 0, 8),
|
||||
Text = "Per ogni assieme della distinta, verifica se tutti i suoi componenti "
|
||||
+ "(ricorsivamente fino ai PRT) sono nell'indice. Nota: la scansione dei .asm "
|
||||
+ "è euristica, quindi qualche falso 'incompleto' è possibile.",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioniCompletezza);
|
||||
|
||||
_btnCompletezza.Text = "Verifica completezza assiemi";
|
||||
_btnCompletezza.Size = new Size(220, 32);
|
||||
_btnCompletezza.Margin = new Padding(0, 0, 0, 20);
|
||||
_btnCompletezza.Click += (_, _) => VerificaCompletezzaAssiemi();
|
||||
contenuto.Controls.Add(_btnCompletezza);
|
||||
|
||||
var lblIstruzioniStep = new Label
|
||||
{
|
||||
AutoSize = true,
|
||||
ForeColor = SystemColors.GrayText,
|
||||
Margin = new Padding(0, 0, 0, 8),
|
||||
Text = "Dopo aver esportato gli STEP con Creo, premi qui per confrontare i "
|
||||
+ "componenti della distinta con i file .stp presenti in 'step' e vedere quali "
|
||||
+ "sono già stati realizzati e quali mancano ancora.",
|
||||
};
|
||||
contenuto.Controls.Add(lblIstruzioniStep);
|
||||
|
||||
_btnReport.Text = "Report STEP realizzati";
|
||||
_btnReport.Size = new Size(220, 32);
|
||||
_btnReport.Click += (_, _) => GeneraReportStep();
|
||||
contenuto.Controls.Add(_btnReport);
|
||||
|
||||
var panel = new Panel();
|
||||
panel.Controls.Add(contenuto);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void ScegliFileDistinta()
|
||||
@@ -271,9 +462,10 @@ public partial class Form1 : Form
|
||||
_gridImport.Rows.Clear();
|
||||
foreach (var (componente, descrizione) in righe)
|
||||
{
|
||||
_gridImport.Rows.Add(componente, descrizione, "");
|
||||
_gridImport.Rows.Add(componente, descrizione);
|
||||
}
|
||||
_btnCreaRaw.Enabled = righe.Count > 0;
|
||||
_gridRaw.Rows.Clear();
|
||||
_ultimaCartellaRaw = null;
|
||||
|
||||
ImpostaStatoImport($"CSV generato ({righe.Count} righe): {csv}", Color.DarkGreen);
|
||||
}
|
||||
@@ -281,10 +473,10 @@ public partial class Form1 : Form
|
||||
{
|
||||
_percorsoInputCorrente = null;
|
||||
_righeCorrenti = null;
|
||||
_btnCreaRaw.Enabled = false;
|
||||
_gridImport.Rows.Clear();
|
||||
ImpostaStatoImport($"Errore durante la conversione: {ex.Message}", Color.DarkRed);
|
||||
}
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
/// <summary>Imposta testo/colore dello stato import e il tooltip con il testo completo
|
||||
@@ -301,25 +493,61 @@ public partial class Form1 : Form
|
||||
if (_percorsoInputCorrente is null || _righeCorrenti is null)
|
||||
return;
|
||||
|
||||
// L'assieme generale è già una riga di _righeCorrenti (aggiunta da
|
||||
// DistintaBaseImporter.ConvertiInCsv leggendo il nome del file distinta), quindi viene
|
||||
// trovato e copiato in raw come qualsiasi altro componente, senza bisogno di un caso speciale.
|
||||
var percorsiTrovati = TrovaPercorsiCad(_righeCorrenti.Select(r => r.Componente));
|
||||
|
||||
for (int i = 0; i < _righeCorrenti.Count; i++)
|
||||
_gridRaw.Rows.Clear();
|
||||
foreach (var (componente, _) in _righeCorrenti)
|
||||
{
|
||||
string percorsoCad = percorsiTrovati.TryGetValue(_righeCorrenti[i].Componente, out var lista)
|
||||
string percorsoCad = percorsiTrovati.TryGetValue(componente, out var lista)
|
||||
? string.Join("; ", lista)
|
||||
: "";
|
||||
_gridImport.Rows[i].Cells[2].Value = percorsoCad;
|
||||
_gridRaw.Rows.Add(componente, percorsoCad);
|
||||
}
|
||||
|
||||
var cartellaRaw = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_percorsoInputCorrente))!, "raw");
|
||||
int copiati = CopiaFileTrovati(percorsiTrovati.Values.SelectMany(p => p).Distinct(), cartellaRaw);
|
||||
_ultimaCartellaRaw = cartellaRaw;
|
||||
_btnTrovaPrt.Enabled = true;
|
||||
|
||||
ImpostaStatoImport(
|
||||
$"{percorsiTrovati.Count} componenti con percorso CAD trovato su {_righeCorrenti.Count}. "
|
||||
+ $"{copiati} file copiati in {cartellaRaw}",
|
||||
Color.DarkGreen);
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
/// <summary>Confronta i componenti della distinta importata con i file .stp presenti nella
|
||||
/// cartella "step" (esportati manualmente con Creo Distributed Batch), per dire quali sono
|
||||
/// già stati realizzati e quali mancano ancora.</summary>
|
||||
private void GeneraReportStep()
|
||||
{
|
||||
if (_percorsoInputCorrente is null || _righeCorrenti is null)
|
||||
return;
|
||||
|
||||
var cartellaStep = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(_percorsoInputCorrente))!, "step");
|
||||
if (!Directory.Exists(cartellaStep))
|
||||
{
|
||||
ImpostaStatoImport($"Cartella 'step' non trovata in {cartellaStep}.", Color.DarkRed);
|
||||
return;
|
||||
}
|
||||
|
||||
var codiciConStep = new HashSet<string>(
|
||||
Directory.GetFiles(cartellaStep, "*.stp").Select(Path.GetFileNameWithoutExtension)!);
|
||||
|
||||
var righeReport = _righeCorrenti
|
||||
.Select(r => (r.Componente, r.Descrizione, Realizzato: codiciConStep.Contains(r.Componente)))
|
||||
.ToList();
|
||||
int realizzati = righeReport.Count(r => r.Realizzato);
|
||||
|
||||
ImpostaStatoImport(
|
||||
$"Report STEP: {realizzati} componenti realizzati su {righeReport.Count} "
|
||||
+ $"({righeReport.Count - realizzati} mancanti).",
|
||||
Color.DarkGreen);
|
||||
|
||||
using var dialog = new ReportStepForm(righeReport, cartellaStep);
|
||||
dialog.ShowDialog(this);
|
||||
}
|
||||
|
||||
/// <summary>Svuota in un colpo solo le cartelle "raw" e "step" nella root (stessa cartella
|
||||
@@ -377,10 +605,77 @@ public partial class Form1 : Form
|
||||
ImpostaStatoImport($"{eliminati} file eliminati da {string.Join(" e ", cartelle)}.", Color.DarkGreen);
|
||||
}
|
||||
|
||||
/// <summary>Risolve ricorsivamente l'albero dei componenti a partire dagli .asm in "raw":
|
||||
/// per ogni assieme legge i codici referenziati al suo interno, li cerca nell'indice, copia
|
||||
/// in "raw" quelli trovati e, se sono a loro volta assiemi, li accoda per essere scansionati
|
||||
/// finché non restano solo PRT (foglie) o codici non presenti nell'indice.</summary>
|
||||
/// <summary>Risolve ricorsivamente l'albero dei componenti a partire dai file .asm indicati
|
||||
/// (già copiati in "raw"): per ogni assieme legge i codici referenziati al suo interno, li
|
||||
/// cerca nell'indice, copia in "raw" quelli trovati e, se sono a loro volta assiemi, li
|
||||
/// accoda per essere scansionati finché non restano solo PRT (foglie) o codici non presenti
|
||||
/// nell'indice. Usata dal bottone "Trova componenti referenziati".</summary>
|
||||
private (List<(string Codice, string Tipo, string Percorso)> Trovati, List<string> NonTrovati) RisolviAlberoComponenti(
|
||||
IEnumerable<string> asmIniziali)
|
||||
{
|
||||
var daScansionare = new Queue<string>(asmIniziali);
|
||||
var codiciVisti = new HashSet<string>(
|
||||
daScansionare.Select(f => Path.GetFileName(f).Split('.')[0]), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var trovati = new List<(string Codice, string Tipo, string Percorso)>();
|
||||
var codiciNonTrovati = new List<string>();
|
||||
if (daScansionare.Count == 0 || _dbPath is null)
|
||||
return (trovati, codiciNonTrovati);
|
||||
|
||||
using var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly");
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione "
|
||||
+ "WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ('prt', 'asm') "
|
||||
+ "ORDER BY tipo_file, data_modifica DESC";
|
||||
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
|
||||
|
||||
while (daScansionare.Count > 0)
|
||||
{
|
||||
var asm = daScansionare.Dequeue();
|
||||
foreach (var codice in AssemblyComponentScanner.EstraiCodiciReferenziati(asm))
|
||||
{
|
||||
if (!codiciVisti.Add(codice))
|
||||
continue;
|
||||
|
||||
pCodice.Value = codice;
|
||||
// Lo stesso codice+tipo può esistere in più cartelle radice (duplicati sulla
|
||||
// share): ordinato per data_modifica DESC, si tiene solo il più recente per tipo.
|
||||
var componenti = new List<(string Tipo, string Percorso)>();
|
||||
var tipiVisti = new HashSet<string>();
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
string tipoLower = reader.GetString(0).ToLowerInvariant();
|
||||
if (tipiVisti.Add(tipoLower))
|
||||
{
|
||||
componenti.Add((tipoLower, reader.GetString(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (componenti.Count == 0)
|
||||
{
|
||||
codiciNonTrovati.Add(codice);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (tipo, percorso) in componenti)
|
||||
{
|
||||
CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw!);
|
||||
trovati.Add((codice, tipo, percorso));
|
||||
if (tipo == "asm")
|
||||
{
|
||||
daScansionare.Enqueue(Path.Combine(_ultimaCartellaRaw!, Path.GetFileName(percorso)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (trovati, codiciNonTrovati);
|
||||
}
|
||||
|
||||
private void TrovaPrtReferenziati()
|
||||
{
|
||||
if (_ultimaCartellaRaw is null || !Directory.Exists(_ultimaCartellaRaw))
|
||||
@@ -392,69 +687,141 @@ public partial class Form1 : Form
|
||||
return;
|
||||
}
|
||||
|
||||
var daScansionare = new Queue<string>(
|
||||
Directory.GetFiles(_ultimaCartellaRaw).Where(f => Path.GetFileName(f).Contains(".asm.")));
|
||||
var codiciVisti = new HashSet<string>(daScansionare.Select(f => Path.GetFileName(f).Split('.')[0]));
|
||||
|
||||
var trovati = new List<(string Codice, string Tipo, string Percorso)>();
|
||||
int codiciNonTrovati = 0;
|
||||
|
||||
using (var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly"))
|
||||
{
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione "
|
||||
+ "WHERE codice = @codice AND tipo_file IN ('prt', 'asm')";
|
||||
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
|
||||
|
||||
while (daScansionare.Count > 0)
|
||||
{
|
||||
var asm = daScansionare.Dequeue();
|
||||
foreach (var codice in AssemblyComponentScanner.EstraiCodiciReferenziati(asm))
|
||||
{
|
||||
if (!codiciVisti.Add(codice))
|
||||
continue;
|
||||
|
||||
pCodice.Value = codice;
|
||||
var componenti = new List<(string Tipo, string Percorso)>();
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
componenti.Add((reader.GetString(0), reader.GetString(1)));
|
||||
}
|
||||
}
|
||||
|
||||
if (componenti.Count == 0)
|
||||
{
|
||||
codiciNonTrovati++;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (tipo, percorso) in componenti)
|
||||
{
|
||||
CopiaFileTrovati(new[] { percorso }, _ultimaCartellaRaw);
|
||||
trovati.Add((codice, tipo, percorso));
|
||||
if (tipo == "asm")
|
||||
{
|
||||
daScansionare.Enqueue(Path.Combine(_ultimaCartellaRaw, Path.GetFileName(percorso)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// L'assieme generale è già in raw come qualsiasi altro assieme importato (vedi
|
||||
// CreaCartellaRaw), quindi rientra qui senza bisogno di un caso speciale.
|
||||
var asmIniziali = Directory.GetFiles(_ultimaCartellaRaw).Where(f => Path.GetFileName(f).Contains(".asm."));
|
||||
var (trovati, codiciNonTrovati) = RisolviAlberoComponenti(asmIniziali);
|
||||
|
||||
int numPrt = trovati.Count(r => r.Tipo == "prt");
|
||||
int numAsm = trovati.Count(r => r.Tipo == "asm");
|
||||
ImpostaStatoImport(
|
||||
$"Componenti referenziati: {numPrt} PRT e {numAsm} sotto-assiemi trovati e copiati in raw, "
|
||||
+ $"{codiciNonTrovati} codici referenziati non trovati nell'indice.",
|
||||
+ $"{codiciNonTrovati.Count} codici referenziati non trovati nell'indice.",
|
||||
Color.DarkGreen);
|
||||
|
||||
using var dialog = new PrtMancantiForm(trovati, codiciNonTrovati, _ultimaCartellaRaw);
|
||||
dialog.ShowDialog(this);
|
||||
}
|
||||
|
||||
/// <summary>Per ogni assieme della distinta, verifica ricorsivamente (leggendo i .asm dai
|
||||
/// percorsi originali sulla share) se tutti i componenti fino ai PRT sono nell'indice.
|
||||
/// I risultati di ogni file/codice vengono in cache e condivisi tra gli assiemi controllati
|
||||
/// nella stessa passata, per non rileggere più volte gli stessi sotto-componenti.</summary>
|
||||
private void VerificaCompletezzaAssiemi()
|
||||
{
|
||||
if (_righeCorrenti is null)
|
||||
return;
|
||||
if (_dbPath is null)
|
||||
{
|
||||
ImpostaStatoImport("Nessun database caricato: impossibile verificare la completezza.", Color.DarkRed);
|
||||
return;
|
||||
}
|
||||
|
||||
using var connection = new SqliteConnection($"Data Source={_dbPath};Mode=ReadOnly");
|
||||
connection.Open();
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione "
|
||||
+ "WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ('prt', 'asm') "
|
||||
+ "ORDER BY tipo_file, data_modifica DESC";
|
||||
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
|
||||
|
||||
var cacheComponenti = new Dictionary<string, List<(string Tipo, string Percorso)>>();
|
||||
var cacheCodiciFile = new Dictionary<string, HashSet<string>>();
|
||||
|
||||
List<(string Tipo, string Percorso)> CercaComponenti(string codice)
|
||||
{
|
||||
if (cacheComponenti.TryGetValue(codice, out var giaTrovati))
|
||||
return giaTrovati;
|
||||
|
||||
pCodice.Value = codice;
|
||||
var risultato = new List<(string Tipo, string Percorso)>();
|
||||
var tipiVisti = new HashSet<string>();
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
string tipo = reader.GetString(0).ToLowerInvariant();
|
||||
if (tipiVisti.Add(tipo))
|
||||
{
|
||||
risultato.Add((tipo, reader.GetString(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
cacheComponenti[codice] = risultato;
|
||||
return risultato;
|
||||
}
|
||||
|
||||
bool AssiemeCompleto(string percorsoAsmIniziale)
|
||||
{
|
||||
var daVisitare = new Queue<string>();
|
||||
daVisitare.Enqueue(percorsoAsmIniziale);
|
||||
var fileVisitati = new HashSet<string> { percorsoAsmIniziale };
|
||||
var codiciVisti = new HashSet<string> { Path.GetFileName(percorsoAsmIniziale).Split('.')[0] };
|
||||
|
||||
while (daVisitare.Count > 0)
|
||||
{
|
||||
var file = daVisitare.Dequeue();
|
||||
if (!cacheCodiciFile.TryGetValue(file, out var codiciReferenziati))
|
||||
{
|
||||
try
|
||||
{
|
||||
codiciReferenziati = AssemblyComponentScanner.EstraiCodiciReferenziati(file);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
return false; // file irraggiungibile (share offline) -> non verificabile, per prudenza "incompleto"
|
||||
}
|
||||
cacheCodiciFile[file] = codiciReferenziati;
|
||||
}
|
||||
|
||||
foreach (var codice in codiciReferenziati)
|
||||
{
|
||||
if (!codiciVisti.Add(codice))
|
||||
continue;
|
||||
|
||||
var componenti = CercaComponenti(codice);
|
||||
if (componenti.Count == 0)
|
||||
return false;
|
||||
|
||||
foreach (var (tipo, percorso) in componenti)
|
||||
{
|
||||
if (tipo == "asm" && fileVisitati.Add(percorso))
|
||||
{
|
||||
daVisitare.Enqueue(percorso);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// L'assieme generale è già una riga di _righeCorrenti (vedi DistintaBaseImporter), quindi
|
||||
// la sua completezza viene verificata insieme a quella di tutti gli altri componenti.
|
||||
var righeReport = new List<(string Componente, string Descrizione, bool? Completo)>();
|
||||
foreach (var (componente, descrizione) in _righeCorrenti)
|
||||
{
|
||||
var componenti = CercaComponenti(componente);
|
||||
string? percorsoAsm = componenti.Where(c => c.Tipo == "asm").Select(c => c.Percorso).FirstOrDefault();
|
||||
|
||||
bool? completo = componenti.Count == 0 ? null
|
||||
: percorsoAsm is null ? true // solo prt trovato: nessuna sotto-struttura da verificare
|
||||
: AssiemeCompleto(percorsoAsm);
|
||||
|
||||
righeReport.Add((componente, descrizione, completo));
|
||||
}
|
||||
|
||||
int completi = righeReport.Count(r => r.Completo == true);
|
||||
int incompleti = righeReport.Count(r => r.Completo == false);
|
||||
int nonTrovati = righeReport.Count(r => r.Completo is null);
|
||||
ImpostaStatoImport(
|
||||
$"Completezza assiemi: {completi} completi, {incompleti} incompleti, "
|
||||
+ $"{nonTrovati} non trovati nell'indice.",
|
||||
Color.DarkGreen);
|
||||
|
||||
using var dialogCompletezza = new ReportCompletezzaForm(righeReport);
|
||||
dialogCompletezza.ShowDialog(this);
|
||||
}
|
||||
|
||||
/// <summary>Copia i file trovati nella cartella "raw" indicata, creandola se necessario.
|
||||
/// Ritorna il numero di file copiati con successo.</summary>
|
||||
internal static int CopiaFileTrovati(IEnumerable<string> percorsi, string cartellaRaw)
|
||||
@@ -490,9 +857,9 @@ public partial class Form1 : Form
|
||||
var nomiParametri = TipiPredefiniti.Select((_, i) => $"@tipo{i}");
|
||||
using var command = connection.CreateCommand();
|
||||
command.CommandText = $"""
|
||||
SELECT percorso_completo FROM files_ultima_versione
|
||||
WHERE codice = @codice AND tipo_file IN ({string.Join(",", nomiParametri)})
|
||||
ORDER BY tipo_file
|
||||
SELECT tipo_file, percorso_completo FROM files_ultima_versione
|
||||
WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ({string.Join(",", nomiParametri)})
|
||||
ORDER BY tipo_file, data_modifica DESC
|
||||
""";
|
||||
var pCodice = command.Parameters.Add("@codice", Microsoft.Data.Sqlite.SqliteType.Text);
|
||||
for (int i = 0; i < TipiPredefiniti.Length; i++)
|
||||
@@ -505,9 +872,15 @@ public partial class Form1 : Form
|
||||
pCodice.Value = codice;
|
||||
using var reader = command.ExecuteReader();
|
||||
var percorsi = new List<string>();
|
||||
// Lo stesso codice+tipo può esistere in più cartelle radice (duplicati sulla share):
|
||||
// essendo ordinato per data_modifica DESC, il primo per tipo è il più recente, gli altri si scartano.
|
||||
var tipiVisti = new HashSet<string>();
|
||||
while (reader.Read())
|
||||
{
|
||||
percorsi.Add(reader.GetString(0));
|
||||
if (tipiVisti.Add(reader.GetString(0).ToLowerInvariant()))
|
||||
{
|
||||
percorsi.Add(reader.GetString(1));
|
||||
}
|
||||
}
|
||||
if (percorsi.Count > 0)
|
||||
risultato[codice] = percorsi;
|
||||
@@ -533,6 +906,8 @@ public partial class Form1 : Form
|
||||
{
|
||||
_lblStatus.Text = "Database non trovato. Usa 'Apri DB...' per selezionarlo oppure 'Aggiorna indice' per crearlo.";
|
||||
_lblDb.Text = "Nessun database caricato. Premi 'Aggiorna indice' per crearlo da X:\\, oppure 'Apri DB...' se ne hai gi\u00e0 uno.";
|
||||
_lblUltimoAggiornamento.Text = "";
|
||||
AggiornaStatoAvanti();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -560,9 +935,14 @@ public partial class Form1 : Form
|
||||
connection.Open();
|
||||
|
||||
long totale = (long)(new SqliteCommand("SELECT COUNT(*) FROM files", connection).ExecuteScalar() ?? 0L);
|
||||
object? ultimaScansioneRaw = new SqliteCommand("SELECT MAX(scansionato_il) FROM files", connection).ExecuteScalar();
|
||||
|
||||
_dbPath = path;
|
||||
_lblDb.Text = $"Database: {path} ({totale:N0} file indicizzati)";
|
||||
_lblUltimoAggiornamento.Text = ultimaScansioneRaw is string testoData
|
||||
&& DateTime.TryParse(testoData, out var ultimaScansione)
|
||||
? $"Ultimo aggiornamento indice: {ultimaScansione.ToLocalTime():dd/MM/yyyy HH:mm}"
|
||||
: "Ultimo aggiornamento indice: sconosciuto";
|
||||
_lblStatus.Text = "Pronto.";
|
||||
SalvaUltimoDbUsato(path);
|
||||
}
|
||||
@@ -571,6 +951,7 @@ public partial class Form1 : Form
|
||||
MessageBox.Show(this, $"Impossibile aprire il database:\n{ex.Message}", "Errore",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
AggiornaStatoAvanti();
|
||||
}
|
||||
|
||||
/// <summary>Ricorda l'ultimo DB caricato con successo, così al prossimo avvio si
|
||||
@@ -656,11 +1037,11 @@ public partial class Form1 : Form
|
||||
|
||||
private void ApriCartellaFileSelezionato()
|
||||
{
|
||||
if (_gridImport.CurrentRow is null)
|
||||
if (_gridRaw.CurrentRow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string? percorsi = _gridImport.CurrentRow.Cells[2].Value as string;
|
||||
string? percorsi = _gridRaw.CurrentRow.Cells[1].Value as string;
|
||||
if (string.IsNullOrEmpty(percorsi))
|
||||
{
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user