forked from slavaxom9k/PIBD-23_Fomichev_V.S._MagicCarpet
полная 6 лаба
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
using MagicCarpetContracts.BusinessLogicContracts;
|
using MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
using MagicCarpetContracts.BusinessLogicContracts;
|
||||||
|
using MagicCarpetContracts.DataModels;
|
||||||
|
using MagicCarpetContracts.Exceptions;
|
||||||
|
using MagicCarpetContracts.Extensions;
|
||||||
|
using MagicCarpetContracts.StoragesContracts;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -6,7 +12,139 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
namespace MagicCarpetBusinessLogic.Implementations;
|
namespace MagicCarpetBusinessLogic.Implementations;
|
||||||
|
|
||||||
public class ReportContract : IReportContract
|
public class ReportContract(ITourStorageContract tourStorageContract, ISalaryStorageContract salaryStorageContract, ISaleStorageContract saleStorageContract,
|
||||||
|
BaseWordBuilder baseWordBuilder, BaseExcelBuilder baseExcelBuilder, BasePdfBuilder basePdfBuilder, ILogger logger) : IReportContract
|
||||||
{
|
{
|
||||||
|
private readonly ITourStorageContract _cocktailStorageContract = tourStorageContract;
|
||||||
|
private readonly ISalaryStorageContract _salaryStorageContract = salaryStorageContract;
|
||||||
|
private readonly ISaleStorageContract _saleStorageContract = saleStorageContract;
|
||||||
|
private readonly BaseWordBuilder _baseWordBuilder = baseWordBuilder;
|
||||||
|
private readonly BaseExcelBuilder _baseExcelBuilder = baseExcelBuilder;
|
||||||
|
private readonly BasePdfBuilder _basePdfBuilder = basePdfBuilder;
|
||||||
|
private readonly ILogger _logger = logger;
|
||||||
|
|
||||||
|
internal static readonly string[] documentHeader = ["Название тура", "Старая цена", "Дата"];
|
||||||
|
internal static readonly string[] tableHeader = ["Дата", "Сумма", "Скидка", "Товар", "Кол-во"];
|
||||||
|
|
||||||
|
public Task<List<TourAndTourHistoryDataModel>> GetDataToursHistoryAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Get data PostHistory");
|
||||||
|
return GetToursHistoriesAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> CreateDocumentToursHistoryAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Create report TourHistory");
|
||||||
|
var data = await GetToursHistoriesAsync(ct) ?? throw new InvalidOperationException("No found data");
|
||||||
|
|
||||||
|
return _baseWordBuilder
|
||||||
|
.AddHeader("История туров")
|
||||||
|
.AddParagraph($"Сформировано на дату {DateTime.Now}")
|
||||||
|
.AddTable(
|
||||||
|
[3000, 3000, 3000],
|
||||||
|
[.. new List<string[]>() { documentHeader }
|
||||||
|
.Union(data.SelectMany(x =>
|
||||||
|
(new List<string[]>() { new[] { x.TourName, "", "" } })
|
||||||
|
.Union(x.Histories.Zip(x.Data, (price, date) => new[] { "", price, date }))
|
||||||
|
).ToList())
|
||||||
|
])
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<TourAndTourHistoryDataModel>> GetToursHistoriesAsync(CancellationToken ct) =>
|
||||||
|
[.. (await _cocktailStorageContract.GetHistoriesListAsync(ct)).GroupBy(x => x.TourName).Select(x => new TourAndTourHistoryDataModel {
|
||||||
|
TourName = x.Key, Histories = [.. x.Select(y => y.OldPrice.ToString())], Data = [.. x.Select(y => y.ChangeDate.ToString())] })];
|
||||||
|
|
||||||
|
public async Task<Stream> CreateDocumentSalesByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
string[] tableHeader1 = ["Сотрудник", "Дата", "Сумма", "Скидка", "Товар", "Кол-во"];
|
||||||
|
|
||||||
|
_logger.LogInformation("Create report SalesByPeriod from {dateStart} to {dateFinish}", dateStart, dateFinish);
|
||||||
|
var data = await GetDataBySalesAsync(dateStart, dateFinish, ct) ?? throw new InvalidOperationException("No found data");
|
||||||
|
|
||||||
|
var tableRows = new List<string[]>
|
||||||
|
{
|
||||||
|
tableHeader1
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var sale in data)
|
||||||
|
{
|
||||||
|
tableRows.Add(new string[]
|
||||||
|
{
|
||||||
|
sale.EmployeeFIO ?? "",
|
||||||
|
sale.SaleDate.ToShortDateString(),
|
||||||
|
sale.Sum.ToString("N2"),
|
||||||
|
sale.Discount.ToString("N2"),
|
||||||
|
"", ""
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var cocktail in sale.Tours ?? Enumerable.Empty<SaleTourDataModel>())
|
||||||
|
{
|
||||||
|
tableRows.Add(new string[]
|
||||||
|
{
|
||||||
|
"", "", "", "", cocktail.TourName, cocktail.Count.ToString("N2")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tableRows.Add(new string[]
|
||||||
|
{
|
||||||
|
"Всего", "", data.Sum(x => x.Sum).ToString("N2"), data.Sum(x => x.Discount).ToString("N2"), "", ""
|
||||||
|
});
|
||||||
|
|
||||||
|
return _baseExcelBuilder
|
||||||
|
.AddHeader("Продажи за период", 0, 6)
|
||||||
|
.AddParagraph($"с {dateStart.ToShortDateString()} по {dateFinish.ToShortDateString()}", 2)
|
||||||
|
.AddTable([15, 15, 10, 10, 25, 10], tableRows)
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<SaleDataModel>> GetDataBySalesAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (dateStart.IsDateNotOlder(dateFinish))
|
||||||
|
{
|
||||||
|
throw new IncorrectDatesException(dateStart, dateFinish);
|
||||||
|
}
|
||||||
|
return [.. (await _saleStorageContract.GetListAsync(dateStart,
|
||||||
|
dateFinish, ct)).OrderBy(x => x.SaleDate)];
|
||||||
|
}
|
||||||
|
public Task<List<SaleDataModel>> GetDataSaleByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Get data SalesByPeriod from {dateStart} to {dateFinish}", dateStart, dateFinish);
|
||||||
|
return GetDataBySalesAsync(dateStart, dateFinish, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<EmployeeSalaryByPeriodDataModel>> GetDataSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Get data SalaryByPeriod from {dateStart} to { dateFinish}", dateStart, dateFinish);
|
||||||
|
return GetDataBySalaryAsync(dateStart, dateFinish, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<EmployeeSalaryByPeriodDataModel>> GetDataBySalaryAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (dateStart.IsDateNotOlder(dateFinish))
|
||||||
|
{
|
||||||
|
throw new IncorrectDatesException(dateStart, dateFinish);
|
||||||
|
}
|
||||||
|
return [.. (await _salaryStorageContract.GetListAsync(dateStart, dateFinish, ct))
|
||||||
|
.GroupBy(x => x.EmployeeId)
|
||||||
|
.Select(x => new EmployeeSalaryByPeriodDataModel {
|
||||||
|
EmployeeFIO = x.First().EmployeeFIO,
|
||||||
|
TotalSalary = x.Sum(y => y.Salary),
|
||||||
|
FromPeriod = x.Min(y => y.SalaryDate),
|
||||||
|
ToPeriod = x.Max(y => y.SalaryDate)
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.EmployeeFIO)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> CreateDocumentSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Create report SalaryByPeriod from {dateStart} to {dateFinish}", dateStart, dateFinish);
|
||||||
|
var data = await GetDataBySalaryAsync(dateStart, dateFinish, ct) ?? throw new InvalidOperationException("No found data");
|
||||||
|
return _basePdfBuilder
|
||||||
|
.AddHeader("Зарплатная ведомость")
|
||||||
|
.AddParagraph($"за период с {dateStart.ToShortDateString()} по {dateFinish.ToShortDateString()}")
|
||||||
|
.AddPieChart("Начисления", [.. data.Select(x => (x.EmployeeFIO, x.TotalSalary))])
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
|
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.2" />
|
<PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
|
||||||
|
<PackageReference Include="PdfSharp.MigraDoc.Standard" Version="1.51.15" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
|
||||||
|
public abstract class BaseExcelBuilder
|
||||||
|
{
|
||||||
|
public abstract BaseExcelBuilder AddHeader(string header, int startIndex, int count);
|
||||||
|
public abstract BaseExcelBuilder AddParagraph(string text, int columnIndex);
|
||||||
|
public abstract BaseExcelBuilder AddTable(int[] columnsWidths, List<string[]> data);
|
||||||
|
public abstract Stream Build();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
|
||||||
|
public abstract class BasePdfBuilder
|
||||||
|
{
|
||||||
|
public abstract BasePdfBuilder AddHeader(string header);
|
||||||
|
public abstract BasePdfBuilder AddParagraph(string text);
|
||||||
|
public abstract BasePdfBuilder AddPieChart(string title, List<(string Caption, double Value)> data);
|
||||||
|
public abstract Stream Build();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
|
||||||
|
public abstract class BaseWordBuilder
|
||||||
|
{
|
||||||
|
public abstract BaseWordBuilder AddHeader(string header);
|
||||||
|
public abstract BaseWordBuilder AddParagraph(string text);
|
||||||
|
public abstract BaseWordBuilder AddTable(int[] widths, List<string[]> data);
|
||||||
|
public abstract Stream Build();
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
|
||||||
|
public class MigraDocPdfBuilder : BasePdfBuilder
|
||||||
|
{
|
||||||
|
private readonly Document _document;
|
||||||
|
|
||||||
|
public MigraDocPdfBuilder()
|
||||||
|
{
|
||||||
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
|
_document = new Document();
|
||||||
|
DefineStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BasePdfBuilder AddHeader(string header)
|
||||||
|
{
|
||||||
|
_document.AddSection().AddParagraph(header, "NormalBold");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BasePdfBuilder AddParagraph(string text)
|
||||||
|
{
|
||||||
|
_document.LastSection.AddParagraph(text, "Normal");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BasePdfBuilder AddPieChart(string title, List<(string Caption, double Value)> data)
|
||||||
|
{
|
||||||
|
if (data == null || data.Count == 0)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chart = new Chart(ChartType.Pie2D);
|
||||||
|
var series = chart.SeriesCollection.AddSeries();
|
||||||
|
series.Add(data.Select(x => x.Value).ToArray());
|
||||||
|
|
||||||
|
var xseries = chart.XValues.AddXSeries();
|
||||||
|
xseries.Add(data.Select(x => x.Caption).ToArray());
|
||||||
|
|
||||||
|
chart.DataLabel.Type = DataLabelType.Percent;
|
||||||
|
chart.DataLabel.Position = DataLabelPosition.OutsideEnd;
|
||||||
|
|
||||||
|
chart.Width = Unit.FromCentimeter(16);
|
||||||
|
chart.Height = Unit.FromCentimeter(12);
|
||||||
|
|
||||||
|
chart.TopArea.AddParagraph(title);
|
||||||
|
|
||||||
|
chart.XAxis.MajorTickMark = TickMarkType.Outside;
|
||||||
|
|
||||||
|
chart.YAxis.MajorTickMark = TickMarkType.Outside;
|
||||||
|
chart.YAxis.HasMajorGridlines = true;
|
||||||
|
|
||||||
|
chart.PlotArea.LineFormat.Width = 1;
|
||||||
|
chart.PlotArea.LineFormat.Visible = true;
|
||||||
|
|
||||||
|
chart.TopArea.AddLegend();
|
||||||
|
|
||||||
|
_document.LastSection.Add(chart);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Stream Build()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
var renderer = new PdfDocumentRenderer(true)
|
||||||
|
{
|
||||||
|
Document = _document
|
||||||
|
};
|
||||||
|
renderer.RenderDocument();
|
||||||
|
renderer.PdfDocument.Save(stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DefineStyles()
|
||||||
|
{
|
||||||
|
var style = _document.Styles.AddStyle("NormalBold", "Normal");
|
||||||
|
style.Font.Bold = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
using DocumentFormat.OpenXml.Packaging;
|
||||||
|
using DocumentFormat.OpenXml.Spreadsheet;
|
||||||
|
using DocumentFormat.OpenXml;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
|
||||||
|
public class OpenXmlExcelBuilder : BaseExcelBuilder
|
||||||
|
{
|
||||||
|
private readonly SheetData _sheetData;
|
||||||
|
|
||||||
|
private readonly MergeCells _mergeCells;
|
||||||
|
|
||||||
|
private readonly Columns _columns;
|
||||||
|
|
||||||
|
private uint _rowIndex = 0;
|
||||||
|
|
||||||
|
public OpenXmlExcelBuilder()
|
||||||
|
{
|
||||||
|
_sheetData = new SheetData();
|
||||||
|
_mergeCells = new MergeCells();
|
||||||
|
_columns = new Columns();
|
||||||
|
_rowIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BaseExcelBuilder AddHeader(string header, int startIndex, int count)
|
||||||
|
{
|
||||||
|
CreateCell(startIndex, _rowIndex, header, StyleIndex.BoldTextWithoutBorder);
|
||||||
|
for (int i = startIndex + 1; i < startIndex + count; ++i)
|
||||||
|
{
|
||||||
|
CreateCell(i, _rowIndex, "", StyleIndex.SimpleTextWithoutBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mergeCells.Append(new MergeCell()
|
||||||
|
{
|
||||||
|
Reference =
|
||||||
|
new StringValue($"{GetExcelColumnName(startIndex)}{_rowIndex}:{GetExcelColumnName(startIndex + count - 1)}{_rowIndex}")
|
||||||
|
});
|
||||||
|
|
||||||
|
_rowIndex++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BaseExcelBuilder AddParagraph(string text, int columnIndex)
|
||||||
|
{
|
||||||
|
CreateCell(columnIndex, _rowIndex++, text, StyleIndex.SimpleTextWithoutBorder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BaseExcelBuilder AddTable(int[] columnsWidths, List<string[]> data)
|
||||||
|
{
|
||||||
|
if (columnsWidths == null || columnsWidths.Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(columnsWidths));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == null || data.Count == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.Any(x => x.Length != columnsWidths.Length))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("widths.Length != data.Length");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint counter = 1;
|
||||||
|
int coef = 2;
|
||||||
|
_columns.Append(columnsWidths.Select(x => new Column
|
||||||
|
{
|
||||||
|
Min = counter,
|
||||||
|
Max = counter++,
|
||||||
|
Width = x * coef,
|
||||||
|
CustomWidth = true
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (var j = 0; j < data.First().Length; ++j)
|
||||||
|
{
|
||||||
|
CreateCell(j, _rowIndex, data.First()[j], StyleIndex.BoldTextWithBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
_rowIndex++;
|
||||||
|
for (var i = 1; i < data.Count - 1; ++i)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < data[i].Length; ++j)
|
||||||
|
{
|
||||||
|
CreateCell(j, _rowIndex, data[i][j], StyleIndex.SimpleTextWithBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
_rowIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < data.Last().Length; ++j)
|
||||||
|
{
|
||||||
|
CreateCell(j, _rowIndex, data.Last()[j], StyleIndex.BoldTextWithBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
_rowIndex++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Stream Build()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using var spreadsheetDocument = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook);
|
||||||
|
var workbookpart = spreadsheetDocument.AddWorkbookPart();
|
||||||
|
GenerateStyle(workbookpart);
|
||||||
|
workbookpart.Workbook = new Workbook();
|
||||||
|
var worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
|
||||||
|
worksheetPart.Worksheet = new Worksheet();
|
||||||
|
if (_columns.HasChildren)
|
||||||
|
{
|
||||||
|
worksheetPart.Worksheet.Append(_columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
worksheetPart.Worksheet.Append(_sheetData);
|
||||||
|
var sheets = spreadsheetDocument.WorkbookPart!.Workbook.AppendChild(new Sheets());
|
||||||
|
var sheet = new Sheet()
|
||||||
|
{
|
||||||
|
Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart),
|
||||||
|
SheetId = 1,
|
||||||
|
Name = "Лист 1"
|
||||||
|
};
|
||||||
|
|
||||||
|
sheets.Append(sheet);
|
||||||
|
if (_mergeCells.HasChildren)
|
||||||
|
{
|
||||||
|
worksheetPart.Worksheet.InsertAfter(_mergeCells, worksheetPart.Worksheet.Elements<SheetData>().First());
|
||||||
|
}
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GenerateStyle(WorkbookPart workbookPart)
|
||||||
|
{
|
||||||
|
var workbookStylesPart = workbookPart.AddNewPart<WorkbookStylesPart>();
|
||||||
|
workbookStylesPart.Stylesheet = new Stylesheet();
|
||||||
|
|
||||||
|
var fonts = new Fonts() { Count = 2, KnownFonts = BooleanValue.FromBoolean(true) };
|
||||||
|
fonts.Append(new DocumentFormat.OpenXml.Spreadsheet.Font
|
||||||
|
{
|
||||||
|
FontSize = new FontSize() { Val = 11 },
|
||||||
|
FontName = new FontName() { Val = "Calibri" },
|
||||||
|
FontFamilyNumbering = new FontFamilyNumbering() { Val = 2 },
|
||||||
|
FontScheme = new FontScheme() { Val = new EnumValue<FontSchemeValues>(FontSchemeValues.Minor) }
|
||||||
|
});
|
||||||
|
fonts.Append(new DocumentFormat.OpenXml.Spreadsheet.Font
|
||||||
|
{
|
||||||
|
FontSize = new FontSize() { Val = 11 },
|
||||||
|
FontName = new FontName() { Val = "Calibri" },
|
||||||
|
FontFamilyNumbering = new FontFamilyNumbering() { Val = 2 },
|
||||||
|
FontScheme = new FontScheme() { Val = new EnumValue<FontSchemeValues>(FontSchemeValues.Minor) },
|
||||||
|
Bold = new Bold()
|
||||||
|
});
|
||||||
|
workbookStylesPart.Stylesheet.Append(fonts);
|
||||||
|
|
||||||
|
// Default Fill
|
||||||
|
var fills = new Fills() { Count = 1 };
|
||||||
|
fills.Append(new Fill
|
||||||
|
{
|
||||||
|
PatternFill = new PatternFill() { PatternType = new EnumValue<PatternValues>(PatternValues.None) }
|
||||||
|
});
|
||||||
|
workbookStylesPart.Stylesheet.Append(fills);
|
||||||
|
|
||||||
|
// Default Border
|
||||||
|
var borders = new Borders() { Count = 2 };
|
||||||
|
borders.Append(new Border
|
||||||
|
{
|
||||||
|
LeftBorder = new LeftBorder(),
|
||||||
|
RightBorder = new RightBorder(),
|
||||||
|
TopBorder = new TopBorder(),
|
||||||
|
BottomBorder = new BottomBorder(),
|
||||||
|
DiagonalBorder = new DiagonalBorder()
|
||||||
|
});
|
||||||
|
borders.Append(new Border
|
||||||
|
{
|
||||||
|
LeftBorder = new LeftBorder() { Style = BorderStyleValues.Thin },
|
||||||
|
RightBorder = new RightBorder() { Style = BorderStyleValues.Thin },
|
||||||
|
TopBorder = new TopBorder() { Style = BorderStyleValues.Thin },
|
||||||
|
BottomBorder = new BottomBorder() { Style = BorderStyleValues.Thin }
|
||||||
|
});
|
||||||
|
workbookStylesPart.Stylesheet.Append(borders);
|
||||||
|
|
||||||
|
// Default cell format and a date cell format
|
||||||
|
var cellFormats = new CellFormats() { Count = 4 };
|
||||||
|
cellFormats.Append(new CellFormat
|
||||||
|
{
|
||||||
|
NumberFormatId = 0,
|
||||||
|
FormatId = 0,
|
||||||
|
FontId = 0,
|
||||||
|
BorderId = 0,
|
||||||
|
FillId = 0,
|
||||||
|
Alignment = new Alignment()
|
||||||
|
{
|
||||||
|
Horizontal = HorizontalAlignmentValues.Left,
|
||||||
|
Vertical = VerticalAlignmentValues.Center,
|
||||||
|
WrapText = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cellFormats.Append(new CellFormat
|
||||||
|
{
|
||||||
|
NumberFormatId = 0,
|
||||||
|
FormatId = 0,
|
||||||
|
FontId = 0,
|
||||||
|
BorderId = 1,
|
||||||
|
FillId = 0,
|
||||||
|
Alignment = new Alignment()
|
||||||
|
{
|
||||||
|
Horizontal = HorizontalAlignmentValues.Left,
|
||||||
|
Vertical = VerticalAlignmentValues.Center,
|
||||||
|
WrapText = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cellFormats.Append(new CellFormat
|
||||||
|
{
|
||||||
|
NumberFormatId = 0,
|
||||||
|
FormatId = 0,
|
||||||
|
FontId = 1,
|
||||||
|
BorderId = 0,
|
||||||
|
FillId = 0,
|
||||||
|
Alignment = new Alignment()
|
||||||
|
{
|
||||||
|
Horizontal = HorizontalAlignmentValues.Center,
|
||||||
|
Vertical = VerticalAlignmentValues.Center,
|
||||||
|
WrapText = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cellFormats.Append(new CellFormat
|
||||||
|
{
|
||||||
|
NumberFormatId = 0,
|
||||||
|
FormatId = 0,
|
||||||
|
FontId = 1,
|
||||||
|
BorderId = 1,
|
||||||
|
FillId = 0,
|
||||||
|
Alignment = new Alignment()
|
||||||
|
{
|
||||||
|
Horizontal = HorizontalAlignmentValues.Center,
|
||||||
|
Vertical = VerticalAlignmentValues.Center,
|
||||||
|
WrapText = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
workbookStylesPart.Stylesheet.Append(cellFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum StyleIndex
|
||||||
|
{
|
||||||
|
SimpleTextWithoutBorder = 0,
|
||||||
|
SimpleTextWithBorder = 1,
|
||||||
|
BoldTextWithoutBorder = 2,
|
||||||
|
BoldTextWithBorder = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateCell(int columnIndex, uint rowIndex, string text, StyleIndex styleIndex)
|
||||||
|
{
|
||||||
|
var columnName = GetExcelColumnName(columnIndex);
|
||||||
|
var cellReference = columnName + rowIndex;
|
||||||
|
var row = _sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex! == rowIndex);
|
||||||
|
if (row == null)
|
||||||
|
{
|
||||||
|
row = new Row() { RowIndex = rowIndex };
|
||||||
|
_sheetData.Append(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newCell = row.Elements<Cell>()
|
||||||
|
.FirstOrDefault(c => c.CellReference != null && c.CellReference.Value == columnName + rowIndex);
|
||||||
|
if (newCell == null)
|
||||||
|
{
|
||||||
|
Cell? refCell = null;
|
||||||
|
foreach (Cell cell in row.Elements<Cell>())
|
||||||
|
{
|
||||||
|
if (cell.CellReference?.Value != null && cell.CellReference.Value.Length == cellReference.Length)
|
||||||
|
{
|
||||||
|
if (string.Compare(cell.CellReference.Value, cellReference, true) > 0)
|
||||||
|
{
|
||||||
|
refCell = cell;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newCell = new Cell() { CellReference = cellReference };
|
||||||
|
row.InsertBefore(newCell, refCell);
|
||||||
|
}
|
||||||
|
|
||||||
|
newCell.CellValue = new CellValue(text);
|
||||||
|
newCell.DataType = CellValues.String;
|
||||||
|
newCell.StyleIndex = (uint)styleIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetExcelColumnName(int columnNumber)
|
||||||
|
{
|
||||||
|
columnNumber += 1;
|
||||||
|
int dividend = columnNumber;
|
||||||
|
string columnName = string.Empty;
|
||||||
|
int modulo;
|
||||||
|
|
||||||
|
while (dividend > 0)
|
||||||
|
{
|
||||||
|
modulo = (dividend - 1) % 26;
|
||||||
|
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
|
||||||
|
dividend = (dividend - modulo) / 26;
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
using DocumentFormat.OpenXml;
|
||||||
|
using DocumentFormat.OpenXml.Packaging;
|
||||||
|
using DocumentFormat.OpenXml.Wordprocessing;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
|
||||||
|
public class OpenXmlWordBuilder : BaseWordBuilder
|
||||||
|
{
|
||||||
|
private readonly Document _document;
|
||||||
|
|
||||||
|
private readonly Body _body;
|
||||||
|
|
||||||
|
public OpenXmlWordBuilder()
|
||||||
|
{
|
||||||
|
_document = new Document();
|
||||||
|
_body = _document.AppendChild(new Body());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BaseWordBuilder AddHeader(string header)
|
||||||
|
{
|
||||||
|
var paragraph = _body.AppendChild(new Paragraph());
|
||||||
|
var run = paragraph.AppendChild(new Run());
|
||||||
|
run.AppendChild(new RunProperties(new Bold()));
|
||||||
|
run.AppendChild(new Text(header));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BaseWordBuilder AddParagraph(string text)
|
||||||
|
{
|
||||||
|
var paragraph = _body.AppendChild(new Paragraph());
|
||||||
|
var run = paragraph.AppendChild(new Run());
|
||||||
|
run.AppendChild(new Text(text));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override BaseWordBuilder AddTable(int[] widths, List<string[]> data)
|
||||||
|
{
|
||||||
|
if (widths == null || widths.Length == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(widths));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data == null || data.Count == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.Any(x => x.Length != widths.Length))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("widths.Length != data.Length");
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = new Table();
|
||||||
|
table.AppendChild(new TableProperties(
|
||||||
|
new TableBorders(
|
||||||
|
new TopBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 12 },
|
||||||
|
new BottomBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 12 },
|
||||||
|
new LeftBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 12 },
|
||||||
|
new RightBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 12 },
|
||||||
|
new InsideHorizontalBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 12 },
|
||||||
|
new InsideVerticalBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 12 }
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Заголовок
|
||||||
|
var tr = new TableRow();
|
||||||
|
for (var j = 0; j < widths.Length; ++j)
|
||||||
|
{
|
||||||
|
tr.Append(new TableCell(
|
||||||
|
new TableCellProperties(new TableCellWidth() { Width = widths[j].ToString() }),
|
||||||
|
new Paragraph(new Run(new RunProperties(new Bold()), new Text(data.First()[j])))));
|
||||||
|
}
|
||||||
|
table.Append(tr);
|
||||||
|
|
||||||
|
// Данные
|
||||||
|
table.Append(data.Skip(1).Select(x =>
|
||||||
|
new TableRow(x.Select(y => new TableCell(new Paragraph(new Run(new Text(y))))))));
|
||||||
|
|
||||||
|
_body.Append(table);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Stream Build()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using var wordDocument = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document);
|
||||||
|
var mainPart = wordDocument.AddMainDocumentPart();
|
||||||
|
mainPart.Document = _document;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using MagicCarpetContracts.AdapterContracts.OperationResponses;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -8,5 +9,10 @@ namespace MagicCarpetContracts.AdapterContracts;
|
|||||||
|
|
||||||
public interface IReportAdapter
|
public interface IReportAdapter
|
||||||
{
|
{
|
||||||
|
Task<ReportOperationResponse> GetDataToursHistoryAsync(CancellationToken ct);
|
||||||
|
Task<ReportOperationResponse> GetDataBySalesByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
|
Task<ReportOperationResponse> GetDataSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
|
Task<ReportOperationResponse> CreateDocumentToursHistoryAsync(CancellationToken ct);
|
||||||
|
Task<ReportOperationResponse> CreateDocumentSalesByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
|
Task<ReportOperationResponse> CreateDocumentSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MagicCarpetContracts.Infrastructure;
|
using MagicCarpetContracts.Infrastructure;
|
||||||
|
using MagicCarpetContracts.ViewModels;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -9,11 +10,15 @@ namespace MagicCarpetContracts.AdapterContracts.OperationResponses;
|
|||||||
|
|
||||||
public class ReportOperationResponse : OperationResponse
|
public class ReportOperationResponse : OperationResponse
|
||||||
{
|
{
|
||||||
Task<ReportOperationResponse> GetDataSaleByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
public static ReportOperationResponse OK(List<TourAndTourHistoryViewModel> data) => OK<ReportOperationResponse, List<TourAndTourHistoryViewModel>>(data);
|
||||||
|
|
||||||
Task<ReportOperationResponse> GetDataSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
public static ReportOperationResponse OK(List<SaleViewModel> data) => OK<ReportOperationResponse, List<SaleViewModel>>(data);
|
||||||
|
|
||||||
Task<ReportOperationResponse> CreateDocumentSalesByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
public static ReportOperationResponse OK(List<EmployeeSalaryByPeriodViewModel> data) => OK<ReportOperationResponse, List<EmployeeSalaryByPeriodViewModel>>(data);
|
||||||
|
|
||||||
Task<ReportOperationResponse> CreateDocumentSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
public static ReportOperationResponse OK(Stream data, string fileName) => OK<ReportOperationResponse, Stream>(data, fileName);
|
||||||
|
|
||||||
|
public static ReportOperationResponse BadRequest(string message) => BadRequest<ReportOperationResponse>(message);
|
||||||
|
|
||||||
|
public static ReportOperationResponse InternalServerError(string message) => InternalServerError<ReportOperationResponse>(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using MagicCarpetContracts.DataModels;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -8,4 +9,10 @@ namespace MagicCarpetContracts.BusinessLogicContracts;
|
|||||||
|
|
||||||
public interface IReportContract
|
public interface IReportContract
|
||||||
{
|
{
|
||||||
|
Task<List<TourAndTourHistoryDataModel>> GetDataToursHistoryAsync(CancellationToken ct);
|
||||||
|
Task<Stream> CreateDocumentToursHistoryAsync(CancellationToken ct);
|
||||||
|
Task<List<SaleDataModel>> GetDataBySalesAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
|
Task<Stream> CreateDocumentSalesByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
|
Task<List<EmployeeSalaryByPeriodDataModel>> GetDataSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
|
Task<Stream> CreateDocumentSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetContracts.DataModels;
|
||||||
|
|
||||||
|
public class EmployeeSalaryByPeriodDataModel
|
||||||
|
{
|
||||||
|
public required string EmployeeFIO { get; set; }
|
||||||
|
|
||||||
|
public double TotalSalary { get; set; }
|
||||||
|
|
||||||
|
public DateTime FromPeriod { get; set; }
|
||||||
|
|
||||||
|
public DateTime ToPeriod { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetContracts.DataModels;
|
||||||
|
|
||||||
|
public class TourAndTourHistoryDataModel
|
||||||
|
{
|
||||||
|
public required string TourName { get; set; }
|
||||||
|
public required List<string> Histories { get; set; }
|
||||||
|
public required List<string> Data { get; set; }
|
||||||
|
}
|
||||||
@@ -15,24 +15,33 @@ public class OperationResponse
|
|||||||
|
|
||||||
protected object? Result { get; set; }
|
protected object? Result { get; set; }
|
||||||
|
|
||||||
|
protected string? FileName { get; set; }
|
||||||
|
|
||||||
public IActionResult GetResponse(HttpRequest request, HttpResponse response)
|
public IActionResult GetResponse(HttpRequest request, HttpResponse response)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(request);
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
ArgumentNullException.ThrowIfNull(response);
|
ArgumentNullException.ThrowIfNull(response);
|
||||||
|
|
||||||
response.StatusCode = (int)StatusCode;
|
response.StatusCode = (int)StatusCode;
|
||||||
|
|
||||||
if (Result is null)
|
if (Result is null)
|
||||||
{
|
{
|
||||||
return new StatusCodeResult((int)StatusCode);
|
return new StatusCodeResult((int)StatusCode);
|
||||||
}
|
}
|
||||||
|
if (Result is Stream stream)
|
||||||
|
{
|
||||||
|
return new FileStreamResult(stream, "application/octetstream")
|
||||||
|
{
|
||||||
|
FileDownloadName = FileName
|
||||||
|
};
|
||||||
|
}
|
||||||
return new ObjectResult(Result);
|
return new ObjectResult(Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static TResult OK<TResult, TData>(TData data) where TResult : OperationResponse,
|
protected static TResult OK<TResult, TData>(TData data) where TResult : OperationResponse,
|
||||||
new() => new() { StatusCode = HttpStatusCode.OK, Result = data };
|
new() => new() { StatusCode = HttpStatusCode.OK, Result = data };
|
||||||
|
|
||||||
|
protected static TResult OK<TResult, TData>(TData data, string fileName) where TResult : OperationResponse,
|
||||||
|
new() => new() { StatusCode = HttpStatusCode.OK, Result = data, FileName = fileName };
|
||||||
|
|
||||||
protected static TResult NoContent<TResult>() where TResult : OperationResponse,
|
protected static TResult NoContent<TResult>() where TResult : OperationResponse,
|
||||||
new() => new() { StatusCode = HttpStatusCode.NoContent };
|
new() => new() { StatusCode = HttpStatusCode.NoContent };
|
||||||
|
|
||||||
|
|||||||
@@ -11,5 +11,7 @@ public interface ISalaryStorageContract
|
|||||||
{
|
{
|
||||||
List<SalaryDataModel> GetList(DateTime? startDate, DateTime? endDate, string? employeeId = null);
|
List<SalaryDataModel> GetList(DateTime? startDate, DateTime? endDate, string? employeeId = null);
|
||||||
|
|
||||||
|
Task<List<SalaryDataModel>> GetListAsync(DateTime startDate, DateTime endDate, CancellationToken ct);
|
||||||
|
|
||||||
void AddElement(SalaryDataModel salaryDataModel);
|
void AddElement(SalaryDataModel salaryDataModel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public interface ISaleStorageContract
|
|||||||
List<SaleDataModel> GetList(DateTime? startDate = null, DateTime? endDate = null, string? employeeId = null,
|
List<SaleDataModel> GetList(DateTime? startDate = null, DateTime? endDate = null, string? employeeId = null,
|
||||||
string? clientId = null, string? tourId = null);
|
string? clientId = null, string? tourId = null);
|
||||||
|
|
||||||
|
Task<List<SaleDataModel>> GetListAsync(DateTime startDate, DateTime endDate, CancellationToken ct);
|
||||||
|
|
||||||
SaleDataModel? GetElementById(string id);
|
SaleDataModel? GetElementById(string id);
|
||||||
|
|
||||||
void AddElement(SaleDataModel saleDataModel);
|
void AddElement(SaleDataModel saleDataModel);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public interface ITourStorageContract
|
|||||||
{
|
{
|
||||||
List<TourDataModel> GetList();
|
List<TourDataModel> GetList();
|
||||||
List<TourHistoryDataModel> GetHistoryByTourId(string tourId);
|
List<TourHistoryDataModel> GetHistoryByTourId(string tourId);
|
||||||
|
Task<List<TourHistoryDataModel>> GetHistoriesListAsync(CancellationToken ct);
|
||||||
TourDataModel? GetElementById(string id);
|
TourDataModel? GetElementById(string id);
|
||||||
TourDataModel? GetElementByName(string name);
|
TourDataModel? GetElementByName(string name);
|
||||||
void AddElement(TourDataModel tourDataModel);
|
void AddElement(TourDataModel tourDataModel);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetContracts.ViewModels;
|
||||||
|
|
||||||
|
public class EmployeeSalaryByPeriodViewModel
|
||||||
|
{
|
||||||
|
public required string EmployeeFIO { get; set; }
|
||||||
|
public double TotalSalary { get; set; }
|
||||||
|
public DateTime FromPeriod { get; set; }
|
||||||
|
public DateTime ToPeriod { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetContracts.ViewModels;
|
||||||
|
|
||||||
|
public class TourAndTourHistoryViewModel
|
||||||
|
{
|
||||||
|
public required string TourName { get; set; }
|
||||||
|
public required List<string> Histories { get; set; }
|
||||||
|
public required List<string> Data { get; set; }
|
||||||
|
}
|
||||||
@@ -50,6 +50,22 @@ public class SalaryStorageContract : ISalaryStorageContract
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<SalaryDataModel>> GetListAsync(DateTime startDate, DateTime endDate, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return [.. await _dbContext.Salaries.Include(x =>
|
||||||
|
x.Employee).Where(x => x.SalaryDate >= DateTime.SpecifyKind(startDate, DateTimeKind.Utc) &&
|
||||||
|
x.SalaryDate <= DateTime.SpecifyKind(endDate, DateTimeKind.Utc))
|
||||||
|
.Select(x => _mapper.Map<SalaryDataModel>(x)).ToListAsync(ct)];
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_dbContext.ChangeTracker.Clear();
|
||||||
|
throw new StorageException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void AddElement(SalaryDataModel salaryDataModel)
|
public void AddElement(SalaryDataModel salaryDataModel)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -68,6 +68,27 @@ public class SaleStorageContract : ISaleStorageContract
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<SaleDataModel>> GetListAsync(DateTime startDate, DateTime endDate, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return [.. await _dbContext.Sales
|
||||||
|
.Include(x => x.Employee)
|
||||||
|
.Include(x => x.Client)
|
||||||
|
.Include(x => x.SaleTours)!
|
||||||
|
.ThenInclude(sc => sc.Tour)
|
||||||
|
.Where(x => x.SaleDate >= DateTime.SpecifyKind(startDate, DateTimeKind.Utc)
|
||||||
|
&& x.SaleDate < DateTime.SpecifyKind(endDate, DateTimeKind.Utc))
|
||||||
|
.Select(x => _mapper.Map<SaleDataModel>(x))
|
||||||
|
.ToListAsync(ct)];
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_dbContext.ChangeTracker.Clear();
|
||||||
|
throw new StorageException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SaleDataModel? GetElementById(string id)
|
public SaleDataModel? GetElementById(string id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -56,6 +56,19 @@ public class TourStorageContract : ITourStorageContract
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<TourHistoryDataModel>> GetHistoriesListAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return [.. await _dbContext.TourHistories.Include(x => x.Tour).Select(x => _mapper.Map<TourHistoryDataModel>(x)).ToListAsync(ct)];
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_dbContext.ChangeTracker.Clear();
|
||||||
|
throw new StorageException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public TourDataModel? GetElementById(string id)
|
public TourDataModel? GetElementById(string id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -0,0 +1,480 @@
|
|||||||
|
using MagicCarpetBusinessLogic.Implementations;
|
||||||
|
using MagicCarpetBusinessLogic.OfficePackage;
|
||||||
|
using MagicCarpetContracts.DataModels;
|
||||||
|
using MagicCarpetContracts.Enums;
|
||||||
|
using MagicCarpetContracts.Exceptions;
|
||||||
|
using MagicCarpetContracts.StoragesContracts;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetTests.BusinessLogicContractsTests;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
internal class ReportContractTests
|
||||||
|
{
|
||||||
|
private ReportContract _reportContract;
|
||||||
|
private Mock<ISaleStorageContract> _saleStorageContract;
|
||||||
|
private Mock<ITourStorageContract> _tourStorageContract;
|
||||||
|
private Mock<ISalaryStorageContract> _salaryStorageContract;
|
||||||
|
private Mock<BaseWordBuilder> _baseWordBuilder;
|
||||||
|
private Mock<BaseExcelBuilder> _baseExcelBuilder;
|
||||||
|
private Mock<BasePdfBuilder> _basePdfBuilder;
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void OneTimeSetUp()
|
||||||
|
{
|
||||||
|
_saleStorageContract = new Mock<ISaleStorageContract>();
|
||||||
|
_tourStorageContract = new Mock<ITourStorageContract>();
|
||||||
|
_salaryStorageContract = new Mock<ISalaryStorageContract>();
|
||||||
|
_baseWordBuilder = new Mock<BaseWordBuilder>();
|
||||||
|
_baseExcelBuilder = new Mock<BaseExcelBuilder>();
|
||||||
|
_basePdfBuilder = new Mock<BasePdfBuilder>();
|
||||||
|
_reportContract = new ReportContract(_tourStorageContract.Object, _salaryStorageContract.Object, _saleStorageContract.Object,
|
||||||
|
_baseWordBuilder.Object, _baseExcelBuilder.Object, _basePdfBuilder.Object, new Mock<ILogger>().Object);
|
||||||
|
}
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
_saleStorageContract.Reset();
|
||||||
|
_salaryStorageContract.Reset();
|
||||||
|
_tourStorageContract.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetDataHistoriesByTour_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var TourId1 = Guid.NewGuid().ToString();
|
||||||
|
var TourId2 = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var Tour1 = new TourDataModel(TourId1, "name1","test1", 10.5, TourType.Beach);
|
||||||
|
var Tour2 = new TourDataModel(TourId1, "name2", "test1", 10.5, TourType.Beach);
|
||||||
|
|
||||||
|
_tourStorageContract.Setup(x => x.GetHistoriesListAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(new List<TourHistoryDataModel>()
|
||||||
|
{
|
||||||
|
new(TourId1, 22, DateTime.UtcNow, Tour1),
|
||||||
|
new(TourId2, 21, DateTime.UtcNow, Tour2),
|
||||||
|
new(TourId1, 33, DateTime.UtcNow, Tour1),
|
||||||
|
new(TourId1, 32, DateTime.UtcNow, Tour1),
|
||||||
|
new(TourId2, 65, DateTime.UtcNow, Tour2)
|
||||||
|
}));
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var data = await _reportContract.GetDataToursHistoryAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(2));
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(data.First(x => x.TourName == Tour1.TourName).Histories, Has.Count.EqualTo(3));
|
||||||
|
Assert.That(data.First(x => x.TourName == Tour2.TourName).Histories, Has.Count.EqualTo(2));
|
||||||
|
});
|
||||||
|
_tourStorageContract.Verify(x => x.GetHistoriesListAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetDataHistoriesByTour_WhenNoRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
_tourStorageContract.Setup(x =>
|
||||||
|
x.GetHistoriesListAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new List<TourHistoryDataModel>());
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var data = await _reportContract.GetDataToursHistoryAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(0));
|
||||||
|
_tourStorageContract.Verify(x =>
|
||||||
|
x.GetHistoriesListAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetDataHistoriesByTour_WhenStorageThrowError_ShouldFail_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
_tourStorageContract.Setup(x =>
|
||||||
|
x.GetHistoriesListAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.ThrowsAsync(new StorageException(new InvalidOperationException()));
|
||||||
|
|
||||||
|
//Act & Assert
|
||||||
|
Assert.That(async () => await
|
||||||
|
_reportContract.GetDataToursHistoryAsync(CancellationToken.None),
|
||||||
|
Throws.TypeOf<StorageException>());
|
||||||
|
|
||||||
|
_tourStorageContract.Verify(x =>
|
||||||
|
x.GetHistoriesListAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task CreateDocumentHistoriesByTour_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var TourId1 = Guid.NewGuid().ToString();
|
||||||
|
var TourId2 = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var Tour1 = new TourDataModel(TourId1, "name1","test1", 10.5, TourType.Beach);
|
||||||
|
var Tour2 = new TourDataModel(TourId1, "name2","test1", 10.5, TourType.Beach);
|
||||||
|
|
||||||
|
var histories = new List<TourHistoryDataModel>()
|
||||||
|
{
|
||||||
|
new(TourId1, 22, DateTime.UtcNow, Tour1),
|
||||||
|
new(TourId1, 33, DateTime.UtcNow, Tour1),
|
||||||
|
new(TourId1, 32, DateTime.UtcNow, Tour1),
|
||||||
|
new(TourId2, 21, DateTime.UtcNow, Tour2),
|
||||||
|
new(TourId2, 65, DateTime.UtcNow, Tour2)
|
||||||
|
};
|
||||||
|
|
||||||
|
_tourStorageContract.Setup(x => x.GetHistoriesListAsync(It.IsAny<CancellationToken>()))
|
||||||
|
.Returns(Task.FromResult(histories));
|
||||||
|
|
||||||
|
_baseWordBuilder.Setup(x => x.AddHeader(It.IsAny<string>())).Returns(_baseWordBuilder.Object);
|
||||||
|
_baseWordBuilder.Setup(x => x.AddParagraph(It.IsAny<string>())).Returns(_baseWordBuilder.Object);
|
||||||
|
|
||||||
|
var countRows = 0;
|
||||||
|
string[] firstRow = [];
|
||||||
|
string[] secondRow = [];
|
||||||
|
|
||||||
|
_baseWordBuilder.Setup(x => x.AddTable(It.IsAny<int[]>(),
|
||||||
|
It.IsAny<List<string[]>>()))
|
||||||
|
.Callback((int[] widths, List<string[]> data) =>
|
||||||
|
{
|
||||||
|
countRows = data.Count;
|
||||||
|
if (data.Count > 0) firstRow = data[0];
|
||||||
|
if (data.Count > 1) secondRow = data[1];
|
||||||
|
})
|
||||||
|
.Returns(_baseWordBuilder.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var data = await _reportContract.CreateDocumentToursHistoryAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_tourStorageContract.Verify(x => x.GetHistoriesListAsync(It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_baseWordBuilder.Verify(x => x.AddHeader(It.IsAny<string>()), Times.Once);
|
||||||
|
_baseWordBuilder.Verify(x => x.AddParagraph(It.IsAny<string>()), Times.Once);
|
||||||
|
_baseWordBuilder.Verify(x => x.AddTable(It.IsAny<int[]>(), It.IsAny<List<string[]>>()), Times.Once);
|
||||||
|
_baseWordBuilder.Verify(x => x.Build(), Times.Once);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
// 1 заголовок + 2 тура (name1 и name2) + 5 записей истории
|
||||||
|
Assert.That(countRows, Is.EqualTo(8));
|
||||||
|
|
||||||
|
Assert.That(firstRow, Has.Length.EqualTo(3));
|
||||||
|
Assert.That(secondRow, Has.Length.EqualTo(3));
|
||||||
|
|
||||||
|
Assert.That(firstRow[0], Is.EqualTo("Название тура"));
|
||||||
|
Assert.That(firstRow[1], Is.EqualTo("Старая цена"));
|
||||||
|
|
||||||
|
Assert.That(secondRow[0], Is.EqualTo("name1"));
|
||||||
|
Assert.That(secondRow[1], Is.EqualTo(""));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetDataBySalesByPeriod_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
_saleStorageContract.Setup(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(new
|
||||||
|
List<SaleDataModel>()
|
||||||
|
{
|
||||||
|
new(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), DiscountType.None, false, [new SaleTourDataModel("",
|
||||||
|
Guid.NewGuid().ToString(), 10, 10)]),
|
||||||
|
new(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), DiscountType.None, false, [new SaleTourDataModel("",
|
||||||
|
Guid.NewGuid().ToString(), 10, 10)]),
|
||||||
|
}));
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var data = await _reportContract.GetDataBySalesAsync(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow, CancellationToken.None);
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(2));
|
||||||
|
_saleStorageContract.Verify(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetDataBySalesByPeriod_WhenNoRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
_saleStorageContract.Setup(x => x.GetListAsync(It.IsAny<DateTime>(),
|
||||||
|
It.IsAny<DateTime>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(new List<SaleDataModel>()));
|
||||||
|
//Act
|
||||||
|
var data = await
|
||||||
|
_reportContract.GetDataBySalesAsync(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow, CancellationToken.None);
|
||||||
|
//Assert
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(0));
|
||||||
|
_saleStorageContract.Verify(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetDataBySalesByPeriod_WhenIncorrectDates_ShouldFail_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var date = DateTime.UtcNow;
|
||||||
|
//Act&Assert
|
||||||
|
Assert.That(async () => await
|
||||||
|
_reportContract.GetDataBySalesAsync(date, date, CancellationToken.None),
|
||||||
|
Throws.TypeOf<IncorrectDatesException>());
|
||||||
|
Assert.That(async () => await
|
||||||
|
_reportContract.GetDataBySalesAsync(date, DateTime.UtcNow.AddDays(-1),
|
||||||
|
CancellationToken.None), Throws.TypeOf<IncorrectDatesException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetDataBySalesByPeriod_WhenStorageThrowError_ShouldFail_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
_saleStorageContract.Setup(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
|
||||||
|
.Throws(new StorageException(new InvalidOperationException()));
|
||||||
|
//Act&Assert
|
||||||
|
Assert.That(async () => await _reportContract.GetDataBySalesAsync(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow, CancellationToken.None),
|
||||||
|
Throws.TypeOf<StorageException>());
|
||||||
|
_saleStorageContract.Verify(x => x.GetListAsync(It.IsAny<DateTime>(),
|
||||||
|
It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task CreateDocumentSalesByPeriod_ShouldeSuccess_Test()
|
||||||
|
{
|
||||||
|
var Tour1 = new TourDataModel(Guid.NewGuid().ToString(), "name 1","test1", 10.5, TourType.Beach);
|
||||||
|
var Tour2 = new TourDataModel(Guid.NewGuid().ToString(), "name 2","test1", 10.5, TourType.Beach);
|
||||||
|
_saleStorageContract.Setup(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(new List<SaleDataModel>()
|
||||||
|
{
|
||||||
|
new(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), null, DiscountType.RegularCustomer, false, [new SaleTourDataModel("", Tour1.Id, 10, 10, Tour1), new SaleTourDataModel("", Tour2.Id, 10, 10, Tour2)]),
|
||||||
|
new(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), null, DiscountType.RegularCustomer, false, [new SaleTourDataModel("", Tour2.Id, 10, 10, Tour2)])
|
||||||
|
}));
|
||||||
|
_baseExcelBuilder.Setup(x => x.AddHeader(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>())).Returns(_baseExcelBuilder.Object);
|
||||||
|
_baseExcelBuilder.Setup(x => x.AddParagraph(It.IsAny<string>(), It.IsAny<int>())).Returns(_baseExcelBuilder.Object);
|
||||||
|
var countRows = 0;
|
||||||
|
string[] firstRow = [];
|
||||||
|
string[] secondRow = [];
|
||||||
|
string[] thirdRow = [];
|
||||||
|
_baseExcelBuilder.Setup(x => x.AddTable(It.IsAny<int[]>(), It.IsAny<List<string[]>>()))
|
||||||
|
.Callback((int[] widths, List<string[]> data) =>
|
||||||
|
{
|
||||||
|
countRows = data.Count;
|
||||||
|
firstRow = data[0];
|
||||||
|
secondRow = data[1];
|
||||||
|
thirdRow = data[2];
|
||||||
|
})
|
||||||
|
.Returns(_baseExcelBuilder.Object);
|
||||||
|
//Act
|
||||||
|
var data = await _reportContract.CreateDocumentSalesByPeriodAsync(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow, CancellationToken.None);
|
||||||
|
//Assert
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(countRows, Is.EqualTo(7));
|
||||||
|
Assert.That(firstRow, Is.Not.EqualTo(default));
|
||||||
|
Assert.That(secondRow, Is.Not.EqualTo(default));
|
||||||
|
});
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(firstRow[0], Is.EqualTo("Сотрудник"));
|
||||||
|
Assert.That(firstRow[1], Is.EqualTo("Дата"));
|
||||||
|
Assert.That(firstRow[2], Is.EqualTo("Сумма"));
|
||||||
|
Assert.That(firstRow[3], Is.EqualTo("Скидка"));
|
||||||
|
Assert.That(firstRow[4], Is.EqualTo("Товар"));
|
||||||
|
Assert.That(firstRow[5], Is.EqualTo("Кол-во"));
|
||||||
|
});
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(secondRow[0], Is.Empty);
|
||||||
|
Assert.That(secondRow[1], Is.Not.Empty);
|
||||||
|
Assert.That(secondRow[2], Is.EqualTo(200.ToString("N2")));
|
||||||
|
Assert.That(secondRow[3], Is.EqualTo(100.ToString("N2")));
|
||||||
|
Assert.That(secondRow[4], Is.Empty);
|
||||||
|
Assert.That(secondRow[5], Is.Empty);
|
||||||
|
});
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(thirdRow[0], Is.Empty);
|
||||||
|
Assert.That(thirdRow[1], Is.Empty);
|
||||||
|
Assert.That(thirdRow[2], Is.Empty);
|
||||||
|
Assert.That(thirdRow[3], Is.Empty);
|
||||||
|
Assert.That(thirdRow[4], Is.EqualTo(Tour1.TourName));
|
||||||
|
Assert.That(thirdRow[5], Is.EqualTo(10.ToString("N2")));
|
||||||
|
});
|
||||||
|
_saleStorageContract.Verify(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_baseExcelBuilder.Verify(x => x.AddHeader(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()), Times.Once);
|
||||||
|
_baseExcelBuilder.Verify(x => x.AddParagraph(It.IsAny<string>(), It.IsAny<int>()), Times.Once);
|
||||||
|
_baseExcelBuilder.Verify(x => x.AddTable(It.IsAny<int[]>(), It.IsAny<List<string[]>>()), Times.Once);
|
||||||
|
_baseExcelBuilder.Verify(x => x.Build(), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetDataSalaryByPeriod_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var startDate = DateTime.UtcNow.AddDays(-20);
|
||||||
|
var endDate = DateTime.UtcNow.AddDays(5);
|
||||||
|
|
||||||
|
var employee1 = new EmployeeDataModel(
|
||||||
|
Guid.NewGuid().ToString(),
|
||||||
|
"fio 1",
|
||||||
|
"abc@gmail.com",
|
||||||
|
Guid.NewGuid().ToString(),
|
||||||
|
DateTime.UtcNow.AddYears(-20),
|
||||||
|
DateTime.UtcNow.AddDays(-3));
|
||||||
|
|
||||||
|
var employee2 = new EmployeeDataModel(
|
||||||
|
Guid.NewGuid().ToString(),
|
||||||
|
"fio 2",
|
||||||
|
"abc@gmail.com",
|
||||||
|
Guid.NewGuid().ToString(),
|
||||||
|
DateTime.UtcNow.AddYears(-20),
|
||||||
|
DateTime.UtcNow.AddDays(-3));
|
||||||
|
|
||||||
|
_salaryStorageContract.Setup(x =>
|
||||||
|
x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new List<SalaryDataModel>()
|
||||||
|
{
|
||||||
|
new(employee1.Id, DateTime.UtcNow.AddDays(-10), 100),
|
||||||
|
new(employee1.Id, endDate, 1000),
|
||||||
|
new(employee1.Id, startDate, 1000),
|
||||||
|
new(employee2.Id, DateTime.UtcNow.AddDays(-10), 100),
|
||||||
|
new(employee2.Id, DateTime.UtcNow.AddDays(-5), 200)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var data = await _reportContract.GetDataSalaryByPeriodAsync(
|
||||||
|
DateTime.UtcNow.AddDays(-1),
|
||||||
|
DateTime.UtcNow,
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(2));
|
||||||
|
|
||||||
|
var totalSalary = data.First();
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(totalSalary, Is.Not.Null);
|
||||||
|
Assert.That(totalSalary.TotalSalary, Is.EqualTo(2100));
|
||||||
|
});
|
||||||
|
|
||||||
|
_salaryStorageContract.Verify(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetDataSalaryByPeriod_WhenNoRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_salaryStorageContract.Setup(x =>
|
||||||
|
x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new List<SalaryDataModel>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var data = await _reportContract.GetDataSalaryByPeriodAsync(
|
||||||
|
DateTime.UtcNow.AddDays(-1),
|
||||||
|
DateTime.UtcNow,
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(0));
|
||||||
|
|
||||||
|
_salaryStorageContract.Verify(x =>
|
||||||
|
x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()),
|
||||||
|
Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetDataSalaryByPeriod_WhenIncorrectDates_ShouldFail_Test()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var date = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(async () => await _reportContract.GetDataSalaryByPeriodAsync(
|
||||||
|
date, date, CancellationToken.None),
|
||||||
|
Throws.TypeOf<IncorrectDatesException>());
|
||||||
|
|
||||||
|
Assert.That(async () => await _reportContract.GetDataSalaryByPeriodAsync(
|
||||||
|
date, DateTime.UtcNow.AddDays(-1), CancellationToken.None),
|
||||||
|
Throws.TypeOf<IncorrectDatesException>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void GetDataBySalaryByPeriod_WhenStorageThrowError_ShouldFail_Test()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_salaryStorageContract.Setup(x =>
|
||||||
|
x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()))
|
||||||
|
.ThrowsAsync(new StorageException(new InvalidOperationException()));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
Assert.That(async () => await _reportContract.GetDataSalaryByPeriodAsync(
|
||||||
|
DateTime.UtcNow.AddDays(-1),
|
||||||
|
DateTime.UtcNow,
|
||||||
|
CancellationToken.None),
|
||||||
|
Throws.TypeOf<StorageException>());
|
||||||
|
|
||||||
|
_salaryStorageContract.Verify(x =>
|
||||||
|
x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()),
|
||||||
|
Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task CreateDocumentSalaryByPeriod_ShouldeSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var startDate = DateTime.UtcNow.AddDays(-20);
|
||||||
|
var endDate = DateTime.UtcNow.AddDays(5);
|
||||||
|
var employee1 = new EmployeeDataModel(Guid.NewGuid().ToString(), "fio 1", "abc@gmail.com", Guid.NewGuid().ToString(), DateTime.UtcNow.AddYears(-20), DateTime.UtcNow.AddDays(-3));
|
||||||
|
var employee2 = new EmployeeDataModel(Guid.NewGuid().ToString(), "fio 2", "abc@gmail.com", Guid.NewGuid().ToString(), DateTime.UtcNow.AddYears(-20), DateTime.UtcNow.AddDays(-3));
|
||||||
|
_salaryStorageContract.Setup(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult(new List<SalaryDataModel>()
|
||||||
|
{
|
||||||
|
new(employee1.Id, DateTime.UtcNow.AddDays(-10), 100, employee1),
|
||||||
|
new(employee1.Id, endDate, 1000, employee1),
|
||||||
|
new(employee1.Id, startDate, 1000, employee1),
|
||||||
|
new(employee2.Id, DateTime.UtcNow.AddDays(-10), 100, employee2),
|
||||||
|
new(employee2.Id, DateTime.UtcNow.AddDays(-5), 200, employee2)
|
||||||
|
}));
|
||||||
|
_basePdfBuilder.Setup(x => x.AddHeader(It.IsAny<string>())).Returns(_basePdfBuilder.Object);
|
||||||
|
_basePdfBuilder.Setup(x => x.AddParagraph(It.IsAny<string>())).Returns(_basePdfBuilder.Object);
|
||||||
|
var countRows = 0;
|
||||||
|
(string, double) firstRow = default;
|
||||||
|
(string, double) secondRow = default;
|
||||||
|
_basePdfBuilder.Setup(x => x.AddPieChart(It.IsAny<string>(), It.IsAny<List<(string, double)>>()))
|
||||||
|
.Callback((string header, List<(string, double)> data) =>
|
||||||
|
{
|
||||||
|
countRows = data.Count;
|
||||||
|
firstRow = data[0];
|
||||||
|
secondRow = data[1];
|
||||||
|
})
|
||||||
|
.Returns(_basePdfBuilder.Object);
|
||||||
|
//Act
|
||||||
|
var data = await _reportContract.CreateDocumentSalaryByPeriodAsync(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow, CancellationToken.None);
|
||||||
|
//Assert
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(countRows, Is.EqualTo(2));
|
||||||
|
Assert.That(firstRow, Is.Not.EqualTo(default));
|
||||||
|
Assert.That(secondRow, Is.Not.EqualTo(default));
|
||||||
|
});
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(firstRow.Item1, Is.EqualTo(employee1.FIO));
|
||||||
|
Assert.That(firstRow.Item2, Is.EqualTo(2100));
|
||||||
|
Assert.That(secondRow.Item1, Is.EqualTo(employee2.FIO));
|
||||||
|
Assert.That(secondRow.Item2, Is.EqualTo(300));
|
||||||
|
});
|
||||||
|
_salaryStorageContract.Verify(x => x.GetListAsync(It.IsAny<DateTime>(), It.IsAny<DateTime>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_basePdfBuilder.Verify(x => x.AddHeader(It.IsAny<string>()), Times.Once);
|
||||||
|
_basePdfBuilder.Verify(x => x.AddParagraph(It.IsAny<string>()), Times.Once);
|
||||||
|
_basePdfBuilder.Verify(x => x.AddPieChart(It.IsAny<string>(), It.IsAny<List<(string, double)>>()), Times.Once);
|
||||||
|
_basePdfBuilder.Verify(x => x.Build(), Times.Once);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
using MagicCarpetContracts.Enums;
|
||||||
|
using MagicCarpetContracts.ViewModels;
|
||||||
|
using MagicCarpetTests.Infrastructure;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MagicCarpetTests.WebApiControllersTests;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
internal class ReportControllerTests : BaseWebApiControllerTest
|
||||||
|
{
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
MagicCarpetDbContext.RemoveToursFromDatabase();
|
||||||
|
MagicCarpetDbContext.RemoveSalesFromDatabase();
|
||||||
|
MagicCarpetDbContext.RemoveEmployeesFromDatabase();
|
||||||
|
MagicCarpetDbContext.RemovePostsFromDatabase();
|
||||||
|
MagicCarpetDbContext.RemoveSalariesFromDatabase();
|
||||||
|
MagicCarpetDbContext.RemoveClientsFromDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetHistories_WhenHaveRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var tourId1 = Guid.NewGuid().ToString();
|
||||||
|
var tourId2 = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var tour1 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourId1, "name1", "test1", TourType.Beach, 10);
|
||||||
|
var tour2 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourId2, "name2", "test1", TourType.Beach, 10);
|
||||||
|
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId1, 22, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId2, 21, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId1, 33, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId1, 32, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId2, 65, DateTime.UtcNow);
|
||||||
|
//Act
|
||||||
|
var response = await HttpClient.GetAsync("/api/report/GetHistories");
|
||||||
|
//Assert
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||||
|
var data = await GetModelFromResponseAsync<List<TourHistoryViewModel>>(response);
|
||||||
|
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task LoadHistories_WhenHaveRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var tourId1 = Guid.NewGuid().ToString();
|
||||||
|
var tourId2 = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
var tour1 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourId1, "name1", "test1", TourType.Beach, 10);
|
||||||
|
var tour2 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourId2, "name2", "test1", TourType.Beach, 10);
|
||||||
|
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId1, 22, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId2, 21, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId1, 33, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId1, 32, DateTime.UtcNow);
|
||||||
|
MagicCarpetDbContext.InsertTourHistoryToDatabaseAndReturn(tourId2, 65, DateTime.UtcNow);
|
||||||
|
|
||||||
|
//Act
|
||||||
|
var response = await HttpClient.GetAsync("/api/report/LoadHistories");
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||||
|
using var data = await response.Content.ReadAsStreamAsync();
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data.Length, Is.GreaterThan(0));
|
||||||
|
await AssertStreamAsync(response, "file.docx");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetSales_WhenHaveRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var employee = MagicCarpetDbContext.InsertEmployeeToDatabaseAndReturn();
|
||||||
|
var client = MagicCarpetDbContext.InsertClientToDatabaseAndReturn();
|
||||||
|
var tour1 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourName: "name 1");
|
||||||
|
var tour2 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourName: "name 2");
|
||||||
|
MagicCarpetDbContext.InsertSaleToDatabaseAndReturn(employee.Id, client.Id, tours: [(tour1.Id, 10, 1.1)]);
|
||||||
|
MagicCarpetDbContext.InsertSaleToDatabaseAndReturn(employee.Id, client.Id, tours: [(tour2.Id, 10, 1.1)]);
|
||||||
|
//Act
|
||||||
|
var response = await
|
||||||
|
HttpClient.GetAsync($"/api/report/getsales?fromDate={DateTime.UtcNow.AddDays(-1):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.UtcNow.AddDays(1):MM/dd/yyyy HH:mm:ss}");
|
||||||
|
//Assert
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||||
|
var data = await GetModelFromResponseAsync<List<SaleViewModel>>(response);
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(data, Has.Count.EqualTo(2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
[Test]
|
||||||
|
public async Task GetSales_WhenDateIsIncorrect_ShouldBadRequest_Test()
|
||||||
|
{
|
||||||
|
//Act
|
||||||
|
var response = await
|
||||||
|
HttpClient.GetAsync($"/api/report/getsales?fromDate={DateTime.UtcNow.AddDays(1):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.UtcNow.AddDays(-1):MM/dd/yyyy HH:mm:ss}");
|
||||||
|
//Assert
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task LoadSales_WhenHaveRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var employee = MagicCarpetDbContext.InsertEmployeeToDatabaseAndReturn();
|
||||||
|
var tour1 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourName: "name 1");
|
||||||
|
var tour2 = MagicCarpetDbContext.InsertTourToDatabaseAndReturn(tourName: "name 2");
|
||||||
|
MagicCarpetDbContext.InsertSaleToDatabaseAndReturn(employee.Id, tours: [(tour1.Id, 10, 1.1), (tour2.Id, 10, 1.1)]);
|
||||||
|
MagicCarpetDbContext.InsertSaleToDatabaseAndReturn(employee.Id, tours: [(tour1.Id, 10, 1.1)]);
|
||||||
|
//Act
|
||||||
|
var response = await HttpClient.GetAsync($"/api/report/loadsales?fromDate={DateTime.UtcNow.AddDays(-1):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.UtcNow.AddDays(1):MM/dd/yyyy HH:mm:ss}");
|
||||||
|
//Assert
|
||||||
|
await AssertStreamAsync(response, "file.xlsx");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task LoadSales_WhenDateIsIncorrect_ShouldBadRequest_Test()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await HttpClient.GetAsync(
|
||||||
|
$"/api/report/loadsales?" +
|
||||||
|
$"fromDate={DateTime.UtcNow.AddDays(1):MM/dd/yyyy HH:mm:ss}&" +
|
||||||
|
$"toDate={DateTime.UtcNow.AddDays(-1):MM/dd/yyyy HH:mm:ss}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetSalary_WhenHaveRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
|
||||||
|
var post = MagicCarpetDbContext.InsertPostToDatabaseAndReturn();
|
||||||
|
var employee1 =
|
||||||
|
MagicCarpetDbContext.InsertEmployeeToDatabaseAndReturn(fio: "fio 1", postId:
|
||||||
|
post.PostId);
|
||||||
|
var employee2 =
|
||||||
|
MagicCarpetDbContext.InsertEmployeeToDatabaseAndReturn(fio: "fio 2", postId:
|
||||||
|
post.PostId);
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee1.Id,
|
||||||
|
employeeSalary: 100, salaryDate: DateTime.UtcNow.AddDays(-10));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee1.Id,
|
||||||
|
employeeSalary: 1000, salaryDate: DateTime.UtcNow.AddDays(-5));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee1.Id,
|
||||||
|
employeeSalary: 200, salaryDate: DateTime.UtcNow.AddDays(5));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee2.Id,
|
||||||
|
employeeSalary: 500, salaryDate: DateTime.UtcNow.AddDays(-5));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee2.Id,
|
||||||
|
employeeSalary: 300, salaryDate: DateTime.UtcNow.AddDays(-3));
|
||||||
|
var response = await
|
||||||
|
HttpClient.GetAsync($"/api/report/getsalary?fromDate={DateTime.UtcNow.AddDays(-7):yyyy-MM-dd}&toDate={DateTime.UtcNow.AddDays(-1):yyyy-MM-dd}");
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||||
|
var data = await GetModelFromResponseAsync<List<EmployeeSalaryByPeriodViewModel>>(response);
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data, Has.Count.EqualTo(2));
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(data.First(x => x.EmployeeFIO ==
|
||||||
|
employee1.FIO).TotalSalary, Is.EqualTo(1000));
|
||||||
|
Assert.That(data.First(x => x.EmployeeFIO ==
|
||||||
|
employee2.FIO).TotalSalary, Is.EqualTo(800));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetSalary_WhenDateIsIncorrect_ShouldBadRequest_Test()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await HttpClient.GetAsync(
|
||||||
|
$"/api/report/getsalary?" +
|
||||||
|
$"fromDate={DateTime.UtcNow.AddDays(1):MM/dd/yyyy HH:mm:ss}&" +
|
||||||
|
$"toDate={DateTime.UtcNow.AddDays(-1):MM/dd/yyyy HH:mm:ss}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task LoadSalary_WhenHaveRecords_ShouldSuccess_Test()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
var post = MagicCarpetDbContext.InsertPostToDatabaseAndReturn();
|
||||||
|
var employee1 = MagicCarpetDbContext.InsertEmployeeToDatabaseAndReturn(fio: "fio 1", postId: post.PostId).AddPost(post);
|
||||||
|
var employee2 = MagicCarpetDbContext.InsertEmployeeToDatabaseAndReturn(fio: "fio 2", postId: post.PostId).AddPost(post);
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee1.Id, employeeSalary: 100, salaryDate: DateTime.UtcNow.AddDays(-10));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee1.Id, employeeSalary: 1000, salaryDate: DateTime.UtcNow.AddDays(-5));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee1.Id, employeeSalary: 200, salaryDate: DateTime.UtcNow.AddDays(5));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee2.Id, employeeSalary: 500, salaryDate: DateTime.UtcNow.AddDays(-5));
|
||||||
|
MagicCarpetDbContext.InsertSalaryToDatabaseAndReturn(employee2.Id, employeeSalary: 300, salaryDate: DateTime.UtcNow.AddDays(-3));
|
||||||
|
//Act
|
||||||
|
var response = await HttpClient.GetAsync($"/api/report/loadsalary?fromDate={DateTime.UtcNow.AddDays(-7):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.UtcNow.AddDays(-1):MM/dd/yyyy HH:mm:ss}");
|
||||||
|
//Assert
|
||||||
|
await AssertStreamAsync(response, "file.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task LoadSalary_WhenDateIsIncorrect_ShouldBadRequest_Test()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await HttpClient.GetAsync(
|
||||||
|
$"/api/report/loadsalary?" +
|
||||||
|
$"fromDate={DateTime.UtcNow.AddDays(1):MM/dd/yyyy HH:mm:ss}&" +
|
||||||
|
$"toDate={DateTime.UtcNow.AddDays(-1):MM/dd/yyyy HH:mm:ss}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task AssertStreamAsync(HttpResponseMessage response, string fileNameForSave = "")
|
||||||
|
{
|
||||||
|
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
|
||||||
|
using var data = await response.Content.ReadAsStreamAsync();
|
||||||
|
Assert.That(data, Is.Not.Null);
|
||||||
|
Assert.That(data.Length, Is.GreaterThan(0));
|
||||||
|
await SaveStreamAsync(data, fileNameForSave);
|
||||||
|
}
|
||||||
|
private static async Task SaveStreamAsync(Stream stream, string fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
stream.Position = 0;
|
||||||
|
using var fileStream = new FileStream(path, FileMode.OpenOrCreate);
|
||||||
|
await stream.CopyToAsync(fileStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,207 @@
|
|||||||
namespace MagicCarpetWebApi.Adapters;
|
using AutoMapper;
|
||||||
|
using MagicCarpetContracts.AdapterContracts;
|
||||||
|
using MagicCarpetContracts.AdapterContracts.OperationResponses;
|
||||||
|
using MagicCarpetContracts.BusinessLogicContracts;
|
||||||
|
using MagicCarpetContracts.DataModels;
|
||||||
|
using MagicCarpetContracts.Exceptions;
|
||||||
|
using MagicCarpetContracts.ViewModels;
|
||||||
|
|
||||||
public class ReportAdapter
|
namespace MagicCarpetWebApi.Adapters;
|
||||||
|
|
||||||
|
public class ReportAdapter : IReportAdapter
|
||||||
{
|
{
|
||||||
|
private readonly IReportContract _reportContract;
|
||||||
|
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
private readonly Mapper _mapper;
|
||||||
|
|
||||||
|
public ReportAdapter(IReportContract reportContract, ILogger logger)
|
||||||
|
{
|
||||||
|
_reportContract = reportContract;
|
||||||
|
_logger = logger;
|
||||||
|
var config = new MapperConfiguration(cfg =>
|
||||||
|
{
|
||||||
|
cfg.CreateMap<TourAndTourHistoryDataModel, TourAndTourHistoryViewModel>()
|
||||||
|
.ForMember(dest => dest.Histories, opt => opt.MapFrom(src => src.Histories))
|
||||||
|
.ForMember(dest => dest.Data, opt => opt.MapFrom(src => src.Data));
|
||||||
|
cfg.CreateMap<SaleDataModel, SaleViewModel>();
|
||||||
|
cfg.CreateMap<SaleTourDataModel, SaleTourViewModel>();
|
||||||
|
cfg.CreateMap<EmployeeSalaryByPeriodDataModel, EmployeeSalaryByPeriodViewModel>();
|
||||||
|
});
|
||||||
|
_mapper = new Mapper(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ReportOperationResponse> CreateDocumentToursHistoryAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return SendStream(await _reportContract.CreateDocumentToursHistoryAsync(ct), "histories.xslx");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "InvalidOperationException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (StorageException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "StorageException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception");
|
||||||
|
return
|
||||||
|
ReportOperationResponse.InternalServerError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ReportOperationResponse> CreateDocumentSalesByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return SendStream(await _reportContract.CreateDocumentSalesByPeriodAsync(dateStart, dateFinish, ct),
|
||||||
|
"sales.xslx");
|
||||||
|
}
|
||||||
|
catch (IncorrectDatesException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "IncorrectDatesException");
|
||||||
|
return ReportOperationResponse.BadRequest($"Incorrect dates: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "InvalidOperationException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (StorageException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "StorageException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception");
|
||||||
|
return
|
||||||
|
ReportOperationResponse.InternalServerError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ReportOperationResponse> GetDataToursHistoryAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ReportOperationResponse.OK([.. (await _reportContract.GetDataToursHistoryAsync(ct)).Select(x => _mapper.Map<TourAndTourHistoryViewModel>(x))]);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "InvalidOperationException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (StorageException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "StorageException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception");
|
||||||
|
return
|
||||||
|
ReportOperationResponse.InternalServerError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ReportOperationResponse> GetDataBySalesByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ReportOperationResponse.OK((await _reportContract.GetDataBySalesAsync(dateStart, dateFinish, ct)).Select(x =>
|
||||||
|
_mapper.Map<SaleViewModel>(x)).ToList());
|
||||||
|
}
|
||||||
|
catch (IncorrectDatesException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "IncorrectDatesException");
|
||||||
|
return ReportOperationResponse.BadRequest($"Incorrect dates: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "InvalidOperationException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (StorageException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "StorageException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception");
|
||||||
|
return
|
||||||
|
ReportOperationResponse.InternalServerError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReportOperationResponse SendStream(Stream stream, string fileName)
|
||||||
|
{
|
||||||
|
stream.Position = 0;
|
||||||
|
return ReportOperationResponse.OK(stream, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ReportOperationResponse> GetDataSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ReportOperationResponse.OK((await _reportContract.GetDataSalaryByPeriodAsync(dateStart, dateFinish, ct))
|
||||||
|
.Select(x => _mapper.Map<EmployeeSalaryByPeriodViewModel>(x)).ToList());
|
||||||
|
}
|
||||||
|
catch (IncorrectDatesException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "IncorrectDatesException");
|
||||||
|
return ReportOperationResponse.BadRequest($"Incorrect dates: {ex.Message}");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "InvalidOperationException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (StorageException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "StorageException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception");
|
||||||
|
return
|
||||||
|
ReportOperationResponse.InternalServerError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ReportOperationResponse> CreateDocumentSalaryByPeriodAsync(DateTime dateStart, DateTime dateFinish, CancellationToken ct)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return SendStream(await _reportContract.CreateDocumentSalaryByPeriodAsync(dateStart, dateFinish, ct), "salary.pdf");
|
||||||
|
}
|
||||||
|
catch (IncorrectDatesException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "IncorrectDatesException");
|
||||||
|
return ReportOperationResponse.BadRequest($"Incorrect dates: {ex.Message} ");
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "InvalidOperationException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (StorageException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "StorageException");
|
||||||
|
return ReportOperationResponse.InternalServerError($"Error while working with data storage: {ex.InnerException!.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Exception");
|
||||||
|
return
|
||||||
|
ReportOperationResponse.InternalServerError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,61 @@
|
|||||||
using MagicCarpetContracts.AdapterContracts;
|
using MagicCarpetContracts.AdapterContracts;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace MagicCarpetWebApi.Controllers;
|
namespace MagicCarpetWebApi.Controllers;
|
||||||
|
|
||||||
[Route("api/[controller]")]
|
[Authorize]
|
||||||
|
[Route("api/[controller]/[action]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class ReportController(IReportAdapter reportAdapter) : ControllerBase
|
public class ReportController(IReportAdapter adapter) : ControllerBase
|
||||||
{
|
{
|
||||||
private IReportAdapter _reportAdapter = reportAdapter;
|
private IReportAdapter _adapter = adapter;
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Consumes("application/json")]
|
||||||
|
public async Task<IActionResult> GetHistories(CancellationToken ct)
|
||||||
|
{
|
||||||
|
return (await
|
||||||
|
_adapter.GetDataToursHistoryAsync(ct)).GetResponse(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Consumes("application/octet-stream")]
|
||||||
|
public async Task<IActionResult> LoadHistories(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return (await
|
||||||
|
_adapter.CreateDocumentToursHistoryAsync(cancellationToken)).GetResponse(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Consumes("application/json")]
|
||||||
|
public async Task<IActionResult> GetSales(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return (await _adapter.GetDataBySalesByPeriodAsync(fromDate, toDate, cancellationToken)).GetResponse(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Consumes("application/octet-stream")]
|
||||||
|
public async Task<IActionResult> LoadSales(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return (await _adapter.CreateDocumentSalesByPeriodAsync(fromDate,
|
||||||
|
toDate, cancellationToken)).GetResponse(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Consumes("application/json")]
|
||||||
|
public async Task<IActionResult> GetSalary(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return (await _adapter.GetDataSalaryByPeriodAsync(fromDate, toDate,
|
||||||
|
cancellationToken)).GetResponse(Request, Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Consumes("application/octet-stream")]
|
||||||
|
public async Task<IActionResult> LoadSalary(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return (await _adapter.CreateDocumentSalaryByPeriodAsync(fromDate, toDate, cancellationToken)).GetResponse(Request, Response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MagicCarpetBusinessLogic.Implementations;
|
using MagicCarpetBusinessLogic.Implementations;
|
||||||
|
using MagicCarpetBusinessLogic.OfficePackage;
|
||||||
using MagicCarpetContracts.AdapterContracts;
|
using MagicCarpetContracts.AdapterContracts;
|
||||||
using MagicCarpetContracts.BuisnessLogicContracts;
|
using MagicCarpetContracts.BuisnessLogicContracts;
|
||||||
using MagicCarpetContracts.BusinessLogicContracts;
|
using MagicCarpetContracts.BusinessLogicContracts;
|
||||||
@@ -76,6 +77,9 @@ builder.Services.AddTransient<ISalaryAdapter, SalaryAdapter>();
|
|||||||
|
|
||||||
builder.Services.AddTransient<IReportContract, ReportContract>();
|
builder.Services.AddTransient<IReportContract, ReportContract>();
|
||||||
builder.Services.AddTransient<IReportAdapter, ReportAdapter>();
|
builder.Services.AddTransient<IReportAdapter, ReportAdapter>();
|
||||||
|
builder.Services.AddTransient<BaseWordBuilder, OpenXmlWordBuilder>();
|
||||||
|
builder.Services.AddTransient<BaseExcelBuilder, OpenXmlExcelBuilder>();
|
||||||
|
builder.Services.AddTransient<BasePdfBuilder, MigraDocPdfBuilder>();
|
||||||
|
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
|
|||||||
Reference in New Issue
Block a user