Skip to content
Draft
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
707d163
Add comprehensive bond accounts with EOD/ROD flows and GUS CPI integr…
UberDudePL Mar 31, 2026
95c2027
Fix security and data integrity issues from code review
UberDudePL Apr 1, 2026
9d23209
Address Bond review findings: permissions, safety, GUS settings, and …
UberDudePL Apr 1, 2026
f065d6a
Address code review: permissions, N+1 eager load, backfill gating, an…
UberDudePL Apr 1, 2026
b5c70fd
Review cleanup: extract constant, simplify callbacks, fix @gus_stats …
UberDudePL Apr 1, 2026
22f6c3e
Address GitHub code review: API key guard, auto_fetch toggle, SQL ope…
UberDudePL Apr 1, 2026
cae4c33
Review fixes: nil guard CPI import, unique entry_id index, debounce b…
UberDudePL Apr 1, 2026
a9818ed
Address review feedback: safety, correctness, and consistency fixes
UberDudePL Apr 1, 2026
7cd865b
Review feedback: validation, error handling, and model extraction
UberDudePL Apr 1, 2026
166f7a4
Review fixes: error handling, params, pick args, job range, comments
UberDudePL Apr 1, 2026
d425419
Fix redaction placeholder guard and inflation snapshot source
UberDudePL Apr 1, 2026
fb36c81
Fix CodeRabbit findings for GUS settings and BondLot rates
UberDudePL Apr 1, 2026
d0eb273
Address remaining GitHub review comments for bonds and GUS
UberDudePL Apr 1, 2026
50997f6
Apply latest CodeRabbit fixes for dashboard and hostings
UberDudePL Apr 1, 2026
7ccd1fb
Address latest CodeRabbit comments for hostings and bond scoping
UberDudePL Apr 1, 2026
b27d0d9
Address follow-up review nits for bond lots and GUS component
UberDudePL Apr 1, 2026
9c71a46
Fix latest review issues in bond lot update and form
UberDudePL Apr 1, 2026
9e0b341
Apply latest Copilot review fixes for bond dashboard and terms & Code…
UberDudePL Apr 1, 2026
a76162b
Fix auto_fetch toggle visibility and migration from: guard
UberDudePL Apr 1, 2026
01ee0e9
Address CodeRabbit review: live rate check, effective_on, nil guard, …
UberDudePL Apr 1, 2026
774ec7a
Fix bond lot validation message and preserve bond_lot_id on entry update
UberDudePL Apr 1, 2026
e053217
Apply CodeRabbit i18n and BondLot review refinements
UberDudePL Apr 1, 2026
02a13fa
Fix tax param stripping for tax-exempt bond wrappers
UberDudePL Apr 1, 2026
e0e5111
Optimize bond rate review checks and align GUS import defaults
UberDudePL Apr 1, 2026
0117b69
Prevent settlement rollback on enqueue and fix migration rollback nul…
UberDudePL Apr 1, 2026
2927030
Extract BondLot entry logic from controller
UberDudePL Apr 6, 2026
0285c09
Generalize bond subtype taxonomy and product defaults
UberDudePL Apr 6, 2026
47696cb
Add pluggable inflation provider support for bond lots
UberDudePL Apr 6, 2026
535bca4
Etap C: coupon frequencies and product-derived subtype UI
UberDudePL Apr 7, 2026
c452737
Implement US/ES CPI providers and provider-based inflation lookup
UberDudePL Apr 7, 2026
d6dbb04
Add persisted inflation storage for non-GUS providers
UberDudePL Apr 7, 2026
5b33646
Update schema for inflation_rates table
UberDudePL Apr 7, 2026
d4b0072
Finalize bond feature review fixes and squash migrations
UberDudePL Apr 7, 2026
575daf0
Close final bond actions from review checklist
UberDudePL Apr 7, 2026
07f4c6a
Apply PR review fixes: es_ine slash, dead callback, locking, rescue, …
UberDudePL Apr 7, 2026
9502096
Address remaining review comments and secret-scan adjustments
UberDudePL Apr 7, 2026
a29e329
Address latest review comments: fix inflation guards, validations, pr…
UberDudePL Apr 7, 2026
dedc9fa
Address review follow-ups across balance, providers, hostings tests, …
UberDudePL Apr 7, 2026
29e330e
Fix CI: cascade delete bond lots when deleting linked transaction ent…
UberDudePL Apr 7, 2026
f97713f
Merge branch 'main' into feature/bond
jjmata Apr 7, 2026
ec1e9a7
Refactor inflation settings UI and unify CPI import across providers
UberDudePL Apr 7, 2026
dbd634e
Fix CI failures: RuboCop style and database user defaults
UberDudePL Apr 7, 2026
13ed045
Address Copilot PR review feedback on inflation import job
UberDudePL Apr 7, 2026
2791f95
Fix bond inflation review and import edge cases
UberDudePL Apr 7, 2026
596c8c3
Unify inflation import flow and fix provider labeling
UberDudePL Apr 7, 2026
3f2851f
Fix CodeRabbit & Copilot review comments (round 3)
UberDudePL Apr 7, 2026
e9a2a5d
Fix needs_rate_review for lots in first period
UberDudePL Apr 7, 2026
d5efd3b
Address Copilot and CodeRabbit feedback for bond inflation flow
UberDudePL Apr 7, 2026
fc00f6a
Allow manual CPI mode with provider-aware auto-fetch
UberDudePL Apr 7, 2026
8ab59c9
Refine bond lot form UX and controller initialization
UberDudePL Apr 7, 2026
29d7cec
Improve bond inflation UX and preset/provider synchronization
UberDudePL Apr 7, 2026
0cc44ac
Fix bond lot UX and align review-driven consistency changes
UberDudePL Apr 7, 2026
dca603a
Fix CPI indicator: use 1832 (YoY) instead of 639 (MoM)
UberDudePL Apr 7, 2026
1f822e6
Generalize inflation import flow and harden bond lot checks
UberDudePL Apr 7, 2026
d948dd3
Fix review findings for inflation providers and bond lot flow
UberDudePL Apr 8, 2026
b6ed51a
Fix bond review findings
UberDudePL Apr 8, 2026
1d92928
Centralize bond default inflation provider
UberDudePL Apr 8, 2026
7615e81
Use Stimulus target for provider select
UberDudePL Apr 8, 2026
7df6101
Fix latest review findings for bond flow
UberDudePL Apr 8, 2026
37ce1c0
Harden bond lot read paths and review fixes
UberDudePL Apr 8, 2026
f280e33
Fix remaining bond review comments
UberDudePL Apr 8, 2026
ecd0922
Apply latest bond review fixes
UberDudePL Apr 8, 2026
6e1ca73
Address latest bond review comments
UberDudePL Apr 8, 2026
22292a8
Apply bond review batch 3 fixes
UberDudePL Apr 8, 2026
7502dd9
Make bond lots constraint migration no-op
UberDudePL Apr 8, 2026
9fcbb3e
Resolve merge conflict in balance sync cache
UberDudePL Apr 8, 2026
f0bcd0c
Merge upstream/main into feature/bond
UberDudePL Apr 8, 2026
32e306d
Fix Money#exchange_to calls: fallback_rate → custom_rate
UberDudePL Apr 8, 2026
4ef188f
Fix cashflow principal, FX lookup, and locale provider defaults
UberDudePL Apr 8, 2026
d21122e
Optimize bond summary source lookup and fix legacy env presence
UberDudePL Apr 8, 2026
435e013
Fix memoization to cache nullable values and make constraint migratio…
UberDudePL Apr 9, 2026
5dd3b78
Batch 8: Memoize return amounts and fix percent formatting
UberDudePL Apr 9, 2026
20fcc1c
Merge remote-tracking branch 'upstream/main' into feature/bond
UberDudePL Apr 20, 2026
3c139d5
Trim CPI scope for core bond PR
UberDudePL Apr 20, 2026
ed5843f
Fix enable banking psu_type validation for CI
UberDudePL Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ TWELVE_DATA_API_KEY=
# EXCHANGE_RATE_PROVIDER=twelve_data
# SECURITIES_PROVIDER=twelve_data

