diff --git a/StockHistory/ListViewExtensions.cs b/StockHistory/ListViewExtensions.cs new file mode 100644 index 0000000..5d08180 --- /dev/null +++ b/StockHistory/ListViewExtensions.cs @@ -0,0 +1,62 @@ +namespace StockHistory; + +public static class ListViewExtensions +{ + public static void EnableSmartSorting(this ListView lv) + { + lv.ColumnClick += (s, e) => + { + if (lv.Tag is not SortState state) + { + state = new SortState(); + lv.Tag = state; + } + + if (state.Column == e.Column) + { + state.Order = state.Order == SortOrder.Ascending + ? SortOrder.Descending + : SortOrder.Ascending; + } + else + { + state.Column = e.Column; + state.Order = SortOrder.Ascending; + } + + lv.ListViewItemSorter = new SmartListViewComparer(state.Column, state.Order); + lv.Sort(); + + UpdateHeaderArrows(lv, state.Column, state.Order); + HighlightSortedColumn(lv, state.Column); + }; + } + + private static void UpdateHeaderArrows(ListView lv, int col, SortOrder order) + { + foreach (ColumnHeader ch in lv.Columns) + ch.Text = ch.Text.Replace(" ▲", "").Replace(" ▼", ""); + + string arrow = order == SortOrder.Ascending ? " ▲" : " ▼"; + lv.Columns[col].Text += arrow; + } + + private static void HighlightSortedColumn(ListView lv, int col) + { + foreach (ListViewItem item in lv.Items) + { + for (int i = 0; i < item.SubItems.Count; i++) + { + item.SubItems[i].BackColor = i == col + ? Color.FromArgb(235, 245, 255) + : Color.White; + } + } + } + + private class SortState + { + public int Column = -1; + public SortOrder Order = SortOrder.None; + } +} diff --git a/StockHistory/ListViewItemComparer.cs b/StockHistory/ListViewItemComparer.cs new file mode 100644 index 0000000..9c855e2 --- /dev/null +++ b/StockHistory/ListViewItemComparer.cs @@ -0,0 +1,31 @@ +using System.Collections; + +namespace StockHistory; + +public class ListViewItemComparer : IComparer +{ + private int col; + private SortOrder order; + + public ListViewItemComparer(int column, SortOrder order) + { + col = column; + this.order = order; + } + + public int Compare(object x, object y) + { + string a = ((ListViewItem)x).SubItems[col].Text; + string b = ((ListViewItem)y).SubItems[col].Text; + + int result; + + // Försök sortera som tal först + if (double.TryParse(a, out double da) && double.TryParse(b, out double db)) + result = da.CompareTo(db); + else + result = String.Compare(a, b); + + return order == SortOrder.Ascending ? result : -result; + } +} diff --git a/StockHistory/Models/EquityTemp.cs b/StockHistory/Models/EquityTemp.cs new file mode 100644 index 0000000..aeeb9d0 --- /dev/null +++ b/StockHistory/Models/EquityTemp.cs @@ -0,0 +1,10 @@ +namespace StockHistory.Models; + +public class EquityTemp +{ + public string StockCode { get; set; } = string.Empty; + public DateTime Bought { get; set; } + public decimal BoughtPrice { get; set; } + public int Quantity { get; set; } + public decimal TotalValue => BoughtPrice * Quantity; +} diff --git a/StockHistory/Program.cs b/StockHistory/Program.cs index 9370249..b8734ea 100644 --- a/StockHistory/Program.cs +++ b/StockHistory/Program.cs @@ -38,7 +38,7 @@ namespace StockHistory ServiceLifetime.Transient); services.AddSingleton(); - services.AddScoped(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/StockHistory/Services/StockEquityServices.cs b/StockHistory/Services/StockEquityServices.cs index f5642ad..9845383 100644 --- a/StockHistory/Services/StockEquityServices.cs +++ b/StockHistory/Services/StockEquityServices.cs @@ -22,17 +22,20 @@ public class StockEquityServices : IStockEquityServices public async Task> GetAllAsync(DateTime equityDate) { return await _dbContext.Stocks - .Where(s => s.Bought <= equityDate && s.Sold > equityDate) + .Where(s => s.Bought <= equityDate && (s.Sold > equityDate || s.SoldQuant == 0)) .ToListAsync(); } public async Task GenerateStockVision() { - var existingNotes = await _notesServices.GetAllAsync(); - // foreach (var note in await _notesServices.GetAllAsync()) - foreach (var note in existingNotes) + await _dbContext.Stocks.ExecuteDeleteAsync(); + await _dbContext.SaveChangesAsync(); + + var existingNotes = await _notesServices.GetAllAsync(); + + foreach (var note in existingNotes.OrderBy(e => e.TransactionDate).ToList()) { - if (note.TransactionType == null) { } + if (note.TransactionType == null && note.StockCode != null) { } else if (note.TransactionType.ToLower() == "köp") { @@ -51,31 +54,49 @@ public class StockEquityServices : IStockEquityServices } else if (note.TransactionType.ToLower() == "sälj" || note.TransactionType.ToLower() == "försäljning") { - var equity = await _dbContext.Stocks.FirstOrDefaultAsync(s => s.StockCode == note.StockCode && s.SoldQuant == 0); + var equityList = await _dbContext.Stocks.Where(s => s.StockCode == note.StockCode && s.SoldQuant == 0).OrderBy(s => s.Quantity).ToListAsync(); - if (equity != null) + if (equityList != null) { - if (note.StockQuantity < equity.Quantity) + var soldQuant = Math.Abs(note.StockQuantity); + foreach (var eq in equityList.OrderBy(e => e.Quantity)) { - var splitEquity = new StockEquity + if (soldQuant == 0) { - StockCode = equity.StockCode, - Bought = equity.Bought, - BoughtPrice = equity.BoughtPrice, - Quantity = equity.Quantity - Math.Abs(note.StockQuantity), - Sold = DateTime.MinValue, - SoldPrice = 0m, - SoldQuant = 0 - }; - _dbContext.Stocks.Add(splitEquity); - await _dbContext.SaveChangesAsync(); + 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(); + } + 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(); + eq.Quantity -= soldQuant; + _dbContext.Stocks.Update(eq); + await _dbContext.SaveChangesAsync(); + soldQuant = 0; + } + } } - equity.Quantity = Math.Abs(note.StockQuantity); - equity.Sold = note.TransactionDate; - equity.SoldPrice = note.StockPrice; - equity.SoldQuant = Math.Abs(note.StockQuantity); - _dbContext.Stocks.Update(equity); - await _dbContext.SaveChangesAsync(); } } diff --git a/StockHistory/SmartListViewComparer.cs b/StockHistory/SmartListViewComparer.cs new file mode 100644 index 0000000..352982b --- /dev/null +++ b/StockHistory/SmartListViewComparer.cs @@ -0,0 +1,63 @@ +using System.Collections; +using System.Runtime.InteropServices; + +namespace StockHistory; + +public class SmartListViewComparer : IComparer +{ + private readonly int col; + private readonly SortOrder order; + + public SmartListViewComparer(int column, SortOrder order) + { + col = column; + this.order = order; + } + + public int Compare(object x, object y) + { + string a = ((ListViewItem)x).SubItems[col].Text; + string b = ((ListViewItem)y).SubItems[col].Text; + + // Tomma värden sist + if (string.IsNullOrWhiteSpace(a) && string.IsNullOrWhiteSpace(b)) return 0; + if (string.IsNullOrWhiteSpace(a)) return order == SortOrder.Ascending ? 1 : -1; + if (string.IsNullOrWhiteSpace(b)) return order == SortOrder.Ascending ? -1 : 1; + + int result = CompareSmart(a, b); + return order == SortOrder.Ascending ? result : -result; + } + + private int CompareSmart(string a, string b) + { + // Procent + if (a.EndsWith("%") && b.EndsWith("%") && + double.TryParse(a.TrimEnd('%'), out double pa) && + double.TryParse(b.TrimEnd('%'), out double pb)) + return pa.CompareTo(pb); + + // Valuta + string ac = a.Replace("kr", "").Replace("$", "").Replace("€", "").Trim(); + string bc = b.Replace("kr", "").Replace("$", "").Replace("€", "").Trim(); + + if (double.TryParse(ac, out double ca) && double.TryParse(bc, out double cb)) + return ca.CompareTo(cb); + + // Datum + if (DateTime.TryParse(a, out DateTime da) && DateTime.TryParse(b, out DateTime db)) + return da.CompareTo(db); + + // Tal + if (double.TryParse(a, out double na) && double.TryParse(b, out double nb)) + return na.CompareTo(nb); + + // Naturlig sortering + return StrCmpLogicalW(a, b); + } + + [System.Runtime.InteropServices.DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] + private static extern int StrCmpLogicalW(string x, string y); +} + + + diff --git a/StockHistory/frmStockHistoryAnalyse.Designer.cs b/StockHistory/frmStockHistoryAnalyse.Designer.cs index e161237..52ed3cb 100644 --- a/StockHistory/frmStockHistoryAnalyse.Designer.cs +++ b/StockHistory/frmStockHistoryAnalyse.Designer.cs @@ -37,6 +37,8 @@ chBought = new ColumnHeader(); chPrize = new ColumnHeader(); chNumber = new ColumnHeader(); + lblTotalValue = new Label(); + chValue = new ColumnHeader(); SuspendLayout(); // // lblHistAnalyseHeader @@ -87,7 +89,7 @@ // // lwStocks // - lwStocks.Columns.AddRange(new ColumnHeader[] { chStock, chBought, chPrize, chNumber }); + lwStocks.Columns.AddRange(new ColumnHeader[] { chStock, chBought, chPrize, chNumber, chValue }); lwStocks.Location = new Point(31, 110); lwStocks.Name = "lwStocks"; lwStocks.Size = new Size(676, 172); @@ -115,11 +117,25 @@ chNumber.Text = "Antal"; chNumber.Width = 100; // + // lblTotalValue + // + lblTotalValue.AutoSize = true; + lblTotalValue.Location = new Point(345, 295); + lblTotalValue.Name = "lblTotalValue"; + lblTotalValue.Size = new Size(16, 15); + lblTotalValue.TabIndex = 10; + lblTotalValue.Text = "..."; + // + // chValue + // + chValue.Text = "Total"; + // // frmStockHistoryAnalyse // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(800, 450); + Controls.Add(lblTotalValue); Controls.Add(lwStocks); Controls.Add(btnClose); Controls.Add(dtpChosenDate); @@ -142,5 +158,7 @@ private ColumnHeader chBought; private ColumnHeader chPrize; private ColumnHeader chNumber; + private Label lblTotalValue; + private ColumnHeader chValue; } } \ No newline at end of file diff --git a/StockHistory/frmStockHistoryAnalyse.cs b/StockHistory/frmStockHistoryAnalyse.cs index a8634a3..26fd018 100644 --- a/StockHistory/frmStockHistoryAnalyse.cs +++ b/StockHistory/frmStockHistoryAnalyse.cs @@ -1,4 +1,5 @@ -using StockHistory.Services; +using StockHistory.Models; +using StockHistory.Services; namespace StockHistory; @@ -10,16 +11,20 @@ public partial class frmStockHistoryAnalyse : Form { InitializeComponent(); _equityServices = equityServices; + lwStocks.EnableSmartSorting(); + } private void btnGenerateStockScheme_Click(object sender, EventArgs e) { _equityServices.GenerateStockVision().Wait(); + dtpChosenDate.Value = DateTime.Today; + LoadStockListAsync(dtpChosenDate.Value).Wait(); } private void btnClose_Click(object sender, EventArgs e) { - this.Close(); + this.Hide(); } private void dtpChosenDate_ValueChanged(object sender, EventArgs e) @@ -34,14 +39,60 @@ public partial class frmStockHistoryAnalyse : Form // var stocks = await _equityServices.GetAllAsync(); lwStocks.Items.Clear(); - foreach (var t in stocks) + var dTotal = stocks.Sum(s => s.BoughtPrice * s.Quantity); + lblTotalValue.Text = dTotal.ToString("C"); + EquityTemp equityTemp = new EquityTemp { - var item = new ListViewItem(t.StockCode); - item.SubItems.Add(t.Bought.ToString()); - item.SubItems.Add(t.BoughtPrice.ToString()); - item.SubItems.Add(t.Quantity.ToString()); - lwStocks.Items.Add(item); + StockCode = "Total", + Bought = DateTime.MinValue, + BoughtPrice = 0, + Quantity = 0 + }; + + foreach (var t in stocks.OrderBy(s => s.StockCode + s.Bought).ToList()) + { + if (t.StockCode != equityTemp.StockCode) + { + if (equityTemp.StockCode != "Total") + { + var item = new ListViewItem(equityTemp.StockCode); + item.SubItems.Add(equityTemp.Bought.ToString()); + item.SubItems.Add(equityTemp.BoughtPrice.ToString()); + item.SubItems.Add(equityTemp.Quantity.ToString()); + item.SubItems.Add((equityTemp.TotalValue).ToString("C")); + lwStocks.Items.Add(item); + } + equityTemp.StockCode = t.StockCode; + equityTemp.Bought = t.Bought; + equityTemp.BoughtPrice = t.BoughtPrice; + equityTemp.Quantity = t.Quantity; + continue; + } + else + { + if (t.Bought < equityTemp.Bought) + equityTemp.Bought = t.Bought; + if (t.BoughtPrice < equityTemp.BoughtPrice || equityTemp.BoughtPrice == 0) + equityTemp.BoughtPrice = t.BoughtPrice; + equityTemp.Quantity += t.Quantity; + } + } + var l_item = new ListViewItem(equityTemp.StockCode); + l_item.SubItems.Add(equityTemp.Bought.ToString()); + l_item.SubItems.Add(equityTemp.BoughtPrice.ToString()); + l_item.SubItems.Add(equityTemp.Quantity.ToString()); + l_item.SubItems.Add((equityTemp.TotalValue).ToString("C")); + lwStocks.Items.Add(l_item); } + protected override void OnVisibleChanged(EventArgs e) + { + base.OnVisibleChanged(e); + if (this.Visible) + { + dtpChosenDate.Value = DateTime.Today; + LoadStockListAsync(dtpChosenDate.Value).Wait(); + } + } } diff --git a/StockHistory/frmStockHistoryInit.Designer.cs b/StockHistory/frmStockHistoryInit.Designer.cs index a94debc..131c343 100644 --- a/StockHistory/frmStockHistoryInit.Designer.cs +++ b/StockHistory/frmStockHistoryInit.Designer.cs @@ -122,6 +122,7 @@ lwTransactions.TabIndex = 6; lwTransactions.UseCompatibleStateImageBehavior = false; lwTransactions.View = View.Details; +// lwTransactions.ColumnClick += lwTransactions_ColumnClick; // // StockCodeHeader // @@ -224,7 +225,7 @@ Controls.Add(btnChooseFile); Margin = new Padding(3, 2, 3, 2); Name = "frmStockHistoryInit"; - Text = "StockBrokerage"; + Text = "29"; ResumeLayout(false); PerformLayout(); } diff --git a/StockHistory/frmStockHistoryInit.cs b/StockHistory/frmStockHistoryInit.cs index a180660..5b3113d 100644 --- a/StockHistory/frmStockHistoryInit.cs +++ b/StockHistory/frmStockHistoryInit.cs @@ -20,6 +20,9 @@ public partial class frmStockHistoryInit : Form private TransactionNote trans = new(); private bool SHB = false; private string pdfFilePath = string.Empty; + // Sortering + private int _sortColumn = -1; + private SortOrder _sortOrder = SortOrder.None; public frmStockHistoryInit( IPdfOpener pdfOpener, @@ -30,6 +33,7 @@ public partial class frmStockHistoryInit : Form ) { InitializeComponent(); + lwTransactions.EnableSmartSorting(); _pdfOpener = pdfOpener; _pdfFormatter = pdfFormatter; _transactionNotes = transactionNotes; @@ -299,5 +303,7 @@ public partial class frmStockHistoryInit : Form { _analyseForm.Show(); } + + }