This commit is contained in:
@@ -43,8 +43,8 @@ namespace Aberwyn.Controllers
|
||||
var dto = new BudgetDto
|
||||
{
|
||||
Id = period.Id,
|
||||
Year = period.Year,
|
||||
Month = period.Month,
|
||||
Year = period.Year ?? 0,
|
||||
Month = period.Month ?? 0,
|
||||
Categories = period.Categories
|
||||
.OrderBy(cat => cat.Order)
|
||||
.Select(cat => new BudgetCategoryDto
|
||||
@@ -74,6 +74,57 @@ namespace Aberwyn.Controllers
|
||||
return StatusCode(500, $"Fel: {ex.Message} \n{ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("byname/{name}")]
|
||||
public async Task<IActionResult> GetBudgetByName(string name)
|
||||
{
|
||||
var period = await _context.BudgetPeriods
|
||||
.Include(p => p.Categories)
|
||||
.ThenInclude(c => c.Items)
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == name.ToLower());
|
||||
|
||||
if (period == null)
|
||||
{
|
||||
return Ok(new BudgetDto
|
||||
{
|
||||
Name = name,
|
||||
Categories = new List<BudgetCategoryDto>()
|
||||
});
|
||||
}
|
||||
|
||||
var dto = new BudgetDto
|
||||
{
|
||||
Id = period.Id,
|
||||
Name = period.Name,
|
||||
Year = period.Year ?? 0,
|
||||
Month = period.Month ?? 0,
|
||||
Categories = period.Categories
|
||||
.OrderBy(cat => cat.Order)
|
||||
.Select(cat => new BudgetCategoryDto
|
||||
{
|
||||
Id = cat.Id,
|
||||
Name = cat.Name,
|
||||
Color = cat.Color,
|
||||
Items = cat.Items
|
||||
.OrderBy(i => i.Order)
|
||||
.Select(i => new BudgetItemDto
|
||||
{
|
||||
Id = i.Id,
|
||||
Name = i.Name,
|
||||
Amount = i.Amount,
|
||||
IsExpense = i.IsExpense,
|
||||
IncludeInSummary = i.IncludeInSummary,
|
||||
BudgetItemDefinitionId = i.BudgetItemDefinitionId,
|
||||
PaymentStatus = i.PaymentStatus
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
return Ok(dto);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPut("updatePaymentStatus")]
|
||||
public IActionResult UpdatePaymentStatus([FromBody] PaymentStatusUpdateDto dto)
|
||||
{
|
||||
@@ -199,11 +250,37 @@ namespace Aberwyn.Controllers
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreatePeriod([FromBody] BudgetPeriod newPeriod)
|
||||
{
|
||||
_context.BudgetPeriods.Add(newPeriod);
|
||||
await _context.SaveChangesAsync();
|
||||
return CreatedAtAction(nameof(GetBudget), new { year = newPeriod.Year, month = newPeriod.Month }, newPeriod);
|
||||
if (!string.IsNullOrWhiteSpace(newPeriod.Name))
|
||||
{
|
||||
var existingNamed = await _context.BudgetPeriods
|
||||
.FirstOrDefaultAsync(p => p.Name != null && p.Name.ToLower() == newPeriod.Name.ToLower());
|
||||
|
||||
if (existingNamed != null)
|
||||
return Conflict("En budget med detta namn finns redan.");
|
||||
|
||||
_context.BudgetPeriods.Add(newPeriod);
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok(new { id = newPeriod.Id });
|
||||
}
|
||||
|
||||
if (newPeriod.Year.HasValue && newPeriod.Month.HasValue)
|
||||
{
|
||||
var existing = await _context.BudgetPeriods
|
||||
.FirstOrDefaultAsync(p => p.Year == newPeriod.Year && p.Month == newPeriod.Month);
|
||||
|
||||
if (existing != null)
|
||||
return Conflict("En budget för denna månad finns redan.");
|
||||
|
||||
_context.BudgetPeriods.Add(newPeriod);
|
||||
await _context.SaveChangesAsync();
|
||||
return Ok(new { id = newPeriod.Id });
|
||||
}
|
||||
|
||||
return BadRequest("Varken namn eller år/månad angivet.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPut("item/{id}")]
|
||||
public async Task<IActionResult> UpdateItem(int id, [FromBody] BudgetItem updatedItem)
|
||||
{
|
||||
@@ -330,19 +407,12 @@ namespace Aberwyn.Controllers
|
||||
return BadRequest("Ogiltig data.");
|
||||
|
||||
var period = await _context.BudgetPeriods
|
||||
.FirstOrDefaultAsync(p => p.Year == newCategoryDto.Year && p.Month == newCategoryDto.Month);
|
||||
.FirstOrDefaultAsync(p => p.Id == newCategoryDto.BudgetPeriodId);
|
||||
|
||||
if (period == null)
|
||||
{
|
||||
period = new BudgetPeriod
|
||||
{
|
||||
Year = newCategoryDto.Year,
|
||||
Month = newCategoryDto.Month
|
||||
};
|
||||
_context.BudgetPeriods.Add(period);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
return NotFound("Kunde inte hitta angiven budgetperiod.");
|
||||
|
||||
// 🔁 fortsätt som tidigare…
|
||||
var definition = await _context.BudgetCategoryDefinitions
|
||||
.FirstOrDefaultAsync(d => d.Name.ToLower() == newCategoryDto.Name.ToLower());
|
||||
|
||||
@@ -372,6 +442,8 @@ namespace Aberwyn.Controllers
|
||||
return Ok(new { id = category.Id });
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpDelete("category/{id}")]
|
||||
public async Task<IActionResult> DeleteCategory(int id)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,14 @@ namespace Aberwyn.Controllers
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("budget/{name}")]
|
||||
public IActionResult Index(string name)
|
||||
{
|
||||
ViewBag.BudgetName = name;
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
// För fallback när ingen månad/år anges
|
||||
[Route("budget")]
|
||||
public IActionResult Index()
|
||||
|
||||
1060
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.Designer.cs
generated
Normal file
1060
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.cs
Normal file
19
Aberwyn/Migrations/20250708102137_AddNameToBudgetPeriod.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddNameToBudgetPeriod : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
1063
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.Designer.cs
generated
Normal file
1063
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.cs
Normal file
26
Aberwyn/Migrations/20250708102224_AddNameToBudgetPeriod2.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class AddNameToBudgetPeriod2 : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Name",
|
||||
table: "BudgetPeriods",
|
||||
type: "longtext",
|
||||
nullable: true)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Name",
|
||||
table: "BudgetPeriods");
|
||||
}
|
||||
}
|
||||
}
|
||||
1063
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.Designer.cs
generated
Normal file
1063
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.cs
Normal file
51
Aberwyn/Migrations/20250708104331_MakeYearMonthNullable.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Aberwyn.Migrations
|
||||
{
|
||||
public partial class MakeYearMonthNullable : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Year",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Month",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: true,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Year",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Month",
|
||||
table: "BudgetPeriods",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "int",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,13 +226,16 @@ namespace Aberwyn.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Month")
|
||||
b.Property<int?>("Month")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("Year")
|
||||
b.Property<int?>("Year")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
@@ -7,9 +7,11 @@ namespace Aberwyn.Models
|
||||
public class BudgetPeriod
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public int? Month { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
public List<BudgetCategory> Categories { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -62,8 +64,9 @@ namespace Aberwyn.Models
|
||||
public class BudgetDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public int? Month { get; set; }
|
||||
public int Order { get; set; }
|
||||
|
||||
public List<BudgetCategoryDto> Categories { get; set; } = new();
|
||||
@@ -78,7 +81,7 @@ namespace Aberwyn.Models
|
||||
public int Order { get; set; }
|
||||
public int? BudgetCategoryDefinitionId { get; set; }
|
||||
|
||||
|
||||
public int? BudgetPeriodId { get; set; }
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
ViewData["Title"] = "Budget";
|
||||
}
|
||||
|
||||
<div ng-app="budgetApp" ng-controller="BudgetController" class="budget-page" ng-if="!loading">
|
||||
<div ng-app="budgetApp" ng-controller="BudgetController">
|
||||
<div class="budget-header" style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
|
||||
<div class="month-nav-bar" style="display: flex; align-items: center; gap: 10px; position: relative;">
|
||||
<button class="nav-button" ng-click="previousMonth()">←</button>
|
||||
@@ -190,14 +190,22 @@
|
||||
<div>Summa</div>
|
||||
<div class="amount">{{ getCategorySum(cat) | number:0 }}</div>
|
||||
</div>
|
||||
|
||||
<div class="no-data" ng-if="!loading && budget && budget.categories.length === 0">
|
||||
Det finns ingen budgetdata för vald månad ({{ selectedMonth }}/{{ selectedYear }}).
|
||||
<button ng-click="copyPreviousMonthSafe()">[Test] Kopiera föregående</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="no-data" ng-if="!loading && budget && budget.categories.length === 0">
|
||||
<p>Det finns ingen budgetdata för
|
||||
<strong>{{ budget.name || (selectedMonth + '/' + selectedYear) }}</strong>.
|
||||
</p>
|
||||
|
||||
<div style="margin-top: 10px;">
|
||||
<button ng-click="createEmptyBudget()" style="margin-right: 10px;">
|
||||
Skapa ny budget
|
||||
</button>
|
||||
<button ng-click="copyPreviousMonthSafe()">
|
||||
Kopiera föregående månad
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-item-popup" ng-show="addPopupVisible" ng-style="addPopupStyle" ng-class="{ 'above': addPopupAbove }">
|
||||
<label>Typ:</label>
|
||||
<select ng-model="addPopupData.newItemType">
|
||||
@@ -262,8 +270,10 @@
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
||||
<script>
|
||||
window.initialYear = @ViewBag.Year;
|
||||
window.initialMonth = @ViewBag.Month;
|
||||
window.initialYear = @(ViewBag.Year ?? "null");
|
||||
window.initialMonth = @(ViewBag.Month ?? "null");
|
||||
window.initialName = "@(ViewBag.BudgetName ?? "")";
|
||||
|
||||
</script>
|
||||
<script src="~/js/budget.js"></script>
|
||||
<script src="~/js/budget-dragdrop.js"></script>
|
||||
@@ -6,6 +6,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
$scope.error = null;
|
||||
$scope.menuOpen = false;
|
||||
$scope.chartMode = "pie";
|
||||
const initialName = window.initialName;
|
||||
|
||||
$scope.monthNames = ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"];
|
||||
$scope.getMonthName = function (month) {
|
||||
@@ -15,6 +16,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
$scope.selectedYear = window.initialYear || new Date().getFullYear();
|
||||
$scope.selectedMonth = window.initialMonth || new Date().getMonth() + 1;
|
||||
|
||||
|
||||
$scope.tempMonth = $scope.monthNames[$scope.selectedMonth - 1];
|
||||
$scope.tempYear = $scope.selectedYear;
|
||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||
@@ -140,10 +142,21 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
$scope.loading = true;
|
||||
$scope.error = null;
|
||||
$scope.budget = null;
|
||||
$scope.budgetNotFound = false;
|
||||
|
||||
$http.get(`/api/budget/${$scope.selectedYear}/${$scope.selectedMonth}`)
|
||||
const useName = typeof initialName === 'string' && initialName !== "null" && initialName !== "";
|
||||
|
||||
let url = "";
|
||||
if (useName) {
|
||||
url = `/api/budget/byname/${initialName}`;
|
||||
} else {
|
||||
url = `/api/budget/${$scope.selectedYear}/${$scope.selectedMonth}`;
|
||||
}
|
||||
|
||||
$http.get(url)
|
||||
.then(function (response) {
|
||||
const raw = response.data;
|
||||
|
||||
if (raw && raw.Categories) {
|
||||
const categories = raw.Categories.map(cat => ({
|
||||
id: cat.Id,
|
||||
@@ -151,6 +164,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
color: cat.Color,
|
||||
editing: false,
|
||||
allowDrag: false,
|
||||
order: cat.Order ?? 0,
|
||||
items: (cat.Items || []).map((item, index) => {
|
||||
const definition = $scope.itemDefinitions.find(d => d.id === item.BudgetItemDefinitionId || d.Id === item.BudgetItemDefinitionId);
|
||||
return {
|
||||
@@ -165,23 +179,35 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
paymentStatus: item.PaymentStatus
|
||||
};
|
||||
}).sort((a, b) => a.order - b.order)
|
||||
|
||||
}));
|
||||
|
||||
$scope.budget = {
|
||||
id: raw.Id,
|
||||
name: raw.Name || null,
|
||||
year: raw.Year,
|
||||
month: raw.Month,
|
||||
categories: categories.sort((a, b) => a.order - b.order)
|
||||
};
|
||||
|
||||
$scope.budgetNotFound = false;
|
||||
|
||||
if (!useName) {
|
||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||
}
|
||||
} else {
|
||||
$scope.budget = { categories: [] };
|
||||
$scope.budgetNotFound = true;
|
||||
}
|
||||
$scope.selectedMonthName = $scope.getMonthName($scope.selectedMonth);
|
||||
})
|
||||
.catch(function (error) {
|
||||
if (error.status === 404) {
|
||||
$scope.budget = { categories: [] };
|
||||
$scope.budget = {
|
||||
name: useName ? initialName : null,
|
||||
year: useName ? null : $scope.selectedYear,
|
||||
month: useName ? null : $scope.selectedMonth,
|
||||
categories: []
|
||||
};
|
||||
$scope.budgetNotFound = true;
|
||||
} else {
|
||||
$scope.error = "Kunde inte ladda budgetdata.";
|
||||
$scope.showToast("Fel vid laddning av budgetdata", true);
|
||||
@@ -194,6 +220,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.saveCategory = function (category) {
|
||||
if (category.newItemName && category.newItemAmount) {
|
||||
const newItem = {
|
||||
@@ -811,6 +838,28 @@ $scope.addItemFromDefinition = function (cat) {
|
||||
return cat.items.some(i => i.isExpense);
|
||||
};
|
||||
|
||||
$scope.createEmptyBudget = function () {
|
||||
if (!$scope.budget || !$scope.budget.name) {
|
||||
$scope.showToast("Ogiltigt budgetnamn.");
|
||||
return;
|
||||
}
|
||||
|
||||
const dto = {
|
||||
name: $scope.budget.name
|
||||
};
|
||||
|
||||
$http.post('/api/budget', dto)
|
||||
.then(() => {
|
||||
$scope.showToast("Ny budget skapad.");
|
||||
$scope.loadBudget(); // ladda om efter skapandet
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Fel vid skapande:", error);
|
||||
$scope.showToast("Kunde inte skapa budget.");
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.drawCategoryChart = function () {
|
||||
const ctx = document.getElementById("expenseChart");
|
||||
if (!ctx || !$scope.budget?.categories) return;
|
||||
|
||||
Reference in New Issue
Block a user