mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-02 20:15:22 +02:00
Plaid portfolio sync algorithm and calculation improvements (#1526)
* Start tests rework * Cash balance on schema * Add reverse syncer * Reverse balance sync with holdings * Reverse holdings sync * Reverse holdings sync should work with only trade entries * Consolidate brokerage cash * Add forward sync option * Update new balance info after syncs * Intraday balance calculator and sync fixes * Show only balance for trade entries * Tests passing * Update Gemfile.lock * Cleanup, performance improvements * Remove account reloads for reliable sync outputs * Simplify valuation view logic * Special handling for Plaid cash holding
This commit is contained in:
parent
a59ca5b7c6
commit
49c353e10c
72 changed files with 1152 additions and 1046 deletions
|
@ -1,21 +0,0 @@
|
|||
<%# locals: (holding:) %>
|
||||
|
||||
<%= turbo_frame_tag dom_id(holding) do %>
|
||||
<div class="grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4">
|
||||
<div class="col-span-9 flex items-center gap-4">
|
||||
<%= render "shared/circle_logo", name: holding.name %>
|
||||
<div>
|
||||
<%= tag.p holding.name %>
|
||||
<%= tag.p holding.ticker, class: "text-gray-500 text-xs uppercase" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-3 text-right">
|
||||
<% if holding.amount_money %>
|
||||
<%= tag.p format_money holding.amount_money %>
|
||||
<% else %>
|
||||
<%= tag.p "?", class: "text-gray-500" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,18 +0,0 @@
|
|||
<%= turbo_frame_tag dom_id(@account, "cash") do %>
|
||||
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||
<div class="flex items-center justify-between">
|
||||
<%= tag.h2 t(".cash"), class: "font-medium text-lg" %>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl bg-gray-25 p-1">
|
||||
<div class="grid grid-cols-12 items-center uppercase text-xs font-medium text-gray-500 px-4 py-2">
|
||||
<%= tag.p t(".name"), class: "col-span-9" %>
|
||||
<%= tag.p t(".value"), class: "col-span-3 justify-self-end" %>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg bg-white border-alpha-black-25 shadow-xs">
|
||||
<%= render partial: "account/cashes/cash", collection: [brokerage_cash(@account)], as: :holding %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,5 +1,5 @@
|
|||
<%# locals: (entry:, selectable: true, show_balance: false) %>
|
||||
<%# locals: (entry:, selectable: true, balance_trend: nil) %>
|
||||
|
||||
<%= turbo_frame_tag dom_id(entry) do %>
|
||||
<%= render partial: entry.entryable.to_partial_path, locals: { entry:, selectable:, show_balance: } %>
|
||||
<%= render partial: entry.entryable.to_partial_path, locals: { entry:, selectable:, balance_trend: } %>
|
||||
<% end %>
|
||||
|
|
|
@ -73,13 +73,14 @@
|
|||
|
||||
<div>
|
||||
<div class="rounded-tl-lg rounded-tr-lg bg-white border-alpha-black-25 shadow-xs">
|
||||
|
||||
<div class="space-y-4">
|
||||
<% calculator = Account::BalanceTrendCalculator.for(@entries) %>
|
||||
<%= entries_by_date(@entries) do |entries| %>
|
||||
<%= render entries, show_balance: true %>
|
||||
<% entries.each do |entry| %>
|
||||
<%= render entry, balance_trend: calculator&.trend_for(entry) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="p-4 bg-white rounded-bl-lg rounded-br-lg">
|
||||
|
|
32
app/views/account/holdings/_cash.html.erb
Normal file
32
app/views/account/holdings/_cash.html.erb
Normal file
|
@ -0,0 +1,32 @@
|
|||
<%# locals: (account:) %>
|
||||
|
||||
<% currency = Money::Currency.new(account.currency) %>
|
||||
|
||||
<div class="grid grid-cols-12 items-center text-gray-900 text-sm font-medium p-4">
|
||||
<div class="col-span-4 flex items-center gap-4">
|
||||
<%= render "shared/circle_logo", name: currency.iso_code %>
|
||||
|
||||
<div class="space-y-0.5">
|
||||
<%= tag.p t(".brokerage_cash"), class: "text-gray-900" %>
|
||||
<%= tag.p account.currency, class: "text-gray-500 text-xs uppercase" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 flex justify-end items-center gap-2">
|
||||
<% cash_weight = account.balance.zero? ? 0 : account.cash_balance / account.balance * 100 %>
|
||||
<%= render "shared/progress_circle", progress: cash_weight, text_class: "text-blue-500" %>
|
||||
<%= tag.p number_to_percentage(cash_weight, precision: 1) %>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 text-right">
|
||||
<%= tag.p "--", class: "text-gray-500" %>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 text-right">
|
||||
<%= tag.p format_money account.cash_balance %>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 text-right">
|
||||
<%= tag.p "--", class: "text-gray-500" %>
|
||||
</div>
|
||||
</div>
|
|
@ -2,7 +2,7 @@
|
|||
<div class="bg-white space-y-4 p-5 border border-alpha-black-25 rounded-xl shadow-xs">
|
||||
<div class="flex items-center justify-between">
|
||||
<%= tag.h2 t(".holdings"), class: "font-medium text-lg" %>
|
||||
<%= link_to new_account_trade_path(@account),
|
||||
<%= link_to new_account_trade_path(account_id: @account.id),
|
||||
id: dom_id(@account, "new_trade"),
|
||||
data: { turbo_frame: :modal },
|
||||
class: "flex gap-1 font-medium items-center bg-gray-50 text-gray-900 p-2 rounded-lg" do %>
|
||||
|
@ -21,8 +21,10 @@
|
|||
</div>
|
||||
|
||||
<div class="rounded-lg bg-white border-alpha-black-25 shadow-xs">
|
||||
<% if @holdings.any? %>
|
||||
<%= render partial: "account/holdings/holding", collection: @holdings, spacer_template: "ruler" %>
|
||||
<% if @account.holdings.current.any? %>
|
||||
<%= render "account/holdings/cash", account: @account %>
|
||||
<%= render "account/holdings/ruler" %>
|
||||
<%= render partial: "account/holdings/holding", collection: @account.holdings.current, spacer_template: "ruler" %>
|
||||
<% else %>
|
||||
<p class="text-gray-500 text-sm p-4"><%= t(".no_holdings") %></p>
|
||||
<% end %>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form.date_field :date, label: true, value: Date.today, required: true %>
|
||||
<%= form.date_field :date, label: true, value: Date.current, required: true %>
|
||||
|
||||
<% unless %w[buy sell].include?(type) %>
|
||||
<%= form.money_field :amount, label: t(".amount"), required: true %>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<%# locals: (entry:, selectable: true, show_balance: false) %>
|
||||
<%# locals: (entry:, selectable: true, balance_trend: nil) %>
|
||||
|
||||
<% trade, account = entry.account_trade, entry.account %>
|
||||
|
||||
|
@ -37,6 +37,12 @@
|
|||
</div>
|
||||
|
||||
<div class="col-span-2 justify-self-end">
|
||||
<%= tag.p "--", class: "font-medium text-sm text-gray-400" %>
|
||||
<% if balance_trend&.trend %>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= tag.p format_money(balance_trend.trend.current), class: "font-medium text-sm text-gray-900" %>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= tag.p "--", class: "font-medium text-sm text-gray-400" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
data: { controller: "auto-submit-form" } do |f| %>
|
||||
<%= f.date_field :date,
|
||||
label: t(".date_label"),
|
||||
max: Date.today,
|
||||
max: Date.current,
|
||||
"data-auto-submit-form-target": "auto" %>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<%= f.fields_for :entryable do |ef| %>
|
||||
<%= ef.collection_select :category_id, Current.family.categories.alphabetically, :id, :name, { prompt: t(".category_prompt"), label: t(".category") } %>
|
||||
<% end %>
|
||||
<%= f.date_field :date, label: t(".date"), required: true, min: Account::Entry.min_supported_date, max: Date.today, value: Date.today %>
|
||||
<%= f.date_field :date, label: t(".date"), required: true, min: Account::Entry.min_supported_date, max: Date.current, value: Date.current %>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<%# locals: (entry:, selectable: true, show_balance: false) %>
|
||||
<%# locals: (entry:, selectable: true, balance_trend: nil) %>
|
||||
<% transaction, account = entry.account_transaction, entry.account %>
|
||||
|
||||
<div class="grid grid-cols-12 items-center <%= entry.excluded ? "text-gray-400 bg-gray-25" : "text-gray-900" %> text-sm font-medium p-4">
|
||||
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
|
||||
<% if entry.transfer.present? %>
|
||||
<% unless show_balance %>
|
||||
<% unless balance_trend %>
|
||||
<div class="col-span-2"></div>
|
||||
<% end %>
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
|||
<%= render "categories/menu", transaction: transaction %>
|
||||
</div>
|
||||
|
||||
<% unless show_balance %>
|
||||
<% unless balance_trend %>
|
||||
<%= tag.div class: "col-span-2 overflow-hidden truncate" do %>
|
||||
<% if entry.new_record? %>
|
||||
<%= tag.p account.name %>
|
||||
|
@ -66,12 +66,12 @@
|
|||
class: ["text-green-600": entry.inflow?] %>
|
||||
</div>
|
||||
|
||||
<% if show_balance %>
|
||||
<% if balance_trend %>
|
||||
<div class="col-span-2 justify-self-end">
|
||||
<% if entry.account.investment? %>
|
||||
<%= tag.p "--", class: "font-medium text-sm text-gray-400" %>
|
||||
<% if balance_trend.trend %>
|
||||
<%= tag.p format_money(balance_trend.trend.current), class: "font-medium text-sm text-gray-900" %>
|
||||
<% else %>
|
||||
<%= tag.p format_money(entry.trend.current), class: "font-medium text-sm text-gray-900" %>
|
||||
<%= tag.p "--", class: "font-medium text-sm text-gray-400" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<%= f.collection_select :from_account_id, Current.family.accounts.manual.alphabetically, :id, :name, { prompt: t(".select_account"), label: t(".from") }, required: true %>
|
||||
<%= f.collection_select :to_account_id, Current.family.accounts.manual.alphabetically, :id, :name, { prompt: t(".select_account"), label: t(".to") }, required: true %>
|
||||
<%= f.number_field :amount, label: t(".amount"), required: true, min: 0, placeholder: "100", step: 0.00000001 %>
|
||||
<%= f.date_field :date, value: transfer.date || Date.today, label: t(".date"), required: true, max: Date.current %>
|
||||
<%= f.date_field :date, value: transfer.date || Date.current, label: t(".date"), required: true, max: Date.current %>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<% end %>
|
||||
|
||||
<div class="space-y-3">
|
||||
<%= form.date_field :date, label: true, required: true, value: Date.today, min: Account::Entry.min_supported_date, max: Date.today %>
|
||||
<%= form.date_field :date, label: true, required: true, value: Date.current, min: Account::Entry.min_supported_date, max: Date.current %>
|
||||
<%= form.money_field :amount, label: t(".amount"), required: true %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (entry:, selectable: true, show_balance: false) %>
|
||||
<%# locals: (entry:, selectable: true, balance_trend: nil) %>
|
||||
|
||||
<% account = entry.account %>
|
||||
<% valuation = entry.account_valuation %>
|
||||
<% color = balance_trend&.trend&.color || "#D444F1" %>
|
||||
<% icon = balance_trend&.trend&.icon || "plus" %>
|
||||
|
||||
<div class="p-4 grid grid-cols-12 items-center text-gray-900 text-sm font-medium">
|
||||
<div class="col-span-8 flex items-center gap-4">
|
||||
|
@ -12,15 +12,15 @@
|
|||
<% end %>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<%= tag.div class: "w-8 h-8 rounded-full p-1.5 flex items-center justify-center", style: mixed_hex_styles(valuation.color) do %>
|
||||
<%= lucide_icon valuation.icon, class: "w-4 h-4" %>
|
||||
<%= tag.div class: "w-8 h-8 rounded-full p-1.5 flex items-center justify-center", style: mixed_hex_styles(color) do %>
|
||||
<%= lucide_icon icon, class: "w-4 h-4" %>
|
||||
<% end %>
|
||||
|
||||
<div class="truncate text-gray-900">
|
||||
<% if entry.new_record? %>
|
||||
<%= content_tag :p, entry.name %>
|
||||
<% else %>
|
||||
<%= link_to valuation.name,
|
||||
<%= link_to entry.name || t(".balance_update"),
|
||||
account_entry_path(entry),
|
||||
data: { turbo_frame: "drawer", turbo_prefetch: false },
|
||||
class: "hover:underline hover:text-gray-800" %>
|
||||
|
@ -29,8 +29,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 justify-self-end font-medium text-sm" style="color: <%= valuation.color %>">
|
||||
<%= tag.span format_money(entry.trend.value) %>
|
||||
<div class="col-span-2 justify-self-end font-medium text-sm">
|
||||
<% if balance_trend&.trend %>
|
||||
<%= tag.span format_money(balance_trend.trend.value), style: "color: #{balance_trend.trend.color}" %>
|
||||
<% else %>
|
||||
<%= tag.span "--", class: "text-gray-400" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 justify-self-end">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue