Add project files.
@@ -0,0 +1,30 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
!**/.gitignore
|
||||
!.git/HEAD
|
||||
!.git/config
|
||||
!.git/packed-refs
|
||||
!.git/refs/heads/**
|
||||
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="StockFin/StockFin.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Text.Json;
|
||||
using System.Globalization;
|
||||
|
||||
namespace StockFin
|
||||
{
|
||||
public class AlphaVantageService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public AlphaVantageService(
|
||||
HttpClient httpClient,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
private string ApiKey =>
|
||||
_configuration["AlphaVantage:ApiKey"]!;
|
||||
|
||||
public async Task<StockQuote?> GetQuoteAsync(
|
||||
string isin,
|
||||
string ticker)
|
||||
{
|
||||
var url =
|
||||
$"https://www.alphavantage.co/query" +
|
||||
$"?function=GLOBAL_QUOTE" +
|
||||
$"&symbol={ticker}" +
|
||||
$"&apikey={ApiKey}";
|
||||
|
||||
var json = await _httpClient.GetStringAsync(url);
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
|
||||
if (!doc.RootElement.TryGetProperty("Global Quote", out var quote))
|
||||
return null;
|
||||
|
||||
return new StockQuote
|
||||
{
|
||||
Isin = isin,
|
||||
Symbol = ticker,
|
||||
CurrentPrice =
|
||||
double.Parse(
|
||||
quote.GetProperty("05. price").GetString()!,CultureInfo.InvariantCulture),
|
||||
|
||||
PreviousClose =
|
||||
double.Parse(
|
||||
quote.GetProperty("08. previous close").GetString()!, CultureInfo.InvariantCulture),
|
||||
|
||||
QuoteTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<HistoricalPrice?> GetPriceAtDateAsync(
|
||||
string ticker,
|
||||
DateTime date)
|
||||
{
|
||||
var url =
|
||||
$"https://www.alphavantage.co/query" +
|
||||
$"?function=TIME_SERIES_DAILY" +
|
||||
$"&symbol={ticker}" +
|
||||
$"&outputsize=full" +
|
||||
$"&apikey={ApiKey}";
|
||||
|
||||
var json = await _httpClient.GetStringAsync(url);
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
|
||||
var series =
|
||||
doc.RootElement.GetProperty("Time Series (Daily)");
|
||||
|
||||
var key = date.ToString("yyyy-MM-dd");
|
||||
|
||||
if (!series.TryGetProperty(key, out var day))
|
||||
return null;
|
||||
|
||||
return new HistoricalPrice
|
||||
{
|
||||
Date = date.Date,
|
||||
Close = double.Parse(
|
||||
day.GetProperty("4. close").GetString()!, CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
}
|
||||
public class StockQuote
|
||||
{
|
||||
public string Isin { get; set; } = "";
|
||||
public string Symbol { get; set; } = "";
|
||||
|
||||
public double CurrentPrice { get; set; }
|
||||
public double PreviousClose { get; set; }
|
||||
|
||||
public DateTime QuoteTime { get; set; }
|
||||
}
|
||||
public class HistoricalPrice
|
||||
{
|
||||
public DateTime Date { get; set; }
|
||||
public double Close { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StockFin.Models;
|
||||
using StockFin.ViewModels;
|
||||
|
||||
namespace StockFin.Controllers
|
||||
{
|
||||
public class AccountsController : Controller
|
||||
{
|
||||
private readonly FinancesContext _context = new FinancesContext();
|
||||
private readonly AlphaVantageService _alpha;
|
||||
private readonly IFinnhubClient _finnhub;
|
||||
private readonly ITwelveDataClient _twelveData;
|
||||
private readonly StocksCache _cache;
|
||||
|
||||
public AccountsController(AlphaVantageService alpha, IFinnhubClient finnhub, ITwelveDataClient twelveData, StocksCache cache)
|
||||
{
|
||||
_alpha = alpha;
|
||||
_finnhub = finnhub;
|
||||
_twelveData = twelveData;
|
||||
_cache = cache;
|
||||
}
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var accounts = await _context.Accounts
|
||||
.Include(a => a.Type)
|
||||
.Include(a => a.Increase)
|
||||
.OrderBy(a => a.Bank)
|
||||
.ThenBy(a => a.Title)
|
||||
.ToListAsync();
|
||||
|
||||
return View(accounts);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Details(long? id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var account = await _context.Accounts
|
||||
.Include(a => a.Type)
|
||||
.Include(a => a.Increase)
|
||||
.FirstOrDefaultAsync(a => a.Id == id.Value);
|
||||
|
||||
if (account == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var statements = await _context.Statements
|
||||
.Where(s => s.AccountId == account.Id)
|
||||
.OrderByDescending(s => s.Date)
|
||||
.ToListAsync();
|
||||
|
||||
var transactions = await _context.Transactions
|
||||
.Where(t => t.AccountId == account.Id)
|
||||
.Include(t => t.Stocks)
|
||||
.OrderByDescending(t => t.Date)
|
||||
.ToListAsync();
|
||||
|
||||
var patrimonies = await _context.Patrimonies
|
||||
.Where(p => p.AccountId == account.Id)
|
||||
.OrderByDescending(p => p.BuyingDate)
|
||||
.ThenBy(p => p.Title)
|
||||
.ToListAsync();
|
||||
|
||||
var model = new AccountDetailsViewModel
|
||||
{
|
||||
Account = account,
|
||||
Statements = statements,
|
||||
Transactions = transactions,
|
||||
Patrimonies = patrimonies
|
||||
};
|
||||
ViewData["actuals"] = _cache.actuals;
|
||||
return View(model);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Create()
|
||||
{
|
||||
await PopulateDropDownsAsync();
|
||||
return View(new Account());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create([Bind("Id,Title,IncreaseId,TypeId,Bank")] Account account)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_context.Add(account);
|
||||
await _context.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
await PopulateDropDownsAsync(account.TypeId, account.IncreaseId);
|
||||
return View(account);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Edit(long? id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var account = await _context.Accounts.FindAsync(id.Value);
|
||||
if (account == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await PopulateDropDownsAsync(account.TypeId, account.IncreaseId);
|
||||
return View(account);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(long id, [Bind("Id,Title,IncreaseId,TypeId,Bank")] Account account)
|
||||
{
|
||||
if (id != account.Id)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.Update(account);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!AccountExists(account.Id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
await PopulateDropDownsAsync(account.TypeId, account.IncreaseId);
|
||||
return View(account);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Delete(long? id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var account = await _context.Accounts
|
||||
.Include(a => a.Type)
|
||||
.Include(a => a.Increase)
|
||||
.FirstOrDefaultAsync(a => a.Id == id.Value);
|
||||
|
||||
if (account == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View(account);
|
||||
}
|
||||
|
||||
[HttpPost, ActionName("Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(long id)
|
||||
{
|
||||
var account = await _context.Accounts.FindAsync(id);
|
||||
if (account != null)
|
||||
{
|
||||
_context.Accounts.Remove(account);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
private bool AccountExists(long id)
|
||||
{
|
||||
return _context.Accounts.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
private async Task PopulateDropDownsAsync(long? selectedTypeId = null, long? selectedIncreaseId = null)
|
||||
{
|
||||
var accountTypes = await _context.AccountTypes
|
||||
.OrderBy(t => t.Title)
|
||||
.ToListAsync();
|
||||
|
||||
var increases = await _context.Increases
|
||||
.OrderBy(i => i.Title)
|
||||
.Select(i => new
|
||||
{
|
||||
i.Id,
|
||||
DisplayTitle = i.Value.HasValue
|
||||
? $"{i.Title} ({i.Value.Value:N2}%)"
|
||||
: (i.Title ?? "Sans croissance")
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
ViewData["TypeId"] = new SelectList(accountTypes, "Id", "Title", selectedTypeId);
|
||||
ViewData["IncreaseId"] = new SelectList(increases, "Id", "DisplayTitle", selectedIncreaseId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StockFin.Models;
|
||||
using StockFin.ViewModels;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace StockFin.Controllers
|
||||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
FinancesContext db = new FinancesContext();
|
||||
private readonly AlphaVantageService _alpha;
|
||||
private readonly IFinnhubClient _finnhub;
|
||||
private readonly ITwelveDataClient _twelveData;
|
||||
private readonly StocksCache _cache;
|
||||
|
||||
public HomeController(AlphaVantageService alpha, IFinnhubClient finnhub, ITwelveDataClient twelveData, StocksCache cache)
|
||||
{
|
||||
_alpha = alpha;
|
||||
_finnhub = finnhub;
|
||||
_twelveData = twelveData;
|
||||
_cache = cache;
|
||||
}
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var accountTypes = db.AccountTypes.ToList();
|
||||
|
||||
try
|
||||
{
|
||||
//await _alpha.GetQuoteAsync("IE00B4K48X80", "IMAE.AMS");
|
||||
//var results =
|
||||
//await _twelveData.SearchAsync("IE00B44Z5B48");
|
||||
|
||||
//var quote =
|
||||
// await _twelveData.GetQuoteAsync("SPYY");
|
||||
|
||||
//var price =
|
||||
//await _twelveData.GetPriceAsync("AAPL");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// AlphaVantage unavailable — continue loading the page
|
||||
_ = ex;
|
||||
}
|
||||
|
||||
await _finnhub.GetQuoteAsync("SPY");
|
||||
var accounts = db.Accounts.ToList();
|
||||
List<AccountState> accountStates = new List<AccountState>();
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
switch(account.TypeId)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
accountStates.Add(AccountState.FromAccount(account.Id));
|
||||
break;
|
||||
case 5:
|
||||
case 3:
|
||||
accountStates.Add(await AccountState.FromStocks(account.Id, _cache));
|
||||
break;
|
||||
case 6:
|
||||
accountStates.Add(AccountState.FromPatrimony(account.Id));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
var colorPalette = new[]
|
||||
{
|
||||
"#2563eb",
|
||||
"#0f766e",
|
||||
"#7c3aed",
|
||||
"#ea580c",
|
||||
"#db2777",
|
||||
"#ca8a04",
|
||||
"#1d4ed8",
|
||||
"#0891b2"
|
||||
};
|
||||
|
||||
var totalActualValue = accountStates.Sum(a => a.ActualValue);
|
||||
var totalStartValue = accountStates.Sum(a => a.StartValue);
|
||||
|
||||
var typeSummaries = accountTypes
|
||||
.Select((type, index) =>
|
||||
{
|
||||
var typeAccounts = accountStates
|
||||
.Where(a => a.TypeId == type.Id)
|
||||
.OrderByDescending(a => a.ActualValue)
|
||||
.ToList();
|
||||
|
||||
var actualValue = typeAccounts.Sum(a => a.ActualValue);
|
||||
var startValue = typeAccounts.Sum(a => a.StartValue);
|
||||
|
||||
return new HomeAccountTypeSummaryViewModel
|
||||
{
|
||||
TypeId = type.Id,
|
||||
TypeTitle = type.Title ?? "Sans type",
|
||||
Color = colorPalette[index % colorPalette.Length],
|
||||
ActualValue = actualValue,
|
||||
StartValue = startValue,
|
||||
Percentage = totalActualValue > 0 ? (actualValue / totalActualValue) * 100 : 0,
|
||||
StartPercentage = totalStartValue > 0 ? (startValue / totalStartValue) * 100 : 0,
|
||||
Accounts = typeAccounts
|
||||
};
|
||||
})
|
||||
.Where(summary => summary.Accounts.Count > 0)
|
||||
.OrderByDescending(summary => summary.ActualValue)
|
||||
.ToList();
|
||||
|
||||
var model = new HomeDashboardViewModel
|
||||
{
|
||||
TypeSummaries = typeSummaries,
|
||||
TotalActualValue = totalActualValue,
|
||||
TotalStartValue = totalStartValue,
|
||||
AccountCount = accountStates.Count
|
||||
};
|
||||
|
||||
return View(model);
|
||||
|
||||
}
|
||||
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View();//new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StockFin.Models;
|
||||
using StockFin.ViewModels;
|
||||
|
||||
namespace StockFin.Controllers
|
||||
{
|
||||
public class StatementsController : Controller
|
||||
{
|
||||
private readonly FinancesContext _context=new FinancesContext();
|
||||
|
||||
//public StatementsController(FinancesContext context)
|
||||
//{
|
||||
// _context = context;
|
||||
//}
|
||||
|
||||
// GET: Statements
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var statements = await _context.Statements
|
||||
.Include(s => s.Account)
|
||||
.OrderByDescending(s => s.Date)
|
||||
.ToListAsync();
|
||||
|
||||
return View(statements);
|
||||
}
|
||||
|
||||
// GET: Statements/Create
|
||||
public async Task<IActionResult> Create()
|
||||
{
|
||||
await PopulateAccountsDropDownAsync();
|
||||
return View();
|
||||
}
|
||||
|
||||
// POST: Statements/Create
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create([Bind("Id,Date,AccountId,Value")] Statement statement)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_context.Add(statement);
|
||||
await _context.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
await PopulateAccountsDropDownAsync(statement.AccountId);
|
||||
return View(statement);
|
||||
}
|
||||
|
||||
// GET: Statements/Edit/5
|
||||
public async Task<IActionResult> Edit(long? id)
|
||||
{
|
||||
if (id == null) return NotFound();
|
||||
|
||||
var statement = await _context.Statements.FindAsync(id.Value);
|
||||
if (statement == null) return NotFound();
|
||||
|
||||
await PopulateAccountsDropDownAsync(statement.AccountId);
|
||||
return View(statement);
|
||||
}
|
||||
|
||||
// POST: Statements/Edit/5
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(long id, [Bind("Id,Date,AccountId,Value")] Statement statement)
|
||||
{
|
||||
if (id != statement.Id) return NotFound();
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.Update(statement);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!StatementExists(statement.Id)) return NotFound();
|
||||
else throw;
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
await PopulateAccountsDropDownAsync(statement.AccountId);
|
||||
return View(statement);
|
||||
}
|
||||
|
||||
// GET: Statements/Delete/5
|
||||
public async Task<IActionResult> Delete(long? id)
|
||||
{
|
||||
if (id == null) return NotFound();
|
||||
|
||||
var statement = await _context.Statements
|
||||
.Include(s => s.Account)
|
||||
.FirstOrDefaultAsync(s => s.Id == id.Value);
|
||||
|
||||
if (statement == null) return NotFound();
|
||||
|
||||
return View(statement);
|
||||
}
|
||||
|
||||
// POST: Statements/Delete/5
|
||||
[HttpPost, ActionName("Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(long id)
|
||||
{
|
||||
var statement = await _context.Statements.FindAsync(id);
|
||||
if (statement != null)
|
||||
{
|
||||
_context.Statements.Remove(statement);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
private bool StatementExists(long id)
|
||||
{
|
||||
return _context.Statements.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
private async Task PopulateAccountsDropDownAsync(long? selectedAccountId = null)
|
||||
{
|
||||
var accounts = await _context.Accounts.OrderBy(a => a.Title).Select(a => new
|
||||
{
|
||||
a.Id,
|
||||
DisplayTitle = a.Bank + " - " + a.Title
|
||||
}).ToListAsync();
|
||||
ViewData["AccountId"] = new SelectList(accounts, "Id", "DisplayTitle", selectedAccountId);
|
||||
}
|
||||
|
||||
// GET: Statements/CreateToday
|
||||
public async Task<IActionResult> CreateToday()
|
||||
{
|
||||
var accounts = await _context.Accounts.Where(t=>t.TypeId==1 || t.TypeId == 2 || t.TypeId == 4)
|
||||
.Include(a => a.Type)
|
||||
.OrderBy(a => a.Title)
|
||||
.ToListAsync();
|
||||
|
||||
var vm = new CreateTodayViewModel
|
||||
{
|
||||
Date = DateTime.Today,
|
||||
Accounts = accounts.Select(a => new CreateTodayViewModel.AccountEntry
|
||||
{
|
||||
Bank = a.Bank,
|
||||
AccountId = a.Id,
|
||||
AccountName = a.Title,
|
||||
AccountTypeName = a.Type?.Title,
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
// POST: Statements/CreateToday
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> CreateToday(CreateTodayViewModel vm)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
|
||||
var statements = vm.Accounts
|
||||
.Where(a => a.Amount.HasValue)
|
||||
.Select(a => new Statement
|
||||
{
|
||||
Date = vm.Date,
|
||||
AccountId = a.AccountId,
|
||||
Value = a.Amount!.Value,
|
||||
});
|
||||
|
||||
_context.Statements.AddRange(statements);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StockFin.Models;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using YahooFinanceApi;
|
||||
|
||||
namespace StockFin.Controllers
|
||||
{
|
||||
public class StocksController : Controller
|
||||
{
|
||||
private readonly FinancesContext _context = new FinancesContext();
|
||||
private readonly AlphaVantageService _alpha;
|
||||
private readonly IFinnhubClient _finnhub;
|
||||
private readonly ITwelveDataClient _twelveData;
|
||||
private readonly StocksCache _cache;
|
||||
|
||||
public StocksController(AlphaVantageService alpha, IFinnhubClient finnhub, ITwelveDataClient twelveData, StocksCache cache)
|
||||
{
|
||||
_alpha = alpha;
|
||||
_finnhub = finnhub;
|
||||
_twelveData = twelveData;
|
||||
_cache = cache;
|
||||
}
|
||||
//public StocksController(FinancesContext context)
|
||||
//{
|
||||
// _context = context;
|
||||
//}
|
||||
|
||||
// GET: Stocks
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var stocks = await _context.Stocks
|
||||
.OrderBy(s => s.Title)
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
ViewData["actuals"] = _cache.actuals;
|
||||
return View(stocks);
|
||||
}
|
||||
|
||||
// GET: Stocks/Create
|
||||
public IActionResult Create()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
// POST: Stocks/Create
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create([Bind("Id,Title,Isin,Ticker,Cost,TypeId")] Stock stock)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_context.Add(stock);
|
||||
await _context.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
return View(stock);
|
||||
}
|
||||
|
||||
// GET: Stocks/Edit/5
|
||||
public async Task<IActionResult> Edit(long? id)
|
||||
{
|
||||
if (id == null) return NotFound();
|
||||
|
||||
var stock = await _context.Stocks.FindAsync(id.Value);
|
||||
if (stock == null) return NotFound();
|
||||
|
||||
return View(stock);
|
||||
}
|
||||
|
||||
// POST: Stocks/Edit/5
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(long id, [Bind("Id,Title,Isin,Ticker,Cost,TypeId")] Stock stock)
|
||||
{
|
||||
if (id != stock.Id) return NotFound();
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.Update(stock);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!StockExists(stock.Id)) return NotFound();
|
||||
else throw;
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
return View(stock);
|
||||
}
|
||||
|
||||
// GET: Stocks/Delete/5
|
||||
public async Task<IActionResult> Delete(long? id)
|
||||
{
|
||||
if (id == null) return NotFound();
|
||||
|
||||
var stock = await _context.Stocks
|
||||
.FirstOrDefaultAsync(s => s.Id == id.Value);
|
||||
|
||||
if (stock == null) return NotFound();
|
||||
|
||||
return View(stock);
|
||||
}
|
||||
|
||||
// POST: Stocks/Delete/5
|
||||
[HttpPost, ActionName("Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(long id)
|
||||
{
|
||||
var stock = await _context.Stocks.FindAsync(id);
|
||||
if (stock != null)
|
||||
{
|
||||
_context.Stocks.Remove(stock);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
// GET
|
||||
public IActionResult UpdateFromTradeRepublic()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> UpdateFromTradeRepublic(IFormFile csvFile)
|
||||
{
|
||||
if (csvFile == null || csvFile.Length == 0)
|
||||
{
|
||||
ModelState.AddModelError("csvFile", "Veuillez sélectionner un fichier CSV.");
|
||||
return View();
|
||||
}
|
||||
|
||||
if (!Path.GetExtension(csvFile.FileName).Equals(".csv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ModelState.AddModelError("csvFile", "Le fichier doit avoir l'extension .csv.");
|
||||
return View();
|
||||
}
|
||||
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.csv");
|
||||
try
|
||||
{
|
||||
await using (var stream = System.IO.File.Create(tempPath))
|
||||
await csvFile.CopyToAsync(stream);
|
||||
|
||||
var importer = new TransactionCsvImporter();
|
||||
var rows = importer.Read(tempPath);
|
||||
|
||||
// TODO : traiter les lignes
|
||||
// var transactions = importer.ToTransactions(rows);
|
||||
// _context.Transactions.AddRange(transactions);
|
||||
// await _context.SaveChangesAsync();
|
||||
|
||||
ViewBag.ImportResult = rows.Count;
|
||||
return View();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (System.IO.File.Exists(tempPath))
|
||||
System.IO.File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
// POST
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> UpdateFromTradeRepublic2(IFormFile csvFile)
|
||||
{
|
||||
var traderepaccountid = 18;
|
||||
List<string> notfound = new List<string>();
|
||||
var stocks = await _context.Stocks
|
||||
.OrderBy(s => s.Title)
|
||||
.ToListAsync();
|
||||
var importer = new TransactionCsvImporter();
|
||||
var rows = importer.Read("Exportation de transactions.csv");
|
||||
|
||||
// Filtrer par type
|
||||
var Trade = rows.Where(r => r.Category == "TRADING");
|
||||
var total = 0;
|
||||
var found = 0;
|
||||
foreach (var row in Trade)
|
||||
{
|
||||
total = total + 1;
|
||||
var stock = stocks.FirstOrDefault(s => s.Isin == row.Symbol);
|
||||
if (stock != null)
|
||||
{
|
||||
|
||||
found = found + 1;
|
||||
Transaction T = _context.Transactions.Where(t => t.StocksId == stock.Id && t.AccountId == traderepaccountid && t.Date == row.Date).SingleOrDefault();
|
||||
if (T == null)
|
||||
{
|
||||
T = new Transaction();
|
||||
T.Date = row.Date;
|
||||
T.StocksId = stock.Id;
|
||||
T.Value = row.Price;
|
||||
T.Quantity = row.Shares;
|
||||
T.AccountId = traderepaccountid;
|
||||
T.TypeId = 1;
|
||||
_context.Transactions.Add(T);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!notfound.Contains(row.Symbol + " - " + row.Name))
|
||||
{
|
||||
notfound.Add(row.Symbol + " - " + row.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
_context.SaveChanges();
|
||||
Console.WriteLine($"Total: {total}, Found: {found}");
|
||||
return View(notfound);
|
||||
}
|
||||
private bool StockExists(long id)
|
||||
{
|
||||
return _context.Stocks.Any(e => e.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StockFin.Models;
|
||||
|
||||
namespace StockFin.Controllers;
|
||||
|
||||
public class TimeValuesController : Controller
|
||||
{
|
||||
private readonly FinancesContext _context = new FinancesContext();
|
||||
|
||||
//public TimeValuesController(FinancesContext context)
|
||||
//{
|
||||
// _context = context;
|
||||
//}
|
||||
|
||||
// GET: TimeValues
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var timeValues = await _context.TimeValues
|
||||
.Include(t => t.Stocks)
|
||||
.OrderByDescending(t => t.Date)
|
||||
.ToListAsync();
|
||||
|
||||
return View(timeValues);
|
||||
}
|
||||
|
||||
// GET: TimeValues/Create
|
||||
public IActionResult Create()
|
||||
{
|
||||
PopulateDropdowns();
|
||||
return View(new TimeValue { Date = DateTime.Today });
|
||||
}
|
||||
|
||||
// POST: TimeValues/Create
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create(TimeValue timeValue)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_context.Add(timeValue);
|
||||
await _context.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
PopulateDropdowns(timeValue.StocksId);
|
||||
return View(timeValue);
|
||||
}
|
||||
|
||||
// GET: TimeValues/Edit/5
|
||||
public async Task<IActionResult> Edit(long? id)
|
||||
{
|
||||
if (id is null) return NotFound();
|
||||
|
||||
var timeValue = await _context.TimeValues.FindAsync(id);
|
||||
if (timeValue is null) return NotFound();
|
||||
|
||||
PopulateDropdowns(timeValue.StocksId);
|
||||
return View(timeValue);
|
||||
}
|
||||
|
||||
// POST: TimeValues/Edit/5
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(long id, TimeValue timeValue)
|
||||
{
|
||||
if (id != timeValue.Id) return NotFound();
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.Update(timeValue);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!TimeValueExists(timeValue.Id)) return NotFound();
|
||||
throw;
|
||||
}
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
PopulateDropdowns(timeValue.StocksId);
|
||||
return View(timeValue);
|
||||
}
|
||||
|
||||
// GET: TimeValues/Delete/5
|
||||
public async Task<IActionResult> Delete(long? id)
|
||||
{
|
||||
if (id is null) return NotFound();
|
||||
|
||||
var timeValue = await _context.TimeValues
|
||||
.Include(t => t.Stocks)
|
||||
.FirstOrDefaultAsync(t => t.Id == id);
|
||||
|
||||
if (timeValue is null) return NotFound();
|
||||
|
||||
return View(timeValue);
|
||||
}
|
||||
|
||||
// POST: TimeValues/Delete/5
|
||||
[HttpPost, ActionName("Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(long id)
|
||||
{
|
||||
var timeValue = await _context.TimeValues.FindAsync(id);
|
||||
if (timeValue is not null)
|
||||
_context.TimeValues.Remove(timeValue);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void PopulateDropdowns(object? selectedStock = null)
|
||||
{
|
||||
ViewBag.StockId = new SelectList(
|
||||
_context.Stocks.OrderBy(s => s.Title),
|
||||
"Id", "Title",
|
||||
selectedStock);
|
||||
}
|
||||
|
||||
private bool TimeValueExists(long id) =>
|
||||
_context.TimeValues.Any(e => e.Id == id);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StockFin.Models;
|
||||
|
||||
namespace StockFin.Controllers
|
||||
{
|
||||
public class TransactionsController : Controller
|
||||
{
|
||||
private readonly FinancesContext _context = new FinancesContext();
|
||||
|
||||
//public TransactionsController(FinancesContext context)
|
||||
//{
|
||||
// _context = context;
|
||||
//}
|
||||
|
||||
// GET: Transactions
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var transactions = await _context.Transactions
|
||||
.Include(t => t.Account)
|
||||
.Include(t => t.Stocks)
|
||||
.OrderByDescending(t => t.Date)
|
||||
.ToListAsync();
|
||||
|
||||
return View(transactions);
|
||||
}
|
||||
|
||||
// GET: Transactions/Create
|
||||
public async Task<IActionResult> Create()
|
||||
{
|
||||
await PopulateAccountsAndStocksDropDownsAsync();
|
||||
return View();
|
||||
}
|
||||
|
||||
// POST: Transactions/Create
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create([Bind("Id,TypeId,AccountId,StocksId,Quantity,Value,Date")] Transaction transaction)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_context.Add(transaction);
|
||||
await _context.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
await PopulateAccountsAndStocksDropDownsAsync(transaction.AccountId, transaction.StocksId);
|
||||
return View(transaction);
|
||||
}
|
||||
|
||||
// GET: Transactions/Edit/5
|
||||
public async Task<IActionResult> Edit(long? id)
|
||||
{
|
||||
if (id == null)
|
||||
return NotFound();
|
||||
|
||||
var transaction = await _context.Transactions.FindAsync(id.Value);
|
||||
if (transaction == null)
|
||||
return NotFound();
|
||||
|
||||
await PopulateAccountsAndStocksDropDownsAsync(transaction.AccountId, transaction.StocksId);
|
||||
return View(transaction);
|
||||
}
|
||||
|
||||
// POST: Transactions/Edit/5
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(long id, [Bind("Id,TypeId,AccountId,StocksId,Quantity,Value,Date")] Transaction transaction)
|
||||
{
|
||||
if (id != transaction.Id)
|
||||
return NotFound();
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
try
|
||||
{
|
||||
_context.Update(transaction);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!TransactionExists(transaction.Id))
|
||||
return NotFound();
|
||||
else
|
||||
throw;
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
await PopulateAccountsAndStocksDropDownsAsync(transaction.AccountId, transaction.StocksId);
|
||||
return View(transaction);
|
||||
}
|
||||
|
||||
// GET: Transactions/Delete/5
|
||||
public async Task<IActionResult> Delete(long? id)
|
||||
{
|
||||
if (id == null)
|
||||
return NotFound();
|
||||
|
||||
var transaction = await _context.Transactions
|
||||
.Include(t => t.Account)
|
||||
.Include(t => t.Stocks)
|
||||
.FirstOrDefaultAsync(m => m.Id == id.Value);
|
||||
|
||||
if (transaction == null)
|
||||
return NotFound();
|
||||
|
||||
return View(transaction);
|
||||
}
|
||||
|
||||
// POST: Transactions/Delete/5
|
||||
[HttpPost, ActionName("Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(long id)
|
||||
{
|
||||
var transaction = await _context.Transactions.FindAsync(id);
|
||||
if (transaction != null)
|
||||
{
|
||||
_context.Transactions.Remove(transaction);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
private bool TransactionExists(long id)
|
||||
{
|
||||
return _context.Transactions.Any(e => e.Id == id);
|
||||
}
|
||||
|
||||
private async Task PopulateAccountsAndStocksDropDownsAsync(long? selectedAccountId = null, long? selectedStockId = null)
|
||||
{
|
||||
var accounts = await _context.Accounts.OrderBy(a => a.Title).Select(a => new
|
||||
{
|
||||
a.Id,
|
||||
DisplayTitle = a.Bank+ " - " + a.Title
|
||||
}).ToListAsync();
|
||||
var stocks = await _context.Stocks.OrderBy(s => s.Title).ToListAsync();
|
||||
|
||||
ViewData["AccountId"] = new SelectList(accounts, "Id", "DisplayTitle", selectedAccountId);
|
||||
ViewData["StocksId"] = new SelectList(stocks, "Id", "Title", selectedStockId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
# This stage is used when running from VS in fast mode (Default for Debug configuration)
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
|
||||
# This stage is used to build the service project
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["StockFin/StockFin.csproj", "StockFin/"]
|
||||
RUN dotnet restore "./StockFin/StockFin.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/StockFin"
|
||||
RUN dotnet build "./StockFin.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
# This stage is used to publish the service project to be copied to the final stage
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./StockFin.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "StockFin.dll"]
|
||||
@@ -0,0 +1,347 @@
|
||||
"datetime","date","account_type","category","type","asset_class","name","symbol","shares","price","amount","fee","tax","currency","original_amount","original_currency","fx_rate","description","transaction_id","counterparty_name","counterparty_iban","payment_reference","mcc_code"
|
||||
"2024-09-13T11:15:55.989679Z","2024-09-13","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1800.000000","","","EUR","","","","No SEPA description provided","c9a18afd-bb36-482f-99b6-49e10e16f231","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2024-10-01T02:21:09.875047Z","2024-10-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","3.180000","","0.00","EUR","","","","Interest payment Booking","16b4e8b2-48ff-4774-a8cc-d10345ea8a68","","","",""
|
||||
"2024-10-02T08:27:46.620639Z","2024-10-02","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1500.000000","","","EUR","","","","No SEPA description provided","404afdca-e62c-49db-a5e6-84d9974cd03d","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2024-10-02T14:17:15.084Z","2024-10-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0395560000","252.800000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.039556","a82e8628-8192-4d5b-88a8-ce145761c03c","","","",""
|
||||
"2024-10-02T15:31:04.708Z","2024-10-02","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0264130000","378.600000","-10.00","","","EUR","","","","Savings plan execution US5949181045 MICROSOFT DL-,00000625, quantity: 0.026413","598242e7-cb8a-4b77-8e68-ca5b76f1d014","","","",""
|
||||
"2024-10-02T15:35:05.989Z","2024-10-02","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.0935450000","106.900000","-10.00","","","EUR","","","","Savings plan execution US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.093545","e6c85e6f-2f23-4a6b-b017-383893119a42","","","",""
|
||||
"2024-10-02T15:45:43.003Z","2024-10-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0596300000","167.700000","-10.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.059630","9fd44345-d921-4ce1-b7cc-f2a1b91c1a91","","","",""
|
||||
"2024-10-09T14:30:18.695Z","2024-10-09","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0370850000","269.650000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.037085","ddb23968-6219-43e3-95db-1c2a9036fe23","","","",""
|
||||
"2024-10-09T14:49:17.581321Z","2024-10-09","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","20000.000000","","","EUR","","","","No SEPA description provided","517b3fce-bb72-490b-9b4d-c1c8fef5e6d3","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2024-10-16T08:41:53.411Z","2024-10-16","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0395940000","126.280000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.039594","0d9f17a7-06c0-4e4d-a544-773adeb79411","","","",""
|
||||
"2024-10-16T08:50:00.819Z","2024-10-16","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2089860000","47.850000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.208986","5fe25b03-0802-4c89-891a-d0f6d2872a17","","","",""
|
||||
"2024-10-16T13:44:44.017Z","2024-10-16","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","0.2997510000","33.361000","-10.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 0.299751","c23bf483-0f41-4655-8954-85d4a37d23bc","","","",""
|
||||
"2024-10-16T14:05:54.854Z","2024-10-16","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0361010000","277.000000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.036101","da3805b5-695c-46ab-9e22-b3b5c1e26797","","","",""
|
||||
"2024-10-16T14:24:52.407Z","2024-10-16","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.1006890000","99.315000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.100689","9bb1dcdd-4bc6-4fcd-bf3c-cded824f6499","","","",""
|
||||
"2024-10-16T14:37:55.328Z","2024-10-16","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0952650000","104.970000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.095265","4b9bcc92-c271-471e-93d5-6f5242f84c85","","","",""
|
||||
"2024-10-16T14:54:44.237Z","2024-10-16","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0263050000","380.150000","-10.00","","","EUR","","","","Savings plan execution US5949181045 MICROSOFT DL-,00000625, quantity: 0.026305","b973bfd7-0181-420b-9938-6ea57e97e10e","","","",""
|
||||
"2024-10-16T15:03:31.354Z","2024-10-16","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.0818390000","122.190000","-10.00","","","EUR","","","","Savings plan execution US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.081839","8608cb87-dbbc-4636-8d57-cb42fc4dbb57","","","",""
|
||||
"2024-10-16T15:18:25.103Z","2024-10-16","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0583560000","171.360000","-10.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.058356","5c811a8c-d99a-4241-a39b-b45973194692","","","",""
|
||||
"2024-10-17T13:26:10.442938Z","2024-10-17","DEFAULT","CASH","CARD_ORDERING_FEE","","","","","","0.000000","-50.00","","EUR","","","","Trade Republic Card","7c0d6a1c-5bbd-44a9-85d1-a7ffd46a25bf","","","",""
|
||||
"2024-10-19T06:04:23.617202Z","2024-10-19","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-300.190000","","","EUR","","","","TR Card Transaction","44ecd8ec-f05f-4b17-9cdd-2ad6d5fc8574","","","","5931"
|
||||
"2024-10-20T06:42:47.579165Z","2024-10-20","DEFAULT","CASH","CARD_TRANSACTION","","TEMU.COM","","","","-60.810000","","","EUR","","","","TR Card Transaction","c5c56b38-1566-4503-8633-d70141a1cdad","","","","5399"
|
||||
"2024-10-20T06:03:46.540402Z","2024-10-20","DEFAULT","CASH","CARD_TRANSACTION","","","","","","300.190000","","","EUR","","","","TR Card Transaction","45c1d7b1-0f98-4aa7-a64d-ea6c59a38158","","","",""
|
||||
"2024-10-23T08:26:49.048Z","2024-10-23","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0395380000","126.460000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.039538","c234ff2c-27f9-4c6a-8e5e-f09745321cba","","","",""
|
||||
"2024-10-23T08:34:00.416Z","2024-10-23","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2011100000","49.724000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.201110","85ade077-43a8-43cb-a9c6-5b9673ddc84d","","","",""
|
||||
"2024-10-23T14:04:57.569Z","2024-10-23","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.1000720000","99.928000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.100072","75e49b6b-903b-4e0f-90ec-7107c2494894","","","",""
|
||||
"2024-10-23T14:05:46.406Z","2024-10-23","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0355990000","280.900000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.035599","f4e499b0-2116-4674-8615-21763889aa14","","","",""
|
||||
"2024-10-23T14:22:58.164Z","2024-10-23","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0944280000","105.900000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.094428","93d0a418-41fe-4cee-8c90-67049e61c992","","","",""
|
||||
"2024-10-24T06:10:32.918878Z","2024-10-24","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-45.850000","","","EUR","","","","TR Card Transaction","5f70720a-c69c-44e4-8dbc-7e596a8f432d","","","","5931"
|
||||
"2024-10-28T15:53:08.814Z","2024-10-28","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1712000000","175.240000","-30.00","","","EUR","","","","Buy trade US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.1712","3b81c50c-edce-41a9-804c-6b58c4d0853a","","","",""
|
||||
"2024-10-28T15:53:18.689815Z","2024-10-28","DEFAULT","CASH","STOCKPERK","STOCK","Amazon.com","US0231351067","","","30.000000","","","EUR","","","","Stockperk","6593d990-684c-4c55-bb81-c6eca9f08095","","","",""
|
||||
"2024-10-29T13:50:22.902151Z","2024-10-29","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1000.000000","","","EUR","","","","No SEPA description provided","b568370c-bad2-4bd3-8a2c-dfe438fbb1d5","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2024-10-29T13:50:22.472555Z","2024-10-29","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","500.000000","","","EUR","","","","epargne","86667dea-670c-464b-a6f3-f3707f87b98d","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2024-10-30T08:58:18.260735Z","2024-10-30","DEFAULT","CASH","CARD_TRANSACTION","","QUICK 404 HUY","","","","-14.250000","","","EUR","","","","TR Card Transaction","5595507b-3cd8-4ca1-a176-cfcd8939160e","","","","5814"
|
||||
"2024-10-31T07:04:36.493735Z","2024-10-31","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-30.340000","","","EUR","","","","TR Card Transaction","25476dfa-7ca4-4b8d-8669-daf5a4e6ffa0","","","","5931"
|
||||
"2024-11-01T03:19:52.667359Z","2024-11-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","52.250000","","0.00","EUR","","","","Interest payment Booking","8f79d42b-2dd5-4850-aa21-d8e66714e27d","","","",""
|
||||
"2024-11-02T07:23:15.204918Z","2024-11-02","DEFAULT","CASH","CARD_TRANSACTION","","LEMARC SA","","","","-9.270000","","","EUR","","","","TR Card Transaction","0302a4bd-195d-4bdb-9a81-a0edc9c5fa2a","","","","5411"
|
||||
"2024-11-03T07:38:33.209013Z","2024-11-03","DEFAULT","CASH","CARD_TRANSACTION","","EU.STORE.BAMBULAB.COM","","","","-551.110000","","","EUR","","","","TR Card Transaction","6d07c948-9108-40fe-b32c-3f94c7c55bba","","","","5065"
|
||||
"2024-11-03T10:31:35.702901Z","2024-11-03","DEFAULT","CASH","CARD_TRANSACTION","","Dreambaby Jemappes F7602","","","","-335.950000","","","EUR","","","","TR Card Transaction","4078d57e-0db0-496d-bf88-14909be72c12","","","","5945"
|
||||
"2024-11-04T09:27:02.395Z","2024-11-04","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0399870000","125.040000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.039987","087b8af5-67ca-4e42-b66b-fffedb845e6d","","","",""
|
||||
"2024-11-04T09:35:43.718Z","2024-11-04","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2044320000","48.916000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.204432","69bbeeca-7447-405b-83b6-80ec82c73837","","","",""
|
||||
"2024-11-04T09:38:25.306Z","2024-11-04","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0936290000","48.916000","-4.58","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.093629","eda5c7c4-9763-4c28-a34f-6e82ee37f38c","","","",""
|
||||
"2024-11-04T09:39:48.835903Z","2024-11-04","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","4.580000","","","EUR","","","","Your Saveback payment","a75dae22-c9b8-4401-b45c-700359dfac6b","","","",""
|
||||
"2024-11-04T15:21:13.448Z","2024-11-04","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0363300000","275.250000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.036330","e3ef05a5-9453-4e12-b3ce-8d5da0cf6d4f","","","",""
|
||||
"2024-11-04T16:10:19.962Z","2024-11-04","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.1024620000","97.597000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.102462","443d9b6b-6dd3-44fb-8990-24aba8db2c33","","","",""
|
||||
"2024-11-04T16:42:45.417Z","2024-11-04","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0265780000","376.250000","-10.00","","","EUR","","","","Savings plan execution US5949181045 MICROSOFT DL-,00000625, quantity: 0.026578","545bb6fe-6f38-4f6c-b310-66ed6296860b","","","",""
|
||||
"2024-11-04T16:45:29.800Z","2024-11-04","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0969880000","103.105000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.096988","3e5a1586-76a0-40d1-9e73-a5e676398685","","","",""
|
||||
"2024-11-04T16:48:27.755Z","2024-11-04","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.0791760000","126.300000","-10.00","","","EUR","","","","Savings plan execution US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.079176","88a8b4be-d14a-4336-847c-372e90faaca7","","","",""
|
||||
"2024-11-04T16:57:48.459Z","2024-11-04","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0552660000","180.940000","-10.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.055266","1dd73989-5e64-4dd9-a4b2-03323be668ab","","","",""
|
||||
"2024-11-06T07:06:04.071276Z","2024-11-06","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-19.840000","","","EUR","","","","TR Card Transaction","a081517e-c320-4fca-a481-091f5c55916d","","","","5931"
|
||||
"2024-11-07T14:02:21.892Z","2024-11-07","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0768000000","390.750000","-30.01","","","EUR","","","","Buy trade US5949181045 MICROSOFT DL-,00000625, quantity: 0.0768","c42b9b56-1f73-448e-b991-c62c1e923c5f","","","",""
|
||||
"2024-11-07T14:02:30.855894Z","2024-11-07","DEFAULT","CASH","STOCKPERK","STOCK","Microsoft","US5949181045","","","30.010000","","","EUR","","","","Stockperk","f5f0ac3c-1c1b-4840-bbdd-dc43aff1cb8c","","","",""
|
||||
"2024-11-09T10:20:56.997740Z","2024-11-09","DEFAULT","CASH","CARD_TRANSACTION","","LEMARC SA","","","","-9.950000","","","EUR","","","","TR Card Transaction","ba1c98d8-35df-41b7-9ea9-bb0d7bed8ce1","","","","5411"
|
||||
"2024-11-11T09:26:50.484Z","2024-11-11","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0399870000","125.040000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.039987","63d75cc1-871a-43aa-88ef-d6b01dac704f","","","",""
|
||||
"2024-11-11T09:36:32.672Z","2024-11-11","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2058960000","48.568000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.205896","18dcfa1e-d4f8-48f9-900a-7634b679619c","","","",""
|
||||
"2024-11-11T15:09:04.177Z","2024-11-11","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0966650000","103.450000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.096665","2b8232be-3d7e-40d9-a636-313a1933168a","","","",""
|
||||
"2024-11-11T15:09:52.605Z","2024-11-11","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0318470000","314.000000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.031847","0d82a826-bc91-45a1-997d-4136e022f763","","","",""
|
||||
"2024-11-11T15:25:41.933Z","2024-11-11","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0903340000","110.700000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.090334","a5b7fedd-3883-45c8-916e-f899f72615ac","","","",""
|
||||
"2024-11-13T10:09:04.042479Z","2024-11-13","DEFAULT","CASH","CARD_TRANSACTION","","QUICK 404 HUY","","","","-10.750000","","","EUR","","","","TR Card Transaction","4158ba6c-27b2-4ce5-8700-d064a3935690","","","","5814"
|
||||
"2024-11-15T07:12:59.749803Z","2024-11-15","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-321.190000","","","EUR","","","","TR Card Transaction","a6b11791-6b4a-4f67-9dba-acdee9a050a6","","","","5931"
|
||||
"2024-11-15T07:11:18.674238Z","2024-11-15","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-54.250000","","","EUR","","","","TR Card Transaction","b1f499f6-96dd-4260-9ce4-d9e460c2ef9d","","","","5691"
|
||||
"2024-11-18T09:26:56.746Z","2024-11-18","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0409430000","122.120000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.040943","3cf1ad9f-e4dc-4535-8f90-6c7f49401b74","","","",""
|
||||
"2024-11-18T09:34:46.811Z","2024-11-18","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2096340000","47.702000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.209634","f47c243e-3a46-4ea7-bb80-9cdca12c2362","","","",""
|
||||
"2024-11-18T14:44:50.866Z","2024-11-18","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","0.3065790000","32.618000","-10.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 0.306579","c190bbc4-2e6d-4ed5-aeb3-88d006bddeeb","","","",""
|
||||
"2024-11-18T15:06:17.110Z","2024-11-18","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0309930000","322.650000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.030993","d566fcdd-c9e9-4c78-b5d1-71439023b0b8","","","",""
|
||||
"2024-11-18T15:27:17.343Z","2024-11-18","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0980720000","101.965000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.098072","8ab6594b-d20e-4a3c-821d-0ac0757f3dec","","","",""
|
||||
"2024-11-18T15:38:21.235Z","2024-11-18","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0915200000","109.265000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.091520","035da005-33c7-46ad-adc4-b8ea18a96bc6","","","",""
|
||||
"2024-11-18T15:53:53.226Z","2024-11-18","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0252710000","395.700000","-10.00","","","EUR","","","","Savings plan execution US5949181045 MICROSOFT DL-,00000625, quantity: 0.025271","a2f5e3d3-b8ce-471c-a5cb-55fd2bed82d3","","","",""
|
||||
"2024-11-18T15:59:07.971Z","2024-11-18","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.0753230000","132.760000","-10.00","","","EUR","","","","Savings plan execution US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.075323","86c2c5c5-f294-476d-b007-3cda01591369","","","",""
|
||||
"2024-11-18T16:11:42.489Z","2024-11-18","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0520390000","192.160000","-10.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.052039","a947ffe6-24b2-4133-a3cf-b876a51332b0","","","",""
|
||||
"2024-11-19T10:28:04.613866Z","2024-11-19","DEFAULT","CASH","CARD_TRANSACTION","","Fonteyne The Kitchen Engi","","","","-4.950000","","","EUR","","","","TR Card Transaction","9e55a738-4512-4b47-ba3a-9ce3f0a06d49","","","","5812"
|
||||
"2024-11-19T09:13:10.128399Z","2024-11-19","DEFAULT","CASH","CARD_TRANSACTION","","SELECTA BELGIUM NV.","","","","-1.800000","","","EUR","","","","TR Card Transaction","947bbf37-3774-4739-8387-d484e24b5f64","","","","5499"
|
||||
"2024-11-19T07:31:59.154906Z","2024-11-19","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-22.480000","","","EUR","","","","TR Card Transaction","eef88a33-b592-4497-be46-e8faa562b34b","","","","5691"
|
||||
"2024-11-23T10:20:30.744379Z","2024-11-23","DEFAULT","CASH","CARD_TRANSACTION","","LIDL 1113 Ghlin","","","","-2.670000","","","EUR","","","","TR Card Transaction","f555c5b2-2121-4aba-bb54-0353004b18f1","","","","5411"
|
||||
"2024-11-24T07:38:46.311629Z","2024-11-24","DEFAULT","CASH","CARD_TRANSACTION","","Action 2564","","","","-12.990000","","","EUR","","","","TR Card Transaction","34a8a115-79e5-43e3-97b4-75850e558c04","","","","5310"
|
||||
"2024-11-25T09:26:48.595Z","2024-11-25","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0408220000","122.480000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.040822","bb2560d0-6c53-4309-8f62-063f4149f809","","","",""
|
||||
"2024-11-25T09:33:49.097Z","2024-11-25","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2017590000","49.564000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.201759","758e83a8-ee91-404e-a7f3-15989a6225a3","","","",""
|
||||
"2024-11-25T15:06:57.902Z","2024-11-25","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0955200000","104.690000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.095520","ee5e0843-1610-4df8-bd2c-90c2729834be","","","",""
|
||||
"2024-11-25T15:07:04.825Z","2024-11-25","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0283280000","353.000000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.028328","d2a379aa-4019-4f99-9c41-8aba2c1461b1","","","",""
|
||||
"2024-11-25T15:23:32.071Z","2024-11-25","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0892180000","112.085000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.089218","661aef64-62a3-4a26-a9e9-245aa3790777","","","",""
|
||||
"2024-11-26T07:31:32.767235Z","2024-11-26","DEFAULT","CASH","CARD_TRANSACTION","","","","","","54.250000","","","EUR","","","","TR Card Transaction","45a6522e-fa48-4fe4-83cb-08a985367115","","","",""
|
||||
"2024-11-27T13:24:40.207088Z","2024-11-27","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","2500.000000","","","EUR","","","","No SEPA description provided","3fbbb1ea-2133-4ae8-82dc-57b166f288bd","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2024-11-30T08:18:00.860807Z","2024-11-30","DEFAULT","CASH","CARD_TRANSACTION","","Food Truck Jannine","","","","-436.700000","","","EUR","","","","TR Card Transaction","adc0bb35-5e7b-4366-ad96-962850d653af","","","","5814"
|
||||
"2024-11-30T10:37:22.078142Z","2024-11-30","DEFAULT","CASH","CARD_TRANSACTION","","PNVT JOB","","","","-29.700000","","","EUR","","","","TR Card Transaction","3b8efde7-c93d-4eac-b18c-9875b6603983","","","","0742"
|
||||
"2024-12-02T09:02:59.852884Z","2024-12-02","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","62.980000","","0.00","EUR","","","","Interest payment Booking","11f8c23a-2eb4-466b-8263-0e8ecc693195","","","",""
|
||||
"2024-12-02T09:27:24.858Z","2024-12-02","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0413080000","121.040000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.041308","5e1bc4d5-e4a2-4e7d-a3c3-11f482a0db9f","","","",""
|
||||
"2024-12-02T09:35:27.134Z","2024-12-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2051360000","48.748000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.205136","d1086cfe-828d-4012-a5ce-b89cf8520fc9","","","",""
|
||||
"2024-12-02T09:36:54.042442Z","2024-12-02","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","15.000000","","","EUR","","","","Your Saveback payment","38272364-d43d-456c-9649-55b3f253194a","","","",""
|
||||
"2024-12-02T09:37:52.927Z","2024-12-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.3078310000","48.728000","-15.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.307831","8f39abcc-a8a9-4f63-9b62-d43a329eec9a","","","",""
|
||||
"2024-12-02T16:10:32.945Z","2024-12-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0304360000","328.550000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.030436","78702884-6fda-4f7b-b06b-a7bd916865e6","","","",""
|
||||
"2024-12-02T16:41:41.062Z","2024-12-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0946070000","105.700000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.094607","be149121-8114-414f-bfdd-852382507db2","","","",""
|
||||
"2024-12-02T17:50:50.583Z","2024-12-02","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0882330000","113.335000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.088233","427c267d-cc73-48dd-9aa9-e63f11272745","","","",""
|
||||
"2024-12-02T18:02:56.016Z","2024-12-02","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.0753120000","132.780000","-10.00","","","EUR","","","","Savings plan execution US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.075312","a1db411a-e75f-4f86-8612-42628b888dfa","","","",""
|
||||
"2024-12-02T18:07:29.986Z","2024-12-02","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0241890000","413.400000","-10.00","","","EUR","","","","Savings plan execution US5949181045 MICROSOFT DL-,00000625, quantity: 0.024189","b3d722fa-5ef4-46b2-8e3b-f8d211f82b59","","","",""
|
||||
"2024-12-02T18:17:28.247Z","2024-12-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0495170000","201.950000","-10.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.049517","8ff6195c-526f-4f03-a21a-49e55567f3e9","","","",""
|
||||
"2024-12-04T10:06:47.560812Z","2024-12-04","DEFAULT","CASH","CARD_TRANSACTION","","LEMARC SA","","","","-12.320000","","","EUR","","","","TR Card Transaction","0ea837af-98a1-4405-8f21-f7ae0a20e59d","","","","5411"
|
||||
"2024-12-06T07:04:35.240703Z","2024-12-06","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-45.850000","","","EUR","","","","TR Card Transaction","5fa6b14b-bff0-4c38-8caf-415e69725ffe","","","","5931"
|
||||
"2024-12-08T09:19:11.360859Z","2024-12-08","DEFAULT","CASH","CARD_TRANSACTION","","Takeaway.com","","","","-74.000000","","","EUR","","","","TR Card Transaction","2535f1d2-8249-4f68-9490-cdfd1fe299d6","","","","5814"
|
||||
"2024-12-09T09:26:49.063Z","2024-12-09","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0395380000","126.460000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.039538","0dafec9d-73b5-4076-9f81-f27b4a3b52df","","","",""
|
||||
"2024-12-09T09:34:52.449Z","2024-12-09","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2049510000","48.792000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.204951","0e2673ca-6419-4e10-8032-877b33000c4e","","","",""
|
||||
"2024-12-09T15:06:31.452Z","2024-12-09","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0944100000","105.920000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.094410","31f74e64-a3f9-4455-a2a0-5787890887aa","","","",""
|
||||
"2024-12-09T15:10:25.569Z","2024-12-09","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0290020000","344.800000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.029002","5bcabdba-f16b-492f-84bc-be8936ecd5f4","","","",""
|
||||
"2024-12-09T15:23:10.661Z","2024-12-09","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0885810000","112.890000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.088581","e4af0b33-ee75-4981-a6a9-d427543ff76e","","","",""
|
||||
"2024-12-10T07:23:28.435130Z","2024-12-10","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-51.100000","","","EUR","","","","TR Card Transaction","d0a654aa-5444-4bd0-9616-d3b0360f4796","","","","5931"
|
||||
"2024-12-11T12:15:15.090172Z","2024-12-11","DEFAULT","CASH","CARD_TRANSACTION","","LEMARC SA","","","","-15.620000","","","EUR","","","","TR Card Transaction","62f5937a-8c4b-45c1-bd91-da06bf04bbc4","","","","5411"
|
||||
"2024-12-12T11:24:18.654978Z","2024-12-12","DEFAULT","CASH","DIVIDEND","STOCK","Microsoft","US5949181045","0.1813670000","","0.140000","","-0.02","EUR","0.15","USD","0.951746","Cash Dividend for ISIN US5949181045","6841c50b-6ade-490e-b8d4-a5a0ea3581d4","","","",""
|
||||
"2024-12-13T10:23:17.486959Z","2024-12-13","DEFAULT","CASH","CARD_TRANSACTION","","LEMARC SA","","","","-5.490000","","","EUR","","","","TR Card Transaction","16c6c599-0c2f-4822-b98d-80b7508ee2da","","","","5411"
|
||||
"2024-12-14T07:07:35.775893Z","2024-12-14","DEFAULT","CASH","CARD_TRANSACTION","","Vinted","","","","-109.550000","","","EUR","","","","TR Card Transaction","96f6d0d9-bfce-419c-aa9d-e6ba39e84c97","","","","5691"
|
||||
"2024-12-16T09:27:02.047Z","2024-12-16","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0400000000","125.000000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.040000","bcd569c9-cd4b-4961-8313-75f6acf2b250","","","",""
|
||||
"2024-12-16T09:36:01.164Z","2024-12-16","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2035330000","49.132000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.203533","68918c28-6947-46aa-b3a2-dded718dc2af","","","",""
|
||||
"2024-12-16T14:47:01.062Z","2024-12-16","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","0.2989800000","33.447000","-10.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 0.298980","7b1d1f5e-b3f4-4ad9-9c8a-d18e42a1c7a5","","","",""
|
||||
"2024-12-16T15:27:59.895Z","2024-12-16","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0279950000","357.200000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.027995","0423a377-5df7-4da8-8b47-0c704bcb03f2","","","",""
|
||||
"2024-12-16T15:48:20.897Z","2024-12-16","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0944570000","105.868000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.094457","b341c6bf-52db-48a2-820a-1d5c7025da88","","","",""
|
||||
"2024-12-16T16:24:18.927Z","2024-12-16","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0880630000","113.555000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.088063","dfd8b71c-6b66-4445-b9c7-f1200a44d933","","","",""
|
||||
"2024-12-16T16:48:41.505Z","2024-12-16","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0235100000","425.350000","-10.00","","","EUR","","","","Savings plan execution US5949181045 MICROSOFT DL-,00000625, quantity: 0.023510","4d6fbf3a-56ca-483f-bd31-47343b6429bc","","","",""
|
||||
"2024-12-16T16:54:43.661Z","2024-12-16","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.0796300000","125.580000","-10.00","","","EUR","","","","Savings plan execution US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.079630","138c7cfb-9bca-459c-8961-48112fabd9cd","","","",""
|
||||
"2024-12-16T17:14:43.045Z","2024-12-16","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0457240000","218.700000","-10.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.045724","29ee8fef-7df9-4267-ad22-72014d4f1f81","","","",""
|
||||
"2024-12-17T10:27:28.832404Z","2024-12-17","DEFAULT","CASH","CARD_TRANSACTION","","Brico Soignies","","","","-5.090000","","","EUR","","","","TR Card Transaction","59024d7c-8dd0-4110-b415-8bc86aa0c4ff","","","","5211"
|
||||
"2024-12-22T10:39:44.675379Z","2024-12-22","DEFAULT","CASH","CARD_TRANSACTION","","Brico 3447 Maisieres","","","","-8.490000","","","EUR","","","","TR Card Transaction","21ec33bc-5468-470b-a456-7f29986c1824","","","","5211"
|
||||
"2024-12-23T09:26:48.968Z","2024-12-23","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0404920000","123.480000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.040492","602ad15e-3872-4687-a112-6e002f00d2f2","","","",""
|
||||
"2024-12-23T09:33:58.882Z","2024-12-23","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2037650000","49.076000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.203765","5b9a6629-2c5a-460f-9752-f5f837e31616","","","",""
|
||||
"2024-12-23T10:11:22.588544Z","2024-12-23","DEFAULT","CASH","CARD_TRANSACTION","","FESTI COLMAR","","","","-14.500000","","","EUR","","","","TR Card Transaction","48433a56-29ad-4c9d-b2db-0f3beea01d2a","","","","5814"
|
||||
"2024-12-23T15:05:27.861Z","2024-12-23","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0292010000","342.450000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.029201","67667d67-91fc-4343-bab9-f5b903a74f52","","","",""
|
||||
"2024-12-23T15:12:03.113Z","2024-12-23","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0961260000","104.030000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.096126","7f3003b8-7e20-4a9d-973c-5ba3aa81e485","","","",""
|
||||
"2024-12-23T15:23:33.156Z","2024-12-23","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0893690000","111.895000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.089369","c3f22583-3853-46a4-b47f-6f0aec0e2bd0","","","",""
|
||||
"2024-12-31T11:56:51.056713Z","2024-12-31","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1000.000000","","","EUR","","","","No SEPA description provided","6cab7983-04bf-4863-994e-c33019bebfe8","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2025-01-01T00:55:10.105745Z","2025-01-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","65.700000","","0.00","EUR","","","","Interest payment Booking","74b30b33-b6bc-44da-b870-c8b316e7a4e6","","","",""
|
||||
"2025-01-02T09:30:03.124Z","2025-01-02","DEFAULT","TRADING","BUY","FUND","CAC 40 EUR (Acc)","LU1681046931","0.0401340000","124.580000","-5.00","","","EUR","","","","Savings plan execution LU1681046931 Amundi Index Solutions - Amundi CAC 40 ESG UCITS ETF DR - EUR (C), quantity: 0.040134","c88a7811-1374-440c-9df7-06847627ab2c","","","",""
|
||||
"2025-01-02T09:38:34.726Z","2025-01-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2021100000","49.478000","-10.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.202110","389d6e11-410b-40c4-98a1-1ef55c55152d","","","",""
|
||||
"2025-01-02T09:39:31.776Z","2025-01-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0683100000","49.480000","-3.38","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.068310","ce19f519-3fd8-4a85-b628-7506865715da","","","",""
|
||||
"2025-01-02T09:45:13.847226Z","2025-01-02","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","3.380000","","","EUR","","","","Your Saveback payment","8f3db3aa-f897-4fe5-a5f5-5707d90b230d","","","",""
|
||||
"2025-01-02T15:18:34.354Z","2025-01-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0294550000","339.500000","-10.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.029455","4842c8b8-0f2b-4aea-94db-cbf6e5b07641","","","",""
|
||||
"2025-01-02T16:09:39.998Z","2025-01-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.0953560000","104.870000","-10.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.095356","251d4603-b8d2-48e3-8014-229381038ef5","","","",""
|
||||
"2025-01-02T17:04:09.362Z","2025-01-02","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","0.0885700000","112.905000","-10.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 0.088570","ffb114cf-7a0a-4082-93bb-f26b1e6d58da","","","",""
|
||||
"2025-01-02T17:43:18.140Z","2025-01-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0460720000","217.050000","-10.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.046072","78221605-9a86-4bc5-ae75-61ff111501a7","","","",""
|
||||
"2025-01-02T17:52:15.138Z","2025-01-02","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.0748270000","133.640000","-10.00","","","EUR","","","","Savings plan execution US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.074827","fab3fe1e-62d8-41d6-962c-f9d2564c3609","","","",""
|
||||
"2025-01-02T17:57:59.311Z","2025-01-02","DEFAULT","TRADING","BUY","STOCK","Microsoft","US5949181045","0.0243360000","410.900000","-10.00","","","EUR","","","","Savings plan execution US5949181045 MICROSOFT DL-,00000625, quantity: 0.024336","cb43f64f-7437-484e-a3eb-e12906a412e1","","","",""
|
||||
"2025-01-03T10:12:59.054727Z","2025-01-03","DEFAULT","CASH","CARD_TRANSACTION","","LAURENT SPRL","","","","-236.500000","","","EUR","","","","TR Card Transaction","9a324bc7-7b66-4b4a-8a6a-af39b34bf53c","","","","5944"
|
||||
"2025-01-17T08:08:38.348Z","2025-01-17","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.7637180000","51.215000","-39.11","","","EUR","","","","Buy trade IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.763718","0ea80de2-68c4-47da-858c-097ccf715bea","","","",""
|
||||
"2025-01-17T08:08:38.355Z","2025-01-17","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","9.0000000000","51.215000","-460.94","-1.00","","EUR","","","","Buy trade IE00B4ND3602 iShares Physical Gold ETC, quantity: 9","6cbb616c-1c1a-4d53-b69e-47209441911b","","","",""
|
||||
"2025-01-22T09:59:12.591998Z","2025-01-22","DEFAULT","CASH","CARD_TRANSACTION","","LEMARC SA","","","","-4.760000","","","EUR","","","","TR Card Transaction","f24c2e98-1755-4196-8bac-fdf66c79242c","","","","5411"
|
||||
"2025-01-28T08:17:55.591228Z","2025-01-28","DEFAULT","CASH","CARD_TRANSACTION","","ALLO SOLAR","","","","-5391.480000","","","EUR","","","","TR Card Transaction","050c42da-1841-4681-897f-7391b86b0b0d","","","","1711"
|
||||
"2025-01-31T13:18:21.485201Z","2025-01-31","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1500.000000","","","EUR","","","","No SEPA description provided","18d10936-65c8-4b1f-86b7-9356aa774be5","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2025-02-01T10:23:28.255770Z","2025-02-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","61.940000","","","EUR","","","","Interest payment Booking","813e45e5-19cb-476e-818f-bf7c3804356b","","","",""
|
||||
"2025-02-03T07:25:50.316Z","2025-02-03","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","0.7934000000","111.520000","-88.48","","","EUR","","","","Buy trade US67066G1040 NVIDIA CORP. DL-,001, quantity: 0.7934","68c3c3a6-59a7-45dc-b7bd-cfbc8fbc602c","","","",""
|
||||
"2025-02-03T07:25:50.327Z","2025-02-03","DEFAULT","TRADING","BUY","STOCK","NVIDIA","US67066G1040","1.0000000000","111.520000","-111.52","-1.00","","EUR","","","","Buy trade US67066G1040 NVIDIA CORP. DL-,001, quantity: 1","c6edfd87-1b43-4470-9ed2-d00e2c923532","","","",""
|
||||
"2025-02-03T09:38:36.002Z","2025-02-03","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.9418850000","53.085000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.941885","f896a7f8-0106-4056-82ac-b088c7e8ffe3","","","",""
|
||||
"2025-02-03T09:39:24.014Z","2025-02-03","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2825650000","53.085000","-15.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.282565","0e95569a-9d3d-4ae3-89a0-45b27f207566","","","",""
|
||||
"2025-02-03T11:08:53.651876Z","2025-02-03","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","15.000000","","","EUR","","","","Your Saveback payment","a924f42c-4b37-4d8f-8af3-bb084d332c05","","","",""
|
||||
"2025-02-03T15:00:55.285Z","2025-02-03","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.2597310000","616.020000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.259731","3459392e-b735-4924-aaab-1bb1ed6c7f99","","","",""
|
||||
"2025-02-03T15:26:19.402Z","2025-02-03","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0527280000","379.300000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.052728","bd77908c-9bd9-4631-9731-93d9fa16f7f4","","","",""
|
||||
"2025-02-03T16:03:50.371Z","2025-02-03","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.9344920000","107.010000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.934492","64e0fe47-54f7-4122-aee4-074197339d2c","","","",""
|
||||
"2025-02-03T16:41:50.647Z","2025-02-03","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.0872410000","229.250000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.087241","67c1e47e-a0ed-4229-967d-7a1e0b39a9a3","","","",""
|
||||
"2025-02-03T16:58:12.448Z","2025-02-03","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.3110740000","114.410000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.311074","48a99a15-817a-4ff0-a968-c780d47df43a","","","",""
|
||||
"2025-02-27T14:19:23.358278Z","2025-02-27","DEFAULT","CASH","CARD_TRANSACTION","","PPG*DEXERGIE","","","","-1754.740000","","","EUR","","","","TR Card Transaction","07e463a8-8cf7-44f6-aa62-bc0570804b6c","","","","5065"
|
||||
"2025-03-01T10:52:41.923054Z","2025-03-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","42.900000","","","EUR","","","","Interest payment Booking","a9548354-fb79-4654-bee6-690da5ded778","","","",""
|
||||
"2025-03-03T09:59:30.012Z","2025-03-03","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.9353660000","53.455000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.935366","d52792b2-cb26-4433-9ca2-2f94c1125fa7","","","",""
|
||||
"2025-03-03T10:10:28.590Z","2025-03-03","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.2804520000","53.485000","-15.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.280452","72784211-62d2-4230-83dd-d4e55d03edab","","","",""
|
||||
"2025-03-03T11:59:44.906990Z","2025-03-03","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","15.000000","","","EUR","","","","Your Saveback payment","159178ee-8e61-47e4-8767-380091c9d894","","","",""
|
||||
"2025-03-03T15:00:08.819Z","2025-03-03","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.2655070000","602.620000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.265507","8fc9c085-184f-4236-951a-bf448059c1f5","","","",""
|
||||
"2025-03-03T16:28:04.547Z","2025-03-03","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.3406020000","111.890000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.340602","7aaca187-d15e-4f4e-9c36-dfb1efadd386","","","",""
|
||||
"2025-03-03T16:30:56.819Z","2025-03-03","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.9437520000","105.960000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.943752","d11450f6-1ba5-408a-b10b-dc6207358795","","","",""
|
||||
"2025-03-03T16:48:44.209Z","2025-03-03","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0533830000","374.650000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.053383","cad00b5f-b799-473a-b427-c9009adcae21","","","",""
|
||||
"2025-03-03T17:29:58.548Z","2025-03-03","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1002400000","199.520000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.100240","5276a362-3444-4b14-af8e-4a20020ff6f8","","","",""
|
||||
"2025-03-06T10:26:18.868403Z","2025-03-06","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1000.000000","","","EUR","","","","No SEPA description provided","06d0b52e-a8fc-466c-b925-a08881740105","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2025-03-12T11:28:00.180982Z","2025-03-12","DEFAULT","CASH","CARD_TRANSACTION","","Tiamo","","","","-146.000000","","","EUR","","","","TR Card Transaction","77ba278c-065e-48d7-90cf-293cf35b493c","","","","5814"
|
||||
"2025-03-13T09:24:30.268637Z","2025-03-13","DEFAULT","CASH","DIVIDEND","STOCK","Microsoft","US5949181045","0.2534020000","","0.190000","","-0.03","EUR","0.21","USD","0.918611","Cash Dividend for ISIN US5949181045","4ef3327c-725f-4456-ac8e-95ea58409781","","","",""
|
||||
"2025-03-16T11:24:03.293291Z","2025-03-16","DEFAULT","CASH","CARD_TRANSACTION","","BBQ Home SRL","","","","-92.500000","","","EUR","","","","TR Card Transaction","6cdc33fc-521b-4a07-b3d6-91e9da88a06d","","","","5812"
|
||||
"2025-03-26T11:29:24.830122Z","2025-03-26","DEFAULT","CASH","CARD_TRANSACTION_INTERNATIONAL","","Krale B.V.","","","","-482.650000","","","EUR","","","","TR Card Transaction","aa5678b4-fc55-41af-94d5-56214857774f","","","","5941"
|
||||
"2025-03-28T20:52:24.741823Z","2025-03-28","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","2000.000000","","","EUR","","","","epargne","e63f2afe-5da4-4dfe-9a47-a84081d6feda","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2025-04-01T08:18:20.603639Z","2025-04-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","41.370000","","","EUR","","","","Interest payment Booking","f2bf4978-4012-46a5-8658-6574a4578997","","","",""
|
||||
"2025-04-02T08:41:34.702Z","2025-04-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.1277500000","56.360000","-7.20","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.127750","d30cce00-8b79-4556-83f7-dcff22634645","","","",""
|
||||
"2025-04-02T08:43:13.455Z","2025-04-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.8871540000","56.360000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.887154","05d9ed45-c97b-482e-96bc-86ef39fa8ddc","","","",""
|
||||
"2025-04-02T10:52:23.326616Z","2025-04-02","DEFAULT","CASH","DIVIDEND","STOCK","NVIDIA","US67066G1040","2.3530520000","","0.020000","","","EUR","0.02","USD","0.926956","Cash Dividend for ISIN US67066G1040","81f665dd-f4d8-4976-9072-218a719d68ec","","","",""
|
||||
"2025-04-02T08:44:49.595445Z","2025-04-02","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","7.200000","","","EUR","","","","Your Saveback payment","4c44f119-f9e3-4ce5-a5e5-6a060417beeb","","","",""
|
||||
"2025-04-02T14:04:23.511Z","2025-04-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.2903490000","551.060000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.290349","8cc43236-3661-42f7-bc0d-bc9f38a4d787","","","",""
|
||||
"2025-04-02T15:53:56.298Z","2025-04-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","1.0193260000","98.104000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 1.019326","fdcff930-f528-4759-ba7a-2054c574bd80","","","",""
|
||||
"2025-04-02T16:04:05.492Z","2025-04-02","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.4634140000","102.500000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.463414","6f5d075e-2a3c-4de9-83da-eb752fc32714","","","",""
|
||||
"2025-04-02T16:54:09.841Z","2025-04-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0588400000","339.900000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.058840","8a385da3-0166-4cf1-ad64-a0b00dcc8d51","","","",""
|
||||
"2025-04-02T17:08:30.420Z","2025-04-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1128920000","177.160000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.112892","29762627-48b8-42f5-b5d2-c57f0a965df5","","","",""
|
||||
"2025-05-01T04:52:25.663269Z","2025-05-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","39.540000","","","EUR","","","","Interest payment Booking","2656e8cd-d84f-41bf-9827-6fead1582555","","","",""
|
||||
"2025-05-02T08:40:19.189Z","2025-05-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.8945340000","55.895000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.894534","3f6a7465-7a5f-4570-a578-f6a411d5861c","","","",""
|
||||
"2025-05-02T09:03:02.162695Z","2025-05-02","DEFAULT","CASH","CARD_TRANSACTION","","SUMUP *TEAM ACTION DU RO","","","","-50.000000","","","EUR","","","","TR Card Transaction","f080cc3e-8cdf-4d2a-a60f-14d8d4b462fc","","","","8398"
|
||||
"2025-05-02T13:56:08.671Z","2025-05-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3026170000","528.720000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.302617","c5ea3249-75a1-4410-86e9-fcfeb1932353","","","",""
|
||||
"2025-05-02T14:31:31.241Z","2025-05-02","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.5233680000","98.466000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.523368","95dc98e3-aecd-488a-84cc-e2b408c15352","","","",""
|
||||
"2025-05-02T16:27:06.048Z","2025-05-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","1.0449750000","95.696000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 1.044975","ed7d49d1-0ef5-4a23-9cfa-d6950355bc9e","","","",""
|
||||
"2025-05-02T16:39:25.581Z","2025-05-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1197030000","167.080000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.119703","60294e70-c8bc-4a19-9189-06cf43f8de84","","","",""
|
||||
"2025-05-02T16:42:48.385Z","2025-05-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0514070000","389.050000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.051407","f08c808e-bffb-4e24-8009-df4164e4690a","","","",""
|
||||
"2025-05-04T10:15:06.519747Z","2025-05-04","DEFAULT","CASH","CARD_TRANSACTION","","TUI Hellas S.A.","","","","-138.000000","","","EUR","","","","TR Card Transaction","f68dfa93-1b8e-4be2-acab-993c2a9d61bb","","","","4722"
|
||||
"2025-05-09T13:41:21.208471Z","2025-05-09","DEFAULT","CASH","CARD_TRANSACTION","","BALOS","","","","-5.000000","","","EUR","","","","TR Card Transaction","7e027703-5422-4da8-b48b-231362d1badf","","","","5812"
|
||||
"2025-05-09T13:42:24.936766Z","2025-05-09","DEFAULT","CASH","CARD_TRANSACTION","","BALOS","","","","-5.000000","","","EUR","","","","TR Card Transaction","ffd51553-1658-4c7b-9229-fbc77d58e555","","","","5812"
|
||||
"2025-05-10T14:16:18.817143Z","2025-05-10","DEFAULT","CASH","CARD_TRANSACTION","","DUTY FREE SHOPS","","","","-43.000000","","","EUR","","","","TR Card Transaction","b178ff15-5592-412a-8375-43c705780e78","","","","5977"
|
||||
"2025-05-22T12:30:52.900324Z","2025-05-22","DEFAULT","CASH","CARD_TRANSACTION","","DUO CATERING LOUVAIN-LA-N","","","","-4.100000","","","EUR","","","","TR Card Transaction","9579e2a5-8d82-4da2-b596-0dcfd3b378ac","","","","5499"
|
||||
"2025-06-01T07:04:28.652757Z","2025-06-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","36.490000","","","EUR","","","","Interest payment Booking","309a3bbc-aa6d-4d3a-996d-b0fd73163398","","","",""
|
||||
"2025-06-02T08:41:19.099Z","2025-06-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0429250000","57.075000","-2.45","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.042925","5aebce57-433f-46e4-8fad-7d7af6558fd0","","","",""
|
||||
"2025-06-02T08:42:33.988Z","2025-06-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.8760400000","57.075000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.876040","c0a173a6-417c-4fe1-9207-a802480f6abb","","","",""
|
||||
"2025-06-02T09:10:23.838148Z","2025-06-02","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","2.450000","","","EUR","","","","Your Saveback payment","0780c208-7a81-4bec-b65d-13c0fb906b95","","","",""
|
||||
"2025-06-02T14:16:55.583Z","2025-06-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.2917040000","548.500000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.291704","f0a8bcf2-a39d-479b-9f23-590d6a7a4494","","","",""
|
||||
"2025-06-02T14:56:57.665Z","2025-06-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","1.0138900000","98.630000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 1.013890","7bbfdfbb-4a3c-42c9-9dd7-a097d960fb24","","","",""
|
||||
"2025-06-02T15:29:33.320Z","2025-06-02","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.4747810000","101.710000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.474781","12591dbd-b1d2-4e0d-a8c0-19763ca896d3","","","",""
|
||||
"2025-06-02T16:03:11.553Z","2025-06-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0483850000","413.350000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.048385","723df5fc-7dd0-4897-9374-75335782726a","","","",""
|
||||
"2025-06-02T16:32:27.430Z","2025-06-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1113210000","179.660000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.111321","71bd1a1f-459f-416c-93bb-ccab98b436f2","","","",""
|
||||
"2025-06-12T13:20:52.399557Z","2025-06-12","DEFAULT","CASH","DIVIDEND","STOCK","Microsoft","US5949181045","0.2534020000","","0.180000","","-0.03","EUR","0.21","USD","0.874661","Cash Dividend for ISIN US5949181045","5c526033-71fa-407f-b65f-48a0d0fc00b4","","","",""
|
||||
"2025-06-25T10:29:54.265327Z","2025-06-25","DEFAULT","CASH","CARD_TRANSACTION","","TIHANCE IV","","","","-150.500000","","","EUR","","","","TR Card Transaction","ef9b941f-54d1-45b3-bffc-09c0f9ea0455","","","","5812"
|
||||
"2025-07-01T05:05:01.919142Z","2025-07-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","31.800000","","","EUR","","","","Interest payment for payout collection 501253dd-5ec4-4749-8ebd-25e0b7be717d","55faed63-871b-4b7a-913b-82485ef31ff4","","","",""
|
||||
"2025-07-02T08:36:39.554831Z","2025-07-02","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","1.500000","","","EUR","","","","Your Saveback payment","dc92d38d-a204-4130-afc5-6d074b2cb0af","","","",""
|
||||
"2025-07-02T08:39:36.801Z","2025-07-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0272500000","55.045000","-1.50","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.027250","1b8030a5-c68e-43ae-b61b-b57401d5b856","","","",""
|
||||
"2025-07-02T08:39:42.199Z","2025-07-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.9083470000","55.045000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.908347","a1537050-9bc6-4f0e-a95c-06e0099f07d5","","","",""
|
||||
"2025-07-02T13:50:19.041Z","2025-07-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.2847480000","561.900000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.284748","ec4798d1-c08c-4b07-a534-07cb22b552e8","","","",""
|
||||
"2025-07-02T14:13:04.065Z","2025-07-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.9981530000","100.185000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.998153","5fd20015-2c93-4928-8f87-012391d7cf3a","","","",""
|
||||
"2025-07-02T14:52:51.708Z","2025-07-02","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.4388480000","104.250000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.438848","6e4bbadb-e617-4c3f-9013-f65798265358","","","",""
|
||||
"2025-07-02T15:21:30.358Z","2025-07-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0474890000","421.150000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.047489","b3bb61ea-2ff7-4c63-ba12-5f8906313de3","","","",""
|
||||
"2025-07-02T15:40:51.300Z","2025-07-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1069060000","187.080000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.106906","09918138-9557-4333-99c5-8c0189d3f0b7","","","",""
|
||||
"2025-07-03T08:46:20.711305Z","2025-07-03","DEFAULT","CASH","DIVIDEND","STOCK","NVIDIA","US67066G1040","2.3530520000","","0.020000","","","EUR","0.02","USD","0.850702","Cash Dividend for ISIN US67066G1040","2fba089d-faca-400e-9be9-38e83a3b8377","","","",""
|
||||
"2025-07-09T08:42:05.581435Z","2025-07-09","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PRIME FR 2469664","","","","-69.900000","","","EUR","","","","TR Card Transaction","daf43ef7-b335-4390-a5f0-09434d8119b7","","","","5965"
|
||||
"2025-07-26T07:04:07.934628Z","2025-07-26","DEFAULT","CASH","CARD_TRANSACTION","","OSMOZIS","","","","-20.500000","","","EUR","","","","TR Card Transaction","f725ac02-8708-4337-ba2c-9a6c22192a94","","","","4814"
|
||||
"2025-08-01T06:38:56.025561Z","2025-08-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","30.450000","","","EUR","","","","Interest payment for payout collection 01986375-38d0-71fd-bc0b-3e8e1fc0dd97","7bcd5cfc-9cd0-4963-8704-2f38ee24ae76","","","",""
|
||||
"2025-08-04T08:39:16.267Z","2025-08-04","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.8853470000","56.475000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.885347","aa0574f3-c9bb-439d-8eab-ae4572d88c67","","","",""
|
||||
"2025-08-04T08:39:50.535Z","2025-08-04","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0157590000","56.475000","-0.89","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.015759","5e4e371e-3c5d-4679-afae-ee038027fead","","","",""
|
||||
"2025-08-04T08:40:10.605499Z","2025-08-04","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","0.890000","","","EUR","","","","Your Saveback payment","1f812b59-6466-486f-aa4c-97d8909eba28","","","",""
|
||||
"2025-08-04T13:59:50.951Z","2025-08-04","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.2755950000","580.560000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.275595","49b8f4bc-ba90-4aa1-871d-7ded8b6ada20","","","",""
|
||||
"2025-08-04T14:51:32.701Z","2025-08-04","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.9717700000","102.905000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.971770","ded756ce-36f6-400e-9182-75f37e4121a0","","","",""
|
||||
"2025-08-04T15:07:55.479Z","2025-08-04","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.3920460000","107.755000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.392046","d0b5e90f-17cb-49a2-b484-a62b0ee705c6","","","",""
|
||||
"2025-08-04T15:36:51.358Z","2025-08-04","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0509100000","392.850000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.050910","c1c7d2bd-0975-41b1-b59b-3da291b112fd","","","",""
|
||||
"2025-08-04T15:46:40.960Z","2025-08-04","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1087190000","183.960000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.108719","79d6429e-4f32-4175-83d1-91a54211e675","","","",""
|
||||
"2025-08-06T08:32:32.888789Z","2025-08-06","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1000.000000","","","EUR","","","","No SEPA description provided","3903d821-40b3-44b0-8a80-fb345449f9eb","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2025-08-19T14:10:51.458140Z","2025-08-19","DEFAULT","CASH","CARD_TRANSACTION","","2134 AMAZ* RU3UU2LK4","","","","-56.850000","","","EUR","","","","2134 AMAZ* RU3UU2LK4","77c44900-278b-4ac1-812f-cd3ffdd203d8","","","","5311"
|
||||
"2025-09-01T05:51:38.940703Z","2025-09-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","31.030000","","","EUR","","","","Interest payment for payout collection 01990301-8bab-739b-bc29-bfe9d0f1fd39","32e7bec3-fa23-4ba7-a290-cc9abaf92d55","","","",""
|
||||
"2025-09-02T08:38:43.174560Z","2025-09-02","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","0.560000","","","EUR","","","","Your Saveback payment","b80cc3b1-c947-4e6d-adac-faf44771adb2","","","",""
|
||||
"2025-09-02T08:41:21.082Z","2025-09-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0096510000","58.025000","-0.56","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.009651","6be660a7-37f2-41c2-8f40-9f711849b18d","","","",""
|
||||
"2025-09-02T11:54:16.658Z","2025-09-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.8616970000","58.025000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.861697","276e09f3-2630-4a58-9b2b-2ca106c2fc67","","","",""
|
||||
"2025-09-02T13:56:10.787Z","2025-09-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.2726180000","586.900000","-160.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.272618","4d717ad0-582d-4b6d-a128-6df7d7bf8259","","","",""
|
||||
"2025-09-02T14:52:56.190Z","2025-09-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.9625550000","103.890100","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.962555","aafd9f52-ac95-4f43-9b2c-5c42fe4240be","","","",""
|
||||
"2025-09-02T15:14:37.307Z","2025-09-02","DEFAULT","TRADING","BUY","FUND","S&P 500 EUR (Acc)","LU1681048804","1.3805790000","108.650000","-150.00","","","EUR","","","","Savings plan execution LU1681048804 Amundi Index Solutions - Amundi S&P 500 UCITS ETF - EUR (C), quantity: 1.380579","2cd8800c-de93-4cca-8fe5-64710dd4003c","","","",""
|
||||
"2025-09-02T15:43:19.983Z","2025-09-02","DEFAULT","TRADING","BUY","STOCK","Crowdstrike Holdings (A)","US22788C1053","0.0565290000","353.800000","-20.00","","","EUR","","","","Savings plan execution US22788C1053 CROWDSTRIKE HLD. DL-,0005, quantity: 0.056529","1e009250-7138-48f9-b367-ee5e55c508e1","","","",""
|
||||
"2025-09-02T15:55:10.759Z","2025-09-02","DEFAULT","TRADING","BUY","STOCK","Amazon.com","US0231351067","0.1041880000","191.960000","-20.00","","","EUR","","","","Savings plan execution US0231351067 AMAZON.COM INC. DL-,01, quantity: 0.104188","655d36e7-cf61-4362-b33d-ff956696d1b0","","","",""
|
||||
"2025-09-03T07:02:57.704368Z","2025-09-03","DEFAULT","CASH","CUSTOMER_INBOUND","","M CHRISTOPHE BONTE","","","","1000.000000","","","EUR","","","","No SEPA description provided","87092d43-3164-4c91-8916-27c3757cc337","M CHRISTOPHE BONTE","BE83377000899915","",""
|
||||
"2025-09-11T17:04:07.343604Z","2025-09-11","DEFAULT","CASH","DIVIDEND","STOCK","Microsoft","US5949181045","0.2534020000","","0.180000","","-0.03","EUR","0.21","USD","0.854190","Cash Dividend for ISIN US5949181045","2269adee-8afd-4d53-afd6-f46f6d19ca10","","","",""
|
||||
"2025-09-29T07:27:52.094253Z","2025-09-29","DEFAULT","CASH","BONUS","","","","","","10.000000","","","EUR","","","","Private Markets bonus 6208c7ee-c109-46d6-a6ee-474c1ea35a1b for buy order: 3dd9779b-6901-4a40-8a8d-c419c7c77cda","c119da27-0bb2-4328-a630-c8e04c78d7b5","","","",""
|
||||
"2025-09-29T07:27:52.094271Z","2025-09-29","DEFAULT","CASH","PRIVATE_MARKET_BUY","PRIVATE_FUND","Private Equity","LU3170240538","","","-1000.000000","-1.00","","EUR","","","","Private Markets pre-payment for buy order: 3dd9779b-6901-4a40-8a8d-c419c7c77cda","4bcfbc88-635e-4b2d-95e3-f8e35ed95a05","","","",""
|
||||
"2025-09-29T07:27:52.163381Z","2025-09-29","DEFAULT","CASH","PRIVATE_MARKET_BUY","PRIVATE_FUND","Private Equity","LU3170240538","","","-10.000000","","","EUR","","","","Private Markets pre-payment for buy order: 6208c7ee-c109-46d6-a6ee-474c1ea35a1b","6b4caeb0-3be8-4740-9682-66a84e3be301","","","",""
|
||||
"2025-10-01T03:50:39.094110Z","2025-10-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","30.840000","","","EUR","","","","Interest payment for payout collection 01999d41-1da4-7106-9fc0-b55231718c43","05895a34-05af-4160-8a4c-359bfab82717","","","",""
|
||||
"2025-10-02T07:50:22.750Z","2025-10-02","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6924240000","144.4200000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.692424","99c71794-d915-4b4c-9e3f-f88904d28072","","","",""
|
||||
"2025-10-02T08:46:11.943Z","2025-10-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.7811270000","64.0100000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.781127","0d09afb2-1cd8-4bf2-876c-4dbdae29ca87","","","",""
|
||||
"2025-10-02T13:41:59.500070Z","2025-10-02","DEFAULT","CASH","DIVIDEND","STOCK","NVIDIA","US67066G1040","2.3530520000","","0.020000","","","EUR","0.02","USD","0.852951","Cash Dividend for ISIN US67066G1040","cbe3eb4e-30d6-42f4-a82b-fe3e58ca0b93","","","",""
|
||||
"2025-10-02T13:51:39.475Z","2025-10-02","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.6680890000","37.4800000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.668089","b8d375c3-7d7f-4bf2-81b3-3159b1417568","","","",""
|
||||
"2025-10-02T14:05:14.603Z","2025-10-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.4079230000","612.8600000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.407923","61577ba0-6650-4be8-b61d-40985beecb75","","","",""
|
||||
"2025-10-02T14:54:25.865Z","2025-10-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.9219560000","108.4650000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.921956","480fb55e-3193-49c1-9ea9-843ba6e2b7d4","","","",""
|
||||
"2025-10-08T11:38:14.700723Z","2025-10-08","DEFAULT","CASH","CARD_TRANSACTION","","Google Play Apps","","","","-6.990000","","","EUR","","","","Google Play Apps","8937d727-5747-4dbd-9a01-6c6954c8c012","","","","5817"
|
||||
"2025-10-11T08:04:35.370426Z","2025-10-11","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR*B08NU6FS5","","","","-16.700000","","","EUR","","","","AMZN Mktp FR*B08NU6FS5","1e034cad-7e75-468a-b590-9594687b247c","","","","5999"
|
||||
"2025-10-14T08:23:30.652045Z","2025-10-14","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR*TR7216Y75","","","","-105.300000","","","EUR","","","","AMZN Mktp FR*TR7216Y75","605832e8-60f1-49ed-8865-b3ee0ffe4fcf","","","","5999"
|
||||
"2025-10-22T09:15:56.049Z","2025-10-22","DEFAULT","TRADING","BUY","PRIVATE_FUND","Private Equity","LU3170240538","0.1000000000","100.0000000000","","","","EUR","","","","BUY LU3170240538","b95a8c94-6a3f-478d-b270-7d40a2cbeda4","","","",""
|
||||
"2025-10-22T09:19:52.250Z","2025-10-22","DEFAULT","TRADING","BUY","PRIVATE_FUND","Private Equity","LU3170240538","10.0000000000","100.0000000000","","","","EUR","","","","BUY LU3170240538","597dbcc4-63c1-42cd-b391-92b25533c279","","","",""
|
||||
"2025-10-22T09:25:15.618524Z","2025-10-22","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS 2441535","","","","-95.320000","","","EUR","","","","AMAZON PAYMENTS 2441535","ae9f8a66-85d6-4112-9a47-0b83ce333d95","","","","5965"
|
||||
"2025-10-28T08:42:50.979326Z","2025-10-28","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR*JN1LR3755","","","","-46.400000","","","EUR","","","","AMZN Mktp FR*JN1LR3755","808a9fb1-836e-40f6-a148-7477509eb066","","","","5999"
|
||||
"2025-11-01T06:21:22.361672Z","2025-11-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","29.240000","","","EUR","","","","Interest payment for payout collection 019a3d39-2d67-7f4f-9d53-c8297128a8f6","1dc5375d-d85c-404d-bb77-303b3cd488f6","","","",""
|
||||
"2025-11-03T08:49:51.091Z","2025-11-03","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6839940000","146.2000000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.683994","87c3772f-f05b-42e5-bfab-bd6b809bedea","","","",""
|
||||
"2025-11-03T09:44:17.173992Z","2025-11-03","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","2.680000","","","EUR","","","","Your Saveback payment","ce067f2f-d317-4980-af63-5cbd7486e3e7","","","",""
|
||||
"2025-11-03T09:55:42.567Z","2025-11-03","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0397060000","67.4950000000","-2.68","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.039706","2badfbd7-fd4f-4d1e-b6e0-de0458b2f484","","","",""
|
||||
"2025-11-03T09:56:03.826Z","2025-11-03","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.7407950000","67.4950000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.740795","d1478a5d-080e-4a01-b95a-f0f49c1fcfc2","","","",""
|
||||
"2025-11-03T14:52:55.800Z","2025-11-03","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.5514760000","39.1930000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.551476","f588d960-71aa-4da3-ac0e-3995082e9fd3","","","",""
|
||||
"2025-11-03T15:05:44.682Z","2025-11-03","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3934520000","635.4000000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.393452","609832de-32f8-4689-acea-7d885d90c540","","","",""
|
||||
"2025-11-03T16:09:54.386Z","2025-11-03","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.8944940000","111.7950000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.894494","7b8ef3c2-2e0d-4a75-90f7-cd87527b547a","","","",""
|
||||
"2025-12-01T00:10:10.968934Z","2025-12-01","DEFAULT","CASH","BENEFITS_SAVEBACK","","","","","","1.560000","","","EUR","","","","Fixed income bonus bbea7279-46ee-42cc-872a-5d9e8752aa61 for buy order: abbea51f-3ade-4b8b-846a-67a3f3ee330f","019ad73e-d698-768c-97ee-eadb9b2371cf","","","",""
|
||||
"2025-12-01T06:34:57.607472Z","2025-12-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","27.140000","","","EUR","","","","Interest payment for payout collection 019ad7b6-9ec5-7836-a145-34feed057539","019ad89f-1cc7-728c-a200-1ece85119402","","","",""
|
||||
"2025-12-01T12:17:49.813180Z","2025-12-01","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR*ZX3HO8J84","","","","-156.000000","","","EUR","","","","AMZN Mktp FR*ZX3HO8J84null","019ad9d9-04f5-7ef9-8ad5-0eda5602c801","","","","5999"
|
||||
"2025-12-02T08:50:20.667Z","2025-12-02","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6809200000","146.8600000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.680920","02d95bdd-f232-441e-bdec-39eefb45e175","","","",""
|
||||
"2025-12-02T09:46:21.479Z","2025-12-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0222610000","70.0750000000","-1.56","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.022261","23c8b492-56c6-4afd-90df-81b9f45fb7e5","","","",""
|
||||
"2025-12-02T09:49:37.568Z","2025-12-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.7130630000","70.1200000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.713063","6512422f-32ba-4708-86e3-5ca23a6a60d1","","","",""
|
||||
"2025-12-02T14:46:05.025Z","2025-12-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.8989570000","111.2400000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.898957","cf7a1b01-239a-48af-b5f1-e2468a67454d","","","",""
|
||||
"2025-12-02T14:55:39.842Z","2025-12-02","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.6335880000","37.9710000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.633588","0a3175e4-36ca-4e21-bde9-1665bd9c843c","","","",""
|
||||
"2025-12-02T15:26:12.484Z","2025-12-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3963470000","630.7600000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.396347","f0ff6493-2ab3-4742-aa0b-8e982bfa40eb","","","",""
|
||||
"2025-12-11T12:10:18.175842Z","2025-12-11","DEFAULT","CASH","DIVIDEND","STOCK","Microsoft","US5949181045","0.2534020000","","0.200000","","-0.03","EUR","0.23","USD","0.859550","Cash Dividend for ISIN US5949181045","019b0d51-b8bf-75cc-bf39-e1edde4fe8a2","","","",""
|
||||
"2025-12-11T12:15:04.860689Z","2025-12-11","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS 2441535","","","","-22.130000","","","EUR","","","","AMAZON PAYMENTS 2441535null","019b0d56-189c-74f2-841b-02dfa3ad6ae9","","","","5965"
|
||||
"2025-12-12T09:22:32.275261Z","2025-12-12","DEFAULT","CASH","CARD_TRANSACTION","","Amazon.fr*Z92QS0UE4","","","","-8.550000","","","EUR","","","","Amazon.fr*Z92QS0UE4null","019b11de-7cd3-76dd-8350-6319b6c505ee","","","","5999"
|
||||
"2025-12-14T13:00:51.924065Z","2025-12-14","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS 2441535","","","","-15.890000","","","EUR","","","","AMAZON PAYMENTS 2441535null","019b1cf3-1754-7339-a13d-6a5b00e62850","","","","5965"
|
||||
"2025-12-18T12:23:48.025118Z","2025-12-18","DEFAULT","CASH","CARD_TRANSACTION","","","","","","11.480000","","","EUR","","","","AMAZON PAYMENTS 2441535null","019b316a-9839-7a15-8cbc-3866c1b9b340","","","",""
|
||||
"2025-12-18T08:20:23.416773Z","2025-12-18","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR*ZP6BH20H4","","","","-95.000000","","","EUR","","","","AMZN Mktp FR*ZP6BH20H4null","019b308b-bef8-7faa-a9c4-6eb01a51257c","","","","5999"
|
||||
"2025-12-24T13:19:29.730507Z","2025-12-24","DEFAULT","CASH","DIVIDEND","STOCK","NVIDIA","US67066G1040","2.3530520000","","0.020000","","","EUR","0.02","USD","0.848464","Cash Dividend for ISIN US67066G1040","019b5083-bdc2-70c8-b436-a6fd539053a5","","","",""
|
||||
"2026-01-01T07:55:55.375587Z","2026-01-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","26.650000","","","EUR","","","","Interest payment for payout collection 019b77f6-b513-7d5b-aca9-eab31845517c","019b788e-606f-73af-9654-a7793bfad2c0","","","",""
|
||||
"2026-01-01T02:17:59.994608Z","2026-01-01","DEFAULT","CASH","BENEFITS_SAVEBACK","","","","","","1.410000","","","EUR","","",""," Saveback cash reward 3c4dd0ec-181e-4443-a1a5-5e6aa8eb69cf for reservation: b70adfaf-660c-4525-814a-520b5c69ab27","019b7758-ffba-7379-9131-ea05d7ed8d61","","","",""
|
||||
"2026-01-02T08:51:10.233Z","2026-01-02","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6572890000","152.1400000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.657289","c9d755e2-15cc-4de1-a2db-8c680ae7f98d","","","",""
|
||||
"2026-01-02T10:08:44.149Z","2026-01-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.6878990000","72.6850000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.687899","a4e706c7-b15c-4e0d-926d-02437c72c8d5","","","",""
|
||||
"2026-01-02T10:11:55.341Z","2026-01-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0193980000","72.6850000000","-1.41","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.019398","115e0d62-caeb-49af-bac1-336d7f7c352e","","","",""
|
||||
"2026-01-02T14:50:26.907Z","2026-01-02","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.5516710000","39.1900000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.551671","260925b7-567c-46bf-9d28-6156a75a7f25","","","",""
|
||||
"2026-01-02T15:04:18.364Z","2026-01-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3972160000","629.3800000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.397216","21537df3-66ea-4771-85ef-e3de0c96e913","","","",""
|
||||
"2026-01-02T16:22:02.849Z","2026-01-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.8976250000","111.4050000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.897625","2ae7393f-103e-4a39-941b-4eadcbd81149","","","",""
|
||||
"2026-02-01T06:23:36.328250Z","2026-02-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","25.570000","","","EUR","","","","Interest payment for payout collection 019c170b-8e9c-7b5b-9028-041bb934061d","019c17de-ff88-708b-9a6f-a24d67567397","","","",""
|
||||
"2026-02-02T08:49:28.171Z","2026-02-02","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6427560000","155.5800000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.642756","d0868075-1d75-4631-ab02-51a19e0b671a","","","",""
|
||||
"2026-02-02T09:40:06.467Z","2026-02-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.6495190000","76.9800000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.649519","9d3673f3-bc8a-4d4a-a911-9f656c4ec9e4","","","",""
|
||||
"2026-02-02T14:46:30.638Z","2026-02-02","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.4452260000","40.8960000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.445226","65f885ab-5d52-4594-985d-2fc6a2ae627b","","","",""
|
||||
"2026-02-02T14:58:25.291Z","2026-02-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3960200000","631.2800000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.396020","13d6d9b2-3af6-41cc-aaff-6c0cddc02da4","","","",""
|
||||
"2026-02-02T16:00:25.757Z","2026-02-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.8848570000","113.0126000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.884857","dd37da5f-fdad-434d-b1fb-56947808e381","","","",""
|
||||
"2026-02-09T10:50:29.760454Z","2026-02-09","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-14.990000","","","EUR","","","","AMAZON PAYMENTS 2441535","019c4206-3800-7b51-8556-f4f6dbb47b3f","","","","5965"
|
||||
"2026-02-16T08:01:07.509476Z","2026-02-16","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR","","","","-66.020000","","","EUR","","","","AMZN Mktp FR*556KT2I55","019c6577-abb5-7b29-b062-9df154a6ae8f","","","","5999"
|
||||
"2026-02-26T12:16:39.219952Z","2026-02-26","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-16.790000","","","EUR","","","","AMAZON PAYMENTS 2441535","019c99e1-3533-72f7-a08f-e329d1c44855","","","","5965"
|
||||
"2026-03-01T07:52:24.797486Z","2026-03-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","22.160000","","","EUR","","","","Interest payment for payout collection 019ca754-80ee-70d1-bb78-819ff3dbbb1b","019ca862-5ddd-7a34-a2fa-562187ba6173","","","",""
|
||||
"2026-03-01T00:07:59.507244Z","2026-03-01","DEFAULT","CASH","BENEFITS_SAVEBACK","","","","","","0.970000","","","EUR","","",""," Saveback cash reward 7b587cdf-22f2-478d-a54b-ed06831f4d5e for reservation: 69cb70aa-9ab7-41cb-bd7b-5b07f66cd1d2","019ca6b9-2d13-7964-942f-8ec17534b5ea","","","",""
|
||||
"2026-03-02T08:50:03.953Z","2026-03-02","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6283770000","159.1400000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.628377","89d0b34e-84f2-4f5d-b6eb-64c3f77a313e","","","",""
|
||||
"2026-03-02T09:53:28.866Z","2026-03-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.5602240000","89.2500000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.560224","14117841-49b8-418d-84ef-8c54c5518b76","","","",""
|
||||
"2026-03-02T09:55:09.337Z","2026-03-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0108690000","89.2400000000","-0.97","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.010869","80f3a508-54eb-46f5-a839-43c421d913ee","","","",""
|
||||
"2026-03-02T14:53:03.795Z","2026-03-02","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.3249860000","43.0110000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.324986","dc3ad1ae-e571-4e3e-9b87-abc6fabc2058","","","",""
|
||||
"2026-03-02T15:06:53.622Z","2026-03-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3990550000","626.4797000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.399055","ca783ad4-6ae2-4364-b2fe-6ea350480b81","","","",""
|
||||
"2026-03-02T16:12:11.975Z","2026-03-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.8783740000","113.8466000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.878374","9f8fc727-3701-4c2c-b32e-b512b0c2e10a","","","",""
|
||||
"2026-03-09T08:30:26.730253Z","2026-03-09","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-56.440000","","","EUR","","","","AMAZON PAYMENTS 2441535","019cd1b8-0faa-7dfa-af07-8f199d52ce2d","","","","5965"
|
||||
"2026-03-11T08:07:57.961454Z","2026-03-11","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR","","","","-242.860000","","","EUR","","","","AMZN Mktp FR*HN7V18ZR5","019cdbf0-3309-7199-8668-2dc2934c7563","","","","5999"
|
||||
"2026-03-11T12:21:03.733928Z","2026-03-11","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR","","","","-60.460000","","","EUR","","","","AMZN Mktp FR*RE4K45Y25","019cdcd7-ea75-7ef8-81cd-0cbcbf5d1331","","","","5999"
|
||||
"2026-03-12T08:15:14.054109Z","2026-03-12","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-136.850000","","","EUR","","","","AMAZON PAYMENTS 2441535","019ce11d-3686-7cd8-a3b9-59f3608df8ca","","","","5965"
|
||||
"2026-03-12T09:40:05.311159Z","2026-03-12","DEFAULT","CASH","CARD_TRANSACTION","","Amazon.fr","","","","-26.110000","","","EUR","","","","Amazon.fr*ET7NT4PJ5","019ce16a-e63f-7046-8a8a-dabfdd8d9cbd","","","","5999"
|
||||
"2026-03-12T11:09:43.199597Z","2026-03-12","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-279.290000","","","EUR","","","","AMAZON PAYMENTS 2441535","019ce1bc-f59f-790e-bead-c7985f70d69a","","","","5965"
|
||||
"2026-03-12T10:03:38.605681Z","2026-03-12","DEFAULT","CASH","DIVIDEND","STOCK","Microsoft","US5949181045","0.2534020000","","0.200000","","-0.03","EUR","0.23","USD","0.863483","Cash Dividend for ISIN US5949181045","019ce180-76ed-79d1-8b21-af39f427b73b","","","",""
|
||||
"2026-03-16T09:04:00.812147Z","2026-03-16","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR","","","","-51.040000","","","EUR","","","","AMZN Mktp FR*FA7DL43W5","019cf5e3-4f2c-76cb-9613-160d9565eecc","","","","5999"
|
||||
"2026-03-16T10:53:57.038489Z","2026-03-16","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-63.020000","","","EUR","","","","AMAZON PAYMENTS 2441535","019cf647-f5ae-7b0f-9d7b-df7f5823aa68","","","","5965"
|
||||
"2026-03-17T12:56:38.149861Z","2026-03-17","DEFAULT","CASH","CARD_TRANSACTION","","Amazon.fr","","","","-5.930000","","","EUR","","","","Amazon.fr*WE4NM9XF5","019cfbde-a405-7b0c-bede-e551f76d8c19","","","","5999"
|
||||
"2026-04-01T14:05:51.934298Z","2026-04-01","DEFAULT","CASH","DIVIDEND","STOCK","NVIDIA","US67066G1040","2.3530520000","","0.020000","","","EUR","0.02","USD","0.869716","Cash Dividend for ISIN US67066G1040","019d495d-69be-79e2-bffe-e7485c4af8ed","","","",""
|
||||
"2026-03-31T22:36:35.589802Z","2026-04-01","DEFAULT","CASH","BENEFITS_SAVEBACK","","","","","","9.220000","","","EUR","","",""," Saveback cash reward c4496a6e-a7f4-4e0d-ad24-f24faf617516 for reservation: 509753db-b464-4aa0-9343-c23a41e44bde","019d460a-a385-7d80-823d-356a3ffd62b1","","","",""
|
||||
"2026-04-01T05:11:58.714801Z","2026-04-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","22.440000","","","EUR","","","","Interest payment for payout collection 019d46dc-06f3-7189-9ca0-9928fd7e7ac1","019d4774-9ffa-7e47-9987-addb785dc557","","","",""
|
||||
"2026-04-01T07:11:40.678777Z","2026-04-01","DEFAULT","CASH","CARD_TRANSACTION","","","","","","60.490000","","","EUR","","","","AMZN Mktp FR","019d47e2-3686-7842-af5d-b301cc9c2c9f","","","",""
|
||||
"2026-04-02T07:49:54.429Z","2026-04-02","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6585870000","151.8400000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.658587","85f798a1-4889-4d43-a507-f43017c6d3ac","","","",""
|
||||
"2026-04-02T08:58:29.853Z","2026-04-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.1182350000","77.9800000000","-9.22","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.118235","c3192307-9eda-4007-8413-5b46bb7b610a","","","",""
|
||||
"2026-04-02T09:00:36.139Z","2026-04-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.6412310000","77.9750000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.641231","090e218e-65f4-4f41-985f-594e0aeaa4fc","","","",""
|
||||
"2026-04-02T13:55:21.363Z","2026-04-02","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.5221310000","39.6490000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.522131","9488b16d-fdc0-403c-8c5f-65b2667e1f6a","","","",""
|
||||
"2026-04-02T14:18:59.116Z","2026-04-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.4115220000","607.5000000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.411522","15e9363b-479d-485d-8c31-7ea3b8876ebd","","","",""
|
||||
"2026-04-02T15:05:52.851Z","2026-04-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.9113870000","109.7228000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.911387","16ddf3fd-7bb0-456c-b3d7-bb44b1df0fc9","","","",""
|
||||
"2026-04-23T10:18:45.015722Z","2026-04-23","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-7.040000","","","EUR","","","","AMAZON PAYMENTS 2441535","019db9d9-6397-76a2-a2ae-5f02ac20439b","","","","5965"
|
||||
"2026-04-30T22:39:06.730055Z","2026-05-01","DEFAULT","CASH","BENEFITS_SAVEBACK","","","","","","1.610000","","","EUR","","",""," Saveback cash reward 57dd4388-903f-46f1-a704-bb139ff5fce5 for reservation: 7ec3e9f9-86e4-492a-a768-f8203ac1b3cd","019de08b-b9ea-73aa-bc1c-349ac7221a11","","","",""
|
||||
"2026-05-01T03:11:20.063994Z","2026-05-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","20.320000","","","EUR","","","","Interest payment for payout collection 019de147-4ed1-7800-b08b-e7f279033a0a","019de184-f3ff-751f-a7ea-c7a0f42638d6","","","",""
|
||||
"2026-05-01T10:03:20.432246Z","2026-05-01","DEFAULT","CASH","CARD_TRANSACTION","","TUI PORTUGAL SA","","","","-154.000000","","","EUR","","","","TUI Portugal SA","019de2fe-27f0-7f4a-b1c3-c5c3788cd600","","","","4722"
|
||||
"2026-05-04T07:50:16.313Z","2026-05-04","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6333920000","157.8800000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.633392","2278a77f-519e-4e57-8c5a-617c3c5d5f63","","","",""
|
||||
"2026-05-04T09:03:51.825Z","2026-05-04","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0212400000","75.8000000000","-1.61","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.021240","2b39008a-ecf6-4713-9a28-64c91d991db9","","","",""
|
||||
"2026-05-04T09:03:58.410Z","2026-05-04","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.6596300000","75.8000000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.659630","855aae57-35e2-4a48-be15-bae42481c619","","","",""
|
||||
"2026-05-04T14:13:38.832Z","2026-05-04","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.2106280000","45.2360000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.210628","2c75fd2d-aaff-4331-bef1-5484c7628d8d","","","",""
|
||||
"2026-05-04T14:23:20.088Z","2026-05-04","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3767440000","663.5800000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.376744","e622c46c-fa15-4893-b6d4-906ac038ee0d","","","",""
|
||||
"2026-05-04T14:48:07.559Z","2026-05-04","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.8483860000","117.8708000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.848386","395a876e-1590-4755-8730-9cd54812daf3","","","",""
|
||||
"2026-05-06T12:42:43.097397Z","2026-05-06","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-14.890000","","","EUR","","","","AMAZON PAYMENTS 2441535","019dfd4f-de19-761a-a6e3-243b4ae1d167","","","","5965"
|
||||
"2026-05-17T07:58:46.570194Z","2026-05-17","DEFAULT","CASH","CARD_TRANSACTION","","AMZN Mktp FR","","","","-7.040000","","","EUR","","","","AMZN Mktp FR*NA19O6EG4","019e34f1-dd2a-7c2a-97eb-7c8eb92a2ba4","","","","5999"
|
||||
"2026-05-18T10:09:29.814463Z","2026-05-18","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PAYMENTS","","","","-16.060000","","","EUR","","","","AMAZON PAYMENTS 2441535","019e3a8f-e6d6-73ab-a4dd-f40563f18ec9","","","","5965"
|
||||
"2026-05-31T23:37:10.343104Z","2026-06-01","DEFAULT","CASH","BENEFITS_SAVEBACK","FUND","Physical Gold USD (Acc)","IE00B4ND3602","","","0.370000","","","EUR","","",""," Saveback cash reward 673b1c49-e40a-4913-b460-a7153771fdb2 for reservation: 21228fd8-f7ad-47dc-88e3-acbabf11e69d","019e8066-05c7-7460-b6ec-b1aeb3bfcf84","","","",""
|
||||
"2026-06-01T04:04:48.610991Z","2026-06-01","DEFAULT","CASH","INTEREST_PAYMENT","","","","","","19.780000","","","EUR","","","","Interest payment for payout collection 019e80f2-61af-72ec-ac25-677430c08534","019e815b-0d62-7431-8e0c-42a3ec422be1","","","",""
|
||||
"2026-06-02T07:50:49.435Z","2026-06-02","DEFAULT","TRADING","BUY","FUND","STOXX Europe 600 EUR (Acc)","LU0328475792","0.6147790000","162.6600000000","-100.00","","","EUR","","","","Savings plan execution LU0328475792 Xtrackers - Xtrackers Stoxx Europe 600 UCITS ETF 1C, quantity: 0.614779","d2e2307b-0485-474d-8a64-fc9f23bde58f","","","",""
|
||||
"2026-06-02T09:15:21.011Z","2026-06-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.0049050000","75.4300000000","-0.37","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.004905","baae8f0d-6c90-46ce-b0d6-395ea7855a10","","","",""
|
||||
"2026-06-02T09:17:33.953Z","2026-06-02","DEFAULT","TRADING","BUY","FUND","Physical Gold USD (Acc)","IE00B4ND3602","0.6626900000","75.4500000000","-50.00","","","EUR","","","","Savings plan execution IE00B4ND3602 iShares Physical Gold ETC, quantity: 0.662690","d2060dbc-0d4b-4707-8a5b-dd1f8bae6ef4","","","",""
|
||||
"2026-06-02T14:11:57.816692Z","2026-06-02","DEFAULT","CASH","CARD_TRANSACTION","","AMAZON PRIME FR","","","","-69.900000","","","EUR","","","","AMAZON PRIME FR 2469664","019e88ad-46f8-7abf-9f11-0e087b03e678","","","","5965"
|
||||
"2026-06-02T15:05:18.776Z","2026-06-02","DEFAULT","TRADING","BUY","FUND","Core MSCI EM IMI USD (Acc)","IE00BKM4GZ66","2.0267280000","49.3406000000","-100.00","","","EUR","","","","Savings plan execution IE00BKM4GZ66 iShares plc - iShares Core MSCI EM IMI UCITS ETF USD (Acc), quantity: 2.026728","311b370f-001b-47c2-9735-e7296dfa1c3d","","","",""
|
||||
"2026-06-02T15:11:51.636Z","2026-06-02","DEFAULT","TRADING","BUY","FUND","Core S&P 500 USD (Acc)","IE00B5BMR087","0.3560540000","702.1400000000","-250.00","","","EUR","","","","Savings plan execution IE00B5BMR087 iShares VII plc - iShares Core S&P 500 UCITS ETF USD (Acc), quantity: 0.356054","402aa9ee-5292-4af7-af8f-6f116b830490","","","",""
|
||||
"2026-06-02T16:11:17.305Z","2026-06-02","DEFAULT","TRADING","BUY","FUND","Core MSCI World USD (Acc)","IE00B4L5Y983","0.8065940000","123.9781000000","-100.00","","","EUR","","","","Savings plan execution IE00B4L5Y983 iShares III plc - iShares Core MSCI World UCITS ETF USD (Acc), quantity: 0.806594","c0e65a42-002b-4c0f-841b-89b09f477709","","","",""
|
||||
"2026-06-11T09:02:37.811307Z","2026-06-11","DEFAULT","CASH","DIVIDEND","STOCK","Microsoft","US5949181045","0.2534020000","","0.200000","","-0.03","EUR","0.23","USD","0.866626","Cash Dividend for ISIN US5949181045","019eb5eb-4ef3-77a8-970c-adb001387a41","","","",""
|
||||
|
@@ -0,0 +1,127 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StockFin
|
||||
{
|
||||
public sealed class FinnhubOptions
|
||||
{
|
||||
public const string SectionName = "Finnhub";
|
||||
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public interface IFinnhubClient
|
||||
{
|
||||
Task<FinnhubQuote?> GetQuoteAsync(
|
||||
string symbol,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed class FinnhubClient : IFinnhubClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly FinnhubOptions _options;
|
||||
private readonly ILogger<FinnhubClient> _logger;
|
||||
|
||||
public FinnhubClient(
|
||||
HttpClient httpClient,
|
||||
IOptions<FinnhubOptions> options,
|
||||
ILogger<FinnhubClient> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_options = options.Value;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<FinnhubQuote?> GetQuoteAsync(
|
||||
string symbol,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(symbol))
|
||||
{
|
||||
_logger.LogWarning("GetQuoteAsync called with empty symbol.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_options.ApiKey))
|
||||
{
|
||||
_logger.LogWarning("Finnhub API key is not configured.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var url = $"quote?symbol={Uri.EscapeDataString(symbol)}&token={_options.ApiKey}";
|
||||
|
||||
var response = await _httpClient.GetAsync(url, cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Finnhub returned {StatusCode} for symbol {Symbol}.",
|
||||
(int)response.StatusCode, symbol);
|
||||
return null;
|
||||
}
|
||||
|
||||
var quote = await response.Content.ReadFromJsonAsync<FinnhubQuote>(cancellationToken);
|
||||
|
||||
if (quote == null || quote.CurrentPrice == 0)
|
||||
{
|
||||
_logger.LogWarning("Finnhub returned no data for symbol {Symbol}.", symbol);
|
||||
return null;
|
||||
}
|
||||
|
||||
return quote;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Network error fetching Finnhub quote for {Symbol}.", symbol);
|
||||
return null;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
_logger.LogError(ex, "JSON deserialization error for Finnhub quote {Symbol}.", symbol);
|
||||
return null;
|
||||
}
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Finnhub request timed out or was cancelled for {Symbol}.", symbol);
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error fetching Finnhub quote for {Symbol}.", symbol);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FinnhubQuote
|
||||
{
|
||||
[JsonPropertyName("c")]
|
||||
public double CurrentPrice { get; set; }
|
||||
|
||||
[JsonPropertyName("d")]
|
||||
public decimal Change { get; set; }
|
||||
|
||||
[JsonPropertyName("dp")]
|
||||
public decimal PercentChange { get; set; }
|
||||
|
||||
[JsonPropertyName("h")]
|
||||
public decimal High { get; set; }
|
||||
|
||||
[JsonPropertyName("l")]
|
||||
public decimal Low { get; set; }
|
||||
|
||||
[JsonPropertyName("o")]
|
||||
public decimal Open { get; set; }
|
||||
|
||||
[JsonPropertyName("pc")]
|
||||
public decimal PreviousClose { get; set; }
|
||||
|
||||
[JsonPropertyName("t")]
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StockFin
|
||||
{
|
||||
public static class FrankfurterCurrencyConverter
|
||||
{
|
||||
private static readonly HttpClient HttpClient = new()
|
||||
{
|
||||
BaseAddress = new Uri("https://api.frankfurter.dev/v1/")
|
||||
};
|
||||
|
||||
private static readonly ConcurrentDictionary<string, CacheEntry> Cache = new();
|
||||
private static readonly ConcurrentDictionary<string, SemaphoreSlim> Locks = new();
|
||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private static readonly TimeSpan CacheDuration = TimeSpan.FromHours(6);
|
||||
|
||||
public static async Task<double?> GetRateAsync(
|
||||
string fromCurrency,
|
||||
string toCurrency,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var normalizedFrom = NormalizeCurrency(fromCurrency);
|
||||
var normalizedTo = NormalizeCurrency(toCurrency);
|
||||
|
||||
if (normalizedFrom == normalizedTo)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
var cacheKey = BuildCacheKey(normalizedFrom, normalizedTo);
|
||||
if (TryGetCachedRate(cacheKey, out var cachedRate))
|
||||
{
|
||||
return cachedRate;
|
||||
}
|
||||
|
||||
var gate = Locks.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
|
||||
await gate.WaitAsync(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
if (TryGetCachedRate(cacheKey, out cachedRate))
|
||||
{
|
||||
return cachedRate;
|
||||
}
|
||||
|
||||
var requestUri = $"latest?from={Uri.EscapeDataString(normalizedFrom)}&to={Uri.EscapeDataString(normalizedTo)}";
|
||||
using var response = await HttpClient.GetAsync(requestUri, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
var payload = await JsonSerializer.DeserializeAsync<FrankfurterLatestResponse>(responseStream, JsonOptions, cancellationToken);
|
||||
|
||||
if (payload?.Rates == null || !payload.Rates.TryGetValue(normalizedTo, out var rate))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Cache[cacheKey] = new CacheEntry(
|
||||
rate,
|
||||
DateTimeOffset.UtcNow.Add(CacheDuration),
|
||||
payload.Date);
|
||||
|
||||
return rate;
|
||||
}
|
||||
finally
|
||||
{
|
||||
gate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<double?> ConvertAsync(
|
||||
double amount,
|
||||
string fromCurrency,
|
||||
string toCurrency,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var rate = await GetRateAsync(fromCurrency, toCurrency, cancellationToken);
|
||||
return rate.HasValue ? amount * rate.Value : null;
|
||||
}
|
||||
|
||||
public static void ClearCache()
|
||||
{
|
||||
Cache.Clear();
|
||||
}
|
||||
|
||||
public static bool TryGetCachedRate(
|
||||
string fromCurrency,
|
||||
string toCurrency,
|
||||
out double rate)
|
||||
{
|
||||
var normalizedFrom = NormalizeCurrency(fromCurrency);
|
||||
var normalizedTo = NormalizeCurrency(toCurrency);
|
||||
var cacheKey = BuildCacheKey(normalizedFrom, normalizedTo);
|
||||
return TryGetCachedRate(cacheKey, out rate);
|
||||
}
|
||||
|
||||
private static bool TryGetCachedRate(string cacheKey, out double rate)
|
||||
{
|
||||
if (Cache.TryGetValue(cacheKey, out var entry) && entry.ExpiresAt > DateTimeOffset.UtcNow)
|
||||
{
|
||||
rate = entry.Rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
Cache.TryRemove(cacheKey, out _);
|
||||
rate = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string NormalizeCurrency(string currency)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(currency))
|
||||
{
|
||||
throw new ArgumentException("Currency code cannot be empty.", nameof(currency));
|
||||
}
|
||||
|
||||
return currency.Trim().ToUpperInvariant();
|
||||
}
|
||||
|
||||
private static string BuildCacheKey(string fromCurrency, string toCurrency)
|
||||
{
|
||||
return string.Create(CultureInfo.InvariantCulture, $"{fromCurrency}:{toCurrency}");
|
||||
}
|
||||
|
||||
private sealed record CacheEntry(double Rate, DateTimeOffset ExpiresAt, string? Date);
|
||||
|
||||
private sealed class FrankfurterLatestResponse
|
||||
{
|
||||
[JsonPropertyName("amount")]
|
||||
public double Amount { get; set; }
|
||||
|
||||
[JsonPropertyName("base")]
|
||||
public string Base { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("date")]
|
||||
public string? Date { get; set; }
|
||||
|
||||
[JsonPropertyName("rates")]
|
||||
public Dictionary<string, double> Rates { get; set; } = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class Account
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public long? IncreaseId { get; set; }
|
||||
|
||||
public long? TypeId { get; set; }
|
||||
|
||||
public string? Bank { get; set; }
|
||||
|
||||
public virtual Increase? Increase { get; set; }
|
||||
|
||||
public virtual ICollection<Patrimony> Patrimonies { get; set; } = new List<Patrimony>();
|
||||
|
||||
public virtual ICollection<Statement> Statements { get; set; } = new List<Statement>();
|
||||
|
||||
public virtual ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||
|
||||
public virtual AccountType? Type { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class AccountType
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public virtual ICollection<Account> Accounts { get; set; } = new List<Account>();
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class FinancesContext : DbContext
|
||||
{
|
||||
public FinancesContext()
|
||||
{
|
||||
}
|
||||
|
||||
public FinancesContext(DbContextOptions<FinancesContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual DbSet<Account> Accounts { get; set; }
|
||||
|
||||
public virtual DbSet<AccountType> AccountTypes { get; set; }
|
||||
|
||||
public virtual DbSet<Increase> Increases { get; set; }
|
||||
|
||||
public virtual DbSet<Patrimony> Patrimonies { get; set; }
|
||||
|
||||
public virtual DbSet<Statement> Statements { get; set; }
|
||||
|
||||
public virtual DbSet<Stock> Stocks { get; set; }
|
||||
|
||||
public virtual DbSet<TimeValue> TimeValues { get; set; }
|
||||
|
||||
public virtual DbSet<Transaction> Transactions { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
|
||||
=> optionsBuilder.UseSqlServer("Server=localhost;Database=Finances;Trusted_Connection=True;TrustServerCertificate=True");
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Account>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Bank).HasMaxLength(50);
|
||||
entity.Property(e => e.Title).HasMaxLength(50);
|
||||
|
||||
entity.HasOne(d => d.Increase).WithMany(p => p.Accounts)
|
||||
.HasForeignKey(d => d.IncreaseId)
|
||||
.HasConstraintName("FK_Accounts_Increases");
|
||||
|
||||
entity.HasOne(d => d.Type).WithMany(p => p.Accounts)
|
||||
.HasForeignKey(d => d.TypeId)
|
||||
.HasConstraintName("FK_Accounts_AccountTypes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<AccountType>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Title).HasMaxLength(50);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Increase>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Title).HasMaxLength(50);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Patrimony>(entity =>
|
||||
{
|
||||
entity.ToTable("Patrimony");
|
||||
|
||||
entity.Property(e => e.Title).HasMaxLength(50);
|
||||
|
||||
entity.HasOne(d => d.Account).WithMany(p => p.Patrimonies)
|
||||
.HasForeignKey(d => d.AccountId)
|
||||
.HasConstraintName("FK_Patrimony_Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Statement>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Date).HasColumnType("datetime");
|
||||
|
||||
entity.HasOne(d => d.Account).WithMany(p => p.Statements)
|
||||
.HasForeignKey(d => d.AccountId)
|
||||
.HasConstraintName("FK_Statements_Accounts");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Stock>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Isin)
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("ISIN");
|
||||
entity.Property(e => e.Ticker).HasMaxLength(50);
|
||||
entity.Property(e => e.Title).HasMaxLength(150);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<TimeValue>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Date).HasColumnType("datetime");
|
||||
|
||||
entity.HasOne(d => d.Stocks).WithMany(p => p.TimeValues)
|
||||
.HasForeignKey(d => d.StocksId)
|
||||
.HasConstraintName("FK_TimeValues_Stocks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Transaction>(entity =>
|
||||
{
|
||||
entity.Property(e => e.Date).HasColumnType("datetime");
|
||||
|
||||
entity.HasOne(d => d.Account).WithMany(p => p.Transactions)
|
||||
.HasForeignKey(d => d.AccountId)
|
||||
.HasConstraintName("FK_Transactions_Accounts");
|
||||
|
||||
entity.HasOne(d => d.Stocks).WithMany(p => p.Transactions)
|
||||
.HasForeignKey(d => d.StocksId)
|
||||
.HasConstraintName("FK_Transactions_Stocks");
|
||||
});
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
}
|
||||
|
||||
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class Increase
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public double? Value { get; set; }
|
||||
|
||||
public long? TypeId { get; set; }
|
||||
|
||||
public virtual ICollection<Account> Accounts { get; set; } = new List<Account>();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class Patrimony
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public DateOnly? BuyingDate { get; set; }
|
||||
|
||||
public double? Price { get; set; }
|
||||
|
||||
public double? Quantity { get; set; }
|
||||
|
||||
public long? AccountId { get; set; }
|
||||
|
||||
public virtual Account? Account { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class Statement
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public long? AccountId { get; set; }
|
||||
|
||||
public double Value { get; set; }
|
||||
|
||||
public virtual Account? Account { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class Stock
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Isin { get; set; }
|
||||
|
||||
public string? Ticker { get; set; }
|
||||
|
||||
public double? Cost { get; set; }
|
||||
|
||||
public long? TypeId { get; set; }
|
||||
|
||||
public virtual ICollection<TimeValue> TimeValues { get; set; } = new List<TimeValue>();
|
||||
|
||||
public virtual ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class TimeValue
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public DateTime? Date { get; set; }
|
||||
|
||||
public double? Value { get; set; }
|
||||
|
||||
public long? StocksId { get; set; }
|
||||
|
||||
public virtual Stock? Stocks { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
public partial class Transaction
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public long? TypeId { get; set; }
|
||||
|
||||
public long? AccountId { get; set; }
|
||||
|
||||
public long? StocksId { get; set; }
|
||||
|
||||
public double? Quantity { get; set; }
|
||||
|
||||
public double? Value { get; set; }
|
||||
|
||||
public DateTime? Date { get; set; }
|
||||
|
||||
public virtual Account? Account { get; set; }
|
||||
|
||||
public virtual Stock? Stocks { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using LINQtoCSV;
|
||||
|
||||
namespace StockFin.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Représente une ligne du fichier "Exportation de transactions.csv" (format Trade Republic).
|
||||
/// Le séparateur est la virgule, les décimales utilisent le point, les valeurs sont entre guillemets.
|
||||
/// </summary>
|
||||
public class TransactionCsvRow
|
||||
{
|
||||
/// <summary>Horodatage UTC de la transaction (ex : "2024-09-13T11:15:55.989679Z").</summary>
|
||||
[CsvColumn(Name = "datetime", FieldIndex = 1)]
|
||||
public DateTime? DateTimeUtc { get; set; }
|
||||
|
||||
/// <summary>Date de la transaction (ex : "2024-09-13").</summary>
|
||||
[CsvColumn(Name = "date", FieldIndex = 2)]
|
||||
public DateTime? Date { get; set; }
|
||||
|
||||
/// <summary>Type de compte (ex : "DEFAULT").</summary>
|
||||
[CsvColumn(Name = "account_type", FieldIndex = 3)]
|
||||
public string? AccountType { get; set; }
|
||||
|
||||
/// <summary>Catégorie de la transaction (ex : "CASH", "ORDER").</summary>
|
||||
[CsvColumn(Name = "category", FieldIndex = 4)]
|
||||
public string? Category { get; set; }
|
||||
|
||||
/// <summary>Type d'opération (ex : "CUSTOMER_INBOUND", "BUY", "SELL", "DIVIDEND").</summary>
|
||||
[CsvColumn(Name = "type", FieldIndex = 5)]
|
||||
public string? Type { get; set; }
|
||||
|
||||
/// <summary>Classe d'actif (ex : "EQUITY", "ETF" — vide pour les opérations en cash).</summary>
|
||||
[CsvColumn(Name = "asset_class", FieldIndex = 6)]
|
||||
public string? AssetClass { get; set; }
|
||||
|
||||
/// <summary>Nom du titre ou de la contrepartie.</summary>
|
||||
[CsvColumn(Name = "name", FieldIndex = 7)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>Symbole boursier / ticker (vide pour les opérations en cash).</summary>
|
||||
[CsvColumn(Name = "symbol", FieldIndex = 8)]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
/// <summary>Nombre de parts (vide pour les opérations en cash).</summary>
|
||||
[CsvColumn(Name = "shares", FieldIndex = 9)]
|
||||
public double? Shares { get; set; }
|
||||
|
||||
/// <summary>Prix unitaire (vide pour les opérations en cash).</summary>
|
||||
[CsvColumn(Name = "price", FieldIndex = 10)]
|
||||
public double? Price { get; set; }
|
||||
|
||||
/// <summary>Montant de la transaction dans la devise du compte.</summary>
|
||||
[CsvColumn(Name = "amount", FieldIndex = 11)]
|
||||
public decimal? Amount { get; set; }
|
||||
|
||||
/// <summary>Frais de courtage.</summary>
|
||||
[CsvColumn(Name = "fee", FieldIndex = 12)]
|
||||
public decimal? Fee { get; set; }
|
||||
|
||||
/// <summary>Taxe (ex : TOB en Belgique).</summary>
|
||||
[CsvColumn(Name = "tax", FieldIndex = 13)]
|
||||
public decimal? Tax { get; set; }
|
||||
|
||||
/// <summary>Devise du compte (ex : "EUR").</summary>
|
||||
[CsvColumn(Name = "currency", FieldIndex = 14)]
|
||||
public string? Currency { get; set; }
|
||||
|
||||
/// <summary>Montant dans la devise d'origine (pour les transactions en devise étrangère).</summary>
|
||||
[CsvColumn(Name = "original_amount", FieldIndex = 15)]
|
||||
public decimal? OriginalAmount { get; set; }
|
||||
|
||||
/// <summary>Devise d'origine (ex : "USD").</summary>
|
||||
[CsvColumn(Name = "original_currency", FieldIndex = 16)]
|
||||
public string? OriginalCurrency { get; set; }
|
||||
|
||||
/// <summary>Taux de change appliqué.</summary>
|
||||
[CsvColumn(Name = "fx_rate", FieldIndex = 17)]
|
||||
public decimal? FxRate { get; set; }
|
||||
|
||||
/// <summary>Description libre de la transaction.</summary>
|
||||
[CsvColumn(Name = "description", FieldIndex = 18)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>Identifiant unique de la transaction (GUID).</summary>
|
||||
[CsvColumn(Name = "transaction_id", FieldIndex = 19)]
|
||||
public string? TransactionId { get; set; }
|
||||
|
||||
/// <summary>Nom de la contrepartie (virement entrant/sortant).</summary>
|
||||
[CsvColumn(Name = "counterparty_name", FieldIndex = 20)]
|
||||
public string? CounterpartyName { get; set; }
|
||||
|
||||
/// <summary>IBAN de la contrepartie.</summary>
|
||||
[CsvColumn(Name = "counterparty_iban", FieldIndex = 21)]
|
||||
public string? CounterpartyIban { get; set; }
|
||||
|
||||
/// <summary>Référence du paiement (communication).</summary>
|
||||
[CsvColumn(Name = "payment_reference", FieldIndex = 22)]
|
||||
public string? PaymentReference { get; set; }
|
||||
|
||||
/// <summary>Code MCC (Merchant Category Code) — uniquement pour les paiements par carte.</summary>
|
||||
[CsvColumn(Name = "mcc_code", FieldIndex = 23)]
|
||||
public string? MccCode { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using StockFin;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||
{
|
||||
var culture = CultureInfo.InvariantCulture;
|
||||
options.DefaultRequestCulture = new RequestCulture(culture);
|
||||
options.SupportedCultures = [culture];
|
||||
options.SupportedUICultures = [culture];
|
||||
});
|
||||
builder.Services.AddHttpClient<AlphaVantageService>();
|
||||
builder.Services.Configure<FinnhubOptions>(
|
||||
builder.Configuration.GetSection(FinnhubOptions.SectionName));
|
||||
|
||||
builder.Services.AddHttpClient<IFinnhubClient, FinnhubClient>(client =>
|
||||
{
|
||||
client.BaseAddress = new Uri("https://finnhub.io/api/v1/");
|
||||
});
|
||||
builder.Services.Configure<TwelveDataOptions>(
|
||||
builder.Configuration.GetSection("TwelveData"));
|
||||
builder.Services
|
||||
.AddHttpClient<ITwelveDataClient, TwelveDataClient>(client =>
|
||||
{
|
||||
client.BaseAddress =
|
||||
new Uri("https://api.twelvedata.com/");
|
||||
});
|
||||
//.AddStandardResilienceHandler();
|
||||
builder.Services.AddSingleton<StocksCache>();
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseRequestLocalization();
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapStaticAssets();
|
||||
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}")
|
||||
.WithStaticAssets();
|
||||
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5147"
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7111;http://localhost:5147"
|
||||
},
|
||||
"Container (Dockerfile)": {
|
||||
"commandName": "Docker",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_HTTPS_PORTS": "8081",
|
||||
"ASPNETCORE_HTTP_PORTS": "8080"
|
||||
},
|
||||
"publishAllPorts": true,
|
||||
"useSSL": true
|
||||
}
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json"
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>b6e7beca-e168-4906-b5ff-f25d3651d15f</UserSecretsId>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LINQtoCSVCore" Version="1.6.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
|
||||
<PackageReference Include="NuGet.Packaging" Version="7.6.0" />
|
||||
<PackageReference Include="NuGet.Protocol" Version="7.6.0" />
|
||||
<PackageReference Include="YahooFinanceApi" Version="2.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Model1.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Model1.tt</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Model1.tt">
|
||||
<Generator>TextTemplatingFileGenerator</Generator>
|
||||
<LastGenOutput>Model1.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\Logos\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="wwwroot\Logos\ING.png" />
|
||||
<None Include="wwwroot\Logos\KEYTRADE.png" />
|
||||
<None Include="wwwroot\Logos\N26.png" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,78 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StockFin.Models;
|
||||
using YahooFinanceApi;
|
||||
|
||||
namespace StockFin
|
||||
{
|
||||
public class StocksCache
|
||||
{
|
||||
public Dictionary<string, double?> actuals = new Dictionary<string, double?>();
|
||||
public Dictionary<string, double?> months = new Dictionary<string, double?>();
|
||||
FinancesContext context;
|
||||
|
||||
public StocksCache() {
|
||||
context = new FinancesContext();
|
||||
var stocks = context.Stocks.Include(s => s.TimeValues)
|
||||
.OrderBy(s => s.Title)
|
||||
.ToList();
|
||||
|
||||
foreach (var stock in stocks)
|
||||
{
|
||||
//var quote2 = await _finnhub.GetQuoteAsync(stock.Ticker);
|
||||
//var quote2 =
|
||||
// await _twelveData.GetPriceAsync("AAPL");
|
||||
if (!string.IsNullOrWhiteSpace(stock.Ticker))
|
||||
{
|
||||
var securities = Yahoo.Symbols(stock.Ticker).Fields(Field.Symbol, Field.RegularMarketPrice, Field.FiftyTwoWeekHigh, Field.RegularMarketPreviousClose,Field.Currency).QueryAsync().GetAwaiter().GetResult();
|
||||
if (securities.Count > 0)
|
||||
{
|
||||
var aapl = securities[stock.Ticker];
|
||||
var price = aapl[Field.RegularMarketPrice];
|
||||
if (price != null)
|
||||
{
|
||||
if (aapl[Field.Currency] == "EUR")
|
||||
{
|
||||
actuals[stock.Ticker] = price;
|
||||
}
|
||||
else
|
||||
{
|
||||
var rate = FrankfurterCurrencyConverter.GetRateAsync(aapl[Field.Currency], "EUR").GetAwaiter().GetResult();
|
||||
if (rate!=null)
|
||||
{
|
||||
actuals[stock.Ticker] = price * rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (stock.TimeValues.Count > 0)
|
||||
{
|
||||
actuals[stock.Ticker] = stock.TimeValues
|
||||
.OrderBy(s => s.Date)
|
||||
.LastOrDefault()?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// var candles = Yahoo.GetHistoricalAsync(
|
||||
//"SPY",
|
||||
//DateTime.Today.AddDays(-30),
|
||||
//DateTime.Today,
|
||||
//Period.Daily).GetAwaiter().GetResult() ;
|
||||
// var history = Yahoo.GetHistoricalAsync(stock.Ticker, new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1), new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).AddDays(2), Period.Daily).GetAwaiter().GetResult();
|
||||
|
||||
// foreach (var candle in history)
|
||||
// {
|
||||
// months[stock.Ticker] = (double?)candle.Close;
|
||||
// Console.WriteLine($"DateTime: {candle.DateTime}, Open: {candle.Open}, High: {candle.High}, Low: {candle.Low}, Close: {candle.Close}, Volume: {candle.Volume}, AdjustedClose: {candle.AdjustedClose}");
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using LINQtoCSV;
|
||||
using StockFin.Models;
|
||||
|
||||
namespace StockFin;
|
||||
|
||||
/// <summary>
|
||||
/// Service de lecture et de traitement du fichier "Exportation de transactions.csv"
|
||||
/// au format Trade Republic, à l'aide de LINQtoCSV.
|
||||
/// </summary>
|
||||
public class TransactionCsvImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Format Trade Republic : séparateur virgule, décimales avec point, en-têtes en ligne 1.
|
||||
/// </summary>
|
||||
private static readonly CsvFileDescription FileDescription = new()
|
||||
{
|
||||
SeparatorChar = ',', // Trade Republic utilise la virgule
|
||||
FirstLineHasColumnNames = true,
|
||||
FileCultureName = "en-US", // Décimales avec point (ex : "1800.000000")
|
||||
EnforceCsvColumnAttribute = true, // N'utilise que les propriétés annotées [CsvColumn]
|
||||
IgnoreUnknownColumns = true, // Ignore les colonnes CSV sans propriété mappée
|
||||
};
|
||||
|
||||
private readonly CsvContext _csv = new();
|
||||
|
||||
/// <summary>
|
||||
/// Lit le fichier CSV et retourne les lignes mappées vers <see cref="TransactionCsvRow"/>.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Chemin absolu ou relatif vers le fichier CSV.</param>
|
||||
/// <returns>Liste des lignes du fichier.</returns>
|
||||
/// <exception cref="FileNotFoundException">Si le fichier est introuvable.</exception>
|
||||
/// <exception cref="AggregatedException">Si des erreurs de parsing sont rencontrées.</exception>
|
||||
public IList<TransactionCsvRow> Read(string filePath)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException("Le fichier CSV est introuvable.", filePath);
|
||||
|
||||
return _csv.Read<TransactionCsvRow>(filePath, FileDescription).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convertit les lignes CSV en entités <see cref="Transaction"/> prêtes à être persistées.
|
||||
/// Adaptez le mapping selon les propriétés réelles de votre modèle Transaction.
|
||||
/// </summary>
|
||||
/// <param name="rows">Lignes lues depuis le CSV.</param>
|
||||
public IEnumerable<Transaction> ToTransactions(IList<TransactionCsvRow> rows)
|
||||
{
|
||||
return rows.Select(MapToTransaction);
|
||||
}
|
||||
|
||||
private static Transaction MapToTransaction(TransactionCsvRow row)
|
||||
{
|
||||
// TODO : adaptez ce mapping aux propriétés réelles de votre modèle Transaction.
|
||||
return new Transaction
|
||||
{
|
||||
// Date = row.Date ?? row.DateTimeUtc ?? DateTime.UtcNow,
|
||||
// Quantity = (int?)row.Shares,
|
||||
// Price = (double?)row.Price,
|
||||
// Fees = (double?)row.Fee,
|
||||
// StockId = ... // à résoudre via row.Symbol ou row.Name
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StockFin
|
||||
{
|
||||
public sealed class TwelveDataClient : ITwelveDataClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly TwelveDataOptions _options;
|
||||
|
||||
public TwelveDataClient(
|
||||
HttpClient httpClient,
|
||||
IOptions<TwelveDataOptions> options)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public async Task<decimal?> GetPriceAsync(
|
||||
string symbol,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var result = await GetAsync<TwelvePrice>(
|
||||
$"price?symbol={Uri.EscapeDataString(symbol)}",
|
||||
ct);
|
||||
|
||||
if (result?.Price == null)
|
||||
return null;
|
||||
|
||||
return decimal.Parse(
|
||||
result.Price,
|
||||
System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public Task<TwelveQuote?> GetQuoteAsync(
|
||||
string symbol,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return GetAsync<TwelveQuote>(
|
||||
$"quote?symbol={Uri.EscapeDataString(symbol)}",
|
||||
ct);
|
||||
}
|
||||
|
||||
public Task<TwelveTimeSeries?> GetTimeSeriesAsync(
|
||||
string symbol,
|
||||
string interval = "1day",
|
||||
int outputSize = 5000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
return GetAsync<TwelveTimeSeries>(
|
||||
$"time_series?symbol={Uri.EscapeDataString(symbol)}" +
|
||||
$"&interval={interval}" +
|
||||
$"&outputsize={outputSize}",
|
||||
ct);
|
||||
}
|
||||
|
||||
public async Task<List<TwelveSearchResult>> SearchAsync(
|
||||
string query,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var response = await GetAsync<SearchResponse>(
|
||||
$"symbol_search?symbol={Uri.EscapeDataString(query)}",
|
||||
ct);
|
||||
|
||||
return response?.Data ?? [];
|
||||
}
|
||||
|
||||
private async Task<T?> GetAsync<T>(
|
||||
string endpoint,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var separator = endpoint.Contains('?') ? '&' : '?';
|
||||
|
||||
var url =
|
||||
$"{endpoint}{separator}apikey={_options.ApiKey}";
|
||||
|
||||
var response = await _httpClient.GetAsync(url, ct);
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync(ct);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new TwelveDataException(content);
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<T>(
|
||||
cancellationToken: ct);
|
||||
}
|
||||
|
||||
private sealed class SearchResponse
|
||||
{
|
||||
public List<TwelveSearchResult> Data { get; set; } = [];
|
||||
}
|
||||
}
|
||||
public sealed class TwelveDataException : Exception
|
||||
{
|
||||
public TwelveDataException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
public sealed class TwelveTimeSeries
|
||||
{
|
||||
[JsonPropertyName("meta")]
|
||||
public TwelveMeta? Meta { get; set; }
|
||||
|
||||
[JsonPropertyName("values")]
|
||||
public List<TwelveBar> Values { get; set; } = [];
|
||||
}
|
||||
|
||||
public sealed class TwelveMeta
|
||||
{
|
||||
[JsonPropertyName("symbol")]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("currency")]
|
||||
public string? Currency { get; set; }
|
||||
|
||||
[JsonPropertyName("exchange")]
|
||||
public string? Exchange { get; set; }
|
||||
}
|
||||
|
||||
public sealed class TwelveBar
|
||||
{
|
||||
[JsonPropertyName("datetime")]
|
||||
public string? DateTime { get; set; }
|
||||
|
||||
[JsonPropertyName("open")]
|
||||
public string? Open { get; set; }
|
||||
|
||||
[JsonPropertyName("high")]
|
||||
public string? High { get; set; }
|
||||
|
||||
[JsonPropertyName("low")]
|
||||
public string? Low { get; set; }
|
||||
|
||||
[JsonPropertyName("close")]
|
||||
public string? Close { get; set; }
|
||||
|
||||
[JsonPropertyName("volume")]
|
||||
public string? Volume { get; set; }
|
||||
}
|
||||
public sealed class TwelveSearchResult
|
||||
{
|
||||
[JsonPropertyName("symbol")]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("instrument_name")]
|
||||
public string? InstrumentName { get; set; }
|
||||
|
||||
[JsonPropertyName("exchange")]
|
||||
public string? Exchange { get; set; }
|
||||
|
||||
[JsonPropertyName("country")]
|
||||
public string? Country { get; set; }
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string? Type { get; set; }
|
||||
}
|
||||
public sealed class TwelveQuote
|
||||
{
|
||||
[JsonPropertyName("symbol")]
|
||||
public string? Symbol { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string? Name { get; set; }
|
||||
|
||||
[JsonPropertyName("exchange")]
|
||||
public string? Exchange { get; set; }
|
||||
|
||||
[JsonPropertyName("currency")]
|
||||
public string? Currency { get; set; }
|
||||
|
||||
[JsonPropertyName("close")]
|
||||
public string? Close { get; set; }
|
||||
|
||||
[JsonPropertyName("previous_close")]
|
||||
public string? PreviousClose { get; set; }
|
||||
|
||||
[JsonPropertyName("change")]
|
||||
public string? Change { get; set; }
|
||||
|
||||
[JsonPropertyName("percent_change")]
|
||||
public string? PercentChange { get; set; }
|
||||
|
||||
[JsonPropertyName("is_market_open")]
|
||||
public bool IsMarketOpen { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public sealed class TwelvePrice
|
||||
{
|
||||
[JsonPropertyName("price")]
|
||||
public string? Price { get; set; }
|
||||
}
|
||||
public interface ITwelveDataClient
|
||||
{
|
||||
Task<decimal?> GetPriceAsync(
|
||||
string symbol,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<TwelveQuote?> GetQuoteAsync(
|
||||
string symbol,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<TwelveTimeSeries?> GetTimeSeriesAsync(
|
||||
string symbol,
|
||||
string interval = "1day",
|
||||
int outputSize = 5000,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task<List<TwelveSearchResult>> SearchAsync(
|
||||
string query,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
public sealed class TwelveDataOptions
|
||||
{
|
||||
public const string SectionName = "TwelveData";
|
||||
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using StockFin.Models;
|
||||
|
||||
namespace StockFin.ViewModels
|
||||
{
|
||||
public class AccountDetailsViewModel
|
||||
{
|
||||
public Account Account { get; init; } = null!;
|
||||
|
||||
public IReadOnlyList<Statement> Statements { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<Transaction> Transactions { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<Patrimony> Patrimonies { get; init; } = [];
|
||||
|
||||
public bool HasStatements => Statements.Count > 0;
|
||||
|
||||
public bool HasTransactions => Transactions.Count > 0;
|
||||
|
||||
public bool HasPatrimonies => Patrimonies.Count > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StockFin.Models;
|
||||
|
||||
namespace StockFin.ViewModels
|
||||
{
|
||||
public class AccountState
|
||||
{
|
||||
public static AccountState FromAccount(Int64 accountid)
|
||||
{
|
||||
using (FinancesContext db = new FinancesContext())
|
||||
{
|
||||
Account account = db.Accounts.Include(a => a.Statements).SingleOrDefault(a => a.Id == accountid);
|
||||
if (account == null)
|
||||
{
|
||||
return new AccountState { Id = 0, Bank = "", Title = "", ActualValue = 0, TypeId = null };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Statement laststate = account.Statements.OrderByDescending(st => st.Date).First();
|
||||
return (new AccountState { Id = account.Id, Bank = account.Bank, Title = account.Title, ActualValue = laststate.Value, TypeId = account.TypeId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (new AccountState { Id = account.Id, Bank = account.Bank, Title = account.Title, ActualValue = 0, TypeId = account.TypeId });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public static AccountState FromPatrimony(Int64 accountid)
|
||||
{
|
||||
using (FinancesContext db = new FinancesContext())
|
||||
{
|
||||
Account account = db.Accounts.Include(a => a.Patrimonies).Include(a=>a.Increase).SingleOrDefault(a => a.Id == accountid);
|
||||
if (account == null)
|
||||
{
|
||||
return new AccountState { Id = 0, Bank = "", Title = "", StartValue = 0, ActualValue = 0, TypeId = null };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
double actualvalue = 0;
|
||||
double startvalue = 0;
|
||||
var patrimonies = account.Patrimonies.OrderByDescending(st => st.BuyingDate);
|
||||
foreach (Patrimony patrimony in patrimonies)
|
||||
{
|
||||
var patrimonyvalue = (patrimony.Price ?? 0) * (patrimony.Quantity ?? 0);
|
||||
DateOnly buyingDate = patrimony.BuyingDate ?? DateOnly.FromDateTime(DateTime.Now);
|
||||
DateOnly today = DateOnly.FromDateTime(DateTime.Now);
|
||||
double patrimonydate = (today.DayNumber - buyingDate.DayNumber) / 365.25;
|
||||
startvalue = startvalue + patrimonyvalue;
|
||||
actualvalue = actualvalue + (patrimonyvalue * Math.Pow(1 + (account.Increase?.Value/100 ?? 0), patrimonydate));
|
||||
}
|
||||
return (new AccountState { Id = account.Id, Bank = account.Bank, Title = account.Title,StartValue=startvalue, ActualValue = actualvalue, TypeId = account.TypeId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (new AccountState { Id = account.Id, Bank = account.Bank, Title = account.Title,StartValue = 0, ActualValue = 0, TypeId = account.TypeId });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public static async Task<AccountState> FromStocks(Int64 accountid, StocksCache Cache)
|
||||
{
|
||||
using (FinancesContext db = new FinancesContext())
|
||||
{
|
||||
|
||||
Account account = db.Accounts.Include(a => a.Transactions).ThenInclude(a=>a.Stocks).SingleOrDefault(a => a.Id == accountid);
|
||||
if (account == null)
|
||||
{
|
||||
return new AccountState { Id = 0, Bank = "", Title = "", ActualValue = 0, TypeId = null };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
double? buyvalue = 0;
|
||||
double? actualvalue = 0;
|
||||
List<Stock> stocks = account.Transactions
|
||||
.Where(t => t.Stocks != null)
|
||||
.Select(t => t.Stocks!)
|
||||
.GroupBy(s => s.Id)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
|
||||
foreach (Stock stock in stocks)
|
||||
{
|
||||
var quote = Cache.actuals.ContainsKey(stock.Ticker) ? Cache.actuals[stock.Ticker] : null;
|
||||
double? subactualvalue = 0;
|
||||
foreach (Transaction transaction in account.Transactions.Where(t => t.StocksId == stock.Id))
|
||||
{
|
||||
if (transaction.TypeId == 1)
|
||||
{
|
||||
buyvalue += transaction.Value*transaction.Quantity;
|
||||
subactualvalue += transaction.Quantity;
|
||||
}
|
||||
else if (transaction.TypeId == 2)
|
||||
{
|
||||
buyvalue -= transaction.Value*transaction.Quantity;
|
||||
subactualvalue -= transaction.Quantity;
|
||||
}
|
||||
}
|
||||
if (quote != null)
|
||||
{
|
||||
subactualvalue *= quote;
|
||||
}
|
||||
else
|
||||
{ actualvalue = 0; }
|
||||
actualvalue += subactualvalue;
|
||||
}
|
||||
return (new AccountState { Id = account.Id, Bank = account.Bank, Title = account.Title,StartValue=buyvalue??0, ActualValue = actualvalue ?? 0, TypeId = account.TypeId });
|
||||
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
return (new AccountState { Id = account.Id, Bank = account.Bank, Title = account.Title,StartValue=0, ActualValue = 0, TypeId = account.TypeId });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public long Id { get; set; }
|
||||
public string Bank { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
||||
public double StartValue { get; set; }
|
||||
public double LastMonthValue { get; set; }
|
||||
public double ActualValue { get; set; }
|
||||
public Int64? TypeId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace StockFin.ViewModels;
|
||||
|
||||
public class CreateTodayViewModel
|
||||
{
|
||||
public DateTime Date { get; set; } = DateTime.Today;
|
||||
public List<AccountEntry> Accounts { get; set; } = new();
|
||||
|
||||
public class AccountEntry
|
||||
{
|
||||
public string Bank { get; set; } = "";
|
||||
public long AccountId { get; set; }
|
||||
public string AccountName { get; set; } = "";
|
||||
public string? AccountTypeName { get; set; }
|
||||
public double? Amount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace StockFin.ViewModels
|
||||
{
|
||||
public class HomeDashboardViewModel
|
||||
{
|
||||
public IReadOnlyList<HomeAccountTypeSummaryViewModel> TypeSummaries { get; init; } = [];
|
||||
|
||||
public double TotalActualValue { get; init; }
|
||||
|
||||
public double TotalStartValue { get; init; }
|
||||
|
||||
public int AccountCount { get; init; }
|
||||
|
||||
public int ActiveTypeCount => TypeSummaries.Count;
|
||||
}
|
||||
|
||||
public class HomeAccountTypeSummaryViewModel
|
||||
{
|
||||
public long TypeId { get; init; }
|
||||
|
||||
public string TypeTitle { get; init; } = string.Empty;
|
||||
|
||||
public string Color { get; init; } = string.Empty;
|
||||
|
||||
public double ActualValue { get; init; }
|
||||
|
||||
public double StartValue { get; init; }
|
||||
|
||||
public double Percentage { get; init; }
|
||||
|
||||
public double StartPercentage { get; init; }
|
||||
|
||||
public IReadOnlyList<AccountState> Accounts { get; init; } = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
@model StockFin.Models.Account
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Nouveau compte";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Création</span>
|
||||
<h1 class="sf-page-title">Nouveau compte</h1>
|
||||
<p>Ajoutez un compte avec sa banque, son type et son hypothèse de croissance pour structurer votre patrimoine.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Fiche compte</h2>
|
||||
<p>Les informations renseignées seront utilisées dans les relevés, transactions et vues de synthèse.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Create" method="post" class="sf-form-shell">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Title" class="form-label">Nom du compte</label>
|
||||
<input asp-for="Title" class="form-control" />
|
||||
<span asp-validation-for="Title" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Bank" class="form-label">Banque</label>
|
||||
<input asp-for="Bank" class="form-control" />
|
||||
<span asp-validation-for="Bank" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="TypeId" class="form-label">Type de compte</label>
|
||||
<select asp-for="TypeId" asp-items="@(ViewData["TypeId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select">
|
||||
<option value="">-- Sélectionner un type --</option>
|
||||
</select>
|
||||
<span asp-validation-for="TypeId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="IncreaseId" class="form-label">Profil de croissance</label>
|
||||
<select asp-for="IncreaseId" asp-items="@(ViewData["IncreaseId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select">
|
||||
<option value="">-- Sélectionner un profil --</option>
|
||||
</select>
|
||||
<span asp-validation-for="IncreaseId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
@model StockFin.Models.Account
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Supprimer le compte";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-warning">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Suppression</span>
|
||||
<h1 class="sf-page-title">Supprimer le compte</h1>
|
||||
<p class="sf-delete-warning">Cette action retire le compte de l'application et peut être bloquée si des données liées existent encore.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-delete-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Vérification avant suppression</h2>
|
||||
<p>Confirmez l'identité et la configuration du compte avant de poursuivre.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="sf-definition-list">
|
||||
<dt>Nom du compte</dt>
|
||||
<dd>@Model.Title</dd>
|
||||
|
||||
<dt>Banque</dt>
|
||||
<dd>@(string.IsNullOrWhiteSpace(Model.Bank) ? "Non renseigné" : Model.Bank)</dd>
|
||||
|
||||
<dt>Type</dt>
|
||||
<dd>@(Model.Type?.Title ?? "—")</dd>
|
||||
|
||||
<dt>Profil de croissance</dt>
|
||||
<dd>@(Model.Increase?.Title ?? "—")</dd>
|
||||
</dl>
|
||||
|
||||
<form asp-action="Delete" class="sf-form-actions">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<button type="submit" class="sf-btn-danger">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,162 @@
|
||||
@using System.Globalization
|
||||
@model StockFin.ViewModels.AccountDetailsViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Détails du compte";
|
||||
var culture = new CultureInfo("fr-BE");
|
||||
var account = Model.Account;
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Compte</span>
|
||||
<h1 class="sf-page-title">@account.Title</h1>
|
||||
<p>@(string.IsNullOrWhiteSpace(account.Bank) ? "Banque non renseignée" : account.Bank) · @(account.Type?.Title ?? "Type non renseigné")</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Edit" asp-route-id="@account.Id" class="sf-btn-secondary">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
<span>Modifier le compte</span>
|
||||
</a>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-account-summary-grid">
|
||||
<article class="sf-summary-tile">
|
||||
<span class="sf-page-kicker">Banque</span>
|
||||
<strong>@(string.IsNullOrWhiteSpace(account.Bank) ? "Non renseignée" : account.Bank)</strong>
|
||||
</article>
|
||||
<article class="sf-summary-tile">
|
||||
<span class="sf-page-kicker">Type</span>
|
||||
<strong>@(account.Type?.Title ?? "—")</strong>
|
||||
</article>
|
||||
<article class="sf-summary-tile">
|
||||
<span class="sf-page-kicker">Croissance</span>
|
||||
<strong>@(account.Increase?.Title ?? "—")</strong>
|
||||
</article>
|
||||
<article class="sf-summary-tile">
|
||||
<span class="sf-page-kicker">Activité</span>
|
||||
<strong>@(Model.Statements.Count + Model.Transactions.Count + Model.Patrimonies.Count) entrée(s)</strong>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
@if (Model.HasStatements)
|
||||
{
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Statements</h2>
|
||||
<p>Historique des relevés associés à ce compte.</p>
|
||||
</div>
|
||||
<span class="sf-badge-soft">@Model.Statements.Count élément(s)</span>
|
||||
</div>
|
||||
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th class="text-end">Valeur</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var statement in Model.Statements)
|
||||
{
|
||||
<tr>
|
||||
<td>@statement.Date.ToString("dd/MM/yyyy")</td>
|
||||
<td class="text-end fw-semibold">@statement.Value.ToString("C", culture)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (Model.HasTransactions)
|
||||
{
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Transactions</h2>
|
||||
<p>Liste des opérations exécutées sur ce compte.</p>
|
||||
</div>
|
||||
<span class="sf-badge-soft">@Model.Transactions.Count élément(s)</span>
|
||||
</div>
|
||||
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Titre</th>
|
||||
<th class="text-end">Quantité</th>
|
||||
<th class="text-end">Valeur</th>
|
||||
<th class="text-end">Total Achat</th>
|
||||
<th class="text-end">Total Ajd</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var transaction in Model.Transactions)
|
||||
{
|
||||
<tr>
|
||||
<td>@(transaction.Date?.ToString("dd/MM/yyyy") ?? "—")</td>
|
||||
<td class="fw-semibold">@(transaction.Stocks?.Title ?? "—")</td>
|
||||
<td class="text-end">@(transaction.Quantity?.ToString("N2", culture) ?? "—")</td>
|
||||
<td class="text-end fw-semibold">@(transaction.Value?.ToString("C", culture) ?? "—")</td>
|
||||
<td class="text-end">@((transaction.Quantity*transaction.Value)?.ToString("N2", culture) ?? "—")</td>
|
||||
<td class="text-end">@(transaction.Stocks.Ticker != null && ((Dictionary<string, double?>)ViewData["actuals"]).TryGetValue(transaction.Stocks.Ticker, out var actual) ? (actual.Value * transaction.Quantity.Value).ToString("N2", culture) : "N/A")</td>
|
||||
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (Model.HasPatrimonies)
|
||||
{
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Patrimony</h2>
|
||||
<p>Actifs patrimoniaux actuellement rattachés à ce compte.</p>
|
||||
</div>
|
||||
<span class="sf-badge-soft">@Model.Patrimonies.Count élément(s)</span>
|
||||
</div>
|
||||
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Date d'achat</th>
|
||||
<th class="text-end">Quantité</th>
|
||||
<th class="text-end">Prix</th>
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var patrimony in Model.Patrimonies)
|
||||
{
|
||||
var total = (patrimony.Quantity ?? 0) * (patrimony.Price ?? 0);
|
||||
<tr>
|
||||
<td class="fw-semibold">@patrimony.Title</td>
|
||||
<td>@(patrimony.BuyingDate?.ToString("dd/MM/yyyy") ?? "—")</td>
|
||||
<td class="text-end">@(patrimony.Quantity?.ToString("N2", culture) ?? "—")</td>
|
||||
<td class="text-end">@(patrimony.Price?.ToString("C", culture) ?? "—")</td>
|
||||
<td class="text-end fw-semibold">@total.ToString("C", culture)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,84 @@
|
||||
@model StockFin.Models.Account
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Modifier le compte";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Mise à jour</span>
|
||||
<h1 class="sf-page-title">Modifier le compte</h1>
|
||||
<p>Adaptez les informations du compte pour garder une structure de données propre et exploitable.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Édition du compte</h2>
|
||||
<p>Les modifications seront visibles dans toutes les vues liées à ce compte.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Edit" method="post" class="sf-form-shell">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Title" class="form-label">Nom du compte</label>
|
||||
<input asp-for="Title" class="form-control" />
|
||||
<span asp-validation-for="Title" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Bank" class="form-label">Banque</label>
|
||||
<input asp-for="Bank" class="form-control" />
|
||||
<span asp-validation-for="Bank" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="TypeId" class="form-label">Type de compte</label>
|
||||
<select asp-for="TypeId" asp-items="@(ViewData["TypeId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select">
|
||||
<option value="">-- Sélectionner un type --</option>
|
||||
</select>
|
||||
<span asp-validation-for="TypeId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="IncreaseId" class="form-label">Profil de croissance</label>
|
||||
<select asp-for="IncreaseId" asp-items="@(ViewData["IncreaseId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select">
|
||||
<option value="">-- Sélectionner un profil --</option>
|
||||
</select>
|
||||
<span asp-validation-for="IncreaseId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
@model IEnumerable<StockFin.Models.Account>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Comptes";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Structure</span>
|
||||
<h1 class="sf-page-title">Comptes</h1>
|
||||
<p>Gérez tous vos comptes financiers, leur typologie et leur hypothèse de croissance dans une interface cohérente.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Create" class="sf-btn-secondary">
|
||||
<i class="bi bi-plus-circle"></i>
|
||||
<span>Nouveau compte</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Référentiel des comptes</h2>
|
||||
<p>Retrouvez la banque, le type et le profil de croissance associés à chaque compte.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Banque</th>
|
||||
<th>Compte</th>
|
||||
<th>Type</th>
|
||||
<th>Croissance</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<div class="sf-bank-cell">
|
||||
<span class="sf-bank-logo">
|
||||
@if (!string.IsNullOrWhiteSpace(item.Bank))
|
||||
{
|
||||
<img src="/Logos/@(item.Bank).png" alt="@item.Bank" />
|
||||
}
|
||||
</span>
|
||||
<span>@(string.IsNullOrWhiteSpace(item.Bank) ? "Non renseigné" : item.Bank)</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="fw-semibold">@item.Title</td>
|
||||
<td>@(item.Type?.Title ?? "—")</td>
|
||||
<td>@(item.Increase?.Title ?? "—")</td>
|
||||
<td>
|
||||
<div class="sf-table-actions">
|
||||
<a asp-action="Details" asp-route-id="@item.Id" class="sf-table-action is-view">
|
||||
<i class="bi bi-eye"></i>
|
||||
<span>Détails</span>
|
||||
</a>
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="sf-table-action is-edit">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
<span>Modifier</span>
|
||||
</a>
|
||||
<a asp-action="Delete" asp-route-id="@item.Id" class="sf-table-action is-delete">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-empty-card">
|
||||
<h3>Aucun compte enregistré</h3>
|
||||
<p>Créez un premier compte pour structurer vos données financières.</p>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,279 @@
|
||||
@using System.Globalization
|
||||
@using System.Text.Json
|
||||
@model StockFin.ViewModels.HomeDashboardViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Dashboard";
|
||||
var displayCulture = new CultureInfo("fr-BE");
|
||||
var gainLoss = Model.TotalActualValue - Model.TotalStartValue;
|
||||
var invariantCulture = CultureInfo.InvariantCulture;
|
||||
var activeSummaries = Model.TypeSummaries.Where(s => s.ActualValue > 0).ToList();
|
||||
var chartLabelsJson = JsonSerializer.Serialize(activeSummaries.Select(s => s.TypeTitle));
|
||||
var chartValuesJson = JsonSerializer.Serialize(activeSummaries.Select(s => Math.Round(s.ActualValue, 2)));
|
||||
var chartColorsJson = JsonSerializer.Serialize(activeSummaries.Select(s => s.Color));
|
||||
var totalAmountLabel = Model.TotalActualValue.ToString("C0", displayCulture);
|
||||
}
|
||||
|
||||
<div class="sf-dashboard">
|
||||
<section class="sf-hero">
|
||||
<div>
|
||||
<span class="sf-eyebrow">Vue d'ensemble patrimoniale</span>
|
||||
<h1>Répartition de vos finances</h1>
|
||||
<p>Visualisez instantanément le poids de chaque type de compte et suivez la structure globale de votre patrimoine.</p>
|
||||
</div>
|
||||
<div class="sf-hero-total">
|
||||
<span>Valorisation totale</span>
|
||||
<strong>@Model.TotalActualValue.ToString("C", displayCulture)</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-kpi-grid">
|
||||
<article class="sf-kpi-card">
|
||||
<span>Capital investi</span>
|
||||
<strong>@Model.TotalStartValue.ToString("C", displayCulture)</strong>
|
||||
</article>
|
||||
<article class="sf-kpi-card">
|
||||
<span>Variation globale</span>
|
||||
<strong class="@(gainLoss >= 0 ? "is-positive" : "is-negative")">@gainLoss.ToString("C", displayCulture)</strong>
|
||||
</article>
|
||||
<article class="sf-kpi-card">
|
||||
<span>Types de comptes actifs</span>
|
||||
<strong>@Model.ActiveTypeCount</strong>
|
||||
</article>
|
||||
<article class="sf-kpi-card">
|
||||
<span>Comptes suivis</span>
|
||||
<strong>@Model.AccountCount</strong>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="sf-dashboard-grid">
|
||||
<article class="sf-panel sf-chart-panel">
|
||||
<div class="sf-panel-header">
|
||||
<div>
|
||||
<span class="sf-panel-label">Allocation</span>
|
||||
<h2>Répartition par type de compte</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (activeSummaries.Any())
|
||||
{
|
||||
<div class="sf-donut-layout">
|
||||
<div class="sf-chart-stage">
|
||||
<div class="sf-chart-badge">Allocation live</div>
|
||||
<div class="sf-chart-shell">
|
||||
<canvas id="financeAllocationChart" aria-label="Répartition des finances par type de compte"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-legend">
|
||||
@foreach (var summary in activeSummaries)
|
||||
{
|
||||
<div class="sf-legend-item">
|
||||
<span class="sf-legend-color" style="background-color:@summary.Color"></span>
|
||||
<div>
|
||||
<strong>@summary.TypeTitle</strong>
|
||||
<div class="sf-legend-values">
|
||||
<span>@summary.ActualValue.ToString("C", displayCulture)</span>
|
||||
<span>@summary.Percentage.ToString("0.0", displayCulture)%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-empty-state">
|
||||
<strong>Aucune valorisation disponible</strong>
|
||||
<p>Ajoutez ou mettez à jour des comptes pour afficher la répartition.</p>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
|
||||
<article class="sf-panel sf-breakdown-panel">
|
||||
<div class="sf-panel-header">
|
||||
<div>
|
||||
<span class="sf-panel-label">Détail</span>
|
||||
<h2>Poids de chaque segment</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-breakdown-list">
|
||||
@foreach (var summary in Model.TypeSummaries)
|
||||
{
|
||||
<div class="sf-breakdown-item">
|
||||
<div class="sf-breakdown-topline">
|
||||
<div class="sf-breakdown-title">
|
||||
<span class="sf-legend-color" style="background-color:@summary.Color"></span>
|
||||
<strong>@summary.TypeTitle</strong>
|
||||
</div>
|
||||
<span>@summary.ActualValue.ToString("C", displayCulture)</span>
|
||||
</div>
|
||||
<div class="sf-progress-track">
|
||||
<div class="sf-progress-value" style="width:@summary.Percentage.ToString("0.##", invariantCulture)%; background-color:@summary.Color"></div>
|
||||
</div>
|
||||
<div class="sf-breakdown-meta">
|
||||
<span>@summary.Accounts.Count compte(s)</span>
|
||||
<span>@summary.Percentage.ToString("0.0", displayCulture)% du total</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="sf-panel sf-details-panel">
|
||||
<div class="sf-panel-header">
|
||||
<div>
|
||||
<span class="sf-panel-label">Portefeuille</span>
|
||||
<h2>Détail des comptes</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-account-groups">
|
||||
@foreach (var summary in Model.TypeSummaries)
|
||||
{
|
||||
<article class="sf-account-group">
|
||||
<div class="sf-account-group-header">
|
||||
<div class="sf-account-group-title">
|
||||
<span class="sf-legend-color" style="background-color:@summary.Color"></span>
|
||||
<div>
|
||||
<h3>@summary.TypeTitle</h3>
|
||||
<p>@summary.ActualValue.ToString("C", displayCulture) · @summary.Percentage.ToString("0.0", displayCulture)% du patrimoine</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table sf-table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Établissement</th>
|
||||
<th>Compte</th>
|
||||
<th>Investi</th>
|
||||
<th>Valorisation</th>
|
||||
<th>Poids</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var account in summary.Accounts)
|
||||
{
|
||||
var accountShare = summary.ActualValue > 0 ? (account.ActualValue / summary.ActualValue) * 100 : 0;
|
||||
var progress = (account.ActualValue - account.StartValue) / account.StartValue;
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<div class="sf-bank-cell">
|
||||
<span class="sf-bank-logo">
|
||||
@if (!string.IsNullOrWhiteSpace(account.Bank))
|
||||
{
|
||||
<img src="/Logos/@(account.Bank).png" height="40" alt="@account.Bank" />
|
||||
}
|
||||
</span>
|
||||
<span>@(string.IsNullOrWhiteSpace(account.Bank) ? "Non renseigné" : account.Bank)</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="fw-semibold">
|
||||
<a asp-controller="Accounts" asp-action="Details" asp-route-id="@account.Id" class="sf-inline-link">@account.Title</a>
|
||||
</td>
|
||||
<td>@account.StartValue.ToString("C", displayCulture)</td>
|
||||
<td class="fw-semibold">@account.ActualValue.ToString("C", displayCulture) <span class="@(double.IsInfinity(progress) ? "sf-progress-infinity" : progress >= 0 ? "sf-progress-positive" : "sf-progress-negative")">@progress.ToString("0.0%", displayCulture)</span> </td>
|
||||
<td>@accountShare.ToString("0.0", displayCulture)%</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
(() => {
|
||||
const canvas = document.getElementById('financeAllocationChart');
|
||||
if (!canvas || typeof Chart === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = @Html.Raw(chartLabelsJson);
|
||||
const values = @Html.Raw(chartValuesJson);
|
||||
const colors = @Html.Raw(chartColorsJson);
|
||||
const totalLabel = @Html.Raw(JsonSerializer.Serialize(totalAmountLabel));
|
||||
const currencyFormatter = new Intl.NumberFormat('fr-BE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 });
|
||||
|
||||
const centerTextPlugin = {
|
||||
id: 'centerTextPlugin',
|
||||
afterDraw(chart) {
|
||||
const { ctx, chartArea } = chart;
|
||||
if (!chartArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
const centerX = (chartArea.left + chartArea.right) / 2;
|
||||
const centerY = (chartArea.top + chartArea.bottom) / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = '#64748b';
|
||||
ctx.font = '600 13px Inter, Segoe UI, sans-serif';
|
||||
ctx.fillText('Total', centerX, centerY - 14);
|
||||
|
||||
ctx.fillStyle = '#0f172a';
|
||||
ctx.font = '700 24px Inter, Segoe UI, sans-serif';
|
||||
ctx.fillText(totalLabel, centerX, centerY + 14);
|
||||
ctx.restore();
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(canvas, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
data: values,
|
||||
backgroundColor: colors,
|
||||
borderColor: '#ffffff',
|
||||
borderWidth: 5,
|
||||
borderRadius: 10,
|
||||
spacing: 3,
|
||||
hoverOffset: 10
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
cutout: '68%',
|
||||
animation: {
|
||||
duration: 900,
|
||||
easing: 'easeOutQuart'
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(15, 23, 42, 0.94)',
|
||||
padding: 14,
|
||||
displayColors: true,
|
||||
cornerRadius: 14,
|
||||
callbacks: {
|
||||
label(context) {
|
||||
const value = Number(context.raw || 0);
|
||||
const total = values.reduce((sum, current) => sum + Number(current || 0), 0);
|
||||
const share = total > 0 ? (value / total) * 100 : 0;
|
||||
return `${context.label}: ${currencyFormatter.format(value)} (${share.toFixed(1)}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [centerTextPlugin]
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
||||
@@ -0,0 +1,25 @@
|
||||
@* @model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p> *@
|
||||
@@ -0,0 +1,134 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - StockFin</title>
|
||||
<script type="importmap"></script>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/StockFin.styles.css" asp-append-version="true" />
|
||||
</head>
|
||||
<body>
|
||||
@{
|
||||
var currentController = ViewContext.RouteData.Values["controller"]?.ToString() ?? string.Empty;
|
||||
var currentAction = ViewContext.RouteData.Values["action"]?.ToString() ?? string.Empty;
|
||||
var isHome = string.Equals(currentController, "Home", StringComparison.OrdinalIgnoreCase) && string.Equals(currentAction, "Index", StringComparison.OrdinalIgnoreCase);
|
||||
var isOperations = string.Equals(currentController, "Accounts", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(currentController, "Transactions", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(currentController, "Statements", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(currentController, "TimeValues", StringComparison.OrdinalIgnoreCase);
|
||||
var isMarket = string.Equals(currentController, "Stocks", StringComparison.OrdinalIgnoreCase);
|
||||
var isSystem = string.Equals(currentController, "Home", StringComparison.OrdinalIgnoreCase) && string.Equals(currentAction, "Privacy", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
<div class="sf-app-shell">
|
||||
<header class="sf-site-header">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg sf-navbar">
|
||||
<a class="navbar-brand sf-brand" asp-area="" asp-controller="Home" asp-action="Index">
|
||||
<span class="sf-brand-mark">SF</span>
|
||||
<span class="sf-brand-copy">
|
||||
<strong>StockFin</strong>
|
||||
<small>Pilotage patrimonial</small>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler sf-navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavigation" aria-controls="mainNavigation"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="mainNavigation">
|
||||
<ul class="navbar-nav sf-nav-list mx-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link sf-nav-link @(isHome ? "active" : string.Empty)" asp-area="" asp-controller="Home" asp-action="Index">
|
||||
<i class="bi bi-pie-chart"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle sf-nav-link @(isOperations ? "active" : string.Empty)" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-wallet2"></i>
|
||||
<span>Opérations</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu sf-dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item sf-dropdown-item" asp-area="" asp-controller="Accounts" asp-action="Index">Comptes</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item sf-dropdown-item" asp-area="" asp-controller="Transactions" asp-action="Index">Transactions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item sf-dropdown-item" asp-area="" asp-controller="Statements" asp-action="Index">Statements</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item sf-dropdown-item" asp-area="" asp-controller="TimeValues" asp-action="Index">Valeurs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle sf-nav-link @(isMarket ? "active" : string.Empty)" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-graph-up-arrow"></i>
|
||||
<span>Marchés</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu sf-dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item sf-dropdown-item" asp-area="" asp-controller="Stocks" asp-action="Index">Stocks</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle sf-nav-link @(isSystem ? "active" : string.Empty)" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-gear"></i>
|
||||
<span>Système</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu sf-dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item sf-dropdown-item" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="sf-nav-actions">
|
||||
<span class="sf-market-pill">
|
||||
<i class="bi bi-stars"></i>
|
||||
<span>Vue premium</span>
|
||||
</span>
|
||||
<a class="btn sf-primary-action" asp-area="" asp-controller="Accounts" asp-action="Index">
|
||||
<i class="bi bi-wallet2"></i>
|
||||
<span>Ouvrir les comptes</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="sf-main-shell">
|
||||
<main role="main" class="container sf-main-content pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="sf-site-footer">
|
||||
<div class="container sf-footer-inner">
|
||||
<div>
|
||||
<strong>StockFin</strong>
|
||||
<p>Suivi financier moderne, clair et structuré.</p>
|
||||
</div>
|
||||
<div class="sf-footer-links">
|
||||
<a asp-area="" asp-controller="Home" asp-action="Index">Dashboard</a>
|
||||
<a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
/* Layout-specific styling is handled centrally in wwwroot/css/site.css to keep the global shell consistent. */
|
||||
@@ -0,0 +1,2 @@
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
|
||||
@@ -0,0 +1,72 @@
|
||||
@model StockFin.Models.Statement
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Create Statement";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Création</span>
|
||||
<h1 class="sf-page-title">Nouveau relevé</h1>
|
||||
<p>Ajoutez une valorisation ponctuelle pour enrichir le suivi historique d'un compte.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Saisie du relevé</h2>
|
||||
<p>Renseignez la date, le compte et la valeur observée.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Create" method="post" class="sf-form-shell">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Date" class="form-label"></label>
|
||||
<input asp-for="Date" class="form-control" type="date" />
|
||||
<span asp-validation-for="Date" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label class="form-label">Compte</label>
|
||||
<select asp-for="AccountId" asp-items="@(ViewData["AccountId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select"></select>
|
||||
<span asp-validation-for="AccountId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Value" class="form-label"></label>
|
||||
<input asp-for="Value" class="form-control" />
|
||||
<span asp-validation-for="Value" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
@model StockFin.ViewModels.CreateTodayViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Relevé du jour";
|
||||
var culture = new System.Globalization.CultureInfo("fr-BE");
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Batch entry</span>
|
||||
<h1 class="sf-page-title">Relevé du jour</h1>
|
||||
<p>Enregistrez rapidement la valeur du jour pour tous les comptes suivis dans une seule interface.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<span class="sf-badge-soft">
|
||||
<i class="bi bi-calendar-event"></i>
|
||||
<span>@Model.Date.ToString("dd/MM/yyyy")</span>
|
||||
</span>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour aux relevés</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Saisie groupée</h2>
|
||||
<p>Renseignez un montant par compte pour constituer votre snapshot quotidien.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="CreateToday" method="post" class="sf-bulk-form">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="row g-3 mb-4 align-items-end">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<label asp-for="Date" class="form-label">Date du relevé</label>
|
||||
<input asp-for="Date" type="date" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bank</th>
|
||||
<th>Compte</th>
|
||||
<th>Type</th>
|
||||
<th class="text-end" style="width:220px">Montant (€)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (int i = 0; i < Model.Accounts.Count; i++)
|
||||
{
|
||||
<input type="hidden" asp-for="Accounts[i].AccountId" />
|
||||
<input type="hidden" asp-for="Accounts[i].AccountName" />
|
||||
<input type="hidden" asp-for="Accounts[i].AccountTypeName" />
|
||||
<tr>
|
||||
<td>
|
||||
<span class="sf-bank-logo">
|
||||
<img src="/Logos/@(Model.Accounts[i].Bank).png" alt="@Model.Accounts[i].Bank" />
|
||||
</span>
|
||||
</td>
|
||||
<td class="fw-semibold">@Model.Accounts[i].AccountName</td>
|
||||
<td class="text-muted">@Model.Accounts[i].AccountTypeName</td>
|
||||
<td>
|
||||
<input type="number"
|
||||
step="0.01"
|
||||
class="form-control text-end"
|
||||
asp-for="Accounts[i].Amount"
|
||||
placeholder="0,00" />
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions mt-4">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
@model StockFin.Models.Statement
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Delete Statement";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-warning">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Suppression</span>
|
||||
<h1 class="sf-page-title">Supprimer le relevé</h1>
|
||||
<p class="sf-delete-warning">Cette action retirera définitivement ce relevé de l'historique.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-delete-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Vérification avant suppression</h2>
|
||||
<p>Confirmez le relevé ciblé avant de poursuivre.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="sf-definition-list">
|
||||
<dt>Date</dt>
|
||||
<dd>@Model.Date.ToString("yyyy-MM-dd")</dd>
|
||||
|
||||
<dt>Compte</dt>
|
||||
<dd>@Model.Account?.Title</dd>
|
||||
|
||||
<dt>Valeur</dt>
|
||||
<dd>@Model.Value</dd>
|
||||
</dl>
|
||||
|
||||
<form asp-action="Delete" class="sf-form-actions">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<button type="submit" class="sf-btn-danger">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,73 @@
|
||||
@model StockFin.Models.Statement
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Edit Statement";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Mise à jour</span>
|
||||
<h1 class="sf-page-title">Modifier le relevé</h1>
|
||||
<p>Mettez à jour ce relevé pour garder votre historique fidèle à la réalité.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Édition du relevé</h2>
|
||||
<p>La modification sera visible dans vos vues de suivi dès l'enregistrement.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Edit" method="post" class="sf-form-shell">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Date" class="form-label"></label>
|
||||
<input asp-for="Date" class="form-control" type="date" />
|
||||
<span asp-validation-for="Date" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label class="form-label">Compte</label>
|
||||
<select asp-for="AccountId" asp-items="@(ViewData["AccountId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select"></select>
|
||||
<span asp-validation-for="AccountId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Value" class="form-label"></label>
|
||||
<input asp-for="Value" class="form-control" />
|
||||
<span asp-validation-for="Value" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
@model IEnumerable<StockFin.Models.Statement>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Statements";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Relevés</span>
|
||||
<h1 class="sf-page-title">Statements</h1>
|
||||
<p>Suivez les valorisations de vos comptes et gardez un historique propre de chaque relevé enregistré.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Create" class="sf-btn-secondary">
|
||||
<i class="bi bi-plus-circle"></i>
|
||||
<span>Nouveau relevé</span>
|
||||
</a>
|
||||
<a asp-action="CreateToday" class="sf-btn-ghost">
|
||||
<i class="bi bi-calendar-check"></i>
|
||||
<span>Relevé du jour</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Historique des relevés</h2>
|
||||
<p>Chaque ligne représente un point de contrôle sur la valeur d'un compte.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Compte</th>
|
||||
<th class="text-end">Valeur</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Date.ToString("yyyy-MM-dd")</td>
|
||||
<td class="fw-semibold">
|
||||
@if (item.AccountId.HasValue)
|
||||
{
|
||||
<a asp-controller="Accounts" asp-action="Details" asp-route-id="@item.AccountId" class="sf-inline-link">@item.Account?.Title</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@item.Account?.Title</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-end fw-semibold">@item.Value</td>
|
||||
<td>
|
||||
<div class="sf-table-actions">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="sf-table-action is-edit">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
<span>Modifier</span>
|
||||
</a>
|
||||
<a asp-action="Delete" asp-route-id="@item.Id" class="sf-table-action is-delete">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-empty-card">
|
||||
<h3>Aucun relevé disponible</h3>
|
||||
<p>Ajoutez un premier relevé pour commencer votre suivi.</p>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,78 @@
|
||||
@model StockFin.Models.Stock
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Create Stock";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Création</span>
|
||||
<h1 class="sf-page-title">Nouveau titre</h1>
|
||||
<p>Ajoutez un instrument financier avec ses identifiants de marché et son prix de référence.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Fiche instrument</h2>
|
||||
<p>Ces informations seront réutilisées dans vos transactions et vos valorisations.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Create" method="post" class="sf-form-shell">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Title" class="form-label"></label>
|
||||
<input asp-for="Title" class="form-control" />
|
||||
<span asp-validation-for="Title" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Isin" class="form-label"></label>
|
||||
<input asp-for="Isin" class="form-control" placeholder="ex: BE0003565737" />
|
||||
<span asp-validation-for="Isin" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Ticker" class="form-label"></label>
|
||||
<input asp-for="Ticker" class="form-control" placeholder="ex: AAPL" />
|
||||
<span asp-validation-for="Ticker" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Cost" class="form-label"></label>
|
||||
<input asp-for="Cost" class="form-control" />
|
||||
<span asp-validation-for="Cost" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
@model StockFin.Models.Stock
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Delete Stock";
|
||||
var displayCulture = new System.Globalization.CultureInfo("fr-BE");
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-warning">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Suppression</span>
|
||||
<h1 class="sf-page-title">Supprimer le titre</h1>
|
||||
<p class="sf-delete-warning">Cette suppression retirera l'instrument de votre référentiel.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-delete-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Vérification avant suppression</h2>
|
||||
<p>Assurez-vous qu'aucune donnée importante n'est encore liée à ce titre.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="sf-definition-list">
|
||||
<dt>Title</dt>
|
||||
<dd>@Model.Title</dd>
|
||||
|
||||
<dt>ISIN</dt>
|
||||
<dd>@Model.Isin</dd>
|
||||
|
||||
<dt>Ticker</dt>
|
||||
<dd>@Model.Ticker</dd>
|
||||
|
||||
<dt>Cost</dt>
|
||||
<dd>@(Model.Cost.HasValue ? Model.Cost.Value.ToString("N2", displayCulture) : "")</dd>
|
||||
</dl>
|
||||
|
||||
<form asp-action="Delete" class="sf-form-actions">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<button type="submit" class="sf-btn-danger">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,79 @@
|
||||
@model StockFin.Models.Stock
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Edit Stock";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Mise à jour</span>
|
||||
<h1 class="sf-page-title">Modifier le titre</h1>
|
||||
<p>Ajustez la fiche instrument pour maintenir des données de marché cohérentes dans l'application.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Édition de l'instrument</h2>
|
||||
<p>Les changements seront réutilisés dans les vues de portefeuille et de transactions.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Edit" method="post" class="sf-form-shell">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Title" class="form-label"></label>
|
||||
<input asp-for="Title" class="form-control" />
|
||||
<span asp-validation-for="Title" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Isin" class="form-label"></label>
|
||||
<input asp-for="Isin" class="form-control" placeholder="ex: BE0003565737" />
|
||||
<span asp-validation-for="Isin" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Ticker" class="form-label"></label>
|
||||
<input asp-for="Ticker" class="form-control" placeholder="ex: AAPL" />
|
||||
<span asp-validation-for="Ticker" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Cost" class="form-label"></label>
|
||||
<input asp-for="Cost" class="form-control" />
|
||||
<span asp-validation-for="Cost" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
@model IEnumerable<StockFin.Models.Stock>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Stocks";
|
||||
var displayCulture = new System.Globalization.CultureInfo("fr-BE");
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Marchés</span>
|
||||
<h1 class="sf-page-title">Stocks</h1>
|
||||
<p>Administrez vos instruments financiers et suivez leur prix courant dans une vue claire et cohérente.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Create" class="sf-btn-secondary">
|
||||
<i class="bi bi-plus-circle"></i>
|
||||
<span>Nouveau titre</span>
|
||||
</a>
|
||||
<a asp-action="UpdateFromTradeRepublic" class="sf-btn-ghost">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Importer Trade Republic</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Catalogue des titres</h2>
|
||||
<p>Conservez une référence propre de vos actions, ETF et autres instruments suivis.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>ISIN</th>
|
||||
<th>Ticker</th>
|
||||
<th class="text-end">Cost</th>
|
||||
<th class="text-end">Current Price</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td class="fw-semibold">@item.Title</td>
|
||||
<td>@item.Isin</td>
|
||||
<td>@item.Ticker</td>
|
||||
<td class="text-end">@(item.Cost.HasValue ? item.Cost.Value.ToString("N2", displayCulture) : "")</td>
|
||||
<td class="text-end fw-semibold">@(item.Ticker != null && ((Dictionary<string, double?>)ViewData["actuals"]).TryGetValue(item.Ticker, out var actual) ? actual.Value.ToString("N2", displayCulture) : "N/A")</td>
|
||||
<td>
|
||||
<div class="sf-table-actions">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="sf-table-action is-edit">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
<span>Modifier</span>
|
||||
</a>
|
||||
<a asp-action="Delete" asp-route-id="@item.Id" class="sf-table-action is-delete">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-empty-card">
|
||||
<h3>Aucun titre enregistré</h3>
|
||||
<p>Créez un premier instrument ou importez vos données Trade Republic.</p>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,77 @@
|
||||
@{
|
||||
ViewData["Title"] = "Import Trade Republic";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Import</span>
|
||||
<h1 class="sf-page-title">Import depuis Trade Republic</h1>
|
||||
<p>Chargez un export CSV pour enrichir automatiquement votre univers titres avec un flux de travail plus propre.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour aux titres</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-alert-stack">
|
||||
@if (ViewBag.ImportResult is not null)
|
||||
{
|
||||
<div class="alert alert-success alert-dismissible fade show mb-0" role="alert">
|
||||
<strong>Import réussi :</strong> @ViewBag.ImportResult lignes traitées.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!ViewData.ModelState.IsValid)
|
||||
{
|
||||
<div class="alert alert-danger mb-0">
|
||||
<ul class="mb-0">
|
||||
@foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors))
|
||||
{
|
||||
<li>@error.ErrorMessage</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="sf-card-header mt-4">
|
||||
<div>
|
||||
<h2>Chargement du fichier</h2>
|
||||
<p>Sélectionnez le fichier <em>Exportation de transactions.csv</em> exporté depuis Trade Republic.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="UpdateFromTradeRepublic" method="post" enctype="multipart/form-data" class="sf-form-shell">
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label for="csvFile" class="form-label">Fichier CSV</label>
|
||||
<input type="file"
|
||||
class="form-control"
|
||||
id="csvFile"
|
||||
name="csvFile"
|
||||
accept=".csv"
|
||||
required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Importer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,76 @@
|
||||
@model StockFin.Models.TimeValue
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Nouvelle valeur";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Création</span>
|
||||
<h1 class="sf-page-title">Nouvelle valeur</h1>
|
||||
<p>Ajoutez une valeur historique pour enrichir le suivi temporel d'un instrument.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Saisie de la valeur</h2>
|
||||
<p>Renseignez le titre, la date et le montant mesuré.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Create" class="sf-form-shell">
|
||||
@Html.AntiForgeryToken()
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="StocksId" class="form-label">Titre</label>
|
||||
<select asp-for="StocksId" class="form-select" asp-items="ViewBag.StockId">
|
||||
<option value="">-- Sélectionner un titre --</option>
|
||||
</select>
|
||||
<span asp-validation-for="StocksId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Date" class="form-label">Date</label>
|
||||
<input asp-for="Date" type="date" class="form-control" />
|
||||
<span asp-validation-for="Date" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Value" class="form-label">Valeur (€)</label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Value" type="number" step="0.01" class="form-control text-end" placeholder="0,00" />
|
||||
<span class="input-group-text">€</span>
|
||||
</div>
|
||||
<span asp-validation-for="Value" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<input type="submit" value="Créer" class="sf-btn-secondary" />
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
@model StockFin.Models.TimeValue
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Supprimer la valeur";
|
||||
var culture = new System.Globalization.CultureInfo("fr-BE");
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-warning">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Suppression</span>
|
||||
<h1 class="sf-page-title">Supprimer la valeur</h1>
|
||||
<p class="sf-delete-warning">Cette action supprimera définitivement cette entrée de votre historique.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-delete-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Vérification avant suppression</h2>
|
||||
<p>Contrôlez les informations de la valeur avant validation.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="sf-definition-list">
|
||||
<dt>Titre</dt>
|
||||
<dd>@Model.Stocks?.Title</dd>
|
||||
|
||||
<dt>Date</dt>
|
||||
<dd>@Model.Date?.ToString("dd/MM/yyyy")</dd>
|
||||
|
||||
<dt>Valeur</dt>
|
||||
<dd>@Model.Value?.ToString("N2", culture) €</dd>
|
||||
</dl>
|
||||
|
||||
<form asp-action="Delete" class="sf-form-actions">
|
||||
@Html.AntiForgeryToken()
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<input type="submit" value="Supprimer" class="sf-btn-danger" />
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,77 @@
|
||||
@model StockFin.Models.TimeValue
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Modifier la valeur";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Mise à jour</span>
|
||||
<h1 class="sf-page-title">Modifier la valeur</h1>
|
||||
<p>Corrigez ou ajustez une mesure historique pour garder vos séries temporelles fiables.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Édition de la valeur</h2>
|
||||
<p>Les changements seront visibles dans l'historique du titre concerné.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Edit" class="sf-form-shell">
|
||||
@Html.AntiForgeryToken()
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<input type="hidden" asp-for="Id" />
|
||||
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="StocksId" class="form-label">Titre</label>
|
||||
<select asp-for="StocksId" class="form-select" asp-items="ViewBag.StockId">
|
||||
<option value="">-- Sélectionner un titre --</option>
|
||||
</select>
|
||||
<span asp-validation-for="StocksId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Date" class="form-label">Date</label>
|
||||
<input asp-for="Date" type="date" class="form-control" />
|
||||
<span asp-validation-for="Date" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Value" class="form-label">Valeur (€)</label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Value" type="number" step="0.01" class="form-control text-end" />
|
||||
<span class="input-group-text">€</span>
|
||||
</div>
|
||||
<span asp-validation-for="Value" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<input type="submit" value="Enregistrer" class="sf-btn-secondary" />
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
@model IEnumerable<StockFin.Models.TimeValue>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Valeurs dans le temps";
|
||||
var culture = new System.Globalization.CultureInfo("fr-BE");
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Historique</span>
|
||||
<h1 class="sf-page-title">Valeurs dans le temps</h1>
|
||||
<p>Conservez l'évolution des prix de vos instruments pour alimenter vos analyses et valorisations historiques.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Create" class="sf-btn-secondary">
|
||||
<i class="bi bi-plus-circle"></i>
|
||||
<span>Nouvelle valeur</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Série temporelle</h2>
|
||||
<p>Chaque entrée représente une valeur observée pour un titre à une date donnée.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Date</th>
|
||||
<th class="text-end">Valeur</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td class="fw-semibold">@item.Stocks?.Title</td>
|
||||
<td>@item.Date?.ToString("dd/MM/yyyy")</td>
|
||||
<td class="text-end fw-semibold">@item.Value?.ToString("N2", culture) €</td>
|
||||
<td>
|
||||
<div class="sf-table-actions">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="sf-table-action is-edit">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
<span>Modifier</span>
|
||||
</a>
|
||||
<a asp-action="Delete" asp-route-id="@item.Id" class="sf-table-action is-delete">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-empty-card">
|
||||
<h3>Aucune valeur enregistrée</h3>
|
||||
<p>Ajoutez un premier point de mesure pour commencer l'historique d'un titre.</p>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,86 @@
|
||||
@model StockFin.Models.Transaction
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Create Transaction";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Création</span>
|
||||
<h1 class="sf-page-title">Nouvelle transaction</h1>
|
||||
<p>Ajoutez un mouvement d'investissement en renseignant le compte, le titre et les montants associés.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Saisie de la transaction</h2>
|
||||
<p>Les informations enregistrées seront utilisées dans le suivi de votre portefeuille.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Create" method="post" class="sf-form-shell">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Date" class="form-label"></label>
|
||||
<input asp-for="Date" class="form-control" type="date" />
|
||||
<span asp-validation-for="Date" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label class="form-label">Compte</label>
|
||||
<select asp-for="AccountId" asp-items="@(ViewData["AccountId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select"></select>
|
||||
<span asp-validation-for="AccountId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label class="form-label">Titre</label>
|
||||
<select asp-for="StocksId" asp-items="@(ViewData["StocksId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select"></select>
|
||||
<span asp-validation-for="StocksId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Quantity" class="form-label"></label>
|
||||
<input asp-for="Quantity" class="form-control" />
|
||||
<span asp-validation-for="Quantity" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Value" class="form-label"></label>
|
||||
<input asp-for="Value" class="form-control" />
|
||||
<span asp-validation-for="Value" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<input asp-for="TypeId" type="hidden" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
@model StockFin.Models.Transaction
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Delete Transaction";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-warning">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Suppression</span>
|
||||
<h1 class="sf-page-title">Supprimer la transaction</h1>
|
||||
<p class="sf-delete-warning">Cette opération retirera définitivement la transaction de votre historique.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-delete-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Vérification avant suppression</h2>
|
||||
<p>Confirmez les informations ci-dessous avant de continuer.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="sf-definition-list">
|
||||
<dt>Date</dt>
|
||||
<dd>@(Model.Date.HasValue ? Model.Date.Value.ToString("yyyy-MM-dd") : "")</dd>
|
||||
|
||||
<dt>Compte</dt>
|
||||
<dd>@Model.Account?.Title</dd>
|
||||
|
||||
<dt>Titre</dt>
|
||||
<dd>@(Model.Stocks?.Title ?? "—")</dd>
|
||||
|
||||
<dt>Quantité</dt>
|
||||
<dd>@Model.Quantity</dd>
|
||||
|
||||
<dt>Valeur</dt>
|
||||
<dd>@Model.Value</dd>
|
||||
</dl>
|
||||
|
||||
<form asp-action="Delete" class="sf-form-actions">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<button type="submit" class="sf-btn-danger">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,87 @@
|
||||
@model StockFin.Models.Transaction
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Edit Transaction";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Mise à jour</span>
|
||||
<h1 class="sf-page-title">Modifier la transaction</h1>
|
||||
<p>Ajustez les informations de cette opération pour conserver un historique précis.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Retour à la liste</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Édition de la transaction</h2>
|
||||
<p>Les modifications seront reflétées immédiatement dans vos tableaux de suivi.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="Edit" method="post" class="sf-form-shell">
|
||||
<input type="hidden" asp-for="Id" />
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="sf-form-grid">
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Date" class="form-label"></label>
|
||||
<input asp-for="Date" class="form-control" type="date" />
|
||||
<span asp-validation-for="Date" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label class="form-label">Compte</label>
|
||||
<select asp-for="AccountId" asp-items="@(ViewData["AccountId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select"></select>
|
||||
<span asp-validation-for="AccountId" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label class="form-label">Titre</label>
|
||||
<select asp-for="StocksId" asp-items="@(ViewData["StocksId"] as Microsoft.AspNetCore.Mvc.Rendering.SelectList)" class="form-select"></select>
|
||||
<span asp-validation-for="StocksId" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-section">
|
||||
<div class="sf-field">
|
||||
<label asp-for="Quantity" class="form-label"></label>
|
||||
<input asp-for="Quantity" class="form-control" />
|
||||
<span asp-validation-for="Quantity" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<div class="sf-field">
|
||||
<label asp-for="Value" class="form-label"></label>
|
||||
<input asp-for="Value" class="form-control" />
|
||||
<span asp-validation-for="Value" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<input asp-for="TypeId" type="hidden" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-form-actions">
|
||||
<button type="submit" class="sf-btn-secondary">
|
||||
<i class="bi bi-check2-circle"></i>
|
||||
<span>Enregistrer</span>
|
||||
</button>
|
||||
<a asp-action="Index" class="sf-btn-ghost">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
<span>Annuler</span>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<partial name="_ValidationScriptsPartial" />
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
@model IEnumerable<StockFin.Models.Transaction>
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Transactions";
|
||||
}
|
||||
|
||||
<div class="sf-page">
|
||||
<section class="sf-page-hero is-dark">
|
||||
<div>
|
||||
<span class="sf-page-kicker">Opérations</span>
|
||||
<h1 class="sf-page-title">Transactions</h1>
|
||||
<p>Centralisez vos achats et ventes pour garder une lecture claire de l'activité de votre portefeuille.</p>
|
||||
</div>
|
||||
<div class="sf-page-actions">
|
||||
<a asp-action="Create" class="sf-btn-secondary">
|
||||
<i class="bi bi-plus-circle"></i>
|
||||
<span>Nouvelle transaction</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="sf-content-card">
|
||||
<div class="sf-card-header">
|
||||
<div>
|
||||
<h2>Journal des transactions</h2>
|
||||
<p>Consultez chaque mouvement enregistré sur vos comptes et titres.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.Any())
|
||||
{
|
||||
<div class="sf-table-wrapper table-responsive">
|
||||
<table class="table sf-data-table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Compte</th>
|
||||
<th>Titre</th>
|
||||
<th class="text-end">Quantité</th>
|
||||
<th class="text-end">Valeur</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model)
|
||||
{
|
||||
<tr>
|
||||
<td>@(item.Date.HasValue ? item.Date.Value.ToString("yyyy-MM-dd") : "")</td>
|
||||
<td class="fw-semibold">
|
||||
@if (item.AccountId.HasValue)
|
||||
{
|
||||
<a asp-controller="Accounts" asp-action="Details" asp-route-id="@item.AccountId" class="sf-inline-link">@item.Account?.Title</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@item.Account?.Title</span>
|
||||
}
|
||||
</td>
|
||||
<td>@(item.Stocks?.Title ?? "—")</td>
|
||||
<td class="text-end">@item.Quantity</td>
|
||||
<td class="text-end fw-semibold">@item.Value</td>
|
||||
<td>
|
||||
<div class="sf-table-actions">
|
||||
<a asp-action="Edit" asp-route-id="@item.Id" class="sf-table-action is-edit">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
<span>Modifier</span>
|
||||
</a>
|
||||
<a asp-action="Delete" asp-route-id="@item.Id" class="sf-table-action is-delete">
|
||||
<i class="bi bi-trash3"></i>
|
||||
<span>Supprimer</span>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sf-empty-card">
|
||||
<h3>Aucune transaction enregistrée</h3>
|
||||
<p>Commencez par créer une transaction pour alimenter votre historique.</p>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
@using StockFin
|
||||
@using StockFin.Models
|
||||
@using System.Globalization
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"AlphaVantage": {
|
||||
"ApiKey": "7P4G6DTVFB0XSG90"
|
||||
},
|
||||
|
||||
"Finnhub": {
|
||||
"ApiKey": "d8ptfvhr01qtgb4j8a10d8ptfvhr01qtgb4j8a1g"
|
||||
},
|
||||
"TwelveData": {
|
||||
"ApiKey": "ee3de89fcf5c42879dde07f78a893685"
|
||||
}
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,995 @@
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 60px;
|
||||
background: linear-gradient(180deg, #f4f7fb 0%, #eef3f9 100%);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
||||
text-align: start;
|
||||
}
|
||||
.sf-progress-positive {
|
||||
color: #1d8f0d;
|
||||
font-size: 0.85rem;
|
||||
margin-left:10px;
|
||||
}
|
||||
.sf-progress-negative {
|
||||
color: #f87171;
|
||||
font-size: 0.85rem;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.sf-progress-positive::before {
|
||||
color: #1d8f0d;
|
||||
content: "▲ ";
|
||||
}
|
||||
|
||||
.sf-progress-negative::before {
|
||||
color: #f87171;
|
||||
content: "▼ ";
|
||||
}
|
||||
.sf-progress-infinity {
|
||||
display:none;
|
||||
}
|
||||
.sf-app-shell {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sf-site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1030;
|
||||
padding: 1rem 0 0;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
.sf-navbar {
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 26px;
|
||||
background: rgba(15, 23, 42, 0.84);
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
box-shadow: 0 24px 50px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
.sf-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sf-brand:hover,
|
||||
.sf-brand:focus {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sf-brand-mark {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(135deg, #38bdf8 0%, #2563eb 100%);
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.05em;
|
||||
box-shadow: 0 12px 24px rgba(37, 99, 235, 0.35);
|
||||
}
|
||||
|
||||
.sf-brand-copy {
|
||||
display: grid;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.sf-brand-copy strong {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sf-brand-copy small {
|
||||
color: rgba(226, 232, 240, 0.8);
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.sf-navbar-toggler {
|
||||
border: 0;
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.sf-navbar-toggler:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(56, 189, 248, 0.2);
|
||||
}
|
||||
|
||||
.sf-navbar .navbar-toggler-icon {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.sf-nav-list {
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.sf-nav-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.8rem 1rem !important;
|
||||
border-radius: 16px;
|
||||
color: rgba(226, 232, 240, 0.88);
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sf-nav-link:hover,
|
||||
.sf-nav-link:focus,
|
||||
.sf-nav-link.active,
|
||||
.show > .sf-nav-link {
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.sf-dropdown-menu {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.55rem;
|
||||
min-width: 14rem;
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
border-radius: 18px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 24px 40px rgba(15, 23, 42, 0.16);
|
||||
}
|
||||
|
||||
.sf-dropdown-item {
|
||||
border-radius: 12px;
|
||||
padding: 0.8rem 0.9rem;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.sf-dropdown-item:hover,
|
||||
.sf-dropdown-item:focus {
|
||||
color: #0f172a;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.sf-nav-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
.sf-market-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 999px;
|
||||
color: #e2e8f0;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.sf-primary-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.85rem 1.15rem;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #38bdf8 0%, #2563eb 100%);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 16px 30px rgba(37, 99, 235, 0.32);
|
||||
}
|
||||
|
||||
.sf-primary-action:hover,
|
||||
.sf-primary-action:focus {
|
||||
color: #fff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.sf-main-shell {
|
||||
flex: 1 0 auto;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.sf-main-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sf-site-footer {
|
||||
margin-top: 2rem;
|
||||
padding: 0 0 1.5rem;
|
||||
}
|
||||
|
||||
.sf-footer-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
padding: 1.35rem 1.5rem;
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
box-shadow: 0 14px 30px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.sf-footer-inner p {
|
||||
margin: 0.2rem 0 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.sf-footer-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sf-footer-links a {
|
||||
color: #334155;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sf-footer-links a:hover,
|
||||
.sf-footer-links a:focus {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.sf-dashboard {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
padding: 1.5rem 0 2.5rem;
|
||||
}
|
||||
|
||||
.sf-page {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
padding: 1.5rem 0 2.5rem;
|
||||
}
|
||||
|
||||
.sf-page-hero,
|
||||
.sf-content-card,
|
||||
.sf-delete-card,
|
||||
.sf-empty-card {
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.sf-page-hero {
|
||||
border-radius: 28px;
|
||||
padding: 1.75rem 2rem;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.sf-page-hero.is-dark {
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1d4ed8 52%, #38bdf8 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sf-page-hero.is-warning {
|
||||
background: linear-gradient(135deg, #7c2d12 0%, #c2410c 55%, #fb923c 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sf-page-kicker {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.4rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sf-page-hero:not(.is-dark):not(.is-warning) .sf-page-kicker {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.sf-page-hero p {
|
||||
margin: 0.6rem 0 0;
|
||||
max-width: 46rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.sf-page-hero.is-dark p,
|
||||
.sf-page-hero.is-warning p,
|
||||
.sf-page-hero.is-dark .sf-page-kicker,
|
||||
.sf-page-hero.is-warning .sf-page-kicker {
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
}
|
||||
|
||||
.sf-page-title {
|
||||
margin: 0;
|
||||
font-size: clamp(1.9rem, 3vw, 2.5rem);
|
||||
}
|
||||
|
||||
.sf-page-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sf-content-card,
|
||||
.sf-delete-card,
|
||||
.sf-empty-card {
|
||||
border-radius: 28px;
|
||||
padding: 1.5rem 1.6rem;
|
||||
}
|
||||
|
||||
.sf-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.sf-card-header h2,
|
||||
.sf-card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.sf-card-header p {
|
||||
margin: 0.25rem 0 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.sf-btn-secondary,
|
||||
.sf-btn-danger,
|
||||
.sf-btn-ghost {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.85rem 1.15rem;
|
||||
border-radius: 16px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sf-btn-secondary {
|
||||
border: 0;
|
||||
background: linear-gradient(135deg, #38bdf8 0%, #2563eb 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 16px 30px rgba(37, 99, 235, 0.28);
|
||||
}
|
||||
|
||||
.sf-btn-secondary:hover,
|
||||
.sf-btn-secondary:focus {
|
||||
color: #fff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.sf-btn-danger {
|
||||
border: 0;
|
||||
background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 16px 30px rgba(185, 28, 28, 0.24);
|
||||
}
|
||||
|
||||
.sf-btn-danger:hover,
|
||||
.sf-btn-danger:focus {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sf-btn-ghost {
|
||||
border: 1px solid rgba(148, 163, 184, 0.26);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.sf-btn-ghost:hover,
|
||||
.sf-btn-ghost:focus {
|
||||
color: #2563eb;
|
||||
border-color: rgba(37, 99, 235, 0.3);
|
||||
}
|
||||
|
||||
.sf-table-wrapper {
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sf-data-table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.sf-data-table thead th {
|
||||
border-bottom-width: 1px;
|
||||
background: #f8fafc;
|
||||
color: #475569;
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.sf-data-table tbody td {
|
||||
padding: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sf-data-table tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.sf-table-actions {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sf-table-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sf-table-action.is-edit {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.sf-table-action.is-view {
|
||||
background: #eefbf3;
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.sf-table-action.is-delete {
|
||||
background: #fef2f2;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.sf-inline-link {
|
||||
color: #0f172a;
|
||||
font-weight: 600;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.sf-inline-link:hover,
|
||||
.sf-inline-link:focus {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.sf-form-shell {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.sf-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.sf-form-section {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sf-field label,
|
||||
.sf-form-section .form-label {
|
||||
font-weight: 700;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.sf-form-shell .form-control,
|
||||
.sf-form-shell .form-select,
|
||||
.sf-form-shell .input-group-text,
|
||||
.sf-bulk-form .form-control,
|
||||
.sf-bulk-form .form-select,
|
||||
.sf-bulk-form .input-group-text {
|
||||
border-radius: 16px;
|
||||
border-color: rgba(148, 163, 184, 0.26);
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
.sf-form-shell .input-group > .form-control,
|
||||
.sf-form-shell .input-group > .form-select,
|
||||
.sf-bulk-form .input-group > .form-control,
|
||||
.sf-bulk-form .input-group > .form-select {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.sf-form-shell .input-group-text,
|
||||
.sf-bulk-form .input-group-text {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.sf-form-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.sf-delete-card {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.sf-delete-warning {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
|
||||
.sf-definition-list {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr;
|
||||
gap: 0.85rem 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sf-definition-list dt {
|
||||
color: #64748b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sf-definition-list dd {
|
||||
margin: 0;
|
||||
color: #0f172a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sf-badge-soft {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.55rem 0.85rem;
|
||||
border-radius: 999px;
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sf-alert-stack {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sf-empty-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sf-empty-card p {
|
||||
margin: 0.4rem 0 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.sf-account-summary-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
.sf-summary-tile {
|
||||
padding: 1.25rem 1.35rem;
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.sf-summary-tile .sf-page-kicker {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.sf-summary-tile strong {
|
||||
display: block;
|
||||
font-size: 1.1rem;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.sf-hero,
|
||||
.sf-panel,
|
||||
.sf-kpi-card,
|
||||
.sf-account-group {
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.sf-hero {
|
||||
border-radius: 28px;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: end;
|
||||
gap: 1.5rem;
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1d4ed8 52%, #38bdf8 100%);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sf-eyebrow,
|
||||
.sf-panel-label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sf-hero h1,
|
||||
.sf-panel h2,
|
||||
.sf-account-group h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sf-hero p {
|
||||
margin: 0.75rem 0 0;
|
||||
max-width: 42rem;
|
||||
color: rgba(255, 255, 255, 0.84);
|
||||
}
|
||||
|
||||
.sf-hero-total {
|
||||
min-width: 16rem;
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-radius: 22px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.sf-hero-total span,
|
||||
.sf-kpi-card span,
|
||||
.sf-account-group p,
|
||||
.sf-legend-values,
|
||||
.sf-breakdown-meta {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.sf-hero-total span {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.sf-hero-total strong {
|
||||
font-size: clamp(1.75rem, 3vw, 2.5rem);
|
||||
}
|
||||
|
||||
.sf-kpi-grid,
|
||||
.sf-dashboard-grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.sf-kpi-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
.sf-kpi-card {
|
||||
border-radius: 22px;
|
||||
padding: 1.35rem 1.5rem;
|
||||
}
|
||||
|
||||
.sf-kpi-card strong {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.is-positive {
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.is-negative {
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.sf-dashboard-grid {
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(320px, 1fr);
|
||||
}
|
||||
|
||||
.sf-panel,
|
||||
.sf-account-group {
|
||||
border-radius: 28px;
|
||||
padding: 1.75rem;
|
||||
}
|
||||
|
||||
.sf-panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: start;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.sf-panel-label {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.sf-donut-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
|
||||
gap: 2rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sf-chart-stage {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sf-chart-badge {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
z-index: 1;
|
||||
padding: 0.45rem 0.75rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(15, 23, 42, 0.88);
|
||||
color: #fff;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.sf-chart-shell {
|
||||
position: relative;
|
||||
min-height: 340px;
|
||||
padding: 1.5rem;
|
||||
border-radius: 28px;
|
||||
background: radial-gradient(circle at top, rgba(37, 99, 235, 0.12), rgba(255, 255, 255, 0.98) 55%);
|
||||
border: 1px solid rgba(148, 163, 184, 0.18);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
#financeAllocationChart {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.sf-legend,
|
||||
.sf-breakdown-list,
|
||||
.sf-account-groups {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sf-legend-item,
|
||||
.sf-breakdown-item {
|
||||
display: grid;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.sf-legend-item {
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: start;
|
||||
padding: 1rem 1.1rem;
|
||||
border-radius: 18px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid rgba(148, 163, 184, 0.14);
|
||||
}
|
||||
|
||||
.sf-legend-color {
|
||||
width: 0.85rem;
|
||||
height: 0.85rem;
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.sf-legend-values,
|
||||
.sf-breakdown-meta,
|
||||
.sf-breakdown-topline,
|
||||
.sf-account-group-title,
|
||||
.sf-bank-cell {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sf-legend-values,
|
||||
.sf-breakdown-meta {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.sf-breakdown-title,
|
||||
.sf-account-group-title {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.sf-progress-track {
|
||||
height: 0.7rem;
|
||||
border-radius: 999px;
|
||||
background: #e2e8f0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sf-progress-value {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.sf-details-panel {
|
||||
padding-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.sf-account-group-header {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.sf-account-group h3 {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.sf-account-group p {
|
||||
margin: 0.2rem 0 0;
|
||||
}
|
||||
|
||||
.sf-table thead th {
|
||||
border-bottom-width: 1px;
|
||||
color: #475569;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.sf-table tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.sf-bank-cell {
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sf-bank-logo {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8fafc;
|
||||
border: 1px solid rgba(148, 163, 184, 0.22);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sf-bank-logo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.sf-empty-state {
|
||||
padding: 2.5rem 1rem;
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.sf-site-header {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.sf-navbar {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.sf-nav-list,
|
||||
.sf-nav-actions {
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sf-nav-list {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.sf-nav-actions {
|
||||
flex-direction: column;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.sf-market-pill,
|
||||
.sf-primary-action {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sf-dashboard-grid,
|
||||
.sf-donut-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sf-hero {
|
||||
align-items: start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sf-hero-total {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.sf-site-header {
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.sf-navbar,
|
||||
.sf-footer-inner {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.sf-footer-inner {
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.sf-footer-links {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sf-dashboard {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sf-hero,
|
||||
.sf-panel,
|
||||
.sf-kpi-card,
|
||||
.sf-account-group {
|
||||
border-radius: 22px;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.sf-chart-shell {
|
||||
min-height: 280px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.sf-legend-values,
|
||||
.sf-breakdown-meta,
|
||||
.sf-breakdown-topline,
|
||||
.sf-account-group-title,
|
||||
.sf-bank-cell {
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,11 @@
|
||||
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
// for details on configuring this project to bundle and minify static web assets.
|
||||
|
||||
// Normalize decimal commas to dots before form submission so both 42.22 and 42,22 are accepted.
|
||||
document.addEventListener('submit', function (e) {
|
||||
e.target.querySelectorAll('input[type="text"], input:not([type])').forEach(function (input) {
|
||||
if (/^-?\d+(,\d+)?$/.test(input.value.trim())) {
|
||||
input.value = input.value.replace(',', '.');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2021 Twitter, Inc.
|
||||
Copyright (c) 2011-2021 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,597 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
@@ -0,0 +1,594 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
||||