using qdr.app.studiou.orders2printpack.Extensions; using qdr.app.studiou.orders2printpack.Properties; using Quadarax.Foundation.Core.Data; using Quadarax.Foundation.Core.IO; using Quadarax.Foundation.Core.Value; using Quadarax.Foundation.Core.Value.Extensions; using System.Data; using System.IO.Abstractions; namespace qdr.app.studiou.orders2printpack { public partial class FormBatch : Form { #region *** Constants *** private const string CS_COL_SOURCE_PATH = "LocalPath"; private const string CS_COL_ID = "LocalID"; private const string CS_COL_OUTPUT = "OutputFileName"; private const string CS_COL_EXTERNALORDER = "ExternalOrder"; private const string CS_COL_EXTERNALORDERDATE = "ExternalOrderDate"; private const string CS_TAG_LB = "{"; private const string CS_TAG_RB = "}"; private const string CS_TAG_EXT = "ext"; private const string CS_TAG_FILE = "file"; private const string CS_TAG_ORDINAL = "ordinal"; #endregion #region *** Private Fields *** private DataTable _source = new(); private readonly Dictionary _sourceColOrdinals = []; private readonly List _sourcePathCache = []; private readonly IFileSystem _fs = new FileSystem(); private string _outputPath; #endregion #region *** Constructor *** public FormBatch() { InitializeComponent(); _outputPath = string.Empty; } #endregion #region *** Form Handlers *** private void FormMain_Load(object sender, EventArgs e) { Text = $"{Application.ProductName} v{Application.ProductVersion}"; RefreshToolButtons(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void butOpenOrdersFile_Click(object sender, EventArgs e) { if (dlgOpenFile.ShowDialog() == DialogResult.OK) OpenOrderBatchFile(dlgOpenFile.FileName); RefreshToolButtons(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void butOpenSource_Click(object sender, EventArgs e) { dlgOpenDir.Description = "Otevřít složku se zdrojovými (RAW) soubory..."; dlgOpenDir.UseDescriptionForTitle = true; dlgOpenDir.InitialDirectory = AppSettings.Default.LastSourceDir; if (dlgOpenDir.ShowDialog() == DialogResult.OK) { BlockErrorHandled(() => { if (!string.IsNullOrEmpty(_outputPath) && dlgOpenDir.SelectedPath == _outputPath) throw new Exception($"Vstupní složka '{dlgOpenDir.SelectedPath}' je stejná jako výstupní."); AppSettings.Default.LastSourceDir = dlgOpenDir.SelectedPath; AppSettings.Default.Save(); OpenSourceDir(dlgOpenDir.SelectedPath); }); } RefreshToolButtons(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void butOutputDir_Click(object sender, EventArgs e) { dlgOpenDir.Description = "Otevřít složku pro výstup..."; dlgOpenDir.UseDescriptionForTitle = true; dlgOpenDir.InitialDirectory = AppSettings.Default.LastOutputDir; if (dlgOpenDir.ShowDialog() == DialogResult.OK) { BlockErrorHandled(() => { if (!string.IsNullOrEmpty(AppSettings.Default.LastSourceDir) && dlgOpenDir.SelectedPath == AppSettings.Default.LastSourceDir) throw new Exception($"Výstupní složka '{dlgOpenDir.SelectedPath}' je stejná jako vstupní."); _outputPath = dlgOpenDir.SelectedPath; Log($"Otevřena složka výstupu '{_outputPath}'"); AppSettings.Default.LastOutputDir = dlgOpenDir.SelectedPath; AppSettings.Default.Save(); tbOutputDir.Text = _outputPath; }); } RefreshToolButtons(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void tsbCheck_Click(object sender, EventArgs e) { CheckSourceOrderFile(); RefreshToolButtons(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void tsbDo_Click(object sender, EventArgs e) { ProcessSourceFile(); RefreshToolButtons(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void tsbAddSourceDir_Click(object sender, EventArgs e) { dlgOpenDir.Description = "Otevřít další složku se zdrojovými (RAW) soubory..."; dlgOpenDir.UseDescriptionForTitle = true; dlgOpenDir.InitialDirectory = AppSettings.Default.LastSourceDir; if (dlgOpenDir.ShowDialog() == DialogResult.OK) { Log("Přidávám další zdrojovou složku..."); OpenSourceDir(dlgOpenDir.SelectedPath, true); } RefreshToolButtons(); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void tsbProtocol_Click(object sender, EventArgs e) { var dlg = new dlgProtocol(); if (dlg.ShowDialog(this) == DialogResult.OK) GenerateProtocol(dlg.ExternalOrder, dlg.ExternalOrderDate, dlg.ProtocolFile); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void lbNotMapped_MouseDoubleClick(object sender, MouseEventArgs e) { if (lbNotMapped.SelectedIndices.Count == 0) return; var id = (int?)lbNotMapped.SelectedValue; if (id == null) return; var row = _source.Rows[id.GetValueOrDefault()]; var fileName = _fs.Path.GetFileName(row.Field(_sourceColOrdinals[AppSettings.Default.MapColUri])); dlgOpenFile.Title = "Vyberte zdrojový soubor soubor pro záznam..."; dlgOpenFile.InitialDirectory = AppSettings.Default.LastSourceDir; dlgOpenFile.FileName = fileName; dlgOpenFile.Filter = "Všechny soubory|*.*"; if (dlgOpenFile.ShowDialog() == DialogResult.OK) { var sourcePath = dlgOpenFile.FileName; row.SetField(_sourceColOrdinals[CS_COL_SOURCE_PATH], sourcePath); RefreshToolButtons(); } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] private void tsbSettings_Click(object sender, EventArgs e) { var dlg = new dlgSettings(); dlg.ShowDialog(); } #endregion #region *** Private Methods *** #region **** Common **** private void Log(string message) { lbLog.Items.Insert(0, message); } private int GetOrderCount() { return _source.AsEnumerable().Select(x => x.Field(_sourceColOrdinals[AppSettings.Default.MapColOrderNo])).Distinct().Count(); } private int GetLinesCount() { return _source.AsEnumerable().Count(); } private string? GetSourceFullFileName(string? webUrl) { if (string.IsNullOrEmpty(webUrl)) return null; var fileName = Path.GetFileName(webUrl); var sourcePath = _sourcePathCache.FirstOrDefault(x => string.Equals(Path.GetFileName(x), fileName)); if (string.IsNullOrEmpty(sourcePath)) Log($"Soubor '{fileName}' nebyl nalezen ve zdrojové složce."); return sourcePath; } private static string NormalizeColumnName(string name) { return name.ToLower().Replace(" ", "_"); } private void ApplyRowToParametrizedString(DataRow row, ParameterizedString ps) { foreach (DataColumn col in row.Table.Columns) { var ord = _sourceColOrdinals[col.ColumnName]; ps.AddOrSetParameter(NormalizeColumnName(col.ColumnName), row[ord]?.ToString()!); } } private void BlockErrorHandled(Action block) { try { block(); } catch (Exception ex) { Log("Error: " + ex.Message); MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); RefreshToolButtons(); } } #endregion #region **** Bussiness **** private void OpenOrderBatchFile(string fileName) { BlockErrorHandled(() => { _source = CsvHelper.CsvToDataTable(_fs, fileName, AppSettings.Default.CSVDelimiter); _source.Columns.Add(new DataColumn(CS_COL_SOURCE_PATH, typeof(string))); _source.Columns.Add(new DataColumn(CS_COL_ID, typeof(int))); _source.Columns.Add(new DataColumn(CS_COL_OUTPUT, typeof(string))); _source.Columns.Add(new DataColumn(CS_COL_EXTERNALORDER, typeof(string))); _source.Columns.Add(new DataColumn(CS_COL_EXTERNALORDERDATE, typeof(string))); _sourceColOrdinals.Clear(); for (int i = 0; i < _source.Columns.Count; i++) _sourceColOrdinals.Add(_source.Columns[i].ColumnName.Replace("\"", ""), i); var invalidRows = new List(); foreach (DataRow row in _source.Rows) { if (string.Equals(row[_sourceColOrdinals[AppSettings.Default.MapColUri]]?.ToString(), "null", StringComparison.CurrentCultureIgnoreCase)) invalidRows.Add(row); } foreach (var row in invalidRows) _source.Rows.Remove(row); Log($"{invalidRows.Count} položek ignorováno, nebylo vyplněno URL"); var cntId = 0; foreach (DataRow row in _source.Rows) row.SetField(_sourceColOrdinals[CS_COL_ID], cntId++); var cntOrders = GetOrderCount(); var cntLines = GetLinesCount(); Log($"Soubor {fileName} načten. Počet objednávek: {cntOrders}, počet záznamů: {cntLines}"); tbOrdersSourceFile.Text = fileName; tspOrdersCount.Text = $"Objednávek: {cntOrders}"; }); } private void OpenSourceDir(string pathToSourceFolder, bool appendCache = false) { BlockErrorHandled(() => { var fsrch = new FileSearch(pathToSourceFolder, true); if (!appendCache) _sourcePathCache.Clear(); tbSourceDir.Text = pathToSourceFolder; Log($"Procházím všechny soubory {AppSettings.Default.SourceSearchPattern} ve složce '{pathToSourceFolder}'..."); Log("Tato operace může chvíli trvat!"); var files = fsrch.Search(new[] { AppSettings.Default.SourceSearchPattern, "*" }); if (files.Length == 0) throw new Exception($"Nenalezeny žádné soubory pro zpracování ve složce '{pathToSourceFolder}'."); foreach (var file in files) if (!_sourcePathCache.Any(x => x == file)) _sourcePathCache.Add(file); Log ("Kontrola duplicit..."); var checkDuplicities = _sourcePathCache.Select(x => _fs.Path.GetFileName(x)).GetDuplicates(); if (checkDuplicities.Count() > 0) { Log ("Duplicity nalezeny pro následující soubory:"); foreach (var dup in checkDuplicities) { Log(dup); } } else { Log("Žádné duplicity nenalezeny."); } tsbFiles.Text = "Souborů: " + _sourcePathCache.Count.ToString(); Log($"Nalezeno {files.Length} souborů."); }); } private void CheckSourceOrderFile() { ssProgress.Value = 0; int cntFailed = 0; BlockErrorHandled(() => { var rows = _source.AsEnumerable().Where(x => string.IsNullOrEmpty(x.Field(_sourceColOrdinals[CS_COL_SOURCE_PATH]))); ssProgress.Maximum = rows.Count(); foreach (var row in rows) { var orderNo = row.Field(_sourceColOrdinals[AppSettings.Default.MapColOrderNo]); var sourceUrl = row[_sourceColOrdinals[AppSettings.Default.MapColUri]]?.ToString(); var sourcePath = GetSourceFullFileName(sourceUrl); if (sourcePath == null) cntFailed++; row.SetField(_sourceColOrdinals[CS_COL_SOURCE_PATH], sourcePath); ssProgress.Increment(1); } }); tsbNotFound.Text = $"Nenalezeno: {cntFailed}"; ssProgress.Value = 0; } private void ProcessSourceFile() { ssProgress.Value = 0; BlockErrorHandled(() => { var rows = _source.AsEnumerable().Where(x => string.IsNullOrEmpty(x.Field(_sourceColOrdinals[CS_COL_SOURCE_PATH]))); if (rows.Any()) throw new Exception("Některé řádky nebyly zkontrolovány. Proveďte kontrolu zdrojových souborů (1. Kontrola)."); rows = _source.AsEnumerable(); //var colNames = _source.Columns.Cast().Select(x => NormalizeColumnName(x.ColumnName)).ToArray(); if (AppSettings.Default.CleanupOutputDir) { Log($"Čistím složku výstupu '{_outputPath}' ..."); CleanupDirectory(_outputPath); } ssProgress.Maximum = rows.Count(); var outputFileNameBuilder = new ParameterizedString(AppSettings.Default.OutputFileMask, CS_TAG_LB, CS_TAG_RB); foreach (var row in rows) { var orderNo = row.Field(_sourceColOrdinals[AppSettings.Default.MapColOrderNo]); var sourceUrl = row[_sourceColOrdinals[AppSettings.Default.MapColUri]]?.ToString(); var qty = int.Parse(row[_sourceColOrdinals[AppSettings.Default.MapColQuantity]]?.ToString()!); var sourcePath = row[_sourceColOrdinals[CS_COL_SOURCE_PATH]]?.ToString(); var sourceExt = _fs.Path.GetExtension(sourcePath); var sourceFileName = _fs.Path.GetFileNameWithoutExtension(sourcePath); ApplyRowToParametrizedString(row, outputFileNameBuilder); outputFileNameBuilder.AddOrSetParameter(CS_TAG_EXT, sourceExt!); outputFileNameBuilder.AddOrSetParameter(CS_TAG_FILE, sourceFileName!); for (int i = 0; i < qty; i++) { outputFileNameBuilder.AddOrSetParameter(CS_TAG_ORDINAL, i.ToString().PadLeft(3, '0')); var outputFileName = _fs.Path.GetFileName(outputFileNameBuilder.ToString()); var outputFilePath = _fs.Path.GetDirectoryName(outputFileNameBuilder.ToString())!; var outputFileNameSanitized = FileUtils.SanitizedFileName(_fs, outputFileName); var outputPath = _fs.Path.Combine(_outputPath, _fs.Path.Combine(outputFilePath, outputFileNameSanitized)); _fs.Directory.CreateDirectory(_fs.Path.GetDirectoryName(outputPath)!); _fs.File.Copy(sourcePath!, outputPath, true); Log($"Soubor '{outputPath}' vytvořen."); row.SetField(_sourceColOrdinals[CS_COL_OUTPUT], outputPath); } ssProgress.Increment(1); } }); ssProgress.Value = 0; } private void GenerateProtocol(string externalOrder, DateTime externalOrderDate, string protocolFile) { BlockErrorHandled(() => { Log($"Nastavuji číslo externí objednávky: {externalOrder} a datum: {externalOrderDate.ToShortDateString()} ..."); foreach (var row in _source.AsEnumerable()) { row.SetField(_sourceColOrdinals[CS_COL_EXTERNALORDER], externalOrder); row.SetField(_sourceColOrdinals[CS_COL_EXTERNALORDERDATE], externalOrderDate.ToFileUtcString()); } Log($"Generuji protokol ..."); CsvUtils.DataTableToCsv(_fs, _source, protocolFile, AppSettings.Default.CSVDelimiter, (column) => { return NormalizeColumnName(column); }); Log($"Protokol uložen do souboru '{protocolFile}'."); }); } #endregion #region **** Refreshes **** private void RefreshToolButtons() { var canBeChecked = GetOrderCount() > 0 && _sourcePathCache.Count > 0; var canBeProcess = canBeChecked && !_source.AsEnumerable().Any(x => string.IsNullOrEmpty(x.Field(_sourceColOrdinals[CS_COL_SOURCE_PATH]))) && !string.IsNullOrEmpty(_outputPath); var canBeCreateProtocol = canBeProcess && _source.AsEnumerable().All(x => !string.IsNullOrEmpty(x.Field(_sourceColOrdinals[CS_COL_OUTPUT]))); tsbCheck.Enabled = canBeChecked; tsbDo.Enabled = canBeProcess; tsbProtocol.Enabled = canBeCreateProtocol; RefreshNotMappedList(); } private void RefreshNotMappedList() { var rows = _source.AsEnumerable().Where(x => string.IsNullOrEmpty(x.Field(_sourceColOrdinals[CS_COL_SOURCE_PATH]))); var list = new List>(); foreach (var row in rows) { var id = row.Field(_sourceColOrdinals[CS_COL_ID]); var fileName = _fs.Path.GetFileName(row.Field(_sourceColOrdinals[AppSettings.Default.MapColUri])); var productName = row.Field(_sourceColOrdinals[AppSettings.Default.MapColProductFormat]); var orderNo = row.Field(_sourceColOrdinals[AppSettings.Default.MapColOrderNo]); list.Add(new Tuple(id, $"{fileName} [Jméno: {productName} / Obj.:{orderNo}]")); } lbNotMapped.DisplayMember = "Item2"; lbNotMapped.ValueMember = "Item1"; lbNotMapped.DataSource = list; lbNotMapped.Update(); } #endregion #region **** Support opperations **** public static void CleanupDirectory(string directoryPath) { return; /* if (string.IsNullOrWhiteSpace(directoryPath)) throw new ArgumentException("Directory path cannot be null or empty.", nameof(directoryPath)); if (!Directory.Exists(directoryPath)) throw new DirectoryNotFoundException($"Directory not found: {directoryPath}"); try { var di = new DirectoryInfo(directoryPath); foreach (var file in di.GetFiles()) { try { file.Delete(); } catch { } } foreach (var dir in di.GetDirectories()) { try { dir.Delete(true); } catch { } } } catch (Exception) { throw; } */ } #endregion #endregion } }