using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Elfisa.Core; namespace Elfisa.Avalonia.ViewModels; public sealed class ReportColumn { public string Header { get; init; } = ""; public bool Numeric { get; init; } public double Weight { get; init; } = 100; // ширина в пикселях (для нетянущихся) public bool Stretch { get; init; } // тянуть на всё свободное место (star) } /// Строка отчёта: ячейки как строки, доступ по индексу для DataGrid. public sealed class ReportRow { private readonly string[] _cells; public ReportRow(string[] cells) => _cells = cells; public string this[int i] => i >= 0 && i < _cells.Length ? _cells[i] : ""; } public partial class ReportsViewModel : ViewModelBase { private readonly ApiClient _api = new(); private List _lines = new(); /// Сигнал View пересобрать колонки DataGrid. public event Action? ColumnsChanged; public ObservableCollection Columns { get; } = new(); public ObservableCollection Rows { get; } = new(); [ObservableProperty] private DateTimeOffset _dateFrom = DateTimeOffset.Now.AddMonths(-1); [ObservableProperty] private DateTimeOffset _dateTo = DateTimeOffset.Now; public string[] ReportTypes { get; } = { "Мои заказы", "По поставщикам", "По товарам", "По аптекам" }; [ObservableProperty] private int _selectedReportIndex; public string[] AiFormats { get; } = { "Word", "Excel" }; [ObservableProperty] private int _selectedAiFormat; [ObservableProperty] private string _aiPrompt = ""; [ObservableProperty] private string _totals = ""; [ObservableProperty] private string? _status; [ObservableProperty] private bool _busy; [ObservableProperty] private bool _aiBusy; public ReportsViewModel() { _api.SetToken(Session.Current.Token); if (Session.Current.IsAuthenticated) _ = BuildAsync(); // авто-загрузка за последний месяц при открытии } partial void OnSelectedReportIndexChanged(int value) => Render(); [RelayCommand] private async Task BuildAsync() { Status = null; try { Busy = true; _lines = await _api.GetBuyerReportAsync(DateFrom.DateTime.Date, DateTo.DateTime.Date); Render(); if (_lines.Count == 0) Status = "За выбранный период заказов нет."; } catch (Exception ex) { Status = ex.Message; } finally { Busy = false; } } [RelayCommand] private async Task GenerateAiAsync() { Status = null; if (string.IsNullOrWhiteSpace(AiPrompt)) { Status = "Введите запрос для ИИ."; return; } try { AiBusy = true; var fmt = SelectedAiFormat == 1 ? "xlsx" : "docx"; var res = await _api.GenerateAiReportAsync(AiPrompt.Trim(), fmt); var dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); var safe = string.Join("_", res.FileName.Split(Path.GetInvalidFileNameChars())); var path = Path.Combine(dir, safe); if (File.Exists(path)) { var name = Path.GetFileNameWithoutExtension(safe); var ext = Path.GetExtension(safe); path = Path.Combine(dir, $"{name}_{DateTime.Now:yyyyMMdd_HHmmss}{ext}"); } File.WriteAllBytes(path, res.Content); Status = "Отчёт сохранён: " + path; OpenFile(path); } catch (Exception ex) { Status = ex.Message; } finally { AiBusy = false; } } private void Render() { Columns.Clear(); Rows.Clear(); var (cols, rows) = Aggregate(_lines, SelectedReportIndex); foreach (var c in cols) Columns.Add(c); foreach (var r in rows) Rows.Add(new ReportRow(r)); var orders = _lines.Select(l => l.OrderId).Distinct().Count(); var sum = _lines.Sum(l => l.Sum); Totals = $"Заказов: {orders} Позиций: {_lines.Count} Сумма: {sum:N2}"; ColumnsChanged?.Invoke(); } private static (List cols, List rows) Aggregate(List lines, int type) { string N2(decimal d) => d.ToString("N2"); string N3(decimal d) => d.ToString("N3"); string D(DateTime? dt) => dt?.ToLocalTime().ToString("dd.MM.yyyy") ?? ""; switch (type) { case 1: // По поставщикам { var cols = new List { new() { Header = "Поставщик", Stretch = true }, new() { Header = "Заказов", Numeric = true, Weight = 90 }, new() { Header = "Позиций", Numeric = true, Weight = 90 }, new() { Header = "Сумма", Numeric = true, Weight = 120 }, }; var rows = lines.GroupBy(l => string.IsNullOrWhiteSpace(l.Supplier) ? "(не указан)" : l.Supplier!) .OrderByDescending(g => g.Sum(x => x.Sum)) .Select(g => new[] { g.Key, g.Select(x => x.OrderId).Distinct().Count().ToString(), g.Count().ToString(), N2(g.Sum(x => x.Sum)) }).ToList(); return (cols, rows); } case 2: // По товарам { var cols = new List { new() { Header = "Товар", Stretch = true }, new() { Header = "Код", Weight = 100 }, new() { Header = "Кол-во", Numeric = true, Weight = 100 }, new() { Header = "Сумма", Numeric = true, Weight = 120 }, }; var rows = lines.GroupBy(l => ((l.ItemName ?? "").Trim() + "|" + (l.ItemCode ?? ""))) .OrderByDescending(g => g.Sum(x => x.Sum)) .Select(g => { var f = g.First(); return new[] { f.ItemName ?? "", f.ItemCode ?? "", N3(g.Sum(x => x.Qty)), N2(g.Sum(x => x.Sum)) }; }).ToList(); return (cols, rows); } case 3: // По аптекам { var cols = new List { new() { Header = "Аптека", Stretch = true }, new() { Header = "Заказов", Numeric = true, Weight = 90 }, new() { Header = "Позиций", Numeric = true, Weight = 90 }, new() { Header = "Сумма", Numeric = true, Weight = 120 }, }; var rows = lines.GroupBy(l => string.IsNullOrWhiteSpace(l.Location) ? "(не указана)" : l.Location!) .OrderByDescending(g => g.Sum(x => x.Sum)) .Select(g => new[] { g.Key, g.Select(x => x.OrderId).Distinct().Count().ToString(), g.Count().ToString(), N2(g.Sum(x => x.Sum)) }).ToList(); return (cols, rows); } default: // Мои заказы { var cols = new List { new() { Header = "Дата", Weight = 105 }, new() { Header = "Номер", Weight = 138 }, new() { Header = "Поставщик", Weight = 150 }, new() { Header = "Аптека", Stretch = true }, new() { Header = "Позиций", Numeric = true, Weight = 95 }, new() { Header = "Сумма", Numeric = true, Weight = 125 }, new() { Header = "Статус", Weight = 100 }, }; var rows = lines.GroupBy(l => l.OrderId) .OrderByDescending(g => g.First().OrderDate ?? DateTime.MinValue) .Select(g => { var f = g.First(); var sup = string.Join(", ", g.Select(x => x.Supplier).Where(s => !string.IsNullOrWhiteSpace(s)).Distinct()); return new[] { D(f.OrderDate), f.GlobalSign ?? "", sup, f.Location ?? "", g.Count().ToString(), N2(g.Sum(x => x.Sum)), f.Status ?? "" }; }).ToList(); return (cols, rows); } } } private static void OpenFile(string path) { try { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true }); else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) Process.Start("open", $"\"{path}\""); else Process.Start("xdg-open", $"\"{path}\""); } catch { /* открыть не удалось — путь показан в статусе */ } } }