diff --git a/Aberwyn/Controllers/BudgetApiController.cs b/Aberwyn/Controllers/BudgetApiController.cs index 6f198aa..42fef4a 100644 --- a/Aberwyn/Controllers/BudgetApiController.cs +++ b/Aberwyn/Controllers/BudgetApiController.cs @@ -61,7 +61,8 @@ namespace Aberwyn.Controllers Amount = i.Amount, IsExpense = i.IsExpense, IncludeInSummary = i.IncludeInSummary, - BudgetItemDefinitionId = i.BudgetItemDefinitionId + BudgetItemDefinitionId = i.BudgetItemDefinitionId, + PaymentStatus = i.PaymentStatus }).ToList() }).ToList() }; @@ -73,6 +74,21 @@ namespace Aberwyn.Controllers return StatusCode(500, $"Fel: {ex.Message} \n{ex.StackTrace}"); } } + [HttpPut("updatePaymentStatus")] + public IActionResult UpdatePaymentStatus([FromBody] PaymentStatusUpdateDto dto) + { + if (dto == null) + return BadRequest("dto is null"); + + var item = _context.BudgetItems.Find(dto.ItemId); + if (item == null) return NotFound(); + + item.PaymentStatus = (PaymentStatus)dto.Status; + _context.SaveChanges(); + + return Ok(); + } + [HttpPut("category/{id}")] public async Task UpdateCategory(int id, [FromBody] BudgetCategoryDto updatedCategory) diff --git a/Aberwyn/Controllers/ReportApiController.cs b/Aberwyn/Controllers/ReportApiController.cs index 256da90..9329760 100644 --- a/Aberwyn/Controllers/ReportApiController.cs +++ b/Aberwyn/Controllers/ReportApiController.cs @@ -22,43 +22,5 @@ namespace Aberwyn.Controllers _context = context; } - [HttpPost] - public async Task GetReport([FromBody] BudgetReportRequestDto request) - { - var start = new DateTime(request.StartYear, request.StartMonth, 1); - var end = new DateTime(request.EndYear, request.EndMonth, 1); - - var items = await _context.BudgetItems - .Include(i => i.BudgetItemDefinition) - .Include(i => i.BudgetCategory) - .ThenInclude(c => c.BudgetPeriod) - .Where(i => - i.BudgetCategory.BudgetPeriod.Year * 12 + i.BudgetCategory.BudgetPeriod.Month >= start.Year * 12 + start.Month && - i.BudgetCategory.BudgetPeriod.Year * 12 + i.BudgetCategory.BudgetPeriod.Month <= end.Year * 12 + end.Month && - request.DefinitionIds.Contains(i.BudgetItemDefinitionId ?? -1)) - .ToListAsync(); - - var grouped = items - .GroupBy(i => new { i.BudgetCategory.BudgetPeriod.Year, i.BudgetCategory.BudgetPeriod.Month }) - .Select(g => new BudgetReportResultDto - { - Year = g.Key.Year, - Month = g.Key.Month, - Definitions = g - .GroupBy(i => new { i.BudgetItemDefinitionId, i.BudgetItemDefinition.Name }) - .Select(dg => new DefinitionSumDto - { - DefinitionId = dg.Key.BudgetItemDefinitionId ?? 0, - DefinitionName = dg.Key.Name, - TotalAmount = dg.Sum(x => x.Amount) - }).ToList() - }) - .OrderBy(r => r.Year).ThenBy(r => r.Month) - .ToList(); - - return Ok(grouped); - } - - } } diff --git a/Aberwyn/Migrations/20250618203117_AddPaymentStatusToBudgetItem.Designer.cs b/Aberwyn/Migrations/20250618203117_AddPaymentStatusToBudgetItem.Designer.cs new file mode 100644 index 0000000..af55b1d --- /dev/null +++ b/Aberwyn/Migrations/20250618203117_AddPaymentStatusToBudgetItem.Designer.cs @@ -0,0 +1,848 @@ +// +using System; +using Aberwyn.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Aberwyn.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250618203117_AddPaymentStatusToBudgetItem")] + partial class AddPaymentStatusToBudgetItem + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.36") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Aberwyn.Models.AppSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("AppSettings"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BudgetCategoryDefinitionId") + .HasColumnType("int"); + + b.Property("BudgetPeriodId") + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryDefinitionId"); + + b.HasIndex("BudgetPeriodId"); + + b.ToTable("BudgetCategories"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategoryDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetCategoryDefinitions"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("BudgetCategoryId") + .HasColumnType("int"); + + b.Property("BudgetItemDefinitionId") + .HasColumnType("int"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("BudgetCategoryId"); + + b.HasIndex("BudgetItemDefinitionId"); + + b.ToTable("BudgetItems"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItemDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultCategory") + .HasColumnType("longtext"); + + b.Property("DefaultPaymentStatus") + .HasColumnType("int"); + + b.Property("IncludeInSummary") + .HasColumnType("tinyint(1)"); + + b.Property("IsExpense") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("BudgetItemDefinitions"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Month") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BudgetPeriods"); + }); + + modelBuilder.Entity("Aberwyn.Models.Ingredient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Item") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("MealId") + .HasColumnType("int"); + + b.Property("Quantity") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("MealId"); + + b.ToTable("Ingredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CarbType") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("ImageData") + .HasColumnType("longblob"); + + b.Property("ImageMimeType") + .HasColumnType("longtext"); + + b.Property("ImageUrl") + .HasColumnType("longtext"); + + b.Property("Instructions") + .HasColumnType("longtext"); + + b.Property("IsAvailable") + .HasColumnType("tinyint(1)"); + + b.Property("MealCategoryId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ProteinType") + .HasColumnType("longtext"); + + b.Property("RecipeUrl") + .HasColumnType("longtext"); + + b.Property("ThumbnailData") + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.HasIndex("MealCategoryId"); + + b.ToTable("Meals"); + }); + + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Color") + .HasColumnType("longtext"); + + b.Property("Description") + .HasColumnType("longtext"); + + b.Property("DisplayOrder") + .HasColumnType("int"); + + b.Property("Icon") + .HasColumnType("longtext"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Slug") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("MealCategories"); + + b.HasData( + new + { + Id = 1, + Color = "#f97316", + DisplayOrder = 1, + Icon = "🍕", + IsActive = true, + Name = "Pizza", + Slug = "pizza" + }); + }); + + modelBuilder.Entity("Aberwyn.Models.PizzaOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CustomerName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IngredientsJson") + .HasColumnType("longtext"); + + b.Property("OrderedAt") + .HasColumnType("datetime(6)"); + + b.Property("PizzaName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("PizzaOrders"); + }); + + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PizzaOrderId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("PizzaOrderId"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscribers"); + }); + + modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Auth") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("P256DH") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("PushSubscriptions"); + }); + + modelBuilder.Entity("Aberwyn.Models.TodoTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AssignedTo") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Status") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Tags") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("TodoTasks"); + }); + + modelBuilder.Entity("Aberwyn.Models.UserPreferences", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("NotifyBudget") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyMenu") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyPizza") + .HasColumnType("tinyint(1)"); + + b.HasKey("UserId"); + + b.ToTable("UserPreferences"); + }); + + modelBuilder.Entity("Aberwyn.Models.WeeklyMenu", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("BreakfastMealId") + .HasColumnType("int"); + + b.Property("Cook") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("DinnerMealId") + .HasColumnType("int"); + + b.Property("LunchMealId") + .HasColumnType("int"); + + b.Property("WeekNumber") + .HasColumnType("int"); + + b.Property("Year") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("WeeklyMenu", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("RoleId") + .HasColumnType("varchar(255)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.HasOne("Aberwyn.Models.BudgetCategoryDefinition", "Definition") + .WithMany() + .HasForeignKey("BudgetCategoryDefinitionId"); + + b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod") + .WithMany("Categories") + .HasForeignKey("BudgetPeriodId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetPeriod"); + + b.Navigation("Definition"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => + { + b.HasOne("Aberwyn.Models.BudgetCategory", null) + .WithMany("Items") + .HasForeignKey("BudgetCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aberwyn.Models.BudgetItemDefinition", "BudgetItemDefinition") + .WithMany() + .HasForeignKey("BudgetItemDefinitionId"); + + b.Navigation("BudgetItemDefinition"); + }); + + modelBuilder.Entity("Aberwyn.Models.Ingredient", b => + { + b.HasOne("Aberwyn.Models.Meal", null) + .WithMany("Ingredients") + .HasForeignKey("MealId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.HasOne("Aberwyn.Models.MealCategory", "Category") + .WithMany("Meals") + .HasForeignKey("MealCategoryId"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b => + { + b.HasOne("Aberwyn.Models.PizzaOrder", "PizzaOrder") + .WithMany() + .HasForeignKey("PizzaOrderId"); + + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PizzaOrder"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Aberwyn.Models.StoredPushSubscription", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Aberwyn.Models.UserPreferences", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", "User") + .WithOne("Preferences") + .HasForeignKey("Aberwyn.Models.UserPreferences", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Aberwyn.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b => + { + b.Navigation("Preferences") + .IsRequired(); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b => + { + b.Navigation("Categories"); + }); + + modelBuilder.Entity("Aberwyn.Models.Meal", b => + { + b.Navigation("Ingredients"); + }); + + modelBuilder.Entity("Aberwyn.Models.MealCategory", b => + { + b.Navigation("Meals"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Aberwyn/Migrations/20250618203117_AddPaymentStatusToBudgetItem.cs b/Aberwyn/Migrations/20250618203117_AddPaymentStatusToBudgetItem.cs new file mode 100644 index 0000000..3b66deb --- /dev/null +++ b/Aberwyn/Migrations/20250618203117_AddPaymentStatusToBudgetItem.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Aberwyn.Migrations +{ + public partial class AddPaymentStatusToBudgetItem : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PaymentStatus", + table: "BudgetItems", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "DefaultPaymentStatus", + table: "BudgetItemDefinitions", + type: "int", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PaymentStatus", + table: "BudgetItems"); + + migrationBuilder.DropColumn( + name: "DefaultPaymentStatus", + table: "BudgetItemDefinitions"); + } + } +} diff --git a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs index d98183d..6f35a94 100644 --- a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs @@ -181,6 +181,9 @@ namespace Aberwyn.Migrations b.Property("Order") .HasColumnType("int"); + b.Property("PaymentStatus") + .HasColumnType("int"); + b.HasKey("Id"); b.HasIndex("BudgetCategoryId"); @@ -199,6 +202,9 @@ namespace Aberwyn.Migrations b.Property("DefaultCategory") .HasColumnType("longtext"); + b.Property("DefaultPaymentStatus") + .HasColumnType("int"); + b.Property("IncludeInSummary") .HasColumnType("tinyint(1)"); @@ -688,7 +694,7 @@ namespace Aberwyn.Migrations modelBuilder.Entity("Aberwyn.Models.BudgetItem", b => { - b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory") + b.HasOne("Aberwyn.Models.BudgetCategory", null) .WithMany("Items") .HasForeignKey("BudgetCategoryId") .OnDelete(DeleteBehavior.Cascade) @@ -698,8 +704,6 @@ namespace Aberwyn.Migrations .WithMany() .HasForeignKey("BudgetItemDefinitionId"); - b.Navigation("BudgetCategory"); - b.Navigation("BudgetItemDefinition"); }); diff --git a/Aberwyn/Models/Budget.cs b/Aberwyn/Models/Budget.cs index 4bacdaf..93a2440 100644 --- a/Aberwyn/Models/Budget.cs +++ b/Aberwyn/Models/Budget.cs @@ -47,7 +47,16 @@ namespace Aberwyn.Models [JsonIgnore] [ValidateNever] - public BudgetCategory BudgetCategory { get; set; } + + + public PaymentStatus PaymentStatus { get; set; } = PaymentStatus.None; + + } + public enum PaymentStatus + { + None = 0, + Due = 1, + Paid = 2 } // DTOs/BudgetDto.cs public class BudgetDto @@ -84,6 +93,7 @@ namespace Aberwyn.Models public bool IsExpense { get; set; } public bool IncludeInSummary { get; set; } + public PaymentStatus PaymentStatus { get; set; } } @@ -91,6 +101,11 @@ namespace Aberwyn.Models { public List BudgetItems { get; set; } = new List(); } + public class PaymentStatusUpdateDto + { + public int ItemId { get; set; } + public PaymentStatus Status { get; set; } + } public class BudgetItemDefinition { @@ -99,6 +114,8 @@ namespace Aberwyn.Models public string? DefaultCategory { get; set; } // valfritt, kan föreslå "Bilar" public bool IsExpense { get; set; } public bool IncludeInSummary { get; set; } + public PaymentStatus? DefaultPaymentStatus { get; set; } + } public class BudgetCategoryDefinition { diff --git a/Aberwyn/Views/Budget/Index.cshtml b/Aberwyn/Views/Budget/Index.cshtml index beb37df..609f132 100644 --- a/Aberwyn/Views/Budget/Index.cshtml +++ b/Aberwyn/Views/Budget/Index.cshtml @@ -125,7 +125,12 @@ item="item" category="cat" on-item-drop="handleItemDrop(event, data, cat)" - ng-class="{ dragging: dragInProgress && draggedItemId === item.id }"> + ng-class="{ + dragging: dragInProgress && draggedItemId === item.id, + due: item.paymentStatus === 1, + paid: item.paymentStatus === 2 + }" + ng-click="ctrlClick($event, item)"> diff --git a/Aberwyn/wwwroot/css/budget.css b/Aberwyn/wwwroot/css/budget.css index 9d74fbd..c953e23 100644 --- a/Aberwyn/wwwroot/css/budget.css +++ b/Aberwyn/wwwroot/css/budget.css @@ -242,7 +242,35 @@ body { text-align: right; font-weight: 600; } +@keyframes fadeRed { + 0% { + color: rgb(200, 50, 50); + } + 100% { + color: rgb(80, 0, 0); + } +} + +@keyframes fadeGreen { + 0% { + color: rgb(50, 200, 50); + } + + 100% { + color: rgb(0, 80, 0); + } +} + +.item-row.due { + animation: fadeRed 0.2s ease-in-out; + animation-fill-mode: forwards; +} + +.item-row.paid { + animation: fadeGreen 0.2s ease-in-out; + animation-fill-mode: forwards; +} .icon-button { diff --git a/Aberwyn/wwwroot/js/budget.js b/Aberwyn/wwwroot/js/budget.js index ec3e44f..ee3c27c 100644 --- a/Aberwyn/wwwroot/js/budget.js +++ b/Aberwyn/wwwroot/js/budget.js @@ -85,11 +85,32 @@ app.controller('BudgetController', function ($scope, $http) { } }); - document.addEventListener('click', function () { + document.addEventListener('click', function () { $scope.$apply(() => { $scope.menuOpen = false; }); }); + $scope.ctrlClick = function (event, item) { + if (!event.ctrlKey) return; + + const current = typeof item.paymentStatus === 'number' && !isNaN(item.paymentStatus) + ? item.paymentStatus + : 0; + + item.paymentStatus = (current + 1) % 3; + + $http.put("/api/budget/updatePaymentStatus", { + itemId: item.id, + status: item.paymentStatus + }).then(() => { + $scope.showToast("Betalstatus uppdaterad"); + }).catch(err => { + console.error("Fel vid uppdatering:", err); + }); + }; + + + $scope.showToast = function (message, isError = false) { const toast = document.createElement("div"); @@ -128,7 +149,8 @@ app.controller('BudgetController', function ($scope, $http) { includeInSummary: item.IncludeInSummary === true, order: item.Order ?? index, budgetItemDefinitionId: item.BudgetItemDefinitionId, - definitionName: definition?.Name || null + definitionName: definition?.Name || null, + paymentStatus: item.PaymentStatus }; }).sort((a, b) => a.order - b.order)