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
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.37614.0 d17.14
|
||||
VisualStudioVersion = 17.14.37614.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StockWebHistory", "StockWebHistory\StockWebHistory.csproj", "{76B5FDA3-FD94-4E06-BB2C-8CDDE8C205DD}"
|
||||
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<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? StockCode { get; set; }
|
||||
public decimal StockPrice { get; set; }
|
||||
public decimal ExchangeRate { get; set; }
|
||||
public decimal? ExchangeRate { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public int StockQuantity { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
public decimal Commission { get; set; }
|
||||
public decimal AmountToPay { get; set; }
|
||||
public decimal? TotalAmount { get; set; }
|
||||
public decimal? Commission { get; set; }
|
||||
public decimal? AmountToPay { get; set; }
|
||||
|
||||
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.Diagnostics;
|
||||
using StockWebHistory.Data;
|
||||
using StockWebHistory.Services;
|
||||
|
||||
namespace StockWebHistory
|
||||
{
|
||||
@ -12,12 +14,24 @@ namespace StockWebHistory
|
||||
// Add services to the container.x
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
|
||||
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();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<StockWebDbContext>();
|
||||
db.Database.Migrate();
|
||||
}
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
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>
|
||||
|
||||
<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.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>
|
||||
|
||||
</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>
|
||||
@ -10,10 +10,10 @@
|
||||
<h1 class="display-4">Stock Notes</h1>
|
||||
<br />
|
||||
<hr />
|
||||
<h3>Total Stock Value $@ViewBag.TotalNotes</h3>
|
||||
<h3>Total Stock Value @ViewBag.TotalNotes SEK</h3>
|
||||
<br />
|
||||
|
||||
<table class="table">
|
||||
<table class="table small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
@ -39,12 +39,12 @@
|
||||
<td>@note.TransactionType</td>
|
||||
<td>@note.StockCode</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.StockQuantity.ToString("F2")</td>
|
||||
<td>@note.TotalAmount.ToString("F2")</td>
|
||||
<td>@note.Commission.ToString("F2")</td>
|
||||
<td>@note.AmountToPay.ToString("F2")</td>
|
||||
<td>@note.StockQuantity.ToString("F")</td>
|
||||
<td>@(note.TotalAmount == null ? "" : ((decimal)note.TotalAmount).ToString("F2", System.Globalization.CultureInfo.InvariantCulture))</td>
|
||||
<td>@(note.Commission == null ? "" : ((decimal)note.Commission).ToString("F2", System.Globalization.CultureInfo.InvariantCulture))</td>
|
||||
<td>@(note.AmountToPay == null ? "" : ((decimal)note.AmountToPay).ToString("F2", System.Globalization.CultureInfo.InvariantCulture))</td>
|
||||
<td>
|
||||
<a asp-action="CreateEditNote" asp-route-id="@note.Id">Edit</a>
|
||||
<a asp-action="DeleteNote" asp-route-id="@note.Id">Delete</a>
|
||||
|
||||
@ -26,6 +26,10 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="DataLoad" asp-action="Index">LoadingMethods</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,5 +5,8 @@
|
||||
"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 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user