From e50274b7366dc4cfa7b448d510fadcf030e004b6 Mon Sep 17 00:00:00 2001 From: Maciej Maciejewski Date: Mon, 11 Nov 2024 22:49:15 +0100 Subject: [PATCH 1/4] dodanie start, stop dnia pracy, generowanie PDF --- Controllers/PdfController.cs | 113 +++++++++++++++++++++++++++++++ Controllers/WorkDayController.cs | 55 +++++++++++++++ FirmTracker-Server.csproj | 1 + Program.cs | 5 +- nHIbernate/PdfData.cs | 98 +++++++++++++++++++++++++++ 5 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 Controllers/PdfController.cs create mode 100644 Controllers/WorkDayController.cs create mode 100644 nHIbernate/PdfData.cs diff --git a/Controllers/PdfController.cs b/Controllers/PdfController.cs new file mode 100644 index 0000000..b9a7229 --- /dev/null +++ b/Controllers/PdfController.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using FirmTracker_Server.nHibernate; +using FirmTracker_Server.nHibernate.Expenses; +using Microsoft.AspNetCore.Mvc; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace FirmTracker_Server.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class PdfController : ControllerBase + { + private readonly IExpenseRepository _expenseRepository; + + public PdfController(IExpenseRepository expenseRepository) + { + _expenseRepository = expenseRepository; + } + + [HttpGet("download")] + public IActionResult DownloadExpenseReport() + { + // Fetch expense data from the repository + List expenses = _expenseRepository.GetAllExpenses(); + + // Generate the PDF file + byte[] pdfBytes = GeneratePdf(expenses); + string date = DateTime.Now.ToString("yyyy-MM-dd"); + string fileName = $"ExpenseReport_{date}.pdf"; + + // Return the PDF as a downloadable file + return File(pdfBytes, "application/pdf", fileName); + } + + private byte[] GeneratePdf(List expenses) + { + using (var ms = new MemoryStream()) + { + // Calculate total and average expenses + decimal totalExpenses = expenses.Sum(e => e.Value); + decimal averageExpense = expenses.Count > 0 ? totalExpenses / expenses.Count : 0; + + // Define the document using QuestPDF + Document.Create(container => + { + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(2, Unit.Centimetre); + page.PageColor(Colors.White); + page.DefaultTextStyle(x => x.FontSize(12)); + + // Main header + page.Header() + .Text("Expense Report") + .FontSize(20) + .SemiBold() + .AlignCenter(); + + // Summary section + page.Content().PaddingVertical(1, Unit.Centimetre).Column(column => + { + column.Spacing(10); + + column.Item().Row(row => + { + row.RelativeItem().Text($"Total Expenses: {totalExpenses:C}").FontSize(14).Bold(); + row.RelativeItem().Text($"Average Expense: {averageExpense:C}").FontSize(14).Bold(); + }); + + column.Item().Text("Expense Details").FontSize(16).Underline(); + + // Add a table header + column.Item().Row(row => + { + row.RelativeItem().Text("Date").SemiBold(); + row.RelativeItem().Text("Value").SemiBold(); + row.RelativeItem().Text("Description").SemiBold(); + }); + + // Populate table rows with expense data + foreach (var expense in expenses) + { + column.Item().Row(row => + { + row.RelativeItem().Text(expense.Date.ToString("yyyy-MM-dd")); + row.RelativeItem().Text(expense.Value.ToString("C")); + row.RelativeItem().Text(expense.Description); + }); + } + }); + + // Footer with generation date + page.Footer() + .AlignCenter() + .Text(text => + { + text.Span("Generated on "); + text.Span(DateTime.Now.ToString("yyyy-MM-dd")).SemiBold(); + }); + }); + }).GeneratePdf(ms); + + return ms.ToArray(); + } + } + } +} diff --git a/Controllers/WorkDayController.cs b/Controllers/WorkDayController.cs new file mode 100644 index 0000000..1abfed7 --- /dev/null +++ b/Controllers/WorkDayController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace YourNamespace.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class WorkdayController : ControllerBase + { + // In-memory storage for simplicity, where the key is the userId. + private static readonly ConcurrentDictionary WorkStartTimes = new ConcurrentDictionary(); + + // Get the current status of the user's workday (started or not) + [HttpGet("status/{userId}")] + public IActionResult GetWorkdayStatus(string userId) + { + if (WorkStartTimes.TryGetValue(userId, out DateTime? startTime)) + { + if (startTime.HasValue) + { + return Ok(new { status = "started", startTime = startTime }); + } + else + { + return Ok(new { status = "stopped" }); + } + } + else + { + return NotFound(new { message = "User not found" }); + } + } + + // Start or stop the user's workday by toggling the start/stop state + [HttpPost("toggle/{userId}")] + public IActionResult ToggleWorkday(string userId) + { + // If the workday has already started, stop it, otherwise start it + if (WorkStartTimes.ContainsKey(userId) && WorkStartTimes[userId].HasValue) + { + // Stop the workday + WorkStartTimes[userId] = null; + return Ok(new { status = "stopped" }); + } + else + { + // Start the workday + WorkStartTimes[userId] = DateTime.Now; + return Ok(new { status = "started", startTime = WorkStartTimes[userId] }); + } + } + } +} diff --git a/FirmTracker-Server.csproj b/FirmTracker-Server.csproj index 91768c3..05849f9 100644 --- a/FirmTracker-Server.csproj +++ b/FirmTracker-Server.csproj @@ -29,6 +29,7 @@ + diff --git a/Program.cs b/Program.cs index 2c10c9c..adedab0 100644 --- a/Program.cs +++ b/Program.cs @@ -37,6 +37,7 @@ using FirmTracker_Server.Middleware; using FirmTracker_Server.Services; using System.Reflection; using FirmTracker_Server.Mappings; +using NuGet.Packaging; namespace FirmTracker_Server @@ -69,7 +70,8 @@ namespace FirmTracker_Server TestClass test = new TestClass(); test.AddTestProduct(); - + QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community; + builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", @@ -173,6 +175,7 @@ namespace FirmTracker_Server services.AddScoped(); services.AddScoped(); services.AddScoped, PasswordHasher>(); + services.AddScoped(); services.AddMvc(); } diff --git a/nHIbernate/PdfData.cs b/nHIbernate/PdfData.cs new file mode 100644 index 0000000..3028bf4 --- /dev/null +++ b/nHIbernate/PdfData.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.Linq; +using FirmTracker_Server.nHibernate.Expenses; +using NHibernate; + +namespace FirmTracker_Server.nHibernate +{ + public interface IExpenseRepository + { + List GetAllExpenses(); + Expense GetExpense(int expenseId); + void AddExpense(Expense expense); + void UpdateExpense(Expense expense); + void DeleteExpense(int expenseId); + } + + public class ExpenseRepository : IExpenseRepository + { + // Retrieve all expenses + public List GetAllExpenses() + { + using (var session = SessionFactory.OpenSession()) + { + return session.Query().ToList(); + } + } + + // Retrieve a specific expense by ID + public Expense GetExpense(int expenseId) + { + using (var session = SessionFactory.OpenSession()) + { + return session.Get(expenseId); + } + } + + // Add a new expense + public void AddExpense(Expense expense) + { + using (var session = SessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + try + { + session.Save(expense); + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + } + + // Update an existing expense + public void UpdateExpense(Expense expense) + { + using (var session = SessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + try + { + session.Update(expense); + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + } + + // Delete an expense by ID + public void DeleteExpense(int expenseId) + { + using (var session = SessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + try + { + var expense = session.Get(expenseId); + if (expense != null) + { + session.Delete(expense); + } + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + } + } +} From 717eb6ed1fedc07a8d536b342d86f56421f1f210 Mon Sep 17 00:00:00 2001 From: Maciej Maciejewski Date: Wed, 13 Nov 2024 20:44:01 +0100 Subject: [PATCH 2/4] =?UTF-8?q?dodanie=20startu=20dnia=20pracy=20u=C5=BCyt?= =?UTF-8?q?kownika=20po=20ID=20usera?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controllers/WorkDayController.cs | 81 +++++++++++++++++--------------- Entities/User.cs | 2 +- Models/EmployeeDto.cs | 11 +++++ Models/Workday.cs | 15 ++++++ Program.cs | 2 + Services/UserService.cs | 4 +- nHIbernate/SessionFactory.cs | 3 +- nHIbernate/UserMapping.cs | 2 +- nHIbernate/Workday.cs | 12 +++++ nHIbernate/WorkdayMapping.cs | 15 ++++++ nHIbernate/WorkdayRepository.cs | 41 ++++++++++++++++ 11 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 Models/EmployeeDto.cs create mode 100644 Models/Workday.cs create mode 100644 nHIbernate/Workday.cs create mode 100644 nHIbernate/WorkdayMapping.cs create mode 100644 nHIbernate/WorkdayRepository.cs diff --git a/Controllers/WorkDayController.cs b/Controllers/WorkDayController.cs index 1abfed7..4c4cc6e 100644 --- a/Controllers/WorkDayController.cs +++ b/Controllers/WorkDayController.cs @@ -1,55 +1,58 @@ -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Concurrent; -using System.Linq; +/* + * This file is part of FirmTracker - Server. + * + * FirmTracker - Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FirmTracker - Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FirmTracker - Server. If not, see . + */ -namespace YourNamespace.Controllers +using FirmTracker_Server.nHibernate; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Security.Claims; + +namespace FirmTracker_Server.Controllers { [Route("api/[controller]")] [ApiController] + [Authorize] public class WorkdayController : ControllerBase { - // In-memory storage for simplicity, where the key is the userId. - private static readonly ConcurrentDictionary WorkStartTimes = new ConcurrentDictionary(); + private readonly WorkdayRepository _workdayCRUD; - // Get the current status of the user's workday (started or not) - [HttpGet("status/{userId}")] - public IActionResult GetWorkdayStatus(string userId) + public WorkdayController() { - if (WorkStartTimes.TryGetValue(userId, out DateTime? startTime)) + _workdayCRUD = new WorkdayRepository(); // Instantiate directly (no DI in this example) + } + + // Endpoint to start a workday + [HttpPost("start")] + [Authorize(Roles = Roles.Admin + "," + Roles.User)] + public IActionResult StartWorkday() + { + try { - if (startTime.HasValue) - { - return Ok(new { status = "started", startTime = startTime }); - } - else - { - return Ok(new { status = "stopped" }); - } + var userIdString = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; + int userId = int.Parse(userIdString); + _workdayCRUD.StartWorkday(userId); + return Ok(new { status = "started", userId }); } - else + catch (Exception ex) { - return NotFound(new { message = "User not found" }); + return BadRequest(new { message = "An error occurred while starting the workday.", error = ex.Message }); } } - // Start or stop the user's workday by toggling the start/stop state - [HttpPost("toggle/{userId}")] - public IActionResult ToggleWorkday(string userId) - { - // If the workday has already started, stop it, otherwise start it - if (WorkStartTimes.ContainsKey(userId) && WorkStartTimes[userId].HasValue) - { - // Stop the workday - WorkStartTimes[userId] = null; - return Ok(new { status = "stopped" }); - } - else - { - // Start the workday - WorkStartTimes[userId] = DateTime.Now; - return Ok(new { status = "started", startTime = WorkStartTimes[userId] }); - } - } + } } diff --git a/Entities/User.cs b/Entities/User.cs index 1282962..6e2619c 100644 --- a/Entities/User.cs +++ b/Entities/User.cs @@ -2,7 +2,7 @@ { public class User { - public virtual int Id { get; set; } + public virtual int UserId { get; set; } public virtual string Login { get; set; } public virtual string Email { get; set; } public virtual string Role { get; set; } = "User"; diff --git a/Models/EmployeeDto.cs b/Models/EmployeeDto.cs new file mode 100644 index 0000000..7a30e5b --- /dev/null +++ b/Models/EmployeeDto.cs @@ -0,0 +1,11 @@ +using FirmTracker_Server.Controllers; + +namespace FirmTracker_Server.Models +{ + public class EmployeeDto + { + public virtual int Id { get; set; } + public virtual string email { get; set; } + + } +} diff --git a/Models/Workday.cs b/Models/Workday.cs new file mode 100644 index 0000000..a93638d --- /dev/null +++ b/Models/Workday.cs @@ -0,0 +1,15 @@ +using FirmTracker_Server.Entities; +using System; + +namespace YourNamespace.Models +{ + public class Workday + { + public virtual int Id { get; set; } + public virtual DateTime? StartTime { get; set; } + public virtual DateTime? EndTime { get; set; } + + // Many-to-One relationship to the User entity + public virtual User User { get; set; } + } +} diff --git a/Program.cs b/Program.cs index adedab0..4c75fbc 100644 --- a/Program.cs +++ b/Program.cs @@ -40,6 +40,7 @@ using FirmTracker_Server.Mappings; using NuGet.Packaging; + namespace FirmTracker_Server { internal static class Program @@ -176,6 +177,7 @@ namespace FirmTracker_Server services.AddScoped(); services.AddScoped, PasswordHasher>(); services.AddScoped(); + // services.AddScoped(); services.AddMvc(); } diff --git a/Services/UserService.cs b/Services/UserService.cs index 4b5cc53..a846b49 100644 --- a/Services/UserService.cs +++ b/Services/UserService.cs @@ -69,7 +69,7 @@ namespace FirmTracker_Server.Services { session.Save(user); transaction.Commit(); - return user.Id; + return user.UserId; } catch { @@ -128,7 +128,7 @@ namespace FirmTracker_Server.Services // Generate JWT token var claims = new List() { - new(ClaimTypes.NameIdentifier, user.Id.ToString()), + new(ClaimTypes.NameIdentifier, user.UserId.ToString()), new(ClaimTypes.Role, user.Role) }; diff --git a/nHIbernate/SessionFactory.cs b/nHIbernate/SessionFactory.cs index 6218b36..1a3deff 100644 --- a/nHIbernate/SessionFactory.cs +++ b/nHIbernate/SessionFactory.cs @@ -55,7 +55,8 @@ namespace FirmTracker_Server.nHibernate .AddFromAssemblyOf() .AddFromAssemblyOf() .AddFromAssemblyOf() - .AddFromAssemblyOf(); + .AddFromAssemblyOf() + .AddFromAssemblyOf(); }) .ExposeConfiguration(cfg => new SchemaExport(cfg).Create(true, true)) //SchemaUpdate . Execute dla only update diff --git a/nHIbernate/UserMapping.cs b/nHIbernate/UserMapping.cs index 44c7e7b..8f1487e 100644 --- a/nHIbernate/UserMapping.cs +++ b/nHIbernate/UserMapping.cs @@ -7,7 +7,7 @@ public class UserMapping : ClassMap { Table("Users"); // The name of your table in the database - Id(x => x.Id); // Mapping the Id property + Id(x => x.UserId); // Mapping the Id property Map(x => x.Email); // Mapping other properties Map(x => x.PassHash); Map(x => x.Role); diff --git a/nHIbernate/Workday.cs b/nHIbernate/Workday.cs new file mode 100644 index 0000000..7f819cc --- /dev/null +++ b/nHIbernate/Workday.cs @@ -0,0 +1,12 @@ +using FirmTracker_Server.Entities; + +namespace FirmTracker_Server.nHibernate +{ + public class Workday + { + public virtual int Id { get; set; } + public virtual DateTime StartTime { get; set; } + public virtual DateTime? EndTime { get; set; } // Nullable EndTime, if not finished + public virtual User User { get; set; } // Assuming a relationship to a User entity + } +} diff --git a/nHIbernate/WorkdayMapping.cs b/nHIbernate/WorkdayMapping.cs new file mode 100644 index 0000000..5cdbfde --- /dev/null +++ b/nHIbernate/WorkdayMapping.cs @@ -0,0 +1,15 @@ +using FluentNHibernate.Mapping; +namespace FirmTracker_Server.nHibernate +{ + public class WorkdayMapping : ClassMap + { + public WorkdayMapping() + { + Table("Workdays"); // Make sure the table name matches the one in the database + Id(x => x.Id).GeneratedBy.Identity(); + Map(x => x.StartTime); + Map(x => x.EndTime); + References(x => x.User).Column("UserId"); // Assuming Workday is related to a User + } + } +} diff --git a/nHIbernate/WorkdayRepository.cs b/nHIbernate/WorkdayRepository.cs new file mode 100644 index 0000000..6c2e992 --- /dev/null +++ b/nHIbernate/WorkdayRepository.cs @@ -0,0 +1,41 @@ +using FirmTracker_Server.Entities; +using NHibernate; +using System; + +namespace FirmTracker_Server.nHibernate +{ + public class WorkdayRepository + { + public void StartWorkday(int userId) + { + using (var session = SessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + try + { + // Fetch the user entity by its ID + var user = session.Get(userId); // Assuming User is a mapped entity + if (user == null) + { + throw new Exception("User not found"); + } + + // Create a new Workday and assign the User reference + var workday = new Workday + { + StartTime = DateTime.Now, + User = user // Set the User reference here + }; + + session.Save(workday); + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + throw new Exception("An error occurred while starting the workday", ex); + } + } + } + } +} From 95513913e3c80f6110b3571fdceaf44f2f14ad14 Mon Sep 17 00:00:00 2001 From: Maciej Maciejewski Date: Wed, 20 Nov 2024 17:08:15 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Generowanie=20raportu=20PDF=20-=20rozwini?= =?UTF-8?q?=C4=99cie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controllers/PdfController.cs | 189 ++++++++++++++++++++++++++++++----- Program.cs | 1 + TestClass.cs | 2 +- nHIbernate/PdfData.cs | 122 ++++++++++++++++++++++ 4 files changed, 287 insertions(+), 27 deletions(-) diff --git a/Controllers/PdfController.cs b/Controllers/PdfController.cs index b9a7229..7232d94 100644 --- a/Controllers/PdfController.cs +++ b/Controllers/PdfController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using FirmTracker_Server.nHibernate; using FirmTracker_Server.nHibernate.Expenses; +using FirmTracker_Server.nHibernate.Transactions; using Microsoft.AspNetCore.Mvc; using QuestPDF.Fluent; using QuestPDF.Helpers; @@ -16,36 +17,86 @@ namespace FirmTracker_Server.Controllers public class PdfController : ControllerBase { private readonly IExpenseRepository _expenseRepository; + private readonly ITransactionRepository _transactionRepository; - public PdfController(IExpenseRepository expenseRepository) + public PdfController(IExpenseRepository expenseRepository, ITransactionRepository transactionRepository) { _expenseRepository = expenseRepository; + _transactionRepository = transactionRepository; } [HttpGet("download")] - public IActionResult DownloadExpenseReport() + public IActionResult DownloadReport( + [FromQuery] string reportType, // "expenses" or "transactions" + [FromQuery] DateTime? startDate, + [FromQuery] DateTime? endDate) { - // Fetch expense data from the repository - List expenses = _expenseRepository.GetAllExpenses(); + try + { + // Validate date inputs and set default values + DateTime start = startDate ?? DateTime.MinValue; + DateTime end = endDate ?? DateTime.MaxValue; - // Generate the PDF file - byte[] pdfBytes = GeneratePdf(expenses); - string date = DateTime.Now.ToString("yyyy-MM-dd"); - string fileName = $"ExpenseReport_{date}.pdf"; + // Validate report type + if (string.IsNullOrEmpty(reportType) || + (reportType.ToLower() != "expenses" && reportType.ToLower() != "transactions")) + { + return BadRequest("Invalid report type. Please specify 'expenses' or 'transactions'."); + } - // Return the PDF as a downloadable file + if (reportType.ToLower() == "expenses") + { + return GenerateExpenseReport(start, end); + } + else + { + return GenerateTransactionReport(start, end); + } + } + catch (Exception ex) + { + return StatusCode(500, $"Internal server error: {ex.Message}"); + } + } + + private IActionResult GenerateExpenseReport(DateTime start, DateTime end) + { + var expenses = _expenseRepository.GetAllExpenses() + .Where(e => e.Date >= start && e.Date <= end) + .ToList(); + + if (!expenses.Any()) + { + return BadRequest($"No expenses found between {start:yyyy-MM-dd} and {end:yyyy-MM-dd}."); + } + + var pdfBytes = GenerateExpensePdf(expenses, start, end); + string fileName = $"ExpenseReport_{start:yyyy-MM-dd}_to_{end:yyyy-MM-dd}.pdf"; return File(pdfBytes, "application/pdf", fileName); } - private byte[] GeneratePdf(List expenses) + private IActionResult GenerateTransactionReport(DateTime start, DateTime end) + { + var transactions = _transactionRepository.GetTransactionsByDateRange(start, end); + + if (!transactions.Any()) + { + return BadRequest($"No transactions found between {start:yyyy-MM-dd} and {end:yyyy-MM-dd}."); + } + + // Fetch transaction products for all transactions in one query + var transactionIds = transactions.Select(t => t.Id).ToList(); + var transactionProducts = _transactionRepository.GetTransactionProductsForTransactions(transactionIds); + + var pdfBytes = GenerateTransactionPdf(transactions, transactionProducts, start, end); + string fileName = $"TransactionReport_{start:yyyy-MM-dd}_to_{end:yyyy-MM-dd}.pdf"; + return File(pdfBytes, "application/pdf", fileName); + } + + private byte[] GenerateTransactionPdf(List transactions, List transactionProducts, DateTime startDate, DateTime endDate) { using (var ms = new MemoryStream()) { - // Calculate total and average expenses - decimal totalExpenses = expenses.Sum(e => e.Value); - decimal averageExpense = expenses.Count > 0 ? totalExpenses / expenses.Count : 0; - - // Define the document using QuestPDF Document.Create(container => { container.Page(page => @@ -57,7 +108,95 @@ namespace FirmTracker_Server.Controllers // Main header page.Header() - .Text("Expense Report") + .Text("Raport transakcji") + .FontSize(20) + .SemiBold() + .AlignCenter(); + + // Summary section + page.Content().PaddingVertical(1, Unit.Centimetre).Column(column => + { + column.Spacing(10); + + column.Item().Text($"Transakcje od ({startDate:yyyy-MM-dd} do {endDate:yyyy-MM-dd})") + .FontSize(16).Underline(); + + // Add table header + column.Item().Row(row => + { + row.RelativeItem().Text("Data").SemiBold(); + row.RelativeItem().Text("Typ płatności").SemiBold(); + row.RelativeItem().Text("Kwota razem").SemiBold(); + row.RelativeItem().Text("Rabat").SemiBold(); + row.RelativeItem().Text("Opis").SemiBold(); + }); + + // Populate table rows with transaction data + foreach (var transaction in transactions) + { + column.Item().Row(row => + { + row.RelativeItem().Text(transaction.Date.ToString("yyyy-MM-dd")); + row.RelativeItem().Text(transaction.PaymentType); + row.RelativeItem().Text(transaction.TotalPrice.ToString("C")); + row.RelativeItem().Text(transaction.Discount.ToString("C")); + row.RelativeItem().Text(transaction.Description); + }); + + // Fetch and display transaction products for this transaction + var products = transactionProducts + .Where(tp => tp.TransactionId == transaction.Id) + .ToList(); + + if (products.Any()) + { + column.Item().Text("Produkty:").SemiBold(); + foreach (var product in products) + { + column.Item().Row(productRow => + { + productRow.RelativeItem().Text($"Nazwa produktu: {product.ProductName}"); + productRow.RelativeItem().Text($"Ilość: {product.Quantity}"); + }); + } + } + } + }); + + // Footer with generation date + page.Footer() + .AlignCenter() + .Text(text => + { + text.Span("Wygenerowano przez automat FT: "); + text.Span(DateTime.Now.ToString("yyyy-MM-dd")).SemiBold(); + }); + }); + }).GeneratePdf(ms); + + return ms.ToArray(); + } + } + + private byte[] GenerateExpensePdf(List expenses, DateTime startDate, DateTime endDate) + { + using (var ms = new MemoryStream()) + { + decimal totalExpenses = expenses.Sum(e => e.Value); + decimal averageExpense = expenses.Any() ? totalExpenses / expenses.Count : 0; + + Document.Create(container => + { + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(2, Unit.Centimetre); + page.PageColor(Colors.White); + page.DefaultTextStyle(x => x.FontSize(12)); + + // Main header + page.Header() + .Text("Raport wydatków") .FontSize(20) .SemiBold() .AlignCenter(); @@ -69,21 +208,20 @@ namespace FirmTracker_Server.Controllers column.Item().Row(row => { - row.RelativeItem().Text($"Total Expenses: {totalExpenses:C}").FontSize(14).Bold(); - row.RelativeItem().Text($"Average Expense: {averageExpense:C}").FontSize(14).Bold(); + row.RelativeItem().Text($"Łączne wydatki: {totalExpenses:C}").FontSize(14).Bold(); + row.RelativeItem().Text($"Średnie wydatki dzienne: {averageExpense:C}").FontSize(14).Bold(); }); - column.Item().Text("Expense Details").FontSize(16).Underline(); + column.Item().Text($"Szczegóły wydatków od ({startDate:yyyy-MM-dd} do {endDate:yyyy-MM-dd})") + .FontSize(16).Underline(); - // Add a table header column.Item().Row(row => { - row.RelativeItem().Text("Date").SemiBold(); - row.RelativeItem().Text("Value").SemiBold(); - row.RelativeItem().Text("Description").SemiBold(); + row.RelativeItem().Text("Data").SemiBold(); + row.RelativeItem().Text("Kwota").SemiBold(); + row.RelativeItem().Text("Opis").SemiBold(); }); - // Populate table rows with expense data foreach (var expense in expenses) { column.Item().Row(row => @@ -95,12 +233,11 @@ namespace FirmTracker_Server.Controllers } }); - // Footer with generation date page.Footer() .AlignCenter() .Text(text => { - text.Span("Generated on "); + text.Span("Wygenerowano przez automat FT: "); text.Span(DateTime.Now.ToString("yyyy-MM-dd")).SemiBold(); }); }); diff --git a/Program.cs b/Program.cs index 4c75fbc..4a73767 100644 --- a/Program.cs +++ b/Program.cs @@ -177,6 +177,7 @@ namespace FirmTracker_Server services.AddScoped(); services.AddScoped, PasswordHasher>(); services.AddScoped(); + services.AddScoped(); // services.AddScoped(); services.AddMvc(); } diff --git a/TestClass.cs b/TestClass.cs index 02cd16a..5cb4440 100644 --- a/TestClass.cs +++ b/TestClass.cs @@ -157,7 +157,7 @@ namespace FirmTracker_Server }; var expense2 = new Expense { - Date = DateTime.Now, + Date = DateTime.Parse("2024-09-10 16:11:17.6232408"), Value = 990.99m, Description = "naprawa pieca - 25.05.2024" }; diff --git a/nHIbernate/PdfData.cs b/nHIbernate/PdfData.cs index 3028bf4..d18d025 100644 --- a/nHIbernate/PdfData.cs +++ b/nHIbernate/PdfData.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using FirmTracker_Server.nHibernate.Expenses; +using FirmTracker_Server.nHibernate.Transactions; using NHibernate; namespace FirmTracker_Server.nHibernate @@ -13,7 +14,128 @@ namespace FirmTracker_Server.nHibernate void UpdateExpense(Expense expense); void DeleteExpense(int expenseId); } + public interface ITransactionRepository + { + List GetAllTransactions(); + Transaction GetTransaction(int transactionId); + List GetTransactionsByDateRange(DateTime startDate, DateTime endDate); + List GetTransactionProducts(int transactionId); + void AddTransaction(Transaction transaction); + void UpdateTransaction(Transaction transaction); + void DeleteTransaction(int transactionId); + List GetTransactionProductsForTransactions(List transactionIds); + } + public class TransactionRepository : ITransactionRepository + { + // Retrieve all transactions + public List GetAllTransactions() + { + using (var session = SessionFactory.OpenSession()) + { + return session.Query().ToList(); + } + } + public List GetTransactionProductsForTransactions(List transactionIds) + { + using (var session = SessionFactory.OpenSession()) + { + return session.Query() + .Where(tp => transactionIds.Contains(tp.TransactionId)) + .ToList(); + } + } + // Retrieve a specific transaction by ID + public Transaction GetTransaction(int transactionId) + { + using (var session = SessionFactory.OpenSession()) + { + return session.Get(transactionId); + } + } + // Retrieve transactions within a specific date range + public List GetTransactionsByDateRange(DateTime startDate, DateTime endDate) + { + using (var session = SessionFactory.OpenSession()) + { + return session.Query() + .Where(t => t.Date >= startDate && t.Date <= endDate) + .ToList(); + } + } + + // Retrieve all products for a specific transaction + public List GetTransactionProducts(int transactionId) + { + using (var session = SessionFactory.OpenSession()) + { + return session.Query() + .Where(tp => tp.TransactionId == transactionId) + .ToList(); + } + } + + // Add a new transaction + public void AddTransaction(Transaction transaction) + { + using (var session = SessionFactory.OpenSession()) + using (var transactionScope = session.BeginTransaction()) + { + try + { + session.Save(transaction); + transactionScope.Commit(); + } + catch + { + transactionScope.Rollback(); + throw; + } + } + } + + // Update an existing transaction + public void UpdateTransaction(Transaction transaction) + { + using (var session = SessionFactory.OpenSession()) + using (var transactionScope = session.BeginTransaction()) + { + try + { + session.Update(transaction); + transactionScope.Commit(); + } + catch + { + transactionScope.Rollback(); + throw; + } + } + } + + // Delete a transaction by ID + public void DeleteTransaction(int transactionId) + { + using (var session = SessionFactory.OpenSession()) + using (var transactionScope = session.BeginTransaction()) + { + try + { + var transaction = session.Get(transactionId); + if (transaction != null) + { + session.Delete(transaction); + } + transactionScope.Commit(); + } + catch + { + transactionScope.Rollback(); + throw; + } + } + } + } public class ExpenseRepository : IExpenseRepository { // Retrieve all expenses From 049899dc644dafb293d9855daf83a6b40c24f274 Mon Sep 17 00:00:00 2001 From: Maciej Maciejewski Date: Tue, 26 Nov 2024 22:12:02 +0100 Subject: [PATCH 4/4] =?UTF-8?q?dodanie=20usuwania=20produktu=20w=20transak?= =?UTF-8?q?cji,=20zmiana=20ilo=C5=9Bci=20w=20klasie=20testowej?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Controllers/PdfController.cs | 3 + Controllers/TransactionController.cs | 36 ++++++- Controllers/WorkDayController.cs | 41 ++++++- Models/Workday.cs | 2 +- TestClass.cs | 4 +- nHIbernate/PdfData.cs | 10 +- nHIbernate/Workday.cs | 14 ++- nHIbernate/WorkdayRepository.cs | 119 ++++++++++++++++----- nHibernate/Transactions/TransactionCRUD.cs | 61 +++++++++++ 9 files changed, 250 insertions(+), 40 deletions(-) diff --git a/Controllers/PdfController.cs b/Controllers/PdfController.cs index 7232d94..bfb24fa 100644 --- a/Controllers/PdfController.cs +++ b/Controllers/PdfController.cs @@ -5,6 +5,7 @@ using System.Linq; using FirmTracker_Server.nHibernate; using FirmTracker_Server.nHibernate.Expenses; using FirmTracker_Server.nHibernate.Transactions; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using QuestPDF.Fluent; using QuestPDF.Helpers; @@ -14,6 +15,7 @@ namespace FirmTracker_Server.Controllers { [Route("api/[controller]")] [ApiController] + [Authorize] public class PdfController : ControllerBase { private readonly IExpenseRepository _expenseRepository; @@ -26,6 +28,7 @@ namespace FirmTracker_Server.Controllers } [HttpGet("download")] + [Authorize(Roles = Roles.Admin)] public IActionResult DownloadReport( [FromQuery] string reportType, // "expenses" or "transactions" [FromQuery] DateTime? startDate, diff --git a/Controllers/TransactionController.cs b/Controllers/TransactionController.cs index 276a50a..fbe2110 100644 --- a/Controllers/TransactionController.cs +++ b/Controllers/TransactionController.cs @@ -55,14 +55,21 @@ namespace FirmTracker_Server.Controllers { try { - + foreach (var product in transaction.TransactionProducts) { + // Validate if the product quantity is positive + if (product.Quantity <= 0) + { + return BadRequest($"Ilość na produktu {product.ProductName} musi być dodatnia."); + } var productByName = _productCRUD.GetProductByName(product.ProductName); if (productByName == null) { throw new InvalidOperationException($"Produkt o nazwie {product.ProductName} nie istnieje."); } + + product.ProductID = productByName.Id; product.TransactionId = transaction.Id; @@ -132,6 +139,11 @@ namespace FirmTracker_Server.Controllers { foreach (var product in transaction.TransactionProducts) { + // Validate if the product quantity is positive + if (product.Quantity <= 0) + { + return BadRequest($"Sprzedawana ilość produktu {product.ProductName} musi być ilością dodatnią."); + } var productByName = _productCRUD.GetProductByName(product.ProductName); if (productByName == null) { @@ -207,5 +219,27 @@ namespace FirmTracker_Server.Controllers return Ok(transactions); } + // DELETE: api/Transaction/5/product/10 + [HttpDelete("{transactionId}/product/{productId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [Authorize(Roles = Roles.Admin + "," + Roles.User)] + public IActionResult DeleteTransactionProduct(int transactionId, int productId) + { + try + { + _transactionCRUD.DeleteTransactionProduct(transactionId, productId); + return NoContent(); // Successfully removed the product + } + catch (InvalidOperationException ioe) + { + return BadRequest(ioe.Message); // If the transaction or product isn't found + } + catch (Exception ex) + { + return NotFound(ex.Message); // Other general errors + } + } } } diff --git a/Controllers/WorkDayController.cs b/Controllers/WorkDayController.cs index 4c4cc6e..844d115 100644 --- a/Controllers/WorkDayController.cs +++ b/Controllers/WorkDayController.cs @@ -32,7 +32,7 @@ namespace FirmTracker_Server.Controllers public WorkdayController() { - _workdayCRUD = new WorkdayRepository(); // Instantiate directly (no DI in this example) + _workdayCRUD = new WorkdayRepository(); } // Endpoint to start a workday @@ -44,15 +44,52 @@ namespace FirmTracker_Server.Controllers { var userIdString = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; int userId = int.Parse(userIdString); + + // Attempt to start a new workday _workdayCRUD.StartWorkday(userId); return Ok(new { status = "started", userId }); } catch (Exception ex) { + // If there's an error (like previous workday not stopped), handle it return BadRequest(new { message = "An error occurred while starting the workday.", error = ex.Message }); } } + // Endpoint to stop a workday + [HttpPost("stop")] + [Authorize(Roles = Roles.Admin + "," + Roles.User)] + public IActionResult StopWorkday() + { + try + { + var userIdString = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; + int userId = int.Parse(userIdString); + + var result = _workdayCRUD.StopWorkday(userId); + return Ok(new { status = result ? "stopped" : "already stopped", userId }); + } + catch (Exception ex) + { + return BadRequest(new { message = "An error occurred while stopping the workday.", error = ex.Message }); + } + } + + // Endpoint to get all workdays for a user + [HttpGet("user/{userMail}/workdays")] + [Authorize(Roles = Roles.Admin + "," + Roles.User)] + public IActionResult GetWorkdays(string userMail) + { + try + { + var workdays = _workdayCRUD.GetWorkdaysByUser(userMail); + return Ok(workdays); + } + catch (Exception ex) + { + return BadRequest(new { message = "An error occurred while fetching workdays.", error = ex.Message }); + } + } + - } } diff --git a/Models/Workday.cs b/Models/Workday.cs index a93638d..31313b8 100644 --- a/Models/Workday.cs +++ b/Models/Workday.cs @@ -8,7 +8,7 @@ namespace YourNamespace.Models public virtual int Id { get; set; } public virtual DateTime? StartTime { get; set; } public virtual DateTime? EndTime { get; set; } - + public TimeSpan WorkedHours { get; set; } // Many-to-One relationship to the User entity public virtual User User { get; set; } } diff --git a/TestClass.cs b/TestClass.cs index 5cb4440..9797a9d 100644 --- a/TestClass.cs +++ b/TestClass.cs @@ -239,9 +239,9 @@ namespace FirmTracker_Server expenseCrud.AddExpense(expense3); List testTransactionProducts = new List { - new TransactionProduct { ProductID =17, Quantity = 10 }, + new TransactionProduct { ProductID =17, Quantity = 3 }, new TransactionProduct { ProductID = 14, Quantity = 1 }, - new TransactionProduct { ProductID = 1, Quantity = 0 }, + new TransactionProduct { ProductID = 1, Quantity = 1 }, }; foreach (var transactionProduct in testTransactionProducts) { diff --git a/nHIbernate/PdfData.cs b/nHIbernate/PdfData.cs index d18d025..5792f4b 100644 --- a/nHIbernate/PdfData.cs +++ b/nHIbernate/PdfData.cs @@ -44,7 +44,7 @@ namespace FirmTracker_Server.nHibernate .ToList(); } } - // Retrieve a specific transaction by ID + public Transaction GetTransaction(int transactionId) { using (var session = SessionFactory.OpenSession()) @@ -53,7 +53,7 @@ namespace FirmTracker_Server.nHibernate } } - // Retrieve transactions within a specific date range + public List GetTransactionsByDateRange(DateTime startDate, DateTime endDate) { using (var session = SessionFactory.OpenSession()) @@ -64,7 +64,7 @@ namespace FirmTracker_Server.nHibernate } } - // Retrieve all products for a specific transaction + public List GetTransactionProducts(int transactionId) { using (var session = SessionFactory.OpenSession()) @@ -75,7 +75,7 @@ namespace FirmTracker_Server.nHibernate } } - // Add a new transaction + public void AddTransaction(Transaction transaction) { using (var session = SessionFactory.OpenSession()) @@ -113,7 +113,7 @@ namespace FirmTracker_Server.nHibernate } } - // Delete a transaction by ID + public void DeleteTransaction(int transactionId) { using (var session = SessionFactory.OpenSession()) diff --git a/nHIbernate/Workday.cs b/nHIbernate/Workday.cs index 7f819cc..ac85b39 100644 --- a/nHIbernate/Workday.cs +++ b/nHIbernate/Workday.cs @@ -7,6 +7,18 @@ namespace FirmTracker_Server.nHibernate public virtual int Id { get; set; } public virtual DateTime StartTime { get; set; } public virtual DateTime? EndTime { get; set; } // Nullable EndTime, if not finished - public virtual User User { get; set; } // Assuming a relationship to a User entity + public virtual TimeSpan WorkedHours + { + get + { + // Calculate the worked hours, using 5 PM as the fallback for the EndTime + return (EndTime ?? DateTime.Today.AddHours(24)) - StartTime; + } + set + { + + } + } + public virtual User User { get; set; } } } diff --git a/nHIbernate/WorkdayRepository.cs b/nHIbernate/WorkdayRepository.cs index 6c2e992..eff5486 100644 --- a/nHIbernate/WorkdayRepository.cs +++ b/nHIbernate/WorkdayRepository.cs @@ -1,40 +1,103 @@ using FirmTracker_Server.Entities; -using NHibernate; -using System; +using FirmTracker_Server.nHibernate; -namespace FirmTracker_Server.nHibernate +public class WorkdayRepository { - public class WorkdayRepository + public void StartWorkday(int userId) { - public void StartWorkday(int userId) + using (var session = SessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) { - using (var session = SessionFactory.OpenSession()) - using (var transaction = session.BeginTransaction()) + try { - try - { - // Fetch the user entity by its ID - var user = session.Get(userId); // Assuming User is a mapped entity - if (user == null) - { - throw new Exception("User not found"); - } + // Check if there is an existing workday that hasn't been stopped yet + var ongoingWorkday = session.Query() + .Where(w => w.User.UserId == userId && w.EndTime == null) + .OrderByDescending(w => w.StartTime) + .FirstOrDefault(); - // Create a new Workday and assign the User reference - var workday = new Workday - { - StartTime = DateTime.Now, - User = user // Set the User reference here - }; - - session.Save(workday); - transaction.Commit(); - } - catch (Exception ex) + if (ongoingWorkday != null) { - transaction.Rollback(); - throw new Exception("An error occurred while starting the workday", ex); + // If there is an ongoing workday, throw an exception or return a specific message + throw new Exception("Previous workday wasn't stopped yet."); } + + // Fetch the user entity + var user = session.Get(userId); + if (user == null) throw new Exception("User not found"); + + // Create a new workday if there is no ongoing one + var workday = new Workday + { + StartTime = DateTime.Now, + User = user + }; + + session.Save(workday); + transaction.Commit(); + } + catch (Exception ex) + { + transaction.Rollback(); + throw new Exception("An error occurred while starting the workday", ex); + } + } + } + + public bool StopWorkday(int userId) + { + using (var session = SessionFactory.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + try + { + var workday = session.Query() + .Where(w => w.User.UserId == userId && w.EndTime == null) + .OrderByDescending(w => w.StartTime) + .FirstOrDefault(); + + if (workday == null) + { + return false; // No ongoing workday found + } + + workday.EndTime = DateTime.Now; + + session.Update(workday); + transaction.Commit(); + + return true; + } + catch (Exception ex) + { + transaction.Rollback(); + throw new Exception("An error occurred while stopping the workday", ex); + } + } + } + + public List GetWorkdaysByUser(string email) + { + using (var session = SessionFactory.OpenSession()) + { + try + { + var workdays = session.Query() + .Where(w => w.User.Email == email) + .Select(w => new Workday + { + Id = w.Id, + StartTime = w.StartTime, + EndTime = w.EndTime ?? DateTime.Today.AddHours(17), + WorkedHours = (w.EndTime ?? DateTime.Today.AddHours(17)) - w.StartTime, + }) + .ToList(); + + return workdays; + } + catch (Exception ex) + { + throw new Exception("An error occurred while fetching workdays", ex); } } } diff --git a/nHibernate/Transactions/TransactionCRUD.cs b/nHibernate/Transactions/TransactionCRUD.cs index c16d574..9be2044 100644 --- a/nHibernate/Transactions/TransactionCRUD.cs +++ b/nHibernate/Transactions/TransactionCRUD.cs @@ -94,6 +94,10 @@ namespace FirmTracker_Server.nHibernate.Transactions { var product = session.Get(tp.ProductID); + if(tp.Quantity < 0) + { + + } if (product.Type != 0) { product.Availability += tp.Quantity; @@ -253,6 +257,63 @@ namespace FirmTracker_Server.nHibernate.Transactions } } } + public void DeleteTransactionProduct(int transactionId, int productId) + { + using (var session = SessionFactory.OpenSession()) + using (var t = session.BeginTransaction()) + { + try + { + // Get the transaction to update + var transaction = session.Get(transactionId); + if (transaction == null) + { + throw new InvalidOperationException($"Transaction with ID {transactionId} not found."); + } + + // Find the transaction product to remove + var transactionProduct = transaction.TransactionProducts.FirstOrDefault(tp => tp.ProductID == productId); + if (transactionProduct == null) + { + throw new InvalidOperationException($"Product with ID {productId} not found in the transaction."); + } + + // Get the product to update availability + var product = session.Get(productId); + if (product == null) + { + throw new InvalidOperationException($"Product with ID {productId} not found."); + } + + // Revert the product availability + if (product.Type != 0) + { + product.Availability += transactionProduct.Quantity; + session.Update(product); + } + + // Remove the product from the transaction + transaction.TotalPrice -= (transactionProduct.Quantity * product.Price * (1 - (transaction.Discount / 100))); + transaction.TotalPrice = Math.Round(transaction.TotalPrice, 2, MidpointRounding.AwayFromZero); + + // Remove the product from the Transaction's Product list + transaction.TransactionProducts.Remove(transactionProduct); + + // Now delete the transaction product + session.Delete(transactionProduct); + + // Update the transaction total price + session.Update(transaction); + + t.Commit(); + } + catch (Exception ex) + { + t.Rollback(); + throw new InvalidOperationException($"Error while deleting product from transaction: {ex.Message}"); + } + } + } public IList GetAllTransactions()