Files
PIbd-32_BuslaevRoman_KOP/WinFormsComponentOrientedHost/UI/FormExtensions.cs
2025-10-21 13:17:55 +04:00

392 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ComponentOrientedPlatform.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.Reflection;
using WinFormsComponentOrientedHost.Data;
using WinFormsComponentOrientedHost.Data.Entities;
public sealed class FormExtensions : Form
{
public static IConfiguration Configuration { get; private set; } = default!;
private readonly IHostServices _host;
private readonly ComboBox _cbText = new() { DropDownStyle = ComboBoxStyle.DropDownList, Dock = DockStyle.Fill };
private readonly Button _btnText = new() { Text = "Excel (большой текст: в наличии)", Dock = DockStyle.Fill };
private readonly ComboBox _cbLine = new() { DropDownStyle = ComboBoxStyle.DropDownList, Dock = DockStyle.Fill };
private readonly Button _btnLine = new() { Text = "Excel (диаграмма: нет в наличии)", Dock = DockStyle.Fill };
private readonly ComboBox _cbRowCol = new() { DropDownStyle = ComboBoxStyle.DropDownList, Dock = DockStyle.Fill };
private readonly Button _btnRowCol = new() { Text = "Excel (таблица: 1-я строка + 1-й столбец)", Dock = DockStyle.Fill };
private readonly ComboBox _cbPiePdf = new() { DropDownStyle = ComboBoxStyle.DropDownList, Dock = DockStyle.Fill };
private readonly Button _btnPiePdf = new() { Text = "PDF (круговая: нет в наличии)", Dock = DockStyle.Fill };
private readonly List<IReportDocumentWithContextTextsContract> _textPlugins = new();
private readonly List<IReportDocumentWithChartLineContract> _linePlugins = new();
private readonly List<IReportDocumentWithTableColumnRowHeaderContract> _rowColPlugins = new();
private readonly List<IReportDocumentWithChartPieContract> _piePdfPlugins = new();
public FormExtensions(IHostServices host)
{
_host = host;
Text = "Отчёты → Excel/PDF";
Width = 860;
Height = 280;
StartPosition = FormStartPosition.CenterParent;
var layout = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 4,
AutoSize = true,
Padding = new Padding(12),
};
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 60));
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 40));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
layout.Controls.Add(_cbText, 0, 0);
layout.Controls.Add(_btnText, 1, 0);
layout.Controls.Add(_cbLine, 0, 1);
layout.Controls.Add(_btnLine, 1, 1);
layout.Controls.Add(_cbRowCol, 0, 2);
layout.Controls.Add(_btnRowCol, 1, 2);
layout.Controls.Add(_cbPiePdf, 0, 3);
layout.Controls.Add(_btnPiePdf, 1, 3);
Controls.Add(layout);
Load += (_, __) => InitializeAndLoadPlugins();
_btnText.Click += async (_, __) => await GenerateExcelBigTextReport();
_btnLine.Click += async (_, __) => await GenerateExcelOutOfStockLine();
_btnRowCol.Click += async (_, __) => await GenerateExcelRowColHeaderTable();
_btnPiePdf.Click += async (_, __) => await GeneratePdfOutOfStockPie();
}
private void InitializeAndLoadPlugins()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.Build();
LoadPlugins();
}
private void LoadPlugins()
{
_cbText.Items.Clear();
_cbLine.Items.Clear();
_cbRowCol.Items.Clear();
_cbPiePdf.Items.Clear();
_textPlugins.Clear();
_linePlugins.Clear();
_rowColPlugins.Clear();
_piePdfPlugins.Clear();
var path = Path.Combine(AppContext.BaseDirectory, "Plugins");
if (!Directory.Exists(path))
{
MessageBox.Show(this, $"Папка с плагинами не найдена:\n{path}", "Расширения",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
foreach (var file in Directory.GetFiles(path, "*.dll"))
{
try
{
var asm = Assembly.LoadFrom(file);
foreach (var type in asm.GetTypes())
{
if (type.IsAbstract || type.IsInterface) continue;
if (typeof(IReportDocumentWithContextTextsContract).IsAssignableFrom(type) &&
Activator.CreateInstance(type) is IReportDocumentWithContextTextsContract t1)
{
_textPlugins.Add(t1);
_cbText.Items.Add($"{type.Name} ({t1.DocumentFormat})");
}
if (typeof(IReportDocumentWithChartLineContract).IsAssignableFrom(type) &&
Activator.CreateInstance(type) is IReportDocumentWithChartLineContract t2)
{
_linePlugins.Add(t2);
_cbLine.Items.Add($"{type.Name} ({t2.DocumentFormat})");
}
if (typeof(IReportDocumentWithTableColumnRowHeaderContract).IsAssignableFrom(type) &&
Activator.CreateInstance(type) is IReportDocumentWithTableColumnRowHeaderContract t3)
{
_rowColPlugins.Add(t3);
_cbRowCol.Items.Add($"{type.Name} ({t3.DocumentFormat})");
}
if (typeof(IReportDocumentWithChartPieContract).IsAssignableFrom(type) &&
Activator.CreateInstance(type) is IReportDocumentWithChartPieContract t4)
{
_piePdfPlugins.Add(t4);
_cbPiePdf.Items.Add($"{type.Name} ({t4.DocumentFormat})");
}
}
}
catch { }
}
if (_cbText.Items.Count > 0) _cbText.SelectedIndex = 0;
if (_cbLine.Items.Count > 0) _cbLine.SelectedIndex = 0;
if (_cbRowCol.Items.Count > 0) _cbRowCol.SelectedIndex = 0;
if (_cbPiePdf.Items.Count > 0) _cbPiePdf.SelectedIndex = 0;
}
private async Task GenerateExcelBigTextReport()
{
if (_cbText.SelectedIndex < 0) return;
var plugin = _textPlugins[_cbText.SelectedIndex];
try
{
var cs = Configuration["Database:ConnectionString"]
?? throw new InvalidOperationException("Не задана строка подключения (Database:ConnectionString).");
var dbFactory = new AppDbContextFactory(cs);
using var db = dbFactory.CreateDbContext();
var productsInStock = await db.Set<Product>()
.AsNoTracking()
.Where(p => (p.quantity ?? 0) > 0)
.OrderBy(p => p.categoryText)
.ThenBy(p => p.name)
.ToListAsync();
if (productsInStock.Count == 0)
{
MessageBox.Show(this, "Нет продуктов в наличии.", "Excel (большой текст)",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var paragraphs = productsInStock.Select(p =>
{
var cat = string.IsNullOrWhiteSpace(p.categoryText) ? "(без категории)" : p.categoryText.Trim();
var name = (p.name ?? "").Trim();
var desc = string.IsNullOrWhiteSpace(p.description) ? "(описание отсутствует)" : p.description!.Trim();
return $"[{cat}] {name} — {desc}";
}).ToList();
using var dlg = new SaveFileDialog
{
Filter = "Excel (*.xlsx)|*.xlsx",
FileName = родукты_в_наличии_текст.xlsx",
OverwritePrompt = true
};
if (dlg.ShowDialog(this) != DialogResult.OK) return;
await plugin.CreateDocumentAsync(
filePath: dlg.FileName,
header: "Продукты в наличии — сводный текст",
paragraphs: paragraphs);
MessageBox.Show(this, $"Excel создан. Строк: {paragraphs.Count}.",
"Готово", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(this, "Ошибка при генерации Excel:\n" + ex.Message,
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async Task GenerateExcelOutOfStockLine()
{
if (_cbLine.SelectedIndex < 0) return;
var plugin = _linePlugins[_cbLine.SelectedIndex];
try
{
var cs = Configuration["Database:ConnectionString"]
?? throw new InvalidOperationException("Не задана строка подключения (Database:ConnectionString).");
var dbFactory = new AppDbContextFactory(cs);
using var db = dbFactory.CreateDbContext();
var byCategory = await db.Set<Product>()
.AsNoTracking()
.GroupBy(p => string.IsNullOrWhiteSpace(p.categoryText) ? "(без категории)" : p.categoryText.Trim())
.Select(g => new
{
Category = g.Key,
OutOfStock = g.Count(p => (p.quantity ?? 0) <= 0)
})
.Where(x => x.OutOfStock > 0)
.OrderByDescending(x => x.OutOfStock)
.ToListAsync();
if (byCategory.Count == 0)
{
MessageBox.Show(this, "Все категории в наличии — нечего показывать.",
"Excel (вертикальные столбцы)", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var series = new Dictionary<string, List<(int Parameter, double Value)>>
{
["Нет в наличии"] = byCategory.Select((x, i) => (Parameter: i + 1, Value: (double)x.OutOfStock)).ToList()
};
using var dlg = new SaveFileDialog
{
Filter = "Excel (*.xlsx)|*.xlsx",
FileName = родукты_нет_в_наличии_поатегориям_столбцы.xlsx",
OverwritePrompt = true
};
if (dlg.ShowDialog(this) != DialogResult.OK) return;
var header = "Отсутствующие товары по категориям";
var chartTitle = "Количество отсутствующих товаров (по категориям)";
await plugin.CreateDocumentAsync(dlg.FileName, header, chartTitle, series);
MessageBox.Show(this, "Excel с диаграммой создан.",
"Готово", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(this, "Ошибка при генерации Excel-диаграммы:\n" + ex.Message,
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async Task GenerateExcelRowColHeaderTable()
{
if (_cbRowCol.SelectedIndex < 0) return;
var plugin = _rowColPlugins[_cbRowCol.SelectedIndex];
try
{
var cs = Configuration["Database:ConnectionString"]
?? throw new InvalidOperationException("Не задана строка подключения (Database:ConnectionString).");
var dbFactory = new AppDbContextFactory(cs);
using var db = dbFactory.CreateDbContext();
var items = await db.Set<Product>()
.AsNoTracking()
.OrderBy(p => p.name)
.ToListAsync();
if (items.Count == 0)
{
MessageBox.Show(this, "Нет данных для отчёта.", "Excel (таблица)",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var rowsHeights = new List<int> { 25, 25 };
var headers = new List<(string Header, string PropertyName, string FiledName)>
{
("Id", "id", "Id"),
("Название", "name", "Name"),
("Категория", "categoryText", "Category"),
("Количество", "quantity", "Quantity"),
};
using var dlg = new SaveFileDialog
{
Filter = "Excel (*.xlsx)|*.xlsx",
FileName = родукты_таблица_две_строки.xlsx",
OverwritePrompt = true
};
if (dlg.ShowDialog(this) != DialogResult.OK) return;
var columnsWidth = new List<int>();
bool isHeaderFirstRow = true;
await plugin.CreateDocumentAsync(
filePath: dlg.FileName,
header: "Продукты — шапка: первые два столбца",
columnsWidth: columnsWidth,
rowsHeights: rowsHeights,
isHeaderFirstRow: isHeaderFirstRow,
headers: headers,
data: items);
MessageBox.Show(this, "Excel-таблица создана.",
"Готово", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(this, "Ошибка при генерации Excel-таблицы:\n" + ex.Message,
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async Task GeneratePdfOutOfStockPie()
{
if (_cbPiePdf.SelectedIndex < 0) return;
var plugin = _piePdfPlugins[_cbPiePdf.SelectedIndex];
try
{
var cs = Configuration["Database:ConnectionString"]
?? throw new InvalidOperationException("Не задана строка подключения (Database:ConnectionString).");
var dbFactory = new AppDbContextFactory(cs);
using var db = dbFactory.CreateDbContext();
var byCategory = await db.Set<Product>()
.AsNoTracking()
.GroupBy(p => string.IsNullOrWhiteSpace(p.categoryText) ? "(без категории)" : p.categoryText.Trim())
.Select(g => new
{
Category = g.Key,
OutOfStock = g.Count(p => (p.quantity ?? 0) <= 0)
})
.Where(x => x.OutOfStock > 0)
.OrderByDescending(x => x.OutOfStock)
.ToListAsync();
if (byCategory.Count == 0)
{
MessageBox.Show(this, "Все категории в наличии — круговую диаграмму строить не из чего.",
"PDF (круговая)", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var series = byCategory
.Select((x, i) => (Parameter: i + 1, Value: (double)x.OutOfStock))
.ToList();
using var dlg = new SaveFileDialog
{
Filter = "PDF (*.pdf)|*.pdf",
FileName = родукты_нет_в_наличии_поатегориям_круговая.pdf",
OverwritePrompt = true
};
if (dlg.ShowDialog(this) != DialogResult.OK) return;
var header = "Отсутствующие товары по категориям";
var chartTitle = "Круговая диаграмма: количество отсутствующих товаров";
await plugin.CreateDocumentAsync(dlg.FileName, header, chartTitle, series);
MessageBox.Show(this, "PDF с круговой диаграммой создан.",
"Готово", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(this, "Ошибка при генерации PDF-диаграммы:\n" + ex.Message,
"Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}