# Optional: GUS SDP inflation import for EOD/ROD bonds
# Disabled by default. Enable only if you want automatic monthly CPI imports.
INFLATION_IMPORT_ENABLED=false
# Optional API key for higher GUS SDP limits (free anonymous mode works without it)
GUS_SDP_API_KEY=
Comment thread
UberDudePL marked this conversation as resolved.
# Optional: CPI source overrides for US and ES inflation providers
# US defaults: base_url=https://api.bls.gov/publicAPI/v2, series_id=CUUR0000SA0
# US_BLS_CPI_BASE_URL=
# US_BLS_CPI_SERIES_ID=
# ES defaults: base_url=https://servicios.ine.es/wstempus/js/EN/DATOS_SERIE/
# ES_INE_CPI_BASE_URL=
# ES_INE_CPI_SERIES_ID=
# Optional: override the default CPI indicator series ID used for inflation imports
# GUS_SDP_CPI_INDICATOR_ID=

# Alternative: Use Yahoo Finance as provider (free, no API key required)
EXCHANGE_RATE_PROVIDER=yahoo_finance
SECURITIES_PROVIDER=yahoo_finance
Expand Down
11 changes: 11 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ ONBOARDING_STATE = open
# Enable Twelve market data (careful, this will use your API credits)
TWELVE_DATA_API_KEY =

# Global CPI import for inflation-linked bond calculations
# Disabled by default; enable only when needed
INFLATION_IMPORT_ENABLED=false
# Optional API key (anonymous mode works without this)
GUS_SDP_API_KEY=
# Optional CPI provider overrides for US/ES inflation-linked bonds
# US_BLS_CPI_BASE_URL=
# US_BLS_CPI_SERIES_ID=
# ES_INE_CPI_BASE_URL=
# ES_INE_CPI_SERIES_ID=

# OpenAI-compatible API endpoint config
OPENAI_ACCESS_TOKEN =
OPENAI_URI_BASE =
Expand Down
6 changes: 6 additions & 0 deletions .env.test.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ OIDC_REDIRECT_URI=http://localhost:3000/auth/openid_connect/callback
# Uncomment and fill in live keys when you need to generate a VCR cassette fixture
# ================

# Global CPI import for inflation-linked bond calculations.
# Leave blank unless you intentionally want ENV to override the persisted setting.
INFLATION_IMPORT_ENABLED=
# Optional API key for GUS SDP
GUS_SDP_API_KEY=


# ================
# Miscellaneous
Expand Down
68 changes: 68 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,3 +281,71 @@ Style for suggestions

Notes from repository config
- If .gemini/config.yaml disables automated code_review, still provide clear summaries and fix suggestions in PRs.

---

## Bond Feature (PR #1330 - UberDudePL/feature/bond)

### Review context
- Polish localization PR has already been merged.
- Do not flag updates in `config/locales/views/**/pl.yml` as out-of-scope by default for Bond follow-up fixes.

### Architecture
- `Bond` → has many `BondLot` → references inflation provider
- Subtypes: `zero_coupon`, `fixed_coupon`, `inflation_linked`, `savings`, `other`
- Product codes: `pl_eod`, `pl_rod`, `us_tips_10y`, `es_letra_3m`, `us_t_bill_4w`
- Coupon frequency: `at_maturity` (EOD/ROD), `semi_annual` (TIPS), `quarterly`, `monthly`

### Inflation Providers (Pluggable)
- `gus_sdp` — GUS SDP API (Poland, implemented)
- `us_bls` — BLS/FRED CPI API (USA, implemented)
- `es_ine` — INE/Eurostat CPI (Spain, implemented)
- `nil` — manual CPI entry

### Key Files
- Models: `app/models/bond.rb`, `app/models/bond_lot.rb`, `app/models/gus_inflation_rate.rb`
- Controllers: `app/controllers/bonds_controller.rb`, `app/controllers/bond_lots_controller.rb`
- Tests: `test/models/bond_lot_test.rb`, `test/controllers/bond_lots_controller_test.rb`

---

## Response Style

### Be Concise
- Short, actionable answers
- No fluff: avoid "Great question!", "Sure, I'd be happy to help!", "Of course!"
- First: facts / results. Then details if needed.
- Don't repeat context already known.

### Tool First
- Run commands / read files first
- Then short summary of what happened
- Then offer next steps if relevant.

### Polish Context
- User (UberDudePL) prefers Polish language in conversation
- Code, commits, docs: English only
- When in doubt, ask.

---

## Caveman-Style Behavior

When working with code:
1. **Tools first** — run commands, read files, check status
2. **Then short summary** — 1-3 sentences max
3. **Then offer action** — if something needs to be done, ask/confirm

Example:
- Don't: "Great question! Let me check the bond model for you. I'll read the file and analyze it."
- Do: "Checking bond.rb..." → "Found SUBTYPES at line 15. Want me to refactor to generic version?"

---

## Pre-PR Checklist for Bond Feature

- [ ] Tests pass: `bin/rails test test/models/bond_lot_test.rb`
- [ ] Tests pass: `bin/rails test test/controllers/bond_lots_controller_test.rb`
- [ ] Rubocop clean: `bin/rubocop app/models/bond* app/controllers/bond*`
- [ ] Backfill migration works (eod/rod → inflation_linked + product_code)
- [ ] US/ES bonds added to PRODUCT_DEFAULTS
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ jobs:
PLAID_CLIENT_ID: foo
PLAID_SECRET: bar
DATABASE_URL: postgres://postgres:postgres@localhost:5432
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
REDIS_URL: redis://localhost:6379
RAILS_ENV: test

Expand Down
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ node_modules/
*.ntvs*
*.njsproj
*.sln

# Code review comments (local development only)
bond-comments.md
*.sw?
*.roo*
# OS specific
Expand All @@ -123,4 +126,7 @@ scripts/
.auto-claude-status
.claude_settings.json
.security-key
logs/security/
logs/security/
# Local bond implementation notes
Bond.md
Bond.txt
7 changes: 7 additions & 0 deletions app/components/UI/account/activity_feed.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
icon: "arrow-left-right",
href: new_trade_path(account_id: account.id),
data: { turbo_frame: :modal }) %>
<% elsif account.bond? %>
<% menu.with_item(
variant: "link",
text: t("accounts.show.activity.new_activity", default: "New activity"),
icon: "arrow-left-right",
href: new_bond_lot_path(account_id: account.id),
data: { turbo_frame: :modal }) %>
<% else %>
<% menu.with_item(
variant: "link",
Expand Down
4 changes: 2 additions & 2 deletions app/components/UI/account/chart.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="flex items-center gap-1">
<%= tag.p title, class: "text-sm font-medium text-secondary" %>

<% if account.investment? %>
<% if account.investment? || account.bond? %>
<%= render "investments/value_tooltip", balance: account.balance_money, holdings: holdings_value_money, cash: account.cash_balance_money %>
<% end %>
</div>
Expand All @@ -19,7 +19,7 @@

<%= form_with url: account_path(account), method: :get, data: { controller: "auto-submit-form" } do |form| %>
<div class="flex items-center gap-2">
<% if account.investment? %>
<% if account.investment? || account.bond? %>
<%= form.select :chart_view,
[["Total value", "balance"], ["Holdings", "holdings_balance"], ["Cash", "cash_balance"]],
{ selected: view },
Expand Down
2 changes: 1 addition & 1 deletion app/components/UI/account/chart.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def view_balance_money

def title
case account.accountable_type
when "Investment", "Crypto"
when "Investment", "Crypto", "Bond"
case view
when "balance"
"Total account value"
Expand Down
6 changes: 5 additions & 1 deletion app/components/UI/account_page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,17 @@ def subtitle
end

def active_tab
return :positions if account.bond? && @active_tab&.to_sym == :holdings

tabs.find { |tab| tab == @active_tab&.to_sym } || tabs.first
end

def tabs
case account.accountable_type
when "Investment", "Crypto"
[ :activity, :holdings ]
when "Bond"
[ :activity, :positions, :closed ]
when "Property", "Vehicle", "Loan"
[ :activity, :overview ]
else
Expand All @@ -51,7 +55,7 @@ def tab_content_for(tab)
case tab
when :activity
activity_feed
when :holdings, :overview
when :holdings, :overview, :positions, :closed
# Accountable is responsible for implementing the partial in the correct folder
render "#{account.accountable_type.downcase.pluralize}/tabs/#{tab}", account: account
end
Expand Down
31 changes: 31 additions & 0 deletions app/components/UI/dashboard/bond_summary_row.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<tr class="<%= row_classes %>">
<td class="px-4 py-4">
<div class="flex items-center gap-4">
<%= render DS::FilledIcon.new(variant: :text, text: subtype_label, size: "md", rounded: true) %>
<div class="space-y-0.5 min-w-0">
<%= link_to subtype_label, account_path(account, tab: "positions"), class: "truncate hover:underline" %>
<p class="text-xs text-secondary truncate"><%= t("pages.dashboard.bond_summary.account_wrapper", account: account.name, wrapper: account.bond_wrapper_label || account.short_subtype_label) %></p>
</div>
</div>
</td>

<td class="px-4 py-4 text-right">
<p class="privacy-sensitive"><%= rate_text %></p>
<p class="font-normal text-secondary"><%= rate_meta %></p>
</td>

<td class="px-4 py-4 text-right">
<p class="privacy-sensitive"><%= helpers.format_money(Money.new(lot.amount, account.currency)) %></p>
<p class="font-normal text-secondary"><%= t("pages.dashboard.bond_summary.principal_term", term: t("pages.dashboard.bond_summary.term_months", count: lot.term_months)) %></p>
</td>

<td class="px-4 py-4 text-right">
<p class="privacy-sensitive"><%= lot.maturity_date ? l(lot.maturity_date) : t("bonds.purchase_holding.unknown") %></p>
<p class="font-normal text-secondary"><%= t("pages.dashboard.bond_summary.maturity_label") %></p>
</td>

<td class="px-4 py-4 text-right">
<p class="privacy-sensitive <%= total_return_class %>"><%= helpers.format_money(Money.new(total_return_amount, account.currency)) %></p>
<p class="font-normal text-secondary"><%= total_return_label %></p>
</td>
</tr>
135 changes: 135 additions & 0 deletions app/components/UI/dashboard/bond_summary_row.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
class UI::Dashboard::BondSummaryRow < ApplicationComponent
attr_reader :account, :lot, :show_border

def initialize(account:, lot:, show_border: false)
@account = account
@lot = lot
@show_border = show_border
end

def subtype_label
Bond.long_subtype_label_for(lot.subtype) || t("bonds.purchase_holding.unknown")
end

def total_return_amount
@total_return_amount ||= if projected_total_return?
lot.projected_total_return_amount(allow_import: false)
else
lot.total_return_amount(allow_import: false)
end
end

def total_return_label
if projected_total_return?
t("bonds.purchase_holding.projected_to_maturity")
else
t("bonds.purchase_holding.since_purchase")
end
end

def total_return_class
total_return_amount.negative? ? "text-destructive" : "text-success"
end
Comment on lines +26 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential NoMethodError if total_return_amount is nil.

Based on the model code, both lot.total_return_amount and lot.projected_total_return_amount can return nil (e.g., when estimated_current_value is nil or interest_rate/annual_rate is missing). Calling .negative? on nil will raise NoMethodError.

Suggested fix
  def total_return_class
-   total_return_amount.negative? ? "text-destructive" : "text-success"
+   return "text-secondary" if total_return_amount.nil?
+
+   total_return_amount.negative? ? "text-destructive" : "text-success"
  end

You may also want to handle the nil case in the view template to avoid rendering invalid return amounts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/UI/dashboard/bond_summary_row.rb` around lines 26 - 28, The
helper total_return_class currently calls total_return_amount.negative? which
can raise NoMethodError when total_return_amount is nil; update
total_return_class to guard against nil (e.g., return a neutral class like "" or
"text-muted" if total_return_amount.nil?) or coerce safely (e.g., use
total_return_amount.to_f.negative?) and ensure you reference total_return_amount
(and the model callers lot.total_return_amount /
lot.projected_total_return_amount) so the view also handles nil by rendering a
placeholder instead of the numeric value.


def rate_text
if lot.inflation_linked?
return t("bonds.purchase_holding.update_needed") if lot.requires_rate_review?

current_rate = lot.current_rate_percent(allow_import: false)
return helpers.number_to_percentage(current_rate, precision: 3) if current_rate.present?

t("bonds.purchase_holding.unknown")
Comment thread
UberDudePL marked this conversation as resolved.
else
lot.interest_rate.present? ? helpers.number_to_percentage(lot.interest_rate, precision: 3) : t("bonds.purchase_holding.unknown")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
end
end

def rate_meta
if lot.inflation_linked?
inflation_linked_rate_meta
else
t(
"bonds.purchase_holding.bond_meta",
rate_type: localized_rate_type,
coupon: localized_coupon_frequency
)
end
end

def row_classes
classes = [ "text-sm", "font-medium", "text-primary" ]
classes << "border-b border-divider" if show_border
classes.join(" ")
end

private
def projected_total_return?
return @projected_total_return if defined?(@projected_total_return)

@projected_total_return = lot.total_return_amount(allow_import: false).abs < 0.01.to_d &&
lot.projected_total_return_amount(allow_import: false).positive?
end
Comment thread
UberDudePL marked this conversation as resolved.
Comment on lines +71 to +76
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Nil values will cause NoMethodError in predicate logic.

Both current_return_amount and projected_return_amount can be nil (per context snippets showing the underlying model methods return nil when data is missing). Calling .abs or .positive? on nil raises NoMethodError.

Suggested fix with nil guards
  def projected_total_return?
    return `@projected_total_return` if defined?(`@projected_total_return`)

-   `@projected_total_return` = current_return_amount.abs < 0.01.to_d &&
-     projected_return_amount.positive?
+   current = current_return_amount
+   projected = projected_return_amount
+
+   `@projected_total_return` = current.present? &&
+     projected.present? &&
+     current.abs < 0.01.to_d &&
+     projected.positive?
  end

This ensures the method returns false when data is unavailable rather than raising an exception.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def projected_total_return?
return @projected_total_return if defined?(@projected_total_return)
@projected_total_return = current_return_amount.abs < 0.01.to_d &&
projected_return_amount.positive?
end
def projected_total_return?
return `@projected_total_return` if defined?(`@projected_total_return`)
current = current_return_amount
projected = projected_return_amount
`@projected_total_return` = current.present? &&
projected.present? &&
current.abs < 0.01.to_d &&
projected.positive?
end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/UI/dashboard/bond_summary_row.rb` around lines 71 - 76, The
predicate method projected_total_return? should guard against nils on
current_return_amount and projected_return_amount to avoid NoMethodError; update
projected_total_return? (and its memoization `@projected_total_return`) to return
false if either current_return_amount or projected_return_amount is nil, and
only call numeric methods (.abs, .positive?, .to_d) when those values are
present (e.g., use explicit nil checks or safe navigation) so missing data
yields false instead of raising.


def inflation_linked_rate_meta
return t("bonds.purchase_holding.pending_review") if lot.requires_rate_review?

inflation_component = lot.current_inflation_component_percent(allow_import: false)
margin_component = lot.current_margin_percent(allow_import: false)
if inflation_component.nil? || margin_component.nil?
if lot.in_first_rate_period?
return t("bonds.purchase_holding.first_period_fixed_rate")
else
reference_str = lot.current_cpi_reference_on&.strftime("%m-%Y") || t("bonds.purchase_holding.unknown")
return t("bonds.purchase_holding.inflation_data_unavailable", reference: reference_str)
end
end

inflation = helpers.number_to_percentage(inflation_component.to_d, precision: 3)
margin = helpers.number_to_percentage(margin_component.to_d, precision: 3)

if lot.gus_inflation_source?(allow_import: false)
t(
"bonds.purchase_holding.inflation_meta_gus",
inflation: inflation,
margin: margin,
indicator: lot.current_inflation_indicator_id
)
elsif current_inflation_source_key.blank? || current_inflation_source_key == "manual"
t(
"bonds.purchase_holding.inflation_meta_manual",
inflation: inflation,
margin: margin
)
else
t(
"bonds.purchase_holding.inflation_meta_provider",
inflation: inflation,
margin: margin,
provider: localized_inflation_provider
)
end
end

def current_inflation_source_key
lot.current_inflation_source(allow_import: false).to_s.presence
end

def localized_inflation_provider
provider = current_inflation_source_key
return t("bonds.purchase_holding.unknown") if provider.blank?

t("bonds.purchase_holding.inflation_providers.#{provider}", default: provider.to_s.humanize)
end

def localized_rate_type
return t("bonds.purchase_holding.unknown") if lot.rate_type.blank?

t("bond_lots.form.rate_types.#{lot.rate_type}", default: t("bonds.purchase_holding.unknown"))
end

def localized_coupon_frequency
return t("bonds.purchase_holding.unknown") if lot.coupon_frequency.blank?

t("bond_lots.form.coupon_frequencies.#{lot.coupon_frequency}", default: t("bonds.purchase_holding.unknown"))
end
end
Loading
Loading