diff --git a/CompShop/CompShop/DocumentsBuilder/ChartReport.cs b/CompShop/CompShop/DocumentsBuilder/ChartReport.cs index 060f1cb..c1e34ac 100644 --- a/CompShop/CompShop/DocumentsBuilder/ChartReport.cs +++ b/CompShop/CompShop/DocumentsBuilder/ChartReport.cs @@ -1,18 +1,22 @@ using CompShop.DocumentsBuilder; using CompShop.Repos; +using Dapper; using Microsoft.Extensions.Logging; +using Npgsql; internal class ChartReport { - private readonly ICheckRepository _checkRepository; - private readonly IProductRepository _productRepository; + private readonly ICheckRepository _checkRepository; // Репозиторий для получения данных о чеках + private readonly IProductRepository _productRepository; // Репозиторий для получения данных о продуктах + private readonly IConnectionString _connectionString; private readonly ILogger _logger; - public ChartReport(ICheckRepository checkRepository, IProductRepository productRepository, ILoggerFactory loggerFactory) + public ChartReport(IConnectionString connectionString, ICheckRepository checkRepository, IProductRepository productRepository, ILoggerFactory loggerFactory) { _checkRepository = checkRepository ?? throw new ArgumentNullException(nameof(checkRepository)); _productRepository = productRepository ?? throw new ArgumentNullException(nameof(productRepository)); _logger = loggerFactory.CreateLogger(); + _connectionString = connectionString ?? throw new ArgumentNullException(nameof(checkRepository)); } public bool CreateChart(string filePath, DateTime dateTime) @@ -21,7 +25,7 @@ internal class ChartReport { new PdfBuilder(filePath) .AddHeader("Отчет по продажам товаров") - .AddPieChart("Проданные товары", GetData(dateTime)) + .AddPieChart("Проданные товары за" + dateTime.ToLongDateString(), GetData(dateTime)) // Диаграмма с продуктами .Build(); return true; @@ -35,21 +39,17 @@ internal class ChartReport private List<(string Caption, double Value)> GetData(DateTime dateTime) { - - var checkData = _checkRepository - .ReadAll() - .Where(x => x.PurchaseDate.Date == dateTime.Date) - .SelectMany(x => x.Products) - .GroupBy(p => p.ProductID) + // Читаем чеки и их продукты за указанную дату с помощью оптимизированного метода + var checkData = _checkRepository.ReadAll(startDate: dateTime.Date, endDate: dateTime.Date.AddDays(1)) + .SelectMany(x => x.Products) + .GroupBy(p => p.ProductID) .Select(g => { - - var product = _productRepository.Read(g.Key); - return (Caption: product.Name, Value: (double)g.Sum(p => p.Count)); + var productName = _productRepository.Read(g.Key).Name; + return (Caption: productName, Value: (double)g.Sum(p => p.Count)); }) .ToList(); return checkData; } - } diff --git a/CompShop/CompShop/DocumentsBuilder/ExcelBuilder.cs b/CompShop/CompShop/DocumentsBuilder/ExcelBuilder.cs index 3ebbae9..b8aac0f 100644 --- a/CompShop/CompShop/DocumentsBuilder/ExcelBuilder.cs +++ b/CompShop/CompShop/DocumentsBuilder/ExcelBuilder.cs @@ -84,10 +84,10 @@ namespace CompShop.DocumentsBuilder CustomWidth = true })); - + // Добавляем строки таблицы for (int i = 0; i < data.Count; ++i) { - var isBoldRow = i == 0 || i == data.Count - 1; + var isBoldRow = i == 0 || i == data.Count - 1; // Только заголовок и последняя строка жирные var styleIndex = isBoldRow ? StyleIndex.BoldTextWithBorders : StyleIndex.SimpleTextWithBorders; for (int j = 0; j < data[i].Length; ++j) @@ -143,6 +143,7 @@ namespace CompShop.DocumentsBuilder var workbookStylesPart = workbookPart.AddNewPart(); workbookStylesPart.Stylesheet = new Stylesheet(); + // Шрифты var fonts = new Fonts() { Count = 2 }; fonts.Append(new DocumentFormat.OpenXml.Spreadsheet.Font { @@ -156,16 +157,16 @@ namespace CompShop.DocumentsBuilder FontName = new FontName() { Val = "Calibri" } }); - + // Заполнение var fills = new Fills() { Count = 1 }; fills.Append(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.None } }); - + // Границы var borders = new Borders() { Count = 2 }; - borders.Append(new Border()); + borders.Append(new Border()); // Без границ borders.Append(new Border { LeftBorder = new LeftBorder { Style = BorderStyleValues.Thin }, @@ -174,7 +175,7 @@ namespace CompShop.DocumentsBuilder BottomBorder = new BottomBorder { Style = BorderStyleValues.Thin } }); - + // Форматы ячеек var cellFormats = new CellFormats() { Count = 4 }; cellFormats.Append(new CellFormat { @@ -182,14 +183,14 @@ namespace CompShop.DocumentsBuilder FillId = 0, BorderId = 0, ApplyFont = true - }); + }); // Обычный текст без границ cellFormats.Append(new CellFormat { FontId = 1, FillId = 0, BorderId = 0, ApplyFont = true - }); + }); // Жирный текст без границ cellFormats.Append(new CellFormat { FontId = 0, @@ -197,7 +198,7 @@ namespace CompShop.DocumentsBuilder BorderId = 1, ApplyFont = true, ApplyBorder = true - }); + }); // Обычный текст с границами cellFormats.Append(new CellFormat { FontId = 1, @@ -205,7 +206,7 @@ namespace CompShop.DocumentsBuilder BorderId = 1, ApplyFont = true, ApplyBorder = true - }); + }); // Жирный текст с границами workbookStylesPart.Stylesheet.Append(fonts); workbookStylesPart.Stylesheet.Append(fills); diff --git a/CompShop/CompShop/DocumentsBuilder/PdfBuilder.cs b/CompShop/CompShop/DocumentsBuilder/PdfBuilder.cs index 73f21e2..edb7136 100644 --- a/CompShop/CompShop/DocumentsBuilder/PdfBuilder.cs +++ b/CompShop/CompShop/DocumentsBuilder/PdfBuilder.cs @@ -1,6 +1,10 @@ using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Shapes.Charts; using MigraDoc.Rendering; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; using System.Threading.Tasks; using Chart = MigraDoc.DocumentObjectModel.Shapes.Charts.Chart; diff --git a/CompShop/CompShop/DocumentsBuilder/QueryBuilder.cs b/CompShop/CompShop/DocumentsBuilder/QueryBuilder.cs new file mode 100644 index 0000000..f233fbd --- /dev/null +++ b/CompShop/CompShop/DocumentsBuilder/QueryBuilder.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CompShop.DocumentsBuilder +{ + public class QueryBuilder + { + private readonly StringBuilder _builder; + + public QueryBuilder() + { + _builder = new(); + } + + public QueryBuilder AddCondition(string condition) + { + if (_builder.Length > 0) + { + _builder.Append(" AND "); + } + + _builder.Append(condition); + + return this; + } + + public string Build() + { + if (_builder.Length == 0) + { + return string.Empty; + } + + return $"WHERE {_builder}"; + } + } +} diff --git a/CompShop/CompShop/DocumentsBuilder/TableReport.cs b/CompShop/CompShop/DocumentsBuilder/TableReport.cs index 03b476b..2158ea4 100644 --- a/CompShop/CompShop/DocumentsBuilder/TableReport.cs +++ b/CompShop/CompShop/DocumentsBuilder/TableReport.cs @@ -1,19 +1,22 @@ using CompShop.DocumentsBuilder; +using CompShop.Entites; using CompShop.Repos; using Microsoft.Extensions.Logging; internal class TableReport { private readonly ICheckRepository _checkRepository; + private readonly IClientRepository _clientRepository; private readonly IProductRepository _productRepository; private readonly ILogger _logger; internal static readonly string[] Headers = { "Клиент", "Дата", "Количество" }; - public TableReport(ICheckRepository checkRepository, IProductRepository productRepository, ILoggerFactory loggerFactory) + public TableReport(IClientRepository clientRepository, ICheckRepository checkRepository, IProductRepository productRepository, ILoggerFactory loggerFactory) { _checkRepository = checkRepository ?? throw new ArgumentNullException(nameof(checkRepository)); _productRepository = productRepository ?? throw new ArgumentNullException(nameof(productRepository)); + _clientRepository = clientRepository ?? throw new ArgumentNullException(nameof(clientRepository)); _logger = loggerFactory.CreateLogger(); } @@ -22,7 +25,7 @@ internal class TableReport try { new ExcelBuilder(filePath) - .AddHeader("Отчет по продажам товара", 0, 3) + .AddHeader("Отчет по продажам товара " + _productRepository.Read(productId).Name, 0, 3) .AddParagraph($"за период с {startDate.ToShortDateString()} по {endDate.ToShortDateString()}", 0) .AddTable(new[] { 20, 20, 15 }, GetData(productId, startDate, endDate)) .Build(); @@ -38,40 +41,23 @@ internal class TableReport private List GetData(int productId, DateTime startDate, DateTime endDate) { - - var checksWithProduct = _checkRepository - .ReadAll() - .Where(c => c.PurchaseDate >= startDate && c.PurchaseDate <= endDate - && c.Products.Any(p => p.ProductID == productId)) - .Select(c => new - { - ClientName = c.Client?.ToString() ?? "Неизвестно", - Date = c.PurchaseDate, - Quantity = c.Products - .Where(p => p.ProductID == productId) - .Sum(p => p.Count) - }) - .OrderBy(x => x.Date); + // Получение данных о чеках через SQL-запрос + var checksWithProduct = _checkRepository.ReadAll(startDate, endDate, productId); - - return new List { Headers } - .Union( - checksWithProduct.Select(x => new string[] - { - x.ClientName, - x.Date.ToShortDateString(), - x.Quantity.ToString() - }) - ) - .Union(new[] - { - new string[] - { - "Всего", - "", - checksWithProduct.Sum(x => x.Quantity).ToString() - } - }) - .ToList(); + // Формирование итоговой таблицы + var headers = new List { Headers }; + var rows = checksWithProduct.Select(x => new string[] + { + _clientRepository.Read(x.ClientId).Name, + x.PurchaseDate.ToShortDateString(), + x.Products.Count.ToString() + }); + + var total = new[] + { + new string[] { "Всего", "", checksWithProduct.Sum(x => x.Products.Count).ToString() } + }; + + return headers.Union(rows).Union(total).ToList(); } } diff --git a/CompShop/CompShop/DocumentsBuilder/WordBuilder.cs b/CompShop/CompShop/DocumentsBuilder/WordBuilder.cs index df01cbe..ba919d3 100644 --- a/CompShop/CompShop/DocumentsBuilder/WordBuilder.cs +++ b/CompShop/CompShop/DocumentsBuilder/WordBuilder.cs @@ -35,12 +35,12 @@ namespace CompShop.DocumentsBuilder var paragraph = _body.AppendChild(new Paragraph()); var run = paragraph.AppendChild(new Run()); - + // Добавляем свойства для жирного текста var runProperties = new RunProperties(); runProperties.AppendChild(new Bold()); run.PrependChild(runProperties); - + // Добавляем текст заголовка run.AppendChild(new Text(header)); return this; @@ -109,7 +109,7 @@ namespace CompShop.DocumentsBuilder ) )); - + // Заголовок var tr = new TableRow(); for (var j = 0; j < widths.Length; ++j) { @@ -124,7 +124,7 @@ namespace CompShop.DocumentsBuilder } table.Append(tr); - + // Данные table.Append(data.Skip(1).Select(x => new TableRow(x.Select(y => new TableCell(new Paragraph(new Run(new Text(y)))))))); diff --git a/CompShop/CompShop/Entites/Check.cs b/CompShop/CompShop/Entites/Check.cs index 6802aca..10ae57f 100644 --- a/CompShop/CompShop/Entites/Check.cs +++ b/CompShop/CompShop/Entites/Check.cs @@ -1,15 +1,20 @@ using CompShop.Entites; +using System.ComponentModel; namespace CompShop.Entites { public class Check { + [System.ComponentModel.Browsable(false)] public int Id { get; set; } + [DisplayName("Товары")] public List Products { get; set; } + [DisplayName("Клиент")] public Client Client { get; set; } [System.ComponentModel.Browsable(false)] - public int ClientId { get; set; } + public int ClientId { get; set; } + [DisplayName("Дата покупки")] public DateTime PurchaseDate { get; set; } public static Check CreateEntity(int id, List products, Client client, DateTime purchaseDate) diff --git a/CompShop/CompShop/Entites/Client.cs b/CompShop/CompShop/Entites/Client.cs index 0bc9180..29734c5 100644 --- a/CompShop/CompShop/Entites/Client.cs +++ b/CompShop/CompShop/Entites/Client.cs @@ -1,6 +1,7 @@ using CompShop.Entites.Enums; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -10,8 +11,12 @@ namespace CompShop.Entites public class Client { public int Id { get; set; } + [DisplayName("Имя")] public string Name { get; set; } + [DisplayName("Номер телефона")] public string PhoneNumber { get; set; } + + [DisplayName("Вид клиента")] public ClientType ClientType { get; set; } public static Client CreateEntity(int id, string name, string phoneNumber, ClientType clientType) diff --git a/CompShop/CompShop/Entites/Product.cs b/CompShop/CompShop/Entites/Product.cs index d339bb4..d6d753e 100644 --- a/CompShop/CompShop/Entites/Product.cs +++ b/CompShop/CompShop/Entites/Product.cs @@ -1,13 +1,18 @@ using CompShop.Entites.Enums; +using System.ComponentModel; namespace CompShop.Entites { public class Product { public int ID { get; private set; } + [DisplayName("Имя")] public string Name { get; private set; } + [DisplayName("Описание")] public string Description { get; private set; } + [DisplayName("Цена")] public decimal Price { get; private set; } + [DisplayName("Вид")] public ProductType ProductType { get; private set; } public static Product CreateEntity(int id, string name, string desc, decimal price, ProductType productType) diff --git a/CompShop/CompShop/Entites/ProductInCheck.cs b/CompShop/CompShop/Entites/ProductInCheck.cs index d88b584..6a6f9e2 100644 --- a/CompShop/CompShop/Entites/ProductInCheck.cs +++ b/CompShop/CompShop/Entites/ProductInCheck.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,9 +9,13 @@ namespace CompShop.Entites { public class ProductInCheck { + [System.ComponentModel.Browsable(false)] public int ID { get; set; } + [Browsable(false)] public int ProductID { get; set; } + [Browsable(false)] public int CheckID { get; set; } + [DisplayName("Количество")] public int Count { get; set; } public static ProductInCheck CreateElement(int id, int count) { diff --git a/CompShop/CompShop/Entites/ProductsOnStorage.cs b/CompShop/CompShop/Entites/ProductsOnStorage.cs index e887310..d60730d 100644 --- a/CompShop/CompShop/Entites/ProductsOnStorage.cs +++ b/CompShop/CompShop/Entites/ProductsOnStorage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,13 +9,17 @@ namespace CompShop.Entites { public class ProductsOnStorage { + [System.ComponentModel.Browsable(false)] public int Id { get; set; } [System.ComponentModel.Browsable(false)] public int ProductId { get; set; } + [DisplayName("Товар")] public Product Product { get; set; } [System.ComponentModel.Browsable(false)] public int StorageId { get; set; } + [DisplayName("Склад")] public Storage Storage { get; set; } + [DisplayName("Количество")] public int Count { get; set; } public static ProductsOnStorage CreateEntity(int id, Product product, Storage storage, int count) diff --git a/CompShop/CompShop/Entites/Storage.cs b/CompShop/CompShop/Entites/Storage.cs index 5e937b4..e269290 100644 --- a/CompShop/CompShop/Entites/Storage.cs +++ b/CompShop/CompShop/Entites/Storage.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -9,8 +10,11 @@ namespace CompShop.Entites { public class Storage { + [System.ComponentModel.Browsable(false)] public int Id { get; set; } + [DisplayName("Вместимость")] public int Size { get; set; } // Вместимость + [DisplayName("Адрес")] public string Adress { get; set; } public static Storage CreateEntity(int id, int size, string adress) diff --git a/CompShop/CompShop/Forms/Clients/ClientForm.cs b/CompShop/CompShop/Forms/Clients/ClientForm.cs index a14ddb5..5ff8bd3 100644 --- a/CompShop/CompShop/Forms/Clients/ClientForm.cs +++ b/CompShop/CompShop/Forms/Clients/ClientForm.cs @@ -37,7 +37,12 @@ namespace CompShop.Forms MessageBox.Show(ex.Message, "Ошибка при загрузке", MessageBoxButtons.OK, MessageBoxIcon.Error); } } - private void LoadList() => productsDataGridView.DataSource = _repository.ReadAll(); + private void LoadList() + { + + productsDataGridView.DataSource = _repository.ReadAll(); + productsDataGridView.Columns["Id"].Visible = false; + } private bool TryGetIdentifierFromSelectedRow(out int id) { diff --git a/CompShop/CompShop/Forms/Products/ProductForm.cs b/CompShop/CompShop/Forms/Products/ProductForm.cs index f051627..b723fb2 100644 --- a/CompShop/CompShop/Forms/Products/ProductForm.cs +++ b/CompShop/CompShop/Forms/Products/ProductForm.cs @@ -36,7 +36,12 @@ namespace CompShop MessageBox.Show(ex.Message, "Ошибка при загрузке", MessageBoxButtons.OK, MessageBoxIcon.Error); } } - private void LoadList() => productsDataGridView.DataSource = _productRepository.ReadAll(); + private void LoadList() + { + + productsDataGridView.DataSource = _productRepository.ReadAll(); + productsDataGridView.Columns["ID"].Visible = false; + } private bool TryGetIdentifierFromSelectedRow(out int id) { diff --git a/CompShop/CompShop/Forms/Receipt/CheckForm.cs b/CompShop/CompShop/Forms/Receipt/CheckForm.cs index 85639e4..b117550 100644 --- a/CompShop/CompShop/Forms/Receipt/CheckForm.cs +++ b/CompShop/CompShop/Forms/Receipt/CheckForm.cs @@ -16,13 +16,15 @@ namespace CompShop.Forms.Receipt { private readonly IUnityContainer _container; private readonly ICheckRepository _checkRepository; + private readonly IClientRepository _clientRepository; - public CheckForm(IUnityContainer unityContainer, ICheckRepository checkRepository) + public CheckForm(IUnityContainer unityContainer, ICheckRepository checkRepository, IClientRepository clientRepository) { InitializeComponent(); _container = unityContainer ?? throw new ArgumentNullException(nameof(unityContainer)); _checkRepository = checkRepository ?? throw new ArgumentNullException(nameof(checkRepository)); + _clientRepository = clientRepository ?? throw new ArgumentNullException(nameof(clientRepository)); } private void CheckForm_Load(object sender, EventArgs e) @@ -37,7 +39,17 @@ namespace CompShop.Forms.Receipt } } - private void LoadList() => checksDataGridView.DataSource = _checkRepository.ReadAll(); + private void LoadList() + { + var checks = _checkRepository.ReadAll(); + + foreach (var check in checks) + { + check.Client = _clientRepository.Read(check.ClientId); + } + + checksDataGridView.DataSource = checks; + } private void addButton_Click(object sender, EventArgs e) { diff --git a/CompShop/CompShop/Repos/ICheckRepository.cs b/CompShop/CompShop/Repos/ICheckRepository.cs index 1572f60..78f74ab 100644 --- a/CompShop/CompShop/Repos/ICheckRepository.cs +++ b/CompShop/CompShop/Repos/ICheckRepository.cs @@ -4,7 +4,7 @@ namespace CompShop.Repos { public interface ICheckRepository { - IEnumerable ReadAll(); + IEnumerable ReadAll(DateTime? startDate = null, DateTime? endDate = null, int? productId = null, int? clientId = null); Check Read(int id); void Create(Check check); } diff --git a/CompShop/CompShop/Repos/Impements/CheckRepo.cs b/CompShop/CompShop/Repos/Impements/CheckRepo.cs index 207582f..964db5a 100644 --- a/CompShop/CompShop/Repos/Impements/CheckRepo.cs +++ b/CompShop/CompShop/Repos/Impements/CheckRepo.cs @@ -1,11 +1,15 @@ -using CompShop.Entites; +using CompShop.DocumentsBuilder; +using CompShop.Entites; using Dapper; +using DocumentFormat.OpenXml.Drawing.Charts; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using Npgsql; using System; using System.Collections.Generic; using System.Data; using System.Linq; +using System.Windows.Forms; namespace CompShop.Repos.Impements { @@ -92,32 +96,54 @@ namespace CompShop.Repos.Impements } } - public IEnumerable ReadAll() + public IEnumerable ReadAll(DateTime? startDate = null, DateTime? endDate = null, int? productId = null, int? clientId = null) { - _logger.LogInformation("Чтение всех чеков"); - using (var connection = CreateConnection()) + _logger.LogInformation("Чтение всех чеков с использованием фильтров"); + try { - try - { - var checksSql = "SELECT * FROM \"Checks\""; - var checks = connection.Query(checksSql).ToList(); + var builder = new QueryBuilder(); - foreach (var check in checks) + // Добавляем условия фильтрации, если параметры заданы + if (startDate.HasValue) + builder.AddCondition("\"Checks\".\"PurchaseDate\" >= @StartDate"); + if (endDate.HasValue) + builder.AddCondition("\"Checks\".\"PurchaseDate\" <= @EndDate"); + if (productId.HasValue) + builder.AddCondition("\"ProductsInCheck\".\"ProductID\" = @ProductId"); + if (clientId.HasValue) + builder.AddCondition("\"Checks\".\"ClientId\" = @ClientId"); + + // Формируем основной SQL-запрос + var query = $"SELECT \"Checks\".*, \"Clients\".\"Name\" AS \"ClientName\" FROM \"Checks\" LEFT JOIN \"Clients\" ON \"Checks\".\"ClientId\" = \"Clients\".\"Id\" LEFT JOIN \"ProductsInCheck\" ON \"ProductsInCheck\".\"CheckId\" = \"Checks\".\"Id\" { builder.Build()} GROUP BY \"Checks\".\"Id\", \"Clients\".\"Name\" ORDER BY \"Checks\".\"PurchaseDate\""; + + // Выполняем запрос + using var connection = CreateConnection(); + var checks = connection.Query( + query, + (check, client) => { - check.Client = _clientRepository.Read(check.ClientId); + check.Client = client; + return check; + }, + new { StartDate = startDate, EndDate = endDate, ProductId = productId, ClientId = clientId }, + splitOn: "ClientName" + ).ToList(); - var productSql = "SELECT * FROM \"ProductsInCheck\" WHERE \"CheckId\" = @CheckId"; - check.Products = connection.Query(productSql, new { CheckId = check.Id }).ToList(); - } - - return checks; - } - catch (Exception ex) + // Загружаем товары для каждого чека + foreach (var check in checks) { - _logger.LogError(ex, "Ошибка при чтении всех чеков"); - throw; + var productQuery = "SELECT * FROM \"ProductsInCheck\" WHERE \"CheckId\" = @CheckId"; + check.Products = connection.Query(productQuery, new { CheckId = check.Id }).ToList(); } + + return checks; + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка при чтении всех чеков"); + throw; } } + } }