Köp och sälj notor kan läsas in i csv-format och kan sedan listas
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.14.37614.0 d17.14
|
VisualStudioVersion = 17.14.37614.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StockWebHistory", "StockWebHistory\StockWebHistory.csproj", "{76B5FDA3-FD94-4E06-BB2C-8CDDE8C205DD}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StockWebHistory", "StockWebHistory\StockWebHistory.csproj", "{76B5FDA3-FD94-4E06-BB2C-8CDDE8C205DD}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|||||||
65
StockWebHistory/Controllers/DataLoadController.cs
Normal file
65
StockWebHistory/Controllers/DataLoadController.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using StockWebHistory.Services;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Controllers
|
||||||
|
{
|
||||||
|
public class DataLoadController : Controller
|
||||||
|
{
|
||||||
|
private readonly IFileProcessingService _processingService;
|
||||||
|
|
||||||
|
public DataLoadController(IFileProcessingService processingService)
|
||||||
|
{
|
||||||
|
_processingService = processingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Files()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> UploadAndProcess(IFormFile file)
|
||||||
|
{
|
||||||
|
// 1. Grundläggande validering
|
||||||
|
if (file == null || file.Length == 0)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Vänligen välj en giltig fil.";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validera filtillägg om det behövs (t.ex. endast .csv)
|
||||||
|
var allowedExtensions = new[] { ".csv", ".json", ".xlsx" };
|
||||||
|
var extension = System.IO.Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||||
|
if (!allowedExtensions.Contains(extension))
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Ogiltigt filformat.";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 2. Skicka filströmmen direkt till processingservicen
|
||||||
|
using (var stream = file.OpenReadStream())
|
||||||
|
{
|
||||||
|
int rowsProcessed = await _processingService.ProcessFileAsync(stream, extension);
|
||||||
|
TempData["Success"] = $"Klart! {rowsProcessed} rader har sparats i databasen.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
// Logga felet i din loggleda
|
||||||
|
TempData["Error"] = $"Ett fel uppstod vid bearbetningen: {ex.Message}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,24 @@ namespace StockWebHistory.Data
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
public DbSet<TransactionNote> TransactionNotes { get; set; }
|
public DbSet<TransactionNote> TransactionNotes { get; set; }
|
||||||
public DbSet<StockEquity> StockEquities { get; set; }
|
public DbSet<StockEquity> Stocks { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
override protected void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
modelBuilder.Entity<TransactionNote>()
|
||||||
|
.HasIndex(t => new { t.StockCode, t.TransactionDate })
|
||||||
|
.IsUnique(true);
|
||||||
|
modelBuilder.Entity<StockEquity>()
|
||||||
|
.HasIndex(s => new { s.Bought })
|
||||||
|
.IsUnique(false);
|
||||||
|
modelBuilder.Entity<StockEquity>()
|
||||||
|
.HasIndex(s => new { s.Sold })
|
||||||
|
.IsUnique(false);
|
||||||
|
modelBuilder.Entity<StockEquity>()
|
||||||
|
.HasIndex(s => new { s.Bought, s.StockCode, s.Quantity })
|
||||||
|
.IsUnique(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
109
StockWebHistory/Migrations/20260909152107_initialcreate.Designer.cs
generated
Normal file
109
StockWebHistory/Migrations/20260909152107_initialcreate.Designer.cs
generated
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(StockWebDbContext))]
|
||||||
|
[Migration("20260909152107_initialcreate")]
|
||||||
|
partial class initialcreate
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.StockEquity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Bought")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("BoughtPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Sold")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("SoldPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SoldQuant")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Bought");
|
||||||
|
|
||||||
|
b.HasIndex("Sold");
|
||||||
|
|
||||||
|
b.HasIndex("Bought", "StockCode", "Quantity");
|
||||||
|
|
||||||
|
b.ToTable("StockEquities");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.TransactionNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("AmountToPay")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("Commission")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("ExchangeRate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("StockPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StockQuantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalAmount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TransactionDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TransactionType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StockCode", "TransactionDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TransactionNotes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
StockWebHistory/Migrations/20260909152107_initialcreate.cs
Normal file
87
StockWebHistory/Migrations/20260909152107_initialcreate.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class initialcreate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "StockEquities",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
StockCode = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Bought = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
BoughtPrice = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
Quantity = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Sold = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
SoldPrice = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
SoldQuant = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_StockEquities", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TransactionNotes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
TransactionDate = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
TransactionType = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
StockCode = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
StockPrice = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
ExchangeRate = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
Currency = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
StockQuantity = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
TotalAmount = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
Commission = table.Column<decimal>(type: "TEXT", nullable: false),
|
||||||
|
AmountToPay = table.Column<decimal>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TransactionNotes", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StockEquities_Bought",
|
||||||
|
table: "StockEquities",
|
||||||
|
column: "Bought");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StockEquities_Bought_StockCode_Quantity",
|
||||||
|
table: "StockEquities",
|
||||||
|
columns: new[] { "Bought", "StockCode", "Quantity" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StockEquities_Sold",
|
||||||
|
table: "StockEquities",
|
||||||
|
column: "Sold");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TransactionNotes_StockCode_TransactionDate",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
columns: new[] { "StockCode", "TransactionDate" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "StockEquities");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TransactionNotes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
StockWebHistory/Migrations/20260911143047_ReconcilingMigration.Designer.cs
generated
Normal file
109
StockWebHistory/Migrations/20260911143047_ReconcilingMigration.Designer.cs
generated
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(StockWebDbContext))]
|
||||||
|
[Migration("20260911143047_ReconcilingMigration")]
|
||||||
|
partial class ReconcilingMigration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.StockEquity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Bought")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("BoughtPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Sold")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("SoldPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SoldQuant")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Bought");
|
||||||
|
|
||||||
|
b.HasIndex("Sold");
|
||||||
|
|
||||||
|
b.HasIndex("Bought", "StockCode", "Quantity");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.TransactionNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("AmountToPay")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("Commission")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("ExchangeRate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("StockPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StockQuantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("TotalAmount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TransactionDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TransactionType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StockCode", "TransactionDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TransactionNotes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ReconcilingMigration : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_StockEquities",
|
||||||
|
table: "StockEquities");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "StockEquities",
|
||||||
|
newName: "Stocks");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_StockEquities_Sold",
|
||||||
|
table: "Stocks",
|
||||||
|
newName: "IX_Stocks_Sold");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_StockEquities_Bought_StockCode_Quantity",
|
||||||
|
table: "Stocks",
|
||||||
|
newName: "IX_Stocks_Bought_StockCode_Quantity");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_StockEquities_Bought",
|
||||||
|
table: "Stocks",
|
||||||
|
newName: "IX_Stocks_Bought");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_Stocks",
|
||||||
|
table: "Stocks",
|
||||||
|
column: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_Stocks",
|
||||||
|
table: "Stocks");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "Stocks",
|
||||||
|
newName: "StockEquities");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_Stocks_Sold",
|
||||||
|
table: "StockEquities",
|
||||||
|
newName: "IX_StockEquities_Sold");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_Stocks_Bought_StockCode_Quantity",
|
||||||
|
table: "StockEquities",
|
||||||
|
newName: "IX_StockEquities_Bought_StockCode_Quantity");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_Stocks_Bought",
|
||||||
|
table: "StockEquities",
|
||||||
|
newName: "IX_StockEquities_Bought");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_StockEquities",
|
||||||
|
table: "StockEquities",
|
||||||
|
column: "Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
StockWebHistory/Migrations/20260912145310_decimal_can_be_nullable.Designer.cs
generated
Normal file
109
StockWebHistory/Migrations/20260912145310_decimal_can_be_nullable.Designer.cs
generated
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(StockWebDbContext))]
|
||||||
|
[Migration("20260912145310_decimal_can_be_nullable")]
|
||||||
|
partial class decimal_can_be_nullable
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.StockEquity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Bought")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("BoughtPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Sold")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("SoldPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SoldQuant")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Bought");
|
||||||
|
|
||||||
|
b.HasIndex("Sold");
|
||||||
|
|
||||||
|
b.HasIndex("Bought", "StockCode", "Quantity");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.TransactionNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal?>("AmountToPay")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Commission")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("ExchangeRate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("StockPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StockQuantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal?>("TotalAmount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TransactionDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TransactionType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StockCode", "TransactionDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TransactionNotes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class decimal_can_be_nullable : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "TotalAmount",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "Commission",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "AmountToPay",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "TotalAmount",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0m,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "Commission",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0m,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "AmountToPay",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0m,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
StockWebHistory/Migrations/20260913143435_decimal_can_be_nullable_more.Designer.cs
generated
Normal file
109
StockWebHistory/Migrations/20260913143435_decimal_can_be_nullable_more.Designer.cs
generated
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(StockWebDbContext))]
|
||||||
|
[Migration("20260913143435_decimal_can_be_nullable_more")]
|
||||||
|
partial class decimal_can_be_nullable_more
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.StockEquity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Bought")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("BoughtPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Sold")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("SoldPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SoldQuant")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Bought");
|
||||||
|
|
||||||
|
b.HasIndex("Sold");
|
||||||
|
|
||||||
|
b.HasIndex("Bought", "StockCode", "Quantity");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.TransactionNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal?>("AmountToPay")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Commission")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ExchangeRate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("StockPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StockQuantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal?>("TotalAmount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TransactionDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TransactionType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StockCode", "TransactionDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TransactionNotes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class decimal_can_be_nullable_more : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "ExchangeRate",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<decimal>(
|
||||||
|
name: "ExchangeRate",
|
||||||
|
table: "TransactionNotes",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0m,
|
||||||
|
oldClrType: typeof(decimal),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
106
StockWebHistory/Migrations/StockWebDbContextModelSnapshot.cs
Normal file
106
StockWebHistory/Migrations/StockWebDbContextModelSnapshot.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace StockWebHistory.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(StockWebDbContext))]
|
||||||
|
partial class StockWebDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.StockEquity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Bought")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("BoughtPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Quantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Sold")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("SoldPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SoldQuant")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Bought");
|
||||||
|
|
||||||
|
b.HasIndex("Sold");
|
||||||
|
|
||||||
|
b.HasIndex("Bought", "StockCode", "Quantity");
|
||||||
|
|
||||||
|
b.ToTable("Stocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("StockWebHistory.Models.TransactionNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal?>("AmountToPay")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal?>("Commission")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Currency")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal?>("ExchangeRate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("StockCode")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<decimal>("StockPrice")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StockQuantity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal?>("TotalAmount")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TransactionDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("TransactionType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("StockCode", "TransactionDate")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TransactionNotes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
StockWebHistory/Models/FileUploadViewModel.cs
Normal file
13
StockWebHistory/Models/FileUploadViewModel.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Models;
|
||||||
|
|
||||||
|
public class FileUploadViewModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "Vänligen välj en fil")]
|
||||||
|
[Display(Name = "Välj fil")]
|
||||||
|
public IFormFile File { get; set; }
|
||||||
|
|
||||||
|
// Du kan även lägga till fler fält om du vill skicka med mer data i formuläret
|
||||||
|
public string Description { get; set; }
|
||||||
|
}
|
||||||
@ -7,12 +7,12 @@ public class TransactionNote
|
|||||||
public string? TransactionType { get; set; }
|
public string? TransactionType { get; set; }
|
||||||
public string? StockCode { get; set; }
|
public string? StockCode { get; set; }
|
||||||
public decimal StockPrice { get; set; }
|
public decimal StockPrice { get; set; }
|
||||||
public decimal ExchangeRate { get; set; }
|
public decimal? ExchangeRate { get; set; }
|
||||||
public string Currency { get; set; }
|
public string Currency { get; set; }
|
||||||
public int StockQuantity { get; set; }
|
public int StockQuantity { get; set; }
|
||||||
public decimal TotalAmount { get; set; }
|
public decimal? TotalAmount { get; set; }
|
||||||
public decimal Commission { get; set; }
|
public decimal? Commission { get; set; }
|
||||||
public decimal AmountToPay { get; set; }
|
public decimal? AmountToPay { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
32
StockWebHistory/Models/TransactionNoteMap.cs
Normal file
32
StockWebHistory/Models/TransactionNoteMap.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using CsvHelper.Configuration;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Models
|
||||||
|
{
|
||||||
|
public class TransactionNoteMap : ClassMap<TransactionNote>
|
||||||
|
{
|
||||||
|
public TransactionNoteMap()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
// Ignorera Id eftersom databasen automatiskt genererar det (Auto-increment)
|
||||||
|
Map(m => m.Id).Ignore();
|
||||||
|
// Matcha CSV-kolumner mot C#-egenskaper
|
||||||
|
Map(m => m.StockCode).Name(@"Värdepapper/beskrivning", @"Värdepapper"); // Exemplet kollar flera alternativa namn
|
||||||
|
Map(m => m.TransactionDate).Name("Datum", "Date", "Affärsdag");
|
||||||
|
Map(m => m.TransactionType).Name("Typ av transaktion", "Transaktion");
|
||||||
|
Map(m => m.StockQuantity).Name("Antal", "Innehavsförändring");
|
||||||
|
Map(m => m.StockPrice).Name("Kurs");
|
||||||
|
Map(m => m.TotalAmount).Name("Belopp", "Likvidbelopp (Betalvaluta)");
|
||||||
|
Map(m => m.Currency).Name("Instrumentvaluta", @"Noteringsvaluta");
|
||||||
|
Map(m => m.Commission).Name("Courtage")
|
||||||
|
.TypeConverterOption
|
||||||
|
.NullValues(string.Empty, " ", "NULL", "null");
|
||||||
|
Map(m => m.ExchangeRate).Name("Valutakurs", "Växlingskurs")
|
||||||
|
.TypeConverterOption
|
||||||
|
.NullValues(string.Empty, " ", "NULL", "null");
|
||||||
|
Map(m => m.AmountToPay).Name("Belopp", "Likvidbelopp (Betalvaluta)")
|
||||||
|
.TypeConverterOption
|
||||||
|
.NullValues(string.Empty, " ", "NULL", "null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using StockWebHistory.Data;
|
using StockWebHistory.Data;
|
||||||
|
using StockWebHistory.Services;
|
||||||
|
|
||||||
namespace StockWebHistory
|
namespace StockWebHistory
|
||||||
{
|
{
|
||||||
@ -12,12 +14,24 @@ namespace StockWebHistory
|
|||||||
// Add services to the container.x
|
// Add services to the container.x
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddDbContext<StockWebDbContext>(options =>
|
builder.Services.AddDbContext<StockWebDbContext>(options =>
|
||||||
options.UseInMemoryDatabase("StockWebHistoryDB")
|
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))
|
||||||
|
.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
builder.Services.AddScoped<ITransactionNotesServices, TransactionNotesServices>();
|
||||||
|
builder.Services.AddScoped<IStockEquityServices, StockEquityServices>();
|
||||||
|
builder.Services.AddScoped<IFileProcessingService, FileProcessingService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<StockWebDbContext>();
|
||||||
|
db.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
|
|||||||
145
StockWebHistory/Services/FileProcessingService.cs
Normal file
145
StockWebHistory/Services/FileProcessingService.cs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
using StockWebHistory.Models;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Services;
|
||||||
|
|
||||||
|
public class FileProcessingService : IFileProcessingService
|
||||||
|
{
|
||||||
|
private readonly StockWebDbContext _db;
|
||||||
|
private readonly ILogger<FileProcessingService> _logger;
|
||||||
|
private readonly ITransactionNotesServices _notesServices;
|
||||||
|
|
||||||
|
public FileProcessingService(StockWebDbContext db,
|
||||||
|
ILogger<FileProcessingService> logger,
|
||||||
|
ITransactionNotesServices notesServices)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_logger = logger;
|
||||||
|
_notesServices = notesServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> ProcessFileAsync(Stream stream, string fileExtension)
|
||||||
|
{
|
||||||
|
var recordsToInsert = new List<TransactionNote>();
|
||||||
|
bool retry = true;
|
||||||
|
// Exempel för CSV-läsning med StreamReader
|
||||||
|
if (fileExtension == ".csv")
|
||||||
|
{
|
||||||
|
|
||||||
|
var config = new CsvConfiguration(new CultureInfo("sv-SE")) // Hanterar t.ex. komma som decimalskiljetecken
|
||||||
|
{
|
||||||
|
HasHeaderRecord = true,
|
||||||
|
TrimOptions = TrimOptions.Trim,
|
||||||
|
MissingFieldFound = null, // Ignorera om något fält saknas istället för att krascha
|
||||||
|
HeaderValidated = null, // Ignorera om någon header inte matchar exakt
|
||||||
|
Delimiter = ";" // Ange avgränsare, t.ex. komma eller semikolon
|
||||||
|
};
|
||||||
|
|
||||||
|
// Om din CSV-fil använder KOMMA (,) som decimaltecken:
|
||||||
|
var configKomma = new CsvConfiguration(new CultureInfo("sv-SE"));
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
// Om din CSV-fil använder semikolon (;) istället för komma (,)
|
||||||
|
// kan du automatiskt upptäcka eller sätta avdelaren så här:
|
||||||
|
/*
|
||||||
|
using (var csvReader = new CsvReader(reader, config))
|
||||||
|
{
|
||||||
|
csvReader.Context.RegisterClassMap<StockPriceMap>();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
using (var csv = new CsvReader(reader, config))
|
||||||
|
{
|
||||||
|
// Registrera din ClassMap om kolumnnamnen skiljer sig från C#-modellen
|
||||||
|
csv.Context.RegisterClassMap<TransactionNoteMap>();
|
||||||
|
|
||||||
|
// 2. Läsa in posterna asynkront
|
||||||
|
var records = new List<TransactionNote>();
|
||||||
|
|
||||||
|
await foreach (var record in csv.GetRecordsAsync<TransactionNote>())
|
||||||
|
{
|
||||||
|
records.Add(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (records.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Spara alla poster till databasen i en batch med EF Core
|
||||||
|
foreach (var record in records)
|
||||||
|
{
|
||||||
|
//recordsToInsert.Add(record);
|
||||||
|
while (retry)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _db.TransactionNotes.AddAsync(record);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
retry = false; // Om det lyckas, sätt retry till false
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (ex.InnerException is SqliteException sqlite && sqlite.SqliteErrorCode == 19)
|
||||||
|
{
|
||||||
|
_logger.LogError("Fel vid sparande till databas (unik nyckel): " + ex.Message + "{" + record.ToString() + "}");
|
||||||
|
if (await check_and_correct_table_unity(record))
|
||||||
|
retry = true; // Om tabellen korrigerades, försök igen
|
||||||
|
else
|
||||||
|
retry = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Fel vid sparande till databas: " + ex.Message);
|
||||||
|
retry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retry = true; // Återställ retry för nästa record
|
||||||
|
}
|
||||||
|
return records.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spara alla rader i databasen i en batch
|
||||||
|
if (recordsToInsert.Count > 0)
|
||||||
|
{
|
||||||
|
await _db.TransactionNotes.AddRangeAsync(recordsToInsert);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return recordsToInsert.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> check_and_correct_table_unity(TransactionNote localTrans)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
if (string.IsNullOrEmpty(localTrans.StockCode))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var jmfPost = await _notesServices.GetByIndexAsync(localTrans.StockCode, localTrans.TransactionDate);
|
||||||
|
if (jmfPost != null)
|
||||||
|
{
|
||||||
|
if (jmfPost.TransactionType == localTrans.TransactionType
|
||||||
|
&& jmfPost.StockPrice == localTrans.StockPrice
|
||||||
|
&& jmfPost.StockQuantity == localTrans.StockQuantity
|
||||||
|
&& jmfPost.AmountToPay == localTrans.AmountToPay
|
||||||
|
)
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DateTime transDatTemp = localTrans.TransactionDate.AddSeconds(5);
|
||||||
|
localTrans.TransactionDate = transDatTemp;
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
134
StockWebHistory/Services/HandleCsvNotes.cs
Normal file
134
StockWebHistory/Services/HandleCsvNotes.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
using StockWebHistory.Models;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace StockWebHistory;
|
||||||
|
|
||||||
|
public class HandleCsvNotes : IHandleCsvNotes
|
||||||
|
{
|
||||||
|
|
||||||
|
private List<string> lines = new();
|
||||||
|
private string[] Header_table = new string[0];
|
||||||
|
|
||||||
|
public List<TransactionNote> GetTransactionNotes(string filePath)
|
||||||
|
{
|
||||||
|
lines = File.ReadAllLines(filePath).ToList();
|
||||||
|
Header_table = lines[0].Split(';');
|
||||||
|
var transactionNotes = new List<TransactionNote>();
|
||||||
|
foreach (var line in lines.Skip(1))
|
||||||
|
{
|
||||||
|
var values = line.Split(';');
|
||||||
|
var test = values[NamePos(Header_table, "Typ av transaktion")];
|
||||||
|
if (test == "Köp" || test == "Sälj")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var transactionNote = new TransactionNote
|
||||||
|
{
|
||||||
|
TransactionDate = DateTime.Parse(values[NamePos(Header_table, "Datum")]),
|
||||||
|
TransactionType = values[NamePos(Header_table, "Typ av transaktion")] == "Köp" ? "KÖP" : "SÄLJ",
|
||||||
|
StockCode = values[NamePos(Header_table, @"Värdepapper/beskrivning")],
|
||||||
|
StockPrice = decimal.Parse((values[NamePos(Header_table, "Kurs")] == "") ? "0" : values[NamePos(Header_table, "Kurs")]),
|
||||||
|
Currency = values[NamePos(Header_table, @"Instrumentvaluta")],
|
||||||
|
StockQuantity = Math.Abs(ParseCsvNumber(values[NamePos(Header_table, "Antal")])),
|
||||||
|
ExchangeRate = decimal.Parse((values[NamePos(Header_table, "Valutakurs")] == "") ? "0" : "0" + values[NamePos(Header_table, "Valutakurs")]),
|
||||||
|
Commission = decimal.Parse((values[NamePos(Header_table, "Courtage")] == "") ? "0" : "0" + values[NamePos(Header_table, "Courtage")]),
|
||||||
|
TotalAmount = Math.Abs(decimal.Parse((values[NamePos(Header_table, "Belopp")] == "") ? "0" : values[NamePos(Header_table, "Belopp")])) - decimal.Parse((values[NamePos(Header_table, "Courtage")] == "") ? "0" : values[NamePos(Header_table, "Courtage")]),
|
||||||
|
AmountToPay = Math.Abs(decimal.Parse((values[NamePos(Header_table, "Belopp")] == "") ? "0" : values[NamePos(Header_table, "Belopp")]))
|
||||||
|
};
|
||||||
|
transactionNotes.Add(transactionNote);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Warning: '{ex.Message}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transactionNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TransactionNote> GetSHBTransactionNotes(string filePath)
|
||||||
|
{
|
||||||
|
lines = File.ReadAllLines(filePath).ToList();
|
||||||
|
Header_table = lines[0].Split(';');
|
||||||
|
var transactionNotes = new List<TransactionNote>();
|
||||||
|
foreach (var line in lines.Skip(1))
|
||||||
|
{
|
||||||
|
var values = line.Split(';');
|
||||||
|
var test = values[NamePos(Header_table, "Transaktion")];
|
||||||
|
if (test == "Köp" || test == "Sälj")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var transactionNote = new TransactionNote
|
||||||
|
{
|
||||||
|
TransactionDate = DateTime.Parse(values[NamePos(Header_table, "Affärsdag")]),
|
||||||
|
TransactionType = values[NamePos(Header_table, "Transaktion")] == "Köp" ? "KÖP" : "SÄLJ",
|
||||||
|
StockCode = values[NamePos(Header_table, @"Värdepapper")],
|
||||||
|
StockPrice = decimal.Parse((values[NamePos(Header_table, "Kurs")] == "") ? "0" : values[NamePos(Header_table, "Kurs")]),
|
||||||
|
Currency = values[NamePos(Header_table, @"Noteringsvaluta")],
|
||||||
|
StockQuantity = Math.Abs(ParseCsvNumber(values[NamePos(Header_table, "Innehavsförändring")])),
|
||||||
|
ExchangeRate = decimal.Parse((values[NamePos(Header_table, "Växlingskurs")] == "") ? "0" : "0" + values[NamePos(Header_table, "Växlingskurs")]),
|
||||||
|
Commission = decimal.Parse((values[NamePos(Header_table, "Courtage")] == "") ? "0" : "0" + values[NamePos(Header_table, "Courtage")]),
|
||||||
|
TotalAmount = Math.Abs(decimal.Parse((values[NamePos(Header_table, "Likvidbelopp (Betalvaluta)")] == "") ? "0" : values[NamePos(Header_table, "Likvidbelopp (Betalvaluta)")])) - decimal.Parse((values[NamePos(Header_table, "Courtage")] == "") ? "0" : values[NamePos(Header_table, "Courtage")]),
|
||||||
|
AmountToPay = Math.Abs(decimal.Parse((values[NamePos(Header_table, "Likvidbelopp (Betalvaluta)")] == "") ? "0" : values[NamePos(Header_table, "Likvidbelopp (Betalvaluta)")]))
|
||||||
|
};
|
||||||
|
transactionNotes.Add(transactionNote);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Warning: '{ex.Message}'");
|
||||||
|
// throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transactionNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private int NamePos(string[] h_table, string value)
|
||||||
|
{
|
||||||
|
var pos = Array.IndexOf(h_table, value);
|
||||||
|
if (pos != -1)
|
||||||
|
{
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Warning: Unable to find '{value}'");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int ParseCsvNumber(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
|
throw new ArgumentException("Input is empty");
|
||||||
|
|
||||||
|
// Försök först som int (snabbast)
|
||||||
|
if (int.TryParse(input, out int intValue))
|
||||||
|
return intValue;
|
||||||
|
|
||||||
|
// Försök som decimal (t.ex. "123.45" eller "123,45")
|
||||||
|
if (decimal.TryParse(
|
||||||
|
input,
|
||||||
|
NumberStyles.Any,
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
out decimal decValue))
|
||||||
|
{
|
||||||
|
return (int)Math.Round(decValue, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Försök med svensk kultur (CSV från Excel brukar använda komma)
|
||||||
|
if (decimal.TryParse(
|
||||||
|
input,
|
||||||
|
NumberStyles.Any,
|
||||||
|
new CultureInfo("sv-SE"),
|
||||||
|
out decValue))
|
||||||
|
{
|
||||||
|
return (int)Math.Round(decValue, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FormatException($"Kan inte tolka talet: '{input}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
7
StockWebHistory/Services/IFileProcessingService.cs
Normal file
7
StockWebHistory/Services/IFileProcessingService.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace StockWebHistory.Services
|
||||||
|
{
|
||||||
|
public interface IFileProcessingService
|
||||||
|
{
|
||||||
|
Task<int> ProcessFileAsync(Stream stream, string fileExtension);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
StockWebHistory/Services/IHandleCsvNotes.cs
Normal file
10
StockWebHistory/Services/IHandleCsvNotes.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using StockWebHistory.Models;
|
||||||
|
|
||||||
|
namespace StockWebHistory
|
||||||
|
{
|
||||||
|
public interface IHandleCsvNotes
|
||||||
|
{
|
||||||
|
List<TransactionNote> GetTransactionNotes(string filePath);
|
||||||
|
List<TransactionNote> GetSHBTransactionNotes(string filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
StockWebHistory/Services/IStockEquityServices.cs
Normal file
13
StockWebHistory/Services/IStockEquityServices.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using StockWebHistory.Models;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Services
|
||||||
|
{
|
||||||
|
public interface IStockEquityServices
|
||||||
|
{
|
||||||
|
Task<List<StockEquity>> GenerateStockVision();
|
||||||
|
Task<List<StockEquity>> GetAllAsync();
|
||||||
|
Task<List<StockEquity>> GetLatestSoldInfoAsync();
|
||||||
|
Task<List<StockEquity>> GetAllAsync(DateTime equityDate);
|
||||||
|
Task<List<StockEquity>> GetByCodeAsync(string stockCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
StockWebHistory/Services/ITransactionNotesServices.cs
Normal file
16
StockWebHistory/Services/ITransactionNotesServices.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using StockWebHistory.Models;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Services
|
||||||
|
{
|
||||||
|
public interface ITransactionNotesServices
|
||||||
|
{
|
||||||
|
Task AddAsync(TransactionNote transactionNote);
|
||||||
|
Task DeleteAsync(int id);
|
||||||
|
Task<List<string>> GetAllExistingStockCodes();
|
||||||
|
Task<List<TransactionNote>> GetAllAsync();
|
||||||
|
Task<TransactionNote?> GetByIdAsync(int id);
|
||||||
|
Task<TransactionNote?> GetByIndexAsync(string stockCode, DateTime transDate);
|
||||||
|
Task<List<TransactionNote>> GetByStockCodeAsync(string stockCode);
|
||||||
|
Task UpdateAsync(TransactionNote transactionNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
229
StockWebHistory/Services/StockEquityServices.cs
Normal file
229
StockWebHistory/Services/StockEquityServices.cs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
using StockWebHistory.Models;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Services;
|
||||||
|
|
||||||
|
public class StockEquityServices : IStockEquityServices
|
||||||
|
{
|
||||||
|
private readonly StockWebDbContext _dbContext;
|
||||||
|
private readonly ITransactionNotesServices _notesServices;
|
||||||
|
|
||||||
|
public StockEquityServices(StockWebDbContext dbContext, ITransactionNotesServices notesServices)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_notesServices = notesServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StockEquity>> GetAllAsync()
|
||||||
|
{
|
||||||
|
return await _dbContext.Stocks.ToListAsync();
|
||||||
|
}
|
||||||
|
public async Task<List<StockEquity>> GetAllAsync(DateTime equityDate)
|
||||||
|
{
|
||||||
|
return await _dbContext.Stocks
|
||||||
|
.Where(s => s.Bought <= equityDate && (s.Sold > equityDate || s.SoldQuant == 0))
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StockEquity>> GetByCodeAsync(string stockCode)
|
||||||
|
{
|
||||||
|
var searchedNotes = await _notesServices.GetByStockCodeAsync(stockCode);
|
||||||
|
|
||||||
|
return await _dbContext.Stocks
|
||||||
|
.Where(s => s.StockCode == stockCode)
|
||||||
|
.OrderBy(s => s.Bought)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StockEquity>> GetLatestSoldInfoAsync()
|
||||||
|
{
|
||||||
|
var today = DateTime.Today;
|
||||||
|
|
||||||
|
var maxPerCode = _dbContext.Stocks
|
||||||
|
.Where(s => s.Sold < today)
|
||||||
|
.GroupBy(s => s.StockCode)
|
||||||
|
.Select(g => new { StockCode = g.Key, MaxSold = g.Max(x => x.Sold) });
|
||||||
|
|
||||||
|
var minBoughtPerCodeAndSold = _dbContext.Stocks
|
||||||
|
.Where(s => s.Sold < today)
|
||||||
|
.Join(maxPerCode,
|
||||||
|
s => new { s.StockCode, s.Sold },
|
||||||
|
m => new { m.StockCode, Sold = m.MaxSold },
|
||||||
|
(s, m) => new { s.StockCode, s.Sold, s.BoughtPrice })
|
||||||
|
.GroupBy(x => new { x.StockCode, x.Sold })
|
||||||
|
.Select(g => new { g.Key.StockCode, g.Key.Sold, MinBought = g.Min(x => (double)x.BoughtPrice) });
|
||||||
|
|
||||||
|
var result = from s in _dbContext.Stocks
|
||||||
|
where s.Sold < today
|
||||||
|
join mm in minBoughtPerCodeAndSold
|
||||||
|
on new { s.StockCode, s.Sold, BoughtPrice = (double)s.BoughtPrice }
|
||||||
|
equals new { mm.StockCode, Sold = mm.Sold, BoughtPrice = mm.MinBought }
|
||||||
|
select s;
|
||||||
|
|
||||||
|
return await result.ToListAsync();
|
||||||
|
|
||||||
|
//var query =
|
||||||
|
// from s in _dbContext.Stocks
|
||||||
|
// where s.Sold < today
|
||||||
|
// group s by s.StockCode into g
|
||||||
|
// let maxSold = g.Max(x => x.Sold)
|
||||||
|
// let minBought = g.Where(x => x.Sold == maxSold).Min(x => x.BoughtPrice)
|
||||||
|
// from row in g
|
||||||
|
// where row.Sold == maxSold && row.BoughtPrice == minBought
|
||||||
|
// select row;
|
||||||
|
|
||||||
|
//return await query.ToListAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skapar en ny lista med aktieinnehav baserat på transaktionsnoteringar.
|
||||||
|
/// Den rensar först den befintliga listan och bygger sedan upp den igen genom att
|
||||||
|
/// utgående från alla befintliga notor skapa en lista med aktieinnehav.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<List<StockEquity>> GenerateStockVision()
|
||||||
|
{
|
||||||
|
var oldStock = "OldStock";
|
||||||
|
var nyAktie = false;
|
||||||
|
List<StockEquity> equityWarningList = new List<StockEquity>();
|
||||||
|
|
||||||
|
await _dbContext.Stocks.ExecuteDeleteAsync();
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
var existingNotes = await _notesServices.GetAllAsync();
|
||||||
|
|
||||||
|
foreach (var note in existingNotes
|
||||||
|
.OrderBy(e => e.StockCode)
|
||||||
|
.ThenBy(e => e.TransactionDate)
|
||||||
|
//.OrderBy(e => e.TransactionType)
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
// Kontrollera om TransactionType är null och StockCode inte är null
|
||||||
|
// (om det är fallet, hoppa över denna iteration)
|
||||||
|
Debug.WriteLine(note.ToString());
|
||||||
|
|
||||||
|
nyAktie = false;
|
||||||
|
if (note.StockCode != null &&
|
||||||
|
note.StockCode != oldStock)
|
||||||
|
{
|
||||||
|
oldStock = note.StockCode;
|
||||||
|
nyAktie = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (note.TransactionType == null && note.StockCode != null) { }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Kontrollera om TransactionType är en köpnotering
|
||||||
|
// (ignorera null-värden)
|
||||||
|
|
||||||
|
if (note.TransactionType.ToLower() == "köp")
|
||||||
|
{
|
||||||
|
// Skapa en ny aktieinnehavspost baserat på köpnoteringen
|
||||||
|
|
||||||
|
var equity = new StockEquity
|
||||||
|
{
|
||||||
|
StockCode = note.StockCode,
|
||||||
|
Bought = note.TransactionDate,
|
||||||
|
BoughtPrice = note.StockPrice,
|
||||||
|
Quantity = note.StockQuantity,
|
||||||
|
Sold = DateTime.MinValue,
|
||||||
|
SoldPrice = 0m,
|
||||||
|
SoldQuant = 0
|
||||||
|
};
|
||||||
|
_dbContext.Stocks.Add(equity);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
// Debug.WriteLine($"Köpnotering: {equity.StockCode} | {equity.Bought} | {equity.BoughtPrice} | {equity.Quantity} | {equity.BoughtPrice * equity.Quantity}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kontrollera om TransactionType är en säljnotering
|
||||||
|
|
||||||
|
else if (note.TransactionType.ToLower() == "sälj")
|
||||||
|
{
|
||||||
|
if (nyAktie)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("<--" + note.ToString());
|
||||||
|
|
||||||
|
if (note.StockCode != null)
|
||||||
|
{
|
||||||
|
equityWarningList.Add(new StockEquity
|
||||||
|
{
|
||||||
|
//StockCode = note.StockCode,
|
||||||
|
//Bought = DateTime.MinValue,
|
||||||
|
//BoughtPrice = 0,
|
||||||
|
//Quantity = 0,
|
||||||
|
//Sold = note.TransactionDate,
|
||||||
|
//SoldPrice = note.StockPrice,
|
||||||
|
//SoldQuant = note.StockQuantity
|
||||||
|
StockCode = note.StockCode,
|
||||||
|
Bought = note.TransactionDate,
|
||||||
|
BoughtPrice = note.StockPrice * -1,
|
||||||
|
Quantity = note.StockQuantity,
|
||||||
|
Sold = DateTime.MinValue,
|
||||||
|
SoldPrice = 0m,
|
||||||
|
SoldQuant = 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var equityList = await _dbContext.Stocks
|
||||||
|
.Where(s => s.StockCode == note.StockCode && s.SoldQuant == 0)
|
||||||
|
.OrderBy(s => s.Quantity).ToListAsync();
|
||||||
|
|
||||||
|
if (equityList != null)
|
||||||
|
{
|
||||||
|
var soldQuant = Math.Abs(note.StockQuantity);
|
||||||
|
foreach (var eq in equityList.OrderBy(e => e.Quantity))
|
||||||
|
{
|
||||||
|
if (soldQuant == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (eq.Quantity <= soldQuant)
|
||||||
|
{
|
||||||
|
eq.Sold = note.TransactionDate;
|
||||||
|
eq.SoldPrice = note.StockPrice;
|
||||||
|
eq.SoldQuant = eq.Quantity;
|
||||||
|
soldQuant -= eq.Quantity;
|
||||||
|
_dbContext.Stocks.Update(eq);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
//Debug.WriteLine($"Säljnotering: {eq.StockCode} | {eq.Sold} | {eq.SoldPrice} | {eq.SoldQuant}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var splitEquity = new StockEquity
|
||||||
|
{
|
||||||
|
StockCode = eq.StockCode,
|
||||||
|
Bought = eq.Bought,
|
||||||
|
BoughtPrice = eq.BoughtPrice,
|
||||||
|
Quantity = soldQuant,
|
||||||
|
Sold = note.TransactionDate,
|
||||||
|
SoldPrice = note.StockPrice,
|
||||||
|
SoldQuant = soldQuant
|
||||||
|
};
|
||||||
|
_dbContext.Stocks.Add(splitEquity);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
//Debug.WriteLine($"Säljnotering (split): {splitEquity.StockCode} | {splitEquity.Bought} | {splitEquity.BoughtPrice} | {splitEquity.SoldQuant} | {splitEquity.Sold} | {splitEquity.SoldPrice} | {splitEquity.SoldQuant}");
|
||||||
|
eq.Quantity -= soldQuant;
|
||||||
|
_dbContext.Stocks.Update(eq);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
//Debug.WriteLine($"Säljnotering (split): {eq.StockCode} | {eq.Bought} | {eq.BoughtPrice} | {eq.SoldQuant} | {eq.Sold} | {eq.SoldPrice} | {eq.SoldQuant}");
|
||||||
|
soldQuant = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return equityWarningList;
|
||||||
|
}
|
||||||
|
}
|
||||||
87
StockWebHistory/Services/TransactionNotesServices.cs
Normal file
87
StockWebHistory/Services/TransactionNotesServices.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using StockWebHistory.Data;
|
||||||
|
using StockWebHistory.Models;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Services;
|
||||||
|
|
||||||
|
public class TransactionNotesServices : ITransactionNotesServices
|
||||||
|
{
|
||||||
|
private readonly StockWebDbContext _dbContext;
|
||||||
|
private readonly IServiceProvider _provider;
|
||||||
|
|
||||||
|
|
||||||
|
// Parameterless constructor
|
||||||
|
public TransactionNotesServices(StockWebDbContext dbContext, IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<TransactionNote>> GetAllAsync()
|
||||||
|
{
|
||||||
|
return await _dbContext.TransactionNotes
|
||||||
|
.OrderBy(t => t.StockCode)
|
||||||
|
.ThenBy(t => t.TransactionDate)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TransactionNote?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
return await _dbContext.TransactionNotes.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<TransactionNote?> GetByIndexAsync(string stockCode, DateTime transDate)
|
||||||
|
{
|
||||||
|
return await _dbContext.TransactionNotes
|
||||||
|
.FirstOrDefaultAsync(s => s.StockCode == stockCode && s.TransactionDate == transDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<List<TransactionNote>> GetByStockCodeAsync(string stockCode)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(stockCode))
|
||||||
|
{
|
||||||
|
return new List<TransactionNote>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _dbContext.TransactionNotes.Where(t => t.StockCode == stockCode).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string?>> GetAllExistingStockCodes()
|
||||||
|
{
|
||||||
|
List<string?> stockCodes = new List<string?>();
|
||||||
|
stockCodes = await _dbContext.TransactionNotes
|
||||||
|
.Select(t => t.StockCode)
|
||||||
|
.Distinct()
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return stockCodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task AddAsync(TransactionNote transactionNote)
|
||||||
|
{
|
||||||
|
using var scope = _provider.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<StockWebDbContext>();
|
||||||
|
|
||||||
|
db.TransactionNotes.Add(transactionNote);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateAsync(TransactionNote transactionNote)
|
||||||
|
{
|
||||||
|
_dbContext.TransactionNotes.Update(transactionNote);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(int id)
|
||||||
|
{
|
||||||
|
var transactionNote = await _dbContext.TransactionNotes.FindAsync(id);
|
||||||
|
if (transactionNote != null)
|
||||||
|
{
|
||||||
|
_dbContext.TransactionNotes.Remove(transactionNote);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -7,8 +7,24 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
|
<Compile Remove="Services\HandleCsvNotes.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.3" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="StockWebHistory.db">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
BIN
StockWebHistory/StockWebHistory.db
Normal file
BIN
StockWebHistory/StockWebHistory.db
Normal file
Binary file not shown.
BIN
StockWebHistory/StockWebHistory.db-shm
Normal file
BIN
StockWebHistory/StockWebHistory.db-shm
Normal file
Binary file not shown.
BIN
StockWebHistory/StockWebHistory.db-wal
Normal file
BIN
StockWebHistory/StockWebHistory.db-wal
Normal file
Binary file not shown.
25
StockWebHistory/Views/DataLoad/Files.cshtml
Normal file
25
StockWebHistory/Views/DataLoad/Files.cshtml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@page
|
||||||
|
|
||||||
|
@model FileUploadViewModel
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2>Ladda upp och processa fil</h2>
|
||||||
|
|
||||||
|
@if (TempData["Success"] != null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-success">@TempData["Success"]</div>
|
||||||
|
}
|
||||||
|
@if (TempData["Error"] != null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">@TempData["Error"]</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form asp-action="UploadAndProcess" method="post" enctype="multipart/form-data">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="fileInput" class="form-label">Välj fil (t.ex. CSV, Excel, JSON):</label>
|
||||||
|
<input type="file" name="file" id="fileInput" class="form-control" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Ladda upp och processa</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
12
StockWebHistory/Views/DataLoad/Files.cshtml.cs
Normal file
12
StockWebHistory/Views/DataLoad/Files.cshtml.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
|
||||||
|
namespace StockWebHistory.Views.DataLoad
|
||||||
|
{
|
||||||
|
public class FilesModel : PageModel
|
||||||
|
{
|
||||||
|
public void OnGet()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
StockWebHistory/Views/DataLoad/Index.cshtml
Normal file
9
StockWebHistory/Views/DataLoad/Index.cshtml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Dataload Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="display-4">Welcome to Data Loading</h1>
|
||||||
|
<a class="btn btn-primary" asp-area="" asp-controller="DataLoad" asp-action="Files">File Types</a>
|
||||||
|
<a class="btn btn-secondary" asp-area="" asp-controller="DataLoad" asp-action="History">History</a>
|
||||||
|
</div>
|
||||||
@ -9,11 +9,11 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h1 class="display-4">Stock Notes</h1>
|
<h1 class="display-4">Stock Notes</h1>
|
||||||
<br />
|
<br />
|
||||||
<hr/>
|
<hr />
|
||||||
<h3>Total Stock Value $@ViewBag.TotalNotes</h3>
|
<h3>Total Stock Value @ViewBag.TotalNotes SEK</h3>
|
||||||
<br/>
|
<br />
|
||||||
|
|
||||||
<table class="table">
|
<table class="table small">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
@ -39,12 +39,12 @@
|
|||||||
<td>@note.TransactionType</td>
|
<td>@note.TransactionType</td>
|
||||||
<td>@note.StockCode</td>
|
<td>@note.StockCode</td>
|
||||||
<td>@note.StockPrice.ToString("F2")</td>
|
<td>@note.StockPrice.ToString("F2")</td>
|
||||||
<td>@note.ExchangeRate.ToString("F2")</td>
|
<td>@(note.ExchangeRate == null ? "" : ((decimal)note.ExchangeRate).ToString("F2", System.Globalization.CultureInfo.InvariantCulture))</td>
|
||||||
<td>@note.Currency</td>
|
<td>@note.Currency</td>
|
||||||
<td>@note.StockQuantity.ToString("F2")</td>
|
<td>@note.StockQuantity.ToString("F")</td>
|
||||||
<td>@note.TotalAmount.ToString("F2")</td>
|
<td>@(note.TotalAmount == null ? "" : ((decimal)note.TotalAmount).ToString("F2", System.Globalization.CultureInfo.InvariantCulture))</td>
|
||||||
<td>@note.Commission.ToString("F2")</td>
|
<td>@(note.Commission == null ? "" : ((decimal)note.Commission).ToString("F2", System.Globalization.CultureInfo.InvariantCulture))</td>
|
||||||
<td>@note.AmountToPay.ToString("F2")</td>
|
<td>@(note.AmountToPay == null ? "" : ((decimal)note.AmountToPay).ToString("F2", System.Globalization.CultureInfo.InvariantCulture))</td>
|
||||||
<td>
|
<td>
|
||||||
<a asp-action="CreateEditNote" asp-route-id="@note.Id">Edit</a>
|
<a asp-action="CreateEditNote" asp-route-id="@note.Id">Edit</a>
|
||||||
<a asp-action="DeleteNote" asp-route-id="@note.Id">Delete</a>
|
<a asp-action="DeleteNote" asp-route-id="@note.Id">Delete</a>
|
||||||
|
|||||||
@ -26,6 +26,10 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-dark" asp-area="" asp-controller="DataLoad" asp-action="Index">LoadingMethods</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,5 +5,8 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Data Source=StockWebHistory.db"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,22 @@
|
|||||||
|
/* Minskar grundstorleken p<> all text i hela appen */
|
||||||
|
body {
|
||||||
|
font-size: 0.875rem; /* Motsvarar ca 14px (standard <20>r annars 1rem / 16px) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Valfritt: Minska rubriker proportionerligt om du tycker de ocks<6B> <20>r f<>r stora */
|
||||||
|
h1 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user