Compare commits
2 Commits
Task_8_Map
...
Task_7_Loc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b758aed6b | ||
|
|
5437a6569f |
422
TwoFromTheCasketContratcs/BaseLocalizationControllerTest.cs
Normal file
422
TwoFromTheCasketContratcs/BaseLocalizationControllerTest.cs
Normal file
@@ -0,0 +1,422 @@
|
||||
using TwoOfCasketDatabase;
|
||||
using TwoOfCasketTests.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Net;
|
||||
using TwoOfCasketContracts.BindingModels;
|
||||
using TwoOfCasketContracts.Enums;
|
||||
using TwoOfCasketDatabase.Models;
|
||||
using TwoOfCasketContracts.Infrastructure.PostConfigurations;
|
||||
|
||||
namespace TwoOfCasketTests.LocalizationTests;
|
||||
|
||||
internal abstract class BaseLocalizationControllerTest
|
||||
{
|
||||
protected abstract string GetLocale();
|
||||
|
||||
private WebApplicationFactory<Program> _webApplication;
|
||||
|
||||
protected HttpClient HttpClient { get; private set; }
|
||||
|
||||
protected static TwoOfCasketDbContext TwoOfCasketDbContext { get; private set; }
|
||||
|
||||
protected static readonly JsonSerializerOptions JsonSerializerOptions = new() { PropertyNameCaseInsensitive = true };
|
||||
|
||||
private static string _roomId;
|
||||
private static string _workerId;
|
||||
private static string _jobId;
|
||||
private static string _postId;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void OneTimeSetUp()
|
||||
{
|
||||
_webApplication = new CustomWebApplicationFactory<Program>();
|
||||
HttpClient = _webApplication
|
||||
.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.ConfigureTestServices(services =>
|
||||
{
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
loggerFactory.AddSerilog(new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json")
|
||||
.Build())
|
||||
.CreateLogger());
|
||||
services.AddSingleton(loggerFactory);
|
||||
});
|
||||
})
|
||||
.CreateClient();
|
||||
|
||||
var request = HttpClient.GetAsync("/login/user").GetAwaiter().GetResult();
|
||||
var data = request.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
HttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {data}");
|
||||
HttpClient.DefaultRequestHeaders.Add("Accept-Language", GetLocale());
|
||||
|
||||
TwoOfCasketDbContext = _webApplication.Services.GetRequiredService<TwoOfCasketDbContext>();
|
||||
TwoOfCasketDbContext.Database.EnsureDeleted();
|
||||
TwoOfCasketDbContext.Database.EnsureCreated();
|
||||
}
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_roomId = TwoOfCasketDbContext.InsertRoomToDatabaseAndReturn(name: "Room", emailClient: "bbbbbbb@mail.com").Id;
|
||||
_workerId = TwoOfCasketDbContext.InsertWorkerToDatabaseAndReturn(fio: "Worker").Id;
|
||||
_jobId = TwoOfCasketDbContext.InsertJobToDatabaseAndReturn(jobName: "Job").Id;
|
||||
_postId = TwoOfCasketDbContext.InsertPostToDatabaseAndReturn(postName: "Post").PostId;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
TwoOfCasketDbContext.RemoveSalariesFromDatabase();
|
||||
TwoOfCasketDbContext.RemoveWorksFromDatabase();
|
||||
TwoOfCasketDbContext.RemoveWorkersFromDatabase();
|
||||
TwoOfCasketDbContext.RemovePostsFromDatabase();
|
||||
TwoOfCasketDbContext.RemoveRoomsFromDatabase();
|
||||
TwoOfCasketDbContext.RemoveJobsFromDatabase();
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
public void OneTimeTearDown()
|
||||
{
|
||||
TwoOfCasketDbContext?.Database.EnsureDeleted();
|
||||
TwoOfCasketDbContext?.Dispose();
|
||||
HttpClient?.Dispose();
|
||||
_webApplication?.Dispose();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoadJobs_WhenHaveRecords_ShouldSuccess_Test()
|
||||
{
|
||||
// Arrange
|
||||
var job1 = TwoOfCasketDbContext.InsertJobToDatabaseAndReturn(jobName: "Job1");
|
||||
var job2 = TwoOfCasketDbContext.InsertJobToDatabaseAndReturn(jobName: "Job2");
|
||||
|
||||
TwoOfCasketDbContext.InsertJobHistoryToDatabaseAndReturn(job1.Id, 100.0);
|
||||
TwoOfCasketDbContext.InsertJobHistoryToDatabaseAndReturn(job1.Id, 150.0);
|
||||
TwoOfCasketDbContext.InsertJobHistoryToDatabaseAndReturn(job1.Id, 200.0, changeDate: DateTime.UtcNow.AddDays(-1));
|
||||
TwoOfCasketDbContext.InsertJobHistoryToDatabaseAndReturn(job2.Id, 100.0);
|
||||
TwoOfCasketDbContext.InsertJobHistoryToDatabaseAndReturn(job2.Id, 90.0);
|
||||
//Act
|
||||
var response = await HttpClient.GetAsync($"/api/report/loadjobs?fromDate={DateTime.Now.AddDays(-1):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.Now.AddDays(1):MM/dd/yyyy HH:mm:ss}");
|
||||
//Assert
|
||||
await AssertStreamAsync(response, $"file-{GetLocale()}.docx");
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task LoadWorks_WhenHaveRecords_ShouldSuccess_Test()
|
||||
{
|
||||
//Arrange
|
||||
var worker = TwoOfCasketDbContext.InsertWorkerToDatabaseAndReturn();
|
||||
var job1 = TwoOfCasketDbContext.InsertJobToDatabaseAndReturn(jobName: "name 1");
|
||||
var job2 = TwoOfCasketDbContext.InsertJobToDatabaseAndReturn(jobName: "name 2");
|
||||
TwoOfCasketDbContext.InsertWorkToDatabaseAndReturn(worker.Id, jobs: [(job1.Id, 10, 1.1), (job2.Id, 10, 1.1)]);
|
||||
TwoOfCasketDbContext.InsertWorkToDatabaseAndReturn(worker.Id, jobs: [(job1.Id, 10, 1.1)]);
|
||||
//Act
|
||||
var response = await HttpClient.GetAsync($"/api/report/loadworks?fromDate={DateTime.Now.AddDays(-1):MM/dd/yyyy HH:mm:ss} &toDate= {DateTime.Now.AddDays(1):MM/dd/yyyy HH:mm:ss}");
|
||||
//Assert
|
||||
await AssertStreamAsync(response, $"file-{GetLocale()}.xlsx");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoadSalary_WhenHaveRecords_ShouldSuccess_Test()
|
||||
{
|
||||
//Arrange
|
||||
var post = TwoOfCasketDbContext.InsertPostToDatabaseAndReturn();
|
||||
var worker1 = TwoOfCasketDbContext.InsertWorkerToDatabaseAndReturn(fio: "fio 1", postId: post.PostId).AddPost(post);
|
||||
var worker2 = TwoOfCasketDbContext.InsertWorkerToDatabaseAndReturn(fio: "fio 2", postId: post.PostId).AddPost(post);
|
||||
TwoOfCasketDbContext.InsertSalaryToDatabaseAndReturn(worker1.Id, workerSalary: 100, salaryDate: DateTime.UtcNow.AddDays(-10));
|
||||
TwoOfCasketDbContext.InsertSalaryToDatabaseAndReturn(worker1.Id, workerSalary: 1000, salaryDate: DateTime.UtcNow.AddDays(-5));
|
||||
TwoOfCasketDbContext.InsertSalaryToDatabaseAndReturn(worker1.Id, workerSalary: 200, salaryDate: DateTime.UtcNow.AddDays(5));
|
||||
TwoOfCasketDbContext.InsertSalaryToDatabaseAndReturn(worker2.Id, workerSalary: 500, salaryDate: DateTime.UtcNow.AddDays(-5));
|
||||
TwoOfCasketDbContext.InsertSalaryToDatabaseAndReturn(worker2.Id, workerSalary: 300, salaryDate: DateTime.UtcNow.AddDays(-3));
|
||||
//Act
|
||||
var response = await HttpClient.GetAsync($"/api/report/loadsalary?fromDate={DateTime.Now.AddDays(-7):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.Now.AddDays(-1):MM/dd/yyyy HH:mm:ss}");
|
||||
//Assert
|
||||
await AssertStreamAsync(response, $"file-{GetLocale()}.pdf");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
protected abstract string MessageElementNotFound();
|
||||
|
||||
[TestCase("rooms")]
|
||||
[TestCase("posts")]
|
||||
[TestCase("jobs/getrecord")]
|
||||
[TestCase("works/getrecord")]
|
||||
[TestCase("workers/getrecord")]
|
||||
public async Task Api_GetElement_NotFound_Test(string path)
|
||||
{
|
||||
//Act
|
||||
var response = await HttpClient.GetAsync($"/api/{path}/{Guid.NewGuid()}");
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await response.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageElementNotFound()));
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> TestDataElementExists()
|
||||
{
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateRoomBindingModel();
|
||||
TwoOfCasketDbContext.InsertRoomToDatabaseAndReturn(model.Id);
|
||||
return model;
|
||||
}, "rooms");
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreatePostModel();
|
||||
TwoOfCasketDbContext.InsertPostToDatabaseAndReturn(model.Id);
|
||||
return model;
|
||||
}, "posts");
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateJobModel();
|
||||
TwoOfCasketDbContext.InsertJobToDatabaseAndReturn(model.Id);
|
||||
return model;
|
||||
}, "jobs/register");
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateWorkerModel(_postId);
|
||||
TwoOfCasketDbContext.InsertWorkerToDatabaseAndReturn(model.Id, postId: _postId);
|
||||
return model;
|
||||
}, "workers/register");
|
||||
}
|
||||
|
||||
protected abstract string MessageElementExists();
|
||||
|
||||
[TestCaseSource(nameof(TestDataElementExists))]
|
||||
public async Task Api_Post_WhenHaveRecordWithSameId_ShouldBadRequest_Test(Func<object> createModel, string path)
|
||||
{
|
||||
//Arrange
|
||||
var model = createModel();
|
||||
//Act
|
||||
var response = await HttpClient.PostAsync($"/api/{path}", MakeContent(model));
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await response.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageElementExists()));
|
||||
}
|
||||
|
||||
private static IEnumerable<TestCaseData> TestDataIdIncorrect()
|
||||
{
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateWorkModel(workerId: _workerId, roomId: _roomId, jobId: _jobId);
|
||||
model.Id = "Id";
|
||||
return model;
|
||||
}, "works/work");
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateJobModel();
|
||||
model.Id = "Id";
|
||||
return model;
|
||||
}, "jobs/register");
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateWorkerModel(_postId);
|
||||
model.Id = "Id";
|
||||
return model;
|
||||
}, "workers/register");
|
||||
}
|
||||
|
||||
protected abstract string MessageElementIdIncorrect();
|
||||
|
||||
[TestCaseSource(nameof(TestDataIdIncorrect))]
|
||||
public async Task Api_Post_WhenDataIsIncorrect_ShouldBadRequest_Test(Func<object> createModel, string path)
|
||||
{
|
||||
//Arrange
|
||||
var model = createModel();
|
||||
//Act
|
||||
var responseWithIdIncorrect = await HttpClient.PostAsync($"/api/{path}", MakeContent(model));
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await responseWithIdIncorrect.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageElementIdIncorrect()));
|
||||
}
|
||||
|
||||
[TestCase("jobs/delete")]
|
||||
[TestCase("works/cancel")]
|
||||
[TestCase("posts")]
|
||||
[TestCase("workers/delete")]
|
||||
public async Task Api_DelElement_NotFound_Test(string path)
|
||||
{
|
||||
//Act
|
||||
var response = await HttpClient.DeleteAsync($"/api/{path}/{Guid.NewGuid()}");
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await response.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageElementNotFound()));
|
||||
}
|
||||
|
||||
private static PostBindingModel CreatePostModel(string? postId = null, string postName = "name", PostType postType = PostType.Plasterer, int salary = 100)
|
||||
=> new()
|
||||
{
|
||||
Id = postId ?? Guid.NewGuid().ToString(),
|
||||
PostName = postName,
|
||||
PostType = postType.ToString(),
|
||||
Salary = salary
|
||||
};
|
||||
|
||||
protected abstract string MessageValidationErrorIDIsEmpty();
|
||||
|
||||
private static IEnumerable<TestCaseData> TestDataValidationErrorIdIsEmpty()
|
||||
{
|
||||
yield return new TestCaseData(() =>
|
||||
{
|
||||
var model = CreatePostModel();
|
||||
model.Id = "";
|
||||
return model;
|
||||
}, "posts");
|
||||
yield return new TestCaseData(() =>
|
||||
{
|
||||
var model = CreateJobModel();
|
||||
model.Id = "";
|
||||
return model;
|
||||
}, "jobs/changeinfo/");
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateWorkerModel(_postId);
|
||||
model.Id = "";
|
||||
return model;
|
||||
}, "workers/changeinfo/");
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(TestDataValidationErrorIdIsEmpty))]
|
||||
public async Task Api_Put_ValidationError_IdIsEmpty_ShouldBadRequest_Test(Func<object> createModel, string path)
|
||||
{
|
||||
//Arrange
|
||||
var model = createModel();
|
||||
//Act
|
||||
var responseWithIdIncorrect = await HttpClient.PutAsync($"/api/{path}", MakeContent(model));
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await responseWithIdIncorrect.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageValidationErrorIDIsEmpty()));
|
||||
}
|
||||
|
||||
protected abstract string MessageValidationErrorIDIsNotGuid();
|
||||
|
||||
private static IEnumerable<TestCaseData> TestDataValidationErrorIdIsNotGuid()
|
||||
{
|
||||
yield return new TestCaseData(() =>
|
||||
{
|
||||
var model = CreatePostModel();
|
||||
model.Id = "id";
|
||||
return model;
|
||||
}, "posts");
|
||||
yield return new TestCaseData(() =>
|
||||
{
|
||||
var model = CreateJobModel();
|
||||
model.Id = "id";
|
||||
return model;
|
||||
}, "jobs/changeinfo/");
|
||||
yield return new TestCaseData(() => {
|
||||
var model = CreateWorkerModel(_postId);
|
||||
model.Id = "id";
|
||||
return model;
|
||||
}, "workers/changeinfo/");
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(TestDataValidationErrorIdIsNotGuid))]
|
||||
public async Task Api_Put_ValidationError_IIsNotGuid_ShouldBadRequest_Test(Func<object> createModel, string path)
|
||||
{
|
||||
//Arrange
|
||||
var model = createModel();
|
||||
//Act
|
||||
var responseWithIdIncorrect = await HttpClient.PutAsync($"/api/{path}", MakeContent(model));
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await responseWithIdIncorrect.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageValidationErrorIDIsNotGuid()));
|
||||
}
|
||||
|
||||
protected abstract string MessageElementValidationErrorPostNameEmpty();
|
||||
|
||||
[TestCase("posts")]
|
||||
public async Task Api_Put_ValidationError_PostNameIsEmpty_ShouldBadRequest_Test(string path)
|
||||
{
|
||||
//Arrange
|
||||
var model = CreatePostModel();
|
||||
model.PostName = "";
|
||||
//Act
|
||||
var responseWithIdIncorrect = await HttpClient.PutAsync($"/api/{path}", MakeContent(model));
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await responseWithIdIncorrect.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageElementValidationErrorPostNameEmpty()));
|
||||
}
|
||||
|
||||
protected abstract string MessageElementValidationErrorFIOIsEmpty();
|
||||
|
||||
[TestCase("workers/changeinfo/")]
|
||||
public async Task Api_Put_ValidationError_FIOIsEmpty_ShouldBadRequest_Test(string path)
|
||||
{
|
||||
//Arrange
|
||||
var model = CreateWorkerModel(_postId);
|
||||
model.FIO = "";
|
||||
//Act
|
||||
var responseWithIdIncorrect = await HttpClient.PutAsync($"/api/{path}", MakeContent(model));
|
||||
//Assert
|
||||
Assert.That(JToken.Parse(await responseWithIdIncorrect.Content.ReadAsStringAsync()).ToString(), Does.StartWith(MessageElementValidationErrorFIOIsEmpty()));
|
||||
}
|
||||
|
||||
private static async Task<T?> GetModelFromResponseAsync<T>(HttpResponseMessage response) =>
|
||||
JsonSerializer.Deserialize<T>(await response.Content.ReadAsStringAsync(), JsonSerializerOptions);
|
||||
|
||||
private static StringContent MakeContent(object model) =>
|
||||
new(JsonSerializer.Serialize(model), Encoding.UTF8, "application/json");
|
||||
|
||||
private static RoomBindingModel CreateRoomBindingModel(string? id = null, string name = "name", string emailClient = "aaaaaa@gmail.com", double size = 10) =>
|
||||
new()
|
||||
{
|
||||
Id = id ?? Guid.NewGuid().ToString(),
|
||||
Name = name,
|
||||
EmailClient = emailClient,
|
||||
Size = size
|
||||
};
|
||||
|
||||
private static JobBindingModel CreateJobModel(string? id = null, string jobName = "name", WorkType jobType = WorkType.Painting, double price = 1)
|
||||
=> new()
|
||||
{
|
||||
Id = id ?? Guid.NewGuid().ToString(),
|
||||
JobName = jobName,
|
||||
WorkType = jobType.ToString(),
|
||||
Price = price
|
||||
};
|
||||
|
||||
private static WorkBindingModel CreateWorkModel(string workerId, string? roomId, string jobId, string? id = null, int sizeWork = 1, double price = 1.1)
|
||||
{
|
||||
var workId = id ?? Guid.NewGuid().ToString();
|
||||
return new()
|
||||
{
|
||||
Id = workId,
|
||||
WorkerId = workerId,
|
||||
RoomId = roomId,
|
||||
Jobs = [new WorkJobBindingModel { WorkId = workId, JobId = jobId, SizeWork = sizeWork, Price = price }]
|
||||
};
|
||||
}
|
||||
|
||||
private static WorkerBindingModel CreateWorkerModel(string postId, string? id = null, string fio = "fio", DateTime? birthDate = null, DateTime? employmentDate = null, string? configuration = null)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Id = id ?? Guid.NewGuid().ToString(),
|
||||
FIO = fio,
|
||||
BirthDate = birthDate ?? DateTime.UtcNow.AddYears(-22),
|
||||
EmploymentDate = employmentDate ?? DateTime.UtcNow.AddDays(-5),
|
||||
ConfigurationJson = configuration ?? JsonSerializer.Serialize(new PostConfiguration() { Rate = 10 }),
|
||||
PostId = postId
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,8 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
var services = await GetDataServicesWithHistoryAsync(dateStart, dateFinish, ct);
|
||||
var builder = _baseWordBuilder
|
||||
.AddHeader(_localizer["ReportServicesHeader"].Value)
|
||||
.AddParagraph(_localizer["ReportServicesPeriod", dateStart.ToString("dd.MM.yyyy"), dateFinish.ToString("dd.MM.yyyy")].Value)
|
||||
.AddParagraph(_localizer["ReportServicesGenerated", DateTime.Now.ToString("dd.MM.yyyy HH:mm")].Value);
|
||||
.AddParagraph(_localizer["ReportServicesPeriod", dateStart.ToUniversalTime().ToString(), dateFinish.ToString("dd.MM.yyyy")].Value)
|
||||
.AddParagraph(_localizer["ReportServicesGenerated", dateStart.ToUniversalTime().ToString()].Value);
|
||||
|
||||
if (!services.Any())
|
||||
{
|
||||
@@ -55,7 +55,6 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
// Создаем таблицу с заголовками
|
||||
var tableData = new List<string[]>
|
||||
{
|
||||
new[] {
|
||||
@@ -71,7 +70,7 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
{
|
||||
if (service.History.Any())
|
||||
{
|
||||
// Если есть история изменений, добавляем каждое изменение как отдельную строку
|
||||
|
||||
var sortedHistory = service.History.OrderByDescending(h => h.ChangeDate).ToList();
|
||||
|
||||
for (int i = 0; i < sortedHistory.Count; i++)
|
||||
@@ -79,29 +78,27 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
var history = sortedHistory[i];
|
||||
tableData.Add(new[]
|
||||
{
|
||||
i == 0 ? service.ServiceName : "", // Название услуги только в первой строке
|
||||
i == 0 ? service.ServiceType.ToString() : "", // Тип только в первой строке
|
||||
i == 0 ? service.Price.ToString("C") : "", // Текущая цена только в первой строке
|
||||
history.ChangeDate.ToString("dd.MM.yyyy HH:mm"),
|
||||
i == 0 ? service.ServiceName : "",
|
||||
i == 0 ? service.ServiceType.ToString() : "",
|
||||
i == 0 ? service.Price.ToString("C") : "",
|
||||
history.ChangeDate.ToUniversalTime().ToString(),
|
||||
history.OldPrice.ToString("C")
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если истории нет, добавляем одну строку с прочерками
|
||||
tableData.Add(new[]
|
||||
{
|
||||
service.ServiceName,
|
||||
service.ServiceType.ToString(),
|
||||
service.Price.ToString("C"),
|
||||
_localizer["ReportServicesNoChanges"].Value, // Прочерк для даты изменения
|
||||
_localizer["ReportServicesNoChanges"].Value // Прочерк для старой цены
|
||||
_localizer["ReportServicesNoChanges"].Value,
|
||||
_localizer["ReportServicesNoChanges"].Value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем таблицу с настройками ширины колонок
|
||||
builder.AddTable(new[] { 2500, 1500, 1500, 2000, 1500 }, tableData);
|
||||
|
||||
return builder.Build();
|
||||
@@ -123,7 +120,6 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
// Получаем информацию о мастере и услугах для этого заказа
|
||||
var orderDetails = await GetOrderDetailsAsync(order.Id, ct);
|
||||
|
||||
if (orderDetails.Any())
|
||||
@@ -144,7 +140,6 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если нет деталей, показываем только основную информацию о заказе
|
||||
tableData.Add(new[]
|
||||
{
|
||||
order.Date.ToString("dd.MM.yyyy"),
|
||||
@@ -178,12 +173,10 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
.Build();
|
||||
}
|
||||
|
||||
// Группируем данные по мастерам для диаграммы
|
||||
var groupedData = data.GroupBy(x => x.MasterFIO)
|
||||
.Select(g => new { MasterFIO = g.Key, TotalSalary = g.Sum(x => x.TotalSalary) })
|
||||
.ToList();
|
||||
|
||||
// Создаем таблицу с данными о зарплатах
|
||||
var tableData = new List<string[]>
|
||||
{
|
||||
new[] {
|
||||
@@ -206,9 +199,9 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
|
||||
return _basePdfBuilder
|
||||
.AddHeader(_localizer["ReportSalaryHeader"].Value)
|
||||
.AddParagraph(_localizer["ReportSalaryPeriod", dateStart.ToString("dd.MM.yyyy"), dateFinish.ToString("dd.MM.yyyy")].Value)
|
||||
.AddParagraph(_localizer["ReportSalaryPeriod", dateStart.ToUniversalTime().ToString(), dateStart.ToUniversalTime().ToString()].Value)
|
||||
.AddPieChart(_localizer["ReportSalaryAccruals"].Value, groupedData.Select(x => (x.MasterFIO, x.TotalSalary)).ToList())
|
||||
.AddParagraph("") // Отступ между диаграммой и таблицей
|
||||
.AddParagraph("")
|
||||
.AddTable(new[] { 200, 150, 150 }, tableData)
|
||||
.Build();
|
||||
}
|
||||
@@ -239,7 +232,6 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
|
||||
try
|
||||
{
|
||||
// Получаем все заказы за указанный период
|
||||
var orders = await _orderStorageContract.GetListAsync(dateStart, dateFinish, ct);
|
||||
return orders.OrderBy(o => o.Date).ToList();
|
||||
}
|
||||
@@ -259,7 +251,6 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
var salaries = await _salaryStorageContract.GetListAsync(dateStart, dateFinish, ct) ?? new List<SalaryDataModel>();
|
||||
var masters = _masterStorageContract.GetList(onlyActive: true) ?? new List<MasterDataModel>();
|
||||
|
||||
// Возвращаем отдельные записи зарплаты вместо агрегированных данных
|
||||
var salaryRecords = salaries.Select(salary =>
|
||||
{
|
||||
var master = masters.FirstOrDefault(m => m.Id == salary.MasterId);
|
||||
@@ -282,17 +273,14 @@ internal class ReportContract(IServiceStorageContract serviceStorageContract, IM
|
||||
throw new IncorrectDatesException(dateStart, dateFinish, _localizer);
|
||||
}
|
||||
|
||||
// Получаем все услуги
|
||||
var services = _serviceStorageContract.GetList() ?? new List<ServiceDataModel>();
|
||||
|
||||
var result = new List<ServiceWithHistoryDataModel>();
|
||||
|
||||
foreach (var service in services)
|
||||
{
|
||||
// Получаем историю изменений для данной услуги
|
||||
var serviceHistories = _serviceStorageContract.GetHistoryByServiceId(service.Id) ?? new List<ServiceHistoryDataModel>();
|
||||
|
||||
// Фильтруем историю по периоду
|
||||
var filteredHistories = serviceHistories
|
||||
.Where(h => h.ChangeDate >= dateStart && h.ChangeDate <= dateFinish)
|
||||
.OrderByDescending(h => h.ChangeDate)
|
||||
|
||||
@@ -75,7 +75,7 @@ internal class OpenXmlExcelBuilder : BaseExcelBuilder
|
||||
}
|
||||
for (var j = 0; j < data.Last().Length; ++j)
|
||||
{
|
||||
CreateCell(j, _rowIndex, data.Last()[j], StyleIndex.BoldTextWithBorder);
|
||||
CreateCell(j, _rowIndex, data.Last()[j], StyleIndex.SimpleTextWithBorder);
|
||||
}
|
||||
_rowIndex++;
|
||||
return this;
|
||||
|
||||
@@ -110,12 +110,22 @@ internal abstract class BaseLocalizationControllerTest
|
||||
RoomType = RoomType.Industrial
|
||||
}).Entity.Id;
|
||||
TwoFromTheCasketDbContext.SaveChanges();
|
||||
|
||||
TwoFromTheCasketDbContext.ServiceOrders.Add(new ServiceOrder
|
||||
{
|
||||
OrderId = _orderId,
|
||||
ServiceId = _serviceId,
|
||||
MasterId = _masterId,
|
||||
TimeOfWorking = 2
|
||||
});
|
||||
TwoFromTheCasketDbContext.SaveChanges();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
TwoFromTheCasketDbContext!.Salaries.RemoveRange(TwoFromTheCasketDbContext.Salaries);
|
||||
TwoFromTheCasketDbContext!.ServiceOrders.RemoveRange(TwoFromTheCasketDbContext.ServiceOrders);
|
||||
TwoFromTheCasketDbContext.Salaries.RemoveRange(TwoFromTheCasketDbContext.Salaries);
|
||||
TwoFromTheCasketDbContext.Orders.RemoveRange(TwoFromTheCasketDbContext.Orders);
|
||||
TwoFromTheCasketDbContext.Masters.RemoveRange(TwoFromTheCasketDbContext.Masters);
|
||||
TwoFromTheCasketDbContext.Posts.RemoveRange(TwoFromTheCasketDbContext.Posts);
|
||||
@@ -135,7 +145,7 @@ internal abstract class BaseLocalizationControllerTest
|
||||
[Test]
|
||||
public async Task LoadSalary_WhenHaveRecords_ShouldSuccess_Test()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
TwoFromTheCasketDbContext!.Salaries.Add(new Salary
|
||||
{
|
||||
MasterId = _masterId,
|
||||
@@ -152,10 +162,9 @@ internal abstract class BaseLocalizationControllerTest
|
||||
});
|
||||
TwoFromTheCasketDbContext.SaveChanges();
|
||||
|
||||
// Act
|
||||
var response = await HttpClient.GetAsync($"/api/report/loadsalary?fromDate={DateTime.Now.AddDays(-15):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.Now.AddDays(1):MM/dd/yyyy HH:mm:ss}");
|
||||
|
||||
// Assert
|
||||
|
||||
await AssertStreamAsync(response, $"salary-report-{GetLocale()}.pdf");
|
||||
}
|
||||
|
||||
@@ -163,24 +172,7 @@ internal abstract class BaseLocalizationControllerTest
|
||||
public async Task LoadOrders_WhenHaveRecords_ShouldSuccess_Test()
|
||||
{
|
||||
// Arrange
|
||||
var order1 = TwoFromTheCasketDbContext!.Orders.Add(new Order
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Date = DateTime.UtcNow.AddDays(-5),
|
||||
Status = StatusType.Ready,
|
||||
RoomType = RoomType.Social
|
||||
}).Entity;
|
||||
|
||||
var order2 = TwoFromTheCasketDbContext.Orders.Add(new Order
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Date = DateTime.UtcNow.AddDays(-3),
|
||||
Status = StatusType.InProcess,
|
||||
RoomType = RoomType.Residential
|
||||
}).Entity;
|
||||
|
||||
TwoFromTheCasketDbContext.SaveChanges();
|
||||
|
||||
|
||||
// Act
|
||||
var response = await HttpClient.GetAsync($"/api/report/loadorders?fromDate={DateTime.Now.AddDays(-10):MM/dd/yyyy HH:mm:ss}&toDate={DateTime.Now.AddDays(1):MM/dd/yyyy HH:mm:ss}");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user