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 4b92e82..f4a51e0 100644 Binary files a/StockHistory/Stockbrokerage.db and b/StockHistory/Stockbrokerage.db differ diff --git a/StockHistory/frmStockHistoryInit.cs b/StockHistory/frmStockHistoryInit.cs index 91a56f1..5a493b8 100644 --- a/StockHistory/frmStockHistoryInit.cs +++ b/StockHistory/frmStockHistoryInit.cs @@ -223,9 +223,14 @@ public partial class frmStockHistoryInit : Form { foreach (var word in line.Split(" ", StringSplitOptions.RemoveEmptyEntries)) { - if (word.ToUpper().Contains("KÖP") || word.ToUpper().Contains("FÖRSÄLJ")) + if (word.ToUpper().Contains("KÖP")) { - trans.TransactionType = word; + trans.TransactionType = "KÖP"; + break; + } + if (word.ToUpper().Contains("FÖRSÄLJ")) + { + trans.TransactionType = "SÄLJ"; break; } }