Compare commits
5 Commits
f531bf54af
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b2041d2bf | ||
|
|
60c8f9b34e | ||
|
|
7b3c0998a0 | ||
|
|
8da7888933 | ||
|
|
9e34e45748 |
@@ -1,6 +1,8 @@
|
||||
using Aberwyn.Models;
|
||||
using Aberwyn.Data;
|
||||
using Aberwyn.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Aberwyn.Controllers
|
||||
{
|
||||
@@ -12,6 +14,9 @@ namespace Aberwyn.Controllers
|
||||
{
|
||||
ViewBag.Year = year;
|
||||
ViewBag.Month = month;
|
||||
DateTime payDate = SalaryDateService.GetSalaryPayDate(year, month -1);
|
||||
ViewBag.Paydate = payDate.ToString("d MMMM", new CultureInfo("sv-SE"));
|
||||
|
||||
return View();
|
||||
}
|
||||
[Route("budget/list")]
|
||||
@@ -20,6 +25,7 @@ namespace Aberwyn.Controllers
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
[Route("budget/{name}")]
|
||||
public IActionResult Index(string name)
|
||||
{
|
||||
@@ -39,9 +45,19 @@ namespace Aberwyn.Controllers
|
||||
public IActionResult Index()
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
return RedirectToAction("Index", new { year = now.Year, month = now.Month });
|
||||
return RedirectToAction("Index", new { year = now.Year, month = now.Month + 1 });
|
||||
}
|
||||
|
||||
[HttpGet("api/budget/{year:int}/{month:int}/salary-date")]
|
||||
public IActionResult SalaryDate(int year, int month)
|
||||
{
|
||||
var date = SalaryDateService.GetSalaryPayDate(year, month);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
iso = date.ToString("yyyy-MM-dd"),
|
||||
display = date.ToString("d MMMM yyyy", new CultureInfo("sv-SE"))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Globalization;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -112,6 +113,70 @@ namespace Aberwyn.Controllers
|
||||
#region Skolmat
|
||||
[HttpGet("skolmat")]
|
||||
public async Task<IActionResult> GetSkolmat(int week, [FromQuery] string sensor = "sensor.engelbrektsskolan")
|
||||
{
|
||||
var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3M2Q5ODIyYzU4ZWI0MjM4OWEyMGQ2MWQ2MWVhOWYzYyIsImlhdCI6MTc0OTE1MzY1MCwiZXhwIjoyMDY0NTEzNjUwfQ.8C_dKm7P1BbFVJKc_wT76YnQqiZxkP9EzrsLbfD0Ml8";
|
||||
var client = new HttpClient();
|
||||
client.BaseAddress = new Uri("https://ha.zcz.se");
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
var res = await client.GetAsync($"/api/states/{sensor}");
|
||||
|
||||
if (!res.IsSuccessStatusCode)
|
||||
return StatusCode((int)res.StatusCode, $"Kunde inte hämta data för {sensor}");
|
||||
|
||||
var json = await res.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
|
||||
var attr = doc.RootElement.GetProperty("attributes");
|
||||
|
||||
if (!attr.TryGetProperty("calendar", out var calendar))
|
||||
return NotFound("Ingen kalender hittades i attributen");
|
||||
|
||||
var result = new List<SchoolMealDto>();
|
||||
var culture = CultureInfo.InvariantCulture;
|
||||
var calendarSystem = culture.Calendar;
|
||||
|
||||
foreach (var property in calendar.EnumerateObject())
|
||||
{
|
||||
var dateString = property.Name; // t.ex 2026-02-09
|
||||
if (!DateTime.TryParse(dateString, out var date))
|
||||
continue;
|
||||
|
||||
int weekNumber = calendarSystem.GetWeekOfYear(
|
||||
date,
|
||||
CalendarWeekRule.FirstFourDayWeek,
|
||||
DayOfWeek.Monday);
|
||||
|
||||
if (weekNumber != week)
|
||||
continue;
|
||||
|
||||
var dishes = property.Value.EnumerateArray()
|
||||
.Select(x => x.GetProperty("dish").GetString())
|
||||
.ToList();
|
||||
|
||||
result.Add(new SchoolMealDto
|
||||
{
|
||||
Weekday = date.ToString("dddd", new CultureInfo("sv-SE")),
|
||||
Date = date.ToString("yyyy-MM-dd"),
|
||||
Courses = dishes
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (!result.Any())
|
||||
return NotFound("Ingen skolmat för vecka " + week);
|
||||
|
||||
return Ok(result.OrderBy(x => x.Date));
|
||||
}
|
||||
public class SchoolMealDto
|
||||
{
|
||||
public string Weekday { get; set; }
|
||||
public string Date { get; set; }
|
||||
public List<string> Courses { get; set; }
|
||||
}
|
||||
|
||||
public async Task<IActionResult> GetSkolmatv0(int week, [FromQuery] string sensor = "sensor.engelbrektsskolan")
|
||||
{
|
||||
var token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3M2Q5ODIyYzU4ZWI0MjM4OWEyMGQ2MWQ2MWVhOWYzYyIsImlhdCI6MTc0OTE1MzY1MCwiZXhwIjoyMDY0NTEzNjUwfQ.8C_dKm7P1BbFVJKc_wT76YnQqiZxkP9EzrsLbfD0Ml8";
|
||||
var client = new HttpClient();
|
||||
|
||||
97
Aberwyn/Data/PayoutService.cs
Normal file
97
Aberwyn/Data/PayoutService.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Aberwyn.Data;
|
||||
|
||||
namespace Aberwyn.Data
|
||||
{
|
||||
public class PayoutService
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public static class SwedishHolidays
|
||||
{
|
||||
public static bool IsRedDay(DateTime date)
|
||||
{
|
||||
var year = date.Year;
|
||||
|
||||
var fixedHolidays = new[]
|
||||
{
|
||||
new DateTime(year, 1, 1), // Nyårsdagen
|
||||
new DateTime(year, 1, 6), // Trettondedag jul
|
||||
new DateTime(year, 5, 1), // Första maj
|
||||
new DateTime(year, 6, 6), // Nationaldagen
|
||||
new DateTime(year, 12, 25), // Juldagen
|
||||
new DateTime(year, 12, 26), // Annandag jul
|
||||
};
|
||||
|
||||
if (fixedHolidays.Contains(date.Date))
|
||||
return true;
|
||||
|
||||
// Rörliga helgdagar
|
||||
var easter = GetEasterSunday(year);
|
||||
|
||||
var movable = new[]
|
||||
{
|
||||
easter.AddDays(-2), // Långfredagen
|
||||
easter, // Påskdagen
|
||||
easter.AddDays(1), // Annandag påsk
|
||||
easter.AddDays(39), // Kristi himmelsfärd
|
||||
easter.AddDays(49), // Pingstdagen
|
||||
GetMidsummerDay(year),
|
||||
};
|
||||
|
||||
return movable.Contains(date.Date);
|
||||
}
|
||||
|
||||
private static DateTime GetMidsummerDay(int year)
|
||||
{
|
||||
// Lördag mellan 20–26 juni
|
||||
for (int day = 20; day <= 26; day++)
|
||||
{
|
||||
var d = new DateTime(year, 6, day);
|
||||
if (d.DayOfWeek == DayOfWeek.Saturday)
|
||||
return d;
|
||||
}
|
||||
throw new Exception("Midsummer not found");
|
||||
}
|
||||
|
||||
|
||||
private static DateTime GetEasterSunday(int year)
|
||||
{
|
||||
// Meeus/Jones/Butcher
|
||||
int a = year % 19;
|
||||
int b = year / 100;
|
||||
int c = year % 100;
|
||||
int d = b / 4;
|
||||
int e = b % 4;
|
||||
int f = (b + 8) / 25;
|
||||
int g = (b - f + 1) / 3;
|
||||
int h = (19 * a + b - d - g + 15) % 30;
|
||||
int i = c / 4;
|
||||
int k = c % 4;
|
||||
int l = (32 + 2 * e + 2 * i - h - k) % 7;
|
||||
int m = (a + 11 * h + 22 * l) / 451;
|
||||
int month = (h + l - 7 * m + 114) / 31;
|
||||
int day = ((h + l - 7 * m + 114) % 31) + 1;
|
||||
|
||||
return new DateTime(year, month, day);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
public static class SalaryDateService
|
||||
{
|
||||
public static DateTime GetSalaryPayDate(int year, int month)
|
||||
{
|
||||
var payDate = new DateTime(year, month, 25);
|
||||
|
||||
while (payDate.DayOfWeek == DayOfWeek.Saturday ||
|
||||
payDate.DayOfWeek == DayOfWeek.Sunday ||
|
||||
SwedishHolidays.IsRedDay(payDate))
|
||||
{
|
||||
payDate = payDate.AddDays(-1);
|
||||
}
|
||||
|
||||
return payDate;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ namespace Aberwyn.Models
|
||||
[JsonIgnore]
|
||||
[ValidateNever]
|
||||
public BudgetPeriod BudgetPeriod { get; set; }
|
||||
|
||||
public List<BudgetItem> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -127,4 +126,6 @@ namespace Aberwyn.Models
|
||||
public string Color { get; set; } // standardfärg, kan modifieras per period
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@attribute [Authorize(Roles = "Budget")]
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Budget";
|
||||
}
|
||||
@@ -51,6 +52,7 @@
|
||||
<div class="budget-summary-box compact">
|
||||
<h3>Sammanställning</h3>
|
||||
<ul class="summary-list">
|
||||
<li><span>Utbetalning:</span>{{ salaryPayDate || '–' }}</li>
|
||||
<li><span>💰 Inkomst:</span> {{ getTotalIncome() | number:0 }} kr</li>
|
||||
<li><span>💸 Utgift:</span> {{ getTotalExpense() | number:0 }} kr</li>
|
||||
<li><span>🏦 Sparande:</span> {{ getTotalSaving() | number:0 }} kr</li>
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
<div class="card-container" ng-show="school.expanded">
|
||||
<div class="meal-card" ng-repeat="day in school.days">
|
||||
<div class="card-content school-meal-card-content">
|
||||
<div class="day">{{ day.weekday }}</div>
|
||||
<div class="meal" ng-repeat="meal in day.courses">{{ meal }}</div>
|
||||
<div class="day">{{ day.Weekday }}</div>
|
||||
<div class="meal" ng-repeat="meal in day.Courses">{{ meal }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -307,7 +307,7 @@ h1 {
|
||||
|
||||
.school-meal-card-content .meal {
|
||||
font-size: clamp(0.55rem, 1.0vw, 0.8rem);
|
||||
line-height: 1.2;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
word-break: break-word;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -60,6 +60,15 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
$scope.loadBudget();
|
||||
};
|
||||
|
||||
$scope.loadSalaryDate = function () {
|
||||
const year = $scope.selectedYear;
|
||||
const month = $scope.selectedMonth -1;
|
||||
|
||||
$http.get(`/api/budget/${year}/${month}/salary-date`)
|
||||
.then(function (res) {
|
||||
$scope.salaryPayDate = res.data.display;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setItemType = function (item, type) {
|
||||
if (type === 'expense') {
|
||||
@@ -126,7 +135,6 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
|
||||
|
||||
|
||||
|
||||
$scope.showToast = function (message, isError = false) {
|
||||
const toast = document.createElement("div");
|
||||
toast.className = "toast" + (isError ? " error" : "") + " show";
|
||||
@@ -216,6 +224,7 @@ app.controller('BudgetController', function ($scope, $http) {
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
$scope.loadSalaryDate();
|
||||
$scope.loading = false;
|
||||
setTimeout($scope.drawCategoryChart, 0);
|
||||
});
|
||||
|
||||
@@ -51,9 +51,9 @@ angular.module('mealMenuApp', ['ngSanitize'])
|
||||
$scope.schoolMealsBySchool = [];
|
||||
|
||||
$scope.schoolSensors = [
|
||||
{ name: "William – Engelbrektsskolan", entity: "sensor.engelbrektsskolan" },
|
||||
{ name: "Louise - Nyeds skolan", entity: "sensor.nyedsskola" },
|
||||
{ name: "Ludwig - Skogsgläntan", entity: "sensor.skogsglantan" }
|
||||
{ name: "William – Engelbrektsskolan", entity: "sensor.william_lunch" },
|
||||
{ name: "Louise - Nyeds skolan", entity: "sensor.louise_lunch" },
|
||||
{ name: "Ludwig - Skogsgläntan", entity: "sensor.ludwig_lunch" }
|
||||
];
|
||||
|
||||
$scope.schoolMealsBySchool = [];
|
||||
|
||||
Reference in New Issue
Block a user