From c72078db67a8cb7d87029a3908a8f9bac86ba415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tommy=20=C3=96man?= Date: Sun, 26 Jul 2026 18:58:30 +0200 Subject: [PATCH] =?UTF-8?q?Lag=20till=20entity=20SglSotckEntity=20som=20?= =?UTF-8?q?=C3=A4r=20kopia=20av=20Stockentity.=20f=C3=B6rb=C3=A4ttrsr=20ut?= =?UTF-8?q?inrn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- StockHistory/Data/AppDbContext.cs | 11 ++ StockHistory/HandleCsvNotes.cs | 2 +- ...5154136_tillaggExtraTemptabell.Designer.cs | 141 +++++++++++++++++ .../20260725154136_tillaggExtraTemptabell.cs | 56 +++++++ .../Migrations/AppDbContextModelSnapshot.cs | 39 +++++ StockHistory/Models/SglStockEquity.cs | 14 ++ StockHistory/Program.cs | 14 +- .../Services/ISglStockEquityServices.cs | 12 ++ .../Services/SqlStockEquityServices.cs | 145 ++++++++++++++++++ StockHistory/Services/StockEquityServices.cs | 12 +- StockHistory/Stockbrokerage.db | Bin 65536 -> 81920 bytes StockHistory/frmStockHistoryInit.cs | 9 +- 12 files changed, 446 insertions(+), 9 deletions(-) create mode 100644 StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.Designer.cs create mode 100644 StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.cs create mode 100644 StockHistory/Models/SglStockEquity.cs create mode 100644 StockHistory/Services/ISglStockEquityServices.cs create mode 100644 StockHistory/Services/SqlStockEquityServices.cs diff --git a/StockHistory/Data/AppDbContext.cs b/StockHistory/Data/AppDbContext.cs index 5c1c913..e15a104 100644 --- a/StockHistory/Data/AppDbContext.cs +++ b/StockHistory/Data/AppDbContext.cs @@ -12,6 +12,8 @@ public class AppDbContext : DbContext public DbSet TransactionNotes { get; set; } public DbSet Stocks { get; set; } + public DbSet SglStocks { get; set; } + override protected void OnModelCreating(ModelBuilder modelBuilder) { @@ -28,5 +30,14 @@ public class AppDbContext : DbContext modelBuilder.Entity() .HasIndex(s => new { s.Bought, s.StockCode, s.Quantity }) .IsUnique(false); + modelBuilder.Entity() + .HasIndex(s => new { s.Bought }) + .IsUnique(false); + modelBuilder.Entity() + .HasIndex(s => new { s.Sold }) + .IsUnique(false); + modelBuilder.Entity() + .HasIndex(s => new { s.Bought, s.StockCode, s.Quantity }) + .IsUnique(false); } } diff --git a/StockHistory/HandleCsvNotes.cs b/StockHistory/HandleCsvNotes.cs index dd173c4..bd120b6 100644 --- a/StockHistory/HandleCsvNotes.cs +++ b/StockHistory/HandleCsvNotes.cs @@ -22,7 +22,7 @@ public class HandleCsvNotes : IHandleCsvNotes var transactionNote = new TransactionNote { TransactionDate = DateTime.Parse(values[NamePos(Header_table, "Datum")]), - TransactionType = values[NamePos(Header_table, "Typ av transaktion")], + 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")]), StockQuantity = Math.Abs(int.Parse(values[NamePos(Header_table, "Antal")])), diff --git a/StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.Designer.cs b/StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.Designer.cs new file mode 100644 index 0000000..e24e7d7 --- /dev/null +++ b/StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.Designer.cs @@ -0,0 +1,141 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using StockHistory.Data; + +#nullable disable + +namespace StockHistory.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260725154136_tillaggExtraTemptabell")] + partial class tillaggExtraTemptabell + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); + + modelBuilder.Entity("StockHistory.Models.SglStockEquity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bought") + .HasColumnType("TEXT"); + + b.Property("BoughtPrice") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasColumnType("INTEGER"); + + b.Property("Sold") + .HasColumnType("TEXT"); + + b.Property("SoldPrice") + .HasColumnType("TEXT"); + + b.Property("SoldQuant") + .HasColumnType("INTEGER"); + + b.Property("StockCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Bought"); + + b.HasIndex("Sold"); + + b.HasIndex("Bought", "StockCode", "Quantity"); + + b.ToTable("SglStocks"); + }); + + modelBuilder.Entity("StockHistory.Models.StockEquity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bought") + .HasColumnType("TEXT"); + + b.Property("BoughtPrice") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasColumnType("INTEGER"); + + b.Property("Sold") + .HasColumnType("TEXT"); + + b.Property("SoldPrice") + .HasColumnType("TEXT"); + + b.Property("SoldQuant") + .HasColumnType("INTEGER"); + + b.Property("StockCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Bought"); + + b.HasIndex("Sold"); + + b.HasIndex("Bought", "StockCode", "Quantity"); + + b.ToTable("Stocks"); + }); + + modelBuilder.Entity("StockHistory.Models.TransactionNote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AmountToPay") + .HasColumnType("TEXT"); + + b.Property("Commission") + .HasColumnType("TEXT"); + + b.Property("StockCode") + .HasColumnType("TEXT"); + + b.Property("StockPrice") + .HasColumnType("TEXT"); + + b.Property("StockQuantity") + .HasColumnType("INTEGER"); + + b.Property("TotalAmount") + .HasColumnType("TEXT"); + + b.Property("TransactionDate") + .HasColumnType("TEXT"); + + b.Property("TransactionType") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("StockCode", "TransactionDate") + .IsUnique(); + + b.ToTable("TransactionNotes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.cs b/StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.cs new file mode 100644 index 0000000..cb568f0 --- /dev/null +++ b/StockHistory/Migrations/20260725154136_tillaggExtraTemptabell.cs @@ -0,0 +1,56 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace StockHistory.Migrations +{ + /// + public partial class tillaggExtraTemptabell : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SglStocks", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + StockCode = table.Column(type: "TEXT", nullable: false), + Bought = table.Column(type: "TEXT", nullable: false), + BoughtPrice = table.Column(type: "TEXT", nullable: false), + Quantity = table.Column(type: "INTEGER", nullable: false), + Sold = table.Column(type: "TEXT", nullable: false), + SoldPrice = table.Column(type: "TEXT", nullable: false), + SoldQuant = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SglStocks", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_SglStocks_Bought", + table: "SglStocks", + column: "Bought"); + + migrationBuilder.CreateIndex( + name: "IX_SglStocks_Bought_StockCode_Quantity", + table: "SglStocks", + columns: new[] { "Bought", "StockCode", "Quantity" }); + + migrationBuilder.CreateIndex( + name: "IX_SglStocks_Sold", + table: "SglStocks", + column: "Sold"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SglStocks"); + } + } +} diff --git a/StockHistory/Migrations/AppDbContextModelSnapshot.cs b/StockHistory/Migrations/AppDbContextModelSnapshot.cs index d8bbebd..c95d8dd 100644 --- a/StockHistory/Migrations/AppDbContextModelSnapshot.cs +++ b/StockHistory/Migrations/AppDbContextModelSnapshot.cs @@ -17,6 +17,45 @@ namespace StockHistory.Migrations #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); + modelBuilder.Entity("StockHistory.Models.SglStockEquity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bought") + .HasColumnType("TEXT"); + + b.Property("BoughtPrice") + .HasColumnType("TEXT"); + + b.Property("Quantity") + .HasColumnType("INTEGER"); + + b.Property("Sold") + .HasColumnType("TEXT"); + + b.Property("SoldPrice") + .HasColumnType("TEXT"); + + b.Property("SoldQuant") + .HasColumnType("INTEGER"); + + b.Property("StockCode") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Bought"); + + b.HasIndex("Sold"); + + b.HasIndex("Bought", "StockCode", "Quantity"); + + b.ToTable("SglStocks"); + }); + modelBuilder.Entity("StockHistory.Models.StockEquity", b => { b.Property("Id") diff --git a/StockHistory/Models/SglStockEquity.cs b/StockHistory/Models/SglStockEquity.cs new file mode 100644 index 0000000..89a83d6 --- /dev/null +++ b/StockHistory/Models/SglStockEquity.cs @@ -0,0 +1,14 @@ +namespace StockHistory.Models +{ + public class SglStockEquity + { + public int Id { get; set; } + public string StockCode { get; set; } = string.Empty; + public DateTime Bought { get; set; } + public decimal BoughtPrice { get; set; } + public int Quantity { get; set; } + public DateTime Sold { get; set; } + public decimal SoldPrice { get; set; } + public int SoldQuant { get; set; } + } +} diff --git a/StockHistory/Program.cs b/StockHistory/Program.cs index b8734ea..1042f83 100644 --- a/StockHistory/Program.cs +++ b/StockHistory/Program.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using StockHistory.Data; using StockHistory.Services; @@ -33,15 +34,22 @@ namespace StockHistory Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { - services.AddDbContext(options => - options.UseSqlite("Data Source=Stockbrokerage.db"), - ServiceLifetime.Transient); + services.AddDbContext(options => options + .UseSqlite("Data Source=Stockbrokerage.db") + .UseLoggerFactory(LoggerFactory.Create(builder => + { + builder + .AddFilter("Microsoft.EntityFrameworkCore.Database.Command", LogLevel.Warning) + .AddFilter("Microsoft.EntityFrameworkCore.Query", LogLevel.Warning); + })) + , ServiceLifetime.Transient); services.AddSingleton(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); }); diff --git a/StockHistory/Services/ISglStockEquityServices.cs b/StockHistory/Services/ISglStockEquityServices.cs new file mode 100644 index 0000000..c8d83ec --- /dev/null +++ b/StockHistory/Services/ISglStockEquityServices.cs @@ -0,0 +1,12 @@ +using StockHistory.Models; + +namespace StockHistory.Services +{ + public interface ISglStockEquityServices + { + Task GenerateStockVision(); + Task> GetAllAsync(); + Task> GetAllAsync(DateTime equityDate); + Task> GetByCodeAsync(string stockCode); + } +} diff --git a/StockHistory/Services/SqlStockEquityServices.cs b/StockHistory/Services/SqlStockEquityServices.cs new file mode 100644 index 0000000..45bc147 --- /dev/null +++ b/StockHistory/Services/SqlStockEquityServices.cs @@ -0,0 +1,145 @@ +using Microsoft.EntityFrameworkCore; +using StockHistory.Data; +using StockHistory.Models; +using System.Diagnostics; + +namespace StockHistory.Services +{ + public class SglStockEquityServices : ISglStockEquityServices + { + private readonly AppDbContext _dbContext; + private readonly ITransactionNotesServices _notesServices; + + public SglStockEquityServices(AppDbContext dbContext, ITransactionNotesServices notesServices) + { + _dbContext = dbContext; + _notesServices = notesServices; + } + + public async Task> GetAllAsync() + { + return await _dbContext.SglStocks.ToListAsync(); + } + public async Task> GetAllAsync(DateTime equityDate) + { + return await _dbContext.SglStocks + .Where(s => s.Bought <= equityDate && (s.Sold > equityDate || s.SoldQuant == 0)) + .ToListAsync(); + } + + public async Task> GetByCodeAsync(string stockCode) + { + var searchedNotes = await _notesServices.GetByStockCodeAsync(stockCode); + + return await _dbContext.SglStocks + .Where(s => s.StockCode == stockCode) + .OrderBy(s => s.Bought) + .ToListAsync(); + } + /// + /// 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. + /// + /// + public async Task GenerateStockVision() + { + + await _dbContext.SglStocks.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) + + 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 SglStockEquity + { + StockCode = note.StockCode, + Bought = note.TransactionDate, + BoughtPrice = note.StockPrice, + Quantity = note.StockQuantity, + Sold = DateTime.MinValue, + SoldPrice = 0m, + SoldQuant = 0 + }; + _dbContext.SglStocks.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") + { + var equityList = await _dbContext.SglStocks + .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.SglStocks.Update(eq); + await _dbContext.SaveChangesAsync(); + Debug.WriteLine($"Säljnotering: {eq.StockCode}, {eq.Sold}, {eq.SoldPrice}, {eq.SoldQuant}"); + } + else + { + var splitEquity = new SglStockEquity + { + StockCode = eq.StockCode, + Bought = eq.Bought, + BoughtPrice = eq.BoughtPrice, + Quantity = soldQuant, + Sold = note.TransactionDate, + SoldPrice = note.StockPrice, + SoldQuant = soldQuant + }; + _dbContext.SglStocks.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.SglStocks.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; + } + } + } + } + } + + } + } + } +} diff --git a/StockHistory/Services/StockEquityServices.cs b/StockHistory/Services/StockEquityServices.cs index 5f47635..f77be63 100644 --- a/StockHistory/Services/StockEquityServices.cs +++ b/StockHistory/Services/StockEquityServices.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using StockHistory.Data; using StockHistory.Models; +using System.Diagnostics; namespace StockHistory.Services; @@ -50,8 +51,9 @@ public class StockEquityServices : IStockEquityServices var existingNotes = await _notesServices.GetAllAsync(); foreach (var note in existingNotes - .OrderByDescending(e => e.TransactionType) - .OrderBy(e => e.TransactionDate) + .OrderBy(e => e.StockCode) + .ThenBy(e => e.TransactionDate) + //.OrderBy(e => e.TransactionType) .ToList()) { // Kontrollera om TransactionType är null och StockCode inte är null @@ -79,11 +81,12 @@ public class StockEquityServices : IStockEquityServices }; _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" || note.TransactionType.ToLower() == "försäljning") + else if (note.TransactionType.ToLower() == "sälj") { var equityList = await _dbContext.Stocks .Where(s => s.StockCode == note.StockCode && s.SoldQuant == 0) @@ -108,6 +111,7 @@ public class StockEquityServices : IStockEquityServices soldQuant -= eq.Quantity; _dbContext.Stocks.Update(eq); await _dbContext.SaveChangesAsync(); + Debug.WriteLine($"Säljnotering: {eq.StockCode}, {eq.Sold}, {eq.SoldPrice}, {eq.SoldQuant}"); } else { @@ -123,9 +127,11 @@ public class StockEquityServices : IStockEquityServices }; _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; } } diff --git a/StockHistory/Stockbrokerage.db b/StockHistory/Stockbrokerage.db index 4b92e820551eb65313da71643f254e47aa75347e..f4a51e0dcbb175a64ef0d8d286183e789ace9d3b 100644 GIT binary patch delta 609 zcmZo@U}m)DGf~G_lY>F8s*so8gn^HBD+9j`A0OXKo?sqEZY$24 zob{aA923};+4iuBvTohjsLNU($HykFFV9$;nU|7U;TaJhoSqY0lAoMi93PyYlLF_7 zI0v~phPWzt`nkA9C@5hlR8sKwQ&55{S5nYW0_j)MY*gW87mt)@Y>dUU!70CV@)Hg* zIk+}qY_?3k#i7oWo->J4yFQhNUECOG7Asb>(lbinCJA9P3F#?j2||qzadh%=MfiL20v>%1n4-z|cvL1QaNXWKkt0N0*Id`g zRKd{H%EZ{pz(CK!%)->t+(_HN0LbG7MjtD`76bn+{>}XD{E_@xn*{|V`1N&JnZw16 z42;YS%#BP9O-&4q&EiWkb8-^X(_Je{iV{Ooa|?hDOwGx$)HBdCn;faXnva#=g@OM8 i|33cd{CWH?Ky!@vwKSQ-8HqB6b#tEmqyvi%H~;|b(z4_L delta 149 zcmZo@U~On%nIJ95&cMJR0K_oBF;T}@k)1)Ws*so8gn^4=0t3GdA0OXKo?sqEZY$24 zob{aA91}JR3J7p)p1=_*u5GGoXsKXeVP$M!Won^kXkuz;Y+