Add project files.

This commit is contained in:
2026-04-25 12:19:04 +02:00
parent d7b4eae828
commit 1e8098996c
24 changed files with 1397 additions and 0 deletions

25
StockHistory.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.37216.2 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StockHistory", "StockHistory\StockHistory.csproj", "{ECAA07D8-A9B5-4661-A3DE-AFC2CC02AE32}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{ECAA07D8-A9B5-4661-A3DE-AFC2CC02AE32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECAA07D8-A9B5-4661-A3DE-AFC2CC02AE32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECAA07D8-A9B5-4661-A3DE-AFC2CC02AE32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECAA07D8-A9B5-4661-A3DE-AFC2CC02AE32}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BF6C1E75-5F37-4AB9-A1DB-5A9CBA141626}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using StockHistory.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace StockHistory.Data;
public class AppDbContext: DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<TransactionNote> TransactionNotes { get; set; }
override protected void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TransactionNote>()
.HasIndex(t => new { t.StockCode, t.TransactionDate })
.IsUnique();
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
namespace StockHistory.Data
{
public class AppDbContextFactory : IDesignTimeDbContextFactory<AppDbContext>
{
public AppDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
// Samma connection string som i Program.cs
optionsBuilder.UseSqlite("Data Source=Stockbrokerage.db");
return new AppDbContext(optionsBuilder.Options);
}
}
}

View File

@ -0,0 +1,7 @@
namespace StockHistory
{
public interface IPdfFormatter
{
string ExtractFormattedTextUsingWords(List<PdfSida> sidLista);
}
}

View File

@ -0,0 +1,8 @@
namespace StockHistory;
public interface IPdfOpener
{
List<PdfSida> PdfSidor { get; }
void OpenPdf(string filePath);
}

View File

@ -0,0 +1,60 @@
// <auto-generated />
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("20260422153418_InitialCreate")]
partial class InitialCreate
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
modelBuilder.Entity("StockHistory.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>("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.ToTable("TransactionNotes");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace StockHistory.Migrations
{
/// <inheritdoc />
public partial class InitialCreate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
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),
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);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TransactionNotes");
}
}
}

View File

@ -0,0 +1,63 @@
// <auto-generated />
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("20260424072448_AddUniqueIndex")]
partial class AddUniqueIndex
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
modelBuilder.Entity("StockHistory.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>("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
}
}
}

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace StockHistory.Migrations
{
/// <inheritdoc />
public partial class AddUniqueIndex : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_TransactionNotes_StockCode_TransactionDate",
table: "TransactionNotes",
columns: new[] { "StockCode", "TransactionDate" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_TransactionNotes_StockCode_TransactionDate",
table: "TransactionNotes");
}
}
}

View File

@ -0,0 +1,60 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using StockHistory.Data;
#nullable disable
namespace StockHistory.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
modelBuilder.Entity("StockHistory.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>("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
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace StockHistory.Models;
public class TransactionNote
{
public int Id { get; set; }
public DateTime TransactionDate { get; set; }
public string? TransactionType { get; set; }
public string? StockCode { get; set; }
public decimal StockPrice { get; set; }
public int StockQuantity { get; set; }
public decimal TotalAmount { get; set; }
public decimal Commission { get; set; }
public decimal AmountToPay { get; set; }
}

View File

@ -0,0 +1,99 @@
using UglyToad.PdfPig;
using UglyToad.PdfPig.Content;
using System.Text;
using System.Linq;
using System.Collections.Generic;
namespace StockHistory;
public class PdfFormatter : IPdfFormatter
{
public string ExtractFormattedTextUsingWords(List<PdfSida> sidLista)
{
var sb = new StringBuilder();
//using (var doc = PdfDocument.Open(path))
{
foreach (var page in sidLista)
{
var words = (page.FoundWords ?? Enumerable.Empty<Word>())
.OrderByDescending(w => w.BoundingBox.Top)
.ThenBy(w => w.BoundingBox.Left)
.ToList();
var lines = GroupWordsIntoLines(words);
foreach (var line in lines)
{
var columns = GroupWordsIntoColumns(line);
foreach (var col in columns)
{
sb.Append(string.Join(" ", col.Select(w => w.Text)));
sb.Append(" "); // mellanrum mellan kolumner
}
sb.AppendLine();
}
sb.AppendLine();
}
}
return sb.ToString();
}
private List<List<Word>> GroupWordsIntoLines(List<Word> words)
{
var lines = new List<List<Word>>();
double threshold = 4; // tolerans för radavstånd
foreach (var word in words)
{
var line = lines.FirstOrDefault(l =>
Math.Abs(l[0].BoundingBox.Top - word.BoundingBox.Top) < threshold);
if (line == null)
{
lines.Add(new List<Word> { word });
}
else
{
line.Add(word);
}
}
// Sortera ord inom varje rad
foreach (var line in lines)
{
line.Sort((a, b) => a.BoundingBox.Left.CompareTo(b.BoundingBox.Left));
}
return lines;
}
private List<List<Word>> GroupWordsIntoColumns(List<Word> line)
{
var columns = new List<List<Word>>();
double threshold = 25; // tolerans för kolumnavstånd
foreach (var word in line)
{
var col = columns.FirstOrDefault(c =>
Math.Abs(c[0].BoundingBox.Left - word.BoundingBox.Left) < threshold);
if (col == null)
{
columns.Add(new List<Word> { word });
}
else
{
col.Add(word);
}
}
return columns;
}
}

35
StockHistory/PdfOpener.cs Normal file
View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
using UglyToad.PdfPig;
using UglyToad.PdfPig.Content;
namespace StockHistory;
public class PdfOpener : IPdfOpener
{
public List<PdfSida> PdfSidor { get; set; } = new();
public void OpenPdf(string filePath)
{
PdfSidor.Clear();
using (PdfDocument document = PdfDocument.Open(filePath))
{
foreach (Page page in document.GetPages())
{
var pdfsida = new PdfSida();
pdfsida.FoundLetters = page.Letters;
string example = string.Join(string.Empty, pdfsida.FoundLetters.Select(x => x.Value));
pdfsida.FoundWords = page.GetWords();
pdfsida.FoundImages = page.GetImages();
PdfSidor.Add(pdfsida);
}
}
}
}

14
StockHistory/PdfSida.cs Normal file
View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
using UglyToad.PdfPig;
using UglyToad.PdfPig.Content;
namespace StockHistory;
public class PdfSida
{
public IEnumerable<IPdfImage>? FoundImages { get; set; }
public IReadOnlyList<Letter>? FoundLetters { get; set; }
public IEnumerable<Word>? FoundWords { get; set; }
}

47
StockHistory/Program.cs Normal file
View File

@ -0,0 +1,47 @@
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StockHistory.Data;
using StockHistory.Services;
namespace StockHistory
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
var host = CreateHostBuilder().Build();
using (var scope = host.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.Migrate(); // skapar databasen om den saknas
}
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
// Starta huvudformul<75>ret via DI
var mainForm = host.Services.GetRequiredService <frmStockHistoryInit>();
Application.Run(mainForm);
}
static IHostBuilder CreateHostBuilder() =>
Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=Stockbrokerage.db"),
ServiceLifetime.Transient);
services.AddSingleton<frmStockHistoryInit>();
services.AddTransient<IPdfOpener, PdfOpener>();
services.AddTransient<IPdfFormatter, PdfFormatter>();
services.AddTransient<ITransactionNotesServices, TransactionNotesServices>();
});
}
}

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace StockHistory.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("StockHistory.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,13 @@
using StockHistory.Models;
namespace StockHistory.Services
{
public interface ITransactionNotesServices
{
Task AddAsync(TransactionNote transactionNote);
Task DeleteAsync(int id);
Task<List<TransactionNote>> GetAllAsync();
Task<TransactionNote?> GetByIdAsync(int id);
Task UpdateAsync(TransactionNote transactionNote);
}
}

View File

@ -0,0 +1,61 @@
using StockHistory.Data;
using StockHistory.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace StockHistory.Services;
public class TransactionNotesServices : ITransactionNotesServices
{
private readonly AppDbContext _dbContext;
private readonly IServiceProvider _provider;
// Parameterless constructor
public TransactionNotesServices(AppDbContext dbContext, IServiceProvider provider)
{
_dbContext = dbContext;
_provider = provider;
}
public async Task<List<TransactionNote>> GetAllAsync()
{
return await _dbContext.TransactionNotes.ToListAsync();
}
public async Task<TransactionNote?> GetByIdAsync(int id)
{
return await _dbContext.TransactionNotes.FindAsync(id);
}
public async Task AddAsync(TransactionNote transactionNote)
{
using var scope = _provider.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
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();
}
}
}

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
<PackageReference Include="PdfPig" Version="0.1.14" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

Binary file not shown.

View File

@ -0,0 +1,209 @@
namespace StockHistory
{
partial class frmStockHistoryInit
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
btnChooseFile = new Button();
ofdFiler = new OpenFileDialog();
lblFileName = new Label();
btnAnalysera = new Button();
txtFound = new TextBox();
btnSaveToDb = new Button();
lwTransactions = new ListView();
StockCodeHeader = new ColumnHeader();
TransactionTypeHeader = new ColumnHeader();
TransactionDateHeader = new ColumnHeader();
StockQuantityHeader = new ColumnHeader();
StockPriceHeader = new ColumnHeader();
TotalAmountHeader = new ColumnHeader();
CommissionHeader = new ColumnHeader();
AmountToPayHeader = new ColumnHeader();
btnClose = new Button();
SuspendLayout();
//
// btnChooseFile
//
btnChooseFile.BackColor = Color.FromArgb(128, 255, 128);
btnChooseFile.FlatStyle = FlatStyle.Flat;
btnChooseFile.Font = new Font("Segoe UI", 13F);
btnChooseFile.Location = new Point(26, 27);
btnChooseFile.Name = "btnChooseFile";
btnChooseFile.Size = new Size(143, 37);
btnChooseFile.TabIndex = 0;
btnChooseFile.Text = "Välj pdf-fil";
btnChooseFile.UseVisualStyleBackColor = false;
btnChooseFile.Click += btnChooseFile_Click;
//
// lblFileName
//
lblFileName.AutoSize = true;
lblFileName.Location = new Point(175, 38);
lblFileName.Name = "lblFileName";
lblFileName.Size = new Size(39, 20);
lblFileName.TabIndex = 1;
lblFileName.Text = "_____";
lblFileName.TextChanged += lblFileName_TextChanged;
//
// btnAnalysera
//
btnAnalysera.BackColor = Color.FromArgb(128, 255, 128);
btnAnalysera.FlatStyle = FlatStyle.Flat;
btnAnalysera.Font = new Font("Segoe UI", 13F);
btnAnalysera.Location = new Point(26, 86);
btnAnalysera.Name = "btnAnalysera";
btnAnalysera.Size = new Size(143, 37);
btnAnalysera.TabIndex = 2;
btnAnalysera.Text = "Analysera";
btnAnalysera.UseVisualStyleBackColor = false;
btnAnalysera.Click += btnAnalysera_Click;
//
// txtFound
//
txtFound.Location = new Point(26, 149);
txtFound.Multiline = true;
txtFound.Name = "txtFound";
txtFound.Size = new Size(468, 334);
txtFound.TabIndex = 3;
//
// btnSaveToDb
//
btnSaveToDb.BackColor = Color.FromArgb(128, 255, 128);
btnSaveToDb.FlatStyle = FlatStyle.Flat;
btnSaveToDb.Font = new Font("Segoe UI", 13F);
btnSaveToDb.Location = new Point(197, 86);
btnSaveToDb.Name = "btnSaveToDb";
btnSaveToDb.Size = new Size(143, 37);
btnSaveToDb.TabIndex = 4;
btnSaveToDb.Text = "Spara till DB";
btnSaveToDb.UseVisualStyleBackColor = false;
btnSaveToDb.Click += btnSaveToDb_Click;
//
// lwTransactions
//
lwTransactions.BackColor = Color.WhiteSmoke;
lwTransactions.Columns.AddRange(new ColumnHeader[] { StockCodeHeader, TransactionTypeHeader, TransactionDateHeader, StockQuantityHeader, StockPriceHeader, TotalAmountHeader, CommissionHeader, AmountToPayHeader });
lwTransactions.ForeColor = SystemColors.InactiveCaptionText;
lwTransactions.Location = new Point(515, 27);
lwTransactions.Name = "lwTransactions";
lwTransactions.Size = new Size(857, 456);
lwTransactions.Sorting = SortOrder.Descending;
lwTransactions.TabIndex = 6;
lwTransactions.UseCompatibleStateImageBehavior = false;
lwTransactions.View = View.Details;
//
// StockCodeHeader
//
StockCodeHeader.Text = "StockCode";
StockCodeHeader.Width = 110;
//
// TransactionTypeHeader
//
TransactionTypeHeader.Text = "TransactionType";
TransactionTypeHeader.Width = 100;
//
// TransactionDateHeader
//
TransactionDateHeader.Text = "TransactionDate";
TransactionDateHeader.Width = 120;
//
// StockQuantityHeader
//
StockQuantityHeader.Text = "StockQuantity";
StockQuantityHeader.Width = 100;
//
// StockPriceHeader
//
StockPriceHeader.Text = "StockPrice";
StockPriceHeader.Width = 100;
//
// TotalAmountHeader
//
TotalAmountHeader.Text = "TotalAmount";
TotalAmountHeader.Width = 100;
//
// CommissionHeader
//
CommissionHeader.Text = "Commission";
//
// AmountToPayHeader
//
AmountToPayHeader.Text = "AmountToPay";
AmountToPayHeader.Width = 110;
//
// btnClose
//
btnClose.BackColor = Color.Chartreuse;
btnClose.FlatStyle = FlatStyle.Popup;
btnClose.Font = new Font("Segoe UI", 13F, FontStyle.Regular, GraphicsUnit.Point, 1, true);
btnClose.Location = new Point(1248, 503);
btnClose.Name = "btnClose";
btnClose.Size = new Size(124, 38);
btnClose.TabIndex = 7;
btnClose.Text = "Stäng";
btnClose.UseVisualStyleBackColor = false;
btnClose.Click += btnClose_Click;
//
// frmStockHistoryInit
//
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
BackColor = Color.LightGray;
ClientSize = new Size(1413, 553);
Controls.Add(btnClose);
Controls.Add(lwTransactions);
Controls.Add(btnSaveToDb);
Controls.Add(txtFound);
Controls.Add(btnAnalysera);
Controls.Add(lblFileName);
Controls.Add(btnChooseFile);
Name = "frmStockHistoryInit";
Text = "StockBrokerage";
ResumeLayout(false);
PerformLayout();
}
#endregion
private Button btnChooseFile;
private OpenFileDialog ofdFiler;
private Label lblFileName;
private Button btnAnalysera;
private TextBox txtFound;
private Button btnSaveToDb;
private ListView lwTransactions;
private ColumnHeader StockCodeHeader;
private ColumnHeader TransactionTypeHeader;
private ColumnHeader TransactionDateHeader;
private ColumnHeader StockQuantityHeader;
private ColumnHeader StockPriceHeader;
private ColumnHeader TotalAmountHeader;
private ColumnHeader CommissionHeader;
private ColumnHeader AmountToPayHeader;
private Button btnClose;
}
}

View File

@ -0,0 +1,218 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using StockHistory.Models;
using StockHistory.Services;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
namespace StockHistory
{
public partial class frmStockHistoryInit : Form
{
private readonly IPdfOpener _pdfOpener;
private readonly IPdfFormatter _pdfFormatter;
private readonly ITransactionNotesServices _transactionNotes;
private List<PdfSida> _pdfSidor = new();
public frmStockHistoryInit(
IPdfOpener pdfOpener,
IPdfFormatter pdfFormatter,
ITransactionNotesServices transactionNotes
)
{
InitializeComponent();
_pdfOpener = pdfOpener;
_pdfFormatter = pdfFormatter;
_transactionNotes = transactionNotes;
btnAnalysera.Enabled = false;
Shown += async (s, e) => await LoadBusinessAsync();
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public List<string> Selected { get; set; } = new();
private void btnChooseFile_Click(object sender, EventArgs e)
{
ofdFiler.Title = "Välj en PDF-fil";
ofdFiler.FileName = "";
ofdFiler.Filter = "PDF-filer (*.pdf)|*.pdf|Alla filer (*.*)|*.*";
var result = ofdFiler.ShowDialog();
if (result == DialogResult.OK)
{
lblFileName.Text = ofdFiler.FileName;
}
}
private void lblFileName_TextChanged(object sender, EventArgs e)
{
if (lblFileName.Text.Length > 5 && lblFileName.Text.EndsWith(".pdf"))
{
_pdfOpener.OpenPdf(lblFileName.Text);
_pdfSidor = _pdfOpener.PdfSidor;
btnAnalysera.Enabled = true;
}
else
{
btnAnalysera.Enabled = false;
}
}
private void btnAnalysera_Click(object sender, EventArgs e)
{
string PdfResult = _pdfFormatter.ExtractFormattedTextUsingWords(_pdfSidor);
txtFound.Text = string.Empty;
List<string> result = PdfResult.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList();
//List<string> selected = new();
foreach (var line in result)
{
if (!string.IsNullOrWhiteSpace(line) && line.StartsWith("Affärsdag")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Handelstidpunkt")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Värdepapper")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Kurs")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Genomsnittlig")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Antal")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Köpeskilling")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Courtage")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Belopp")
|| !string.IsNullOrWhiteSpace(line) && line.StartsWith("Vi bekräftar")
)
{
Selected.Add(line);
txtFound.Text += line + Environment.NewLine;
txtFound.Refresh();
}
}
btnAnalysera.Enabled = false;
lblFileName.Text = string.Empty;
}
private async void btnSaveToDb_Click(object sender, EventArgs e)
{
TransactionNote trans = new();
foreach (var line in Selected)
{
if (line.StartsWith("Affärsdag"))
{
trans.TransactionDate = DateTime.Parse(line.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1]);
}
if (line.StartsWith("Värdepapper"))
{
var templist = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
if (templist.Length > 2)
trans.StockCode = templist[1] + " " + templist[2];
else
trans.StockCode = templist[1];
}
if (line.StartsWith("Kurs"))
{
trans.StockPrice = decimal.Parse(line.Split(" ", StringSplitOptions.RemoveEmptyEntries)[2]);
}
if (line.StartsWith("Antal"))
{
trans.StockQuantity = int.Parse(line.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1]);
}
if (line.StartsWith("Köpeskilling"))
{
var templist = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
if (templist.Length > 3)
trans.TotalAmount = decimal.Parse(templist[2] + templist[3]);
else
trans.TotalAmount = decimal.Parse(templist[2]);
}
if (line.StartsWith("Courtage"))
{
var tmpList = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
trans.Commission = decimal.Parse(tmpList[tmpList.Length - 1]);
}
if (line.StartsWith("Belopp"))
{
var templist = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
if (templist.Length > 5)
trans.AmountToPay = decimal.Parse(templist[4] + templist[5]);
else
trans.AmountToPay = decimal.Parse(templist[4]);
}
if (line.StartsWith("Vi bekräftar"))
{
foreach (var word in line.Split(" ", StringSplitOptions.RemoveEmptyEntries))
{
if (word.ToUpper().Contains("KÖP") || word.ToUpper().Contains("FÖRSÄLJ"))
{
trans.TransactionType = word;
break;
}
}
}
if (line.StartsWith("Genomsnittlig"))
{
var tmpList = line.Split(" ", StringSplitOptions.RemoveEmptyEntries);
trans.StockPrice = decimal.Parse(tmpList[tmpList.Length - 1]);
}
if (line.StartsWith("Handelstidpunkt"))
{
var time = line.Split(" ", StringSplitOptions.RemoveEmptyEntries)[2];
if (time.Contains("."))
{
var hours = int.Parse(time.Split(".")[0]);
var minutes = int.Parse(time.Split(".")[1]);
trans.TransactionDate = trans.TransactionDate.AddHours(hours).AddMinutes(minutes);
}
}
}
try
{
await _transactionNotes.AddAsync(trans);
txtFound.Text = string.Empty;
}
catch (DbUpdateException ex) when (ex.InnerException is SqliteException sqlite && sqlite.SqliteErrorCode == 19)
{
MessageBox.Show("Den här transaktionen finns redan.");
}
catch (Exception ex)
{
MessageBox.Show("Fel vid sparande till databas: " + ex.Message);
}
Selected.Clear();
await LoadBusinessAsync();
}
private async Task LoadBusinessAsync()
{
var transactions = await _transactionNotes.GetAllAsync();
lwTransactions.Items.Clear();
foreach (var t in transactions)
{
var item = new ListViewItem(t.StockCode);
item.SubItems.Add(t.TransactionType);
item.SubItems.Add(t.TransactionDate.ToString());
item.SubItems.Add(t.StockQuantity.ToString());
item.SubItems.Add(t.StockPrice.ToString());
item.SubItems.Add(t.TotalAmount.ToString());
item.SubItems.Add(t.Commission.ToString());
item.SubItems.Add(t.AmountToPay.ToString());
lwTransactions.Items.Add(item);
}
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="ofdFiler.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>25</value>
</metadata>
</root>