Investment and transfer improvements - #45
Merged
Merged
Conversation
-price per share when bought
…trol for site.webmanifest
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enhances investment and transfer handling by introducing an explicit quote_currency (trading currency) for investment accounts, improving how investment values and transfer purchase details are represented across the UI and API.
Changes:
- Add
quote_currencysupport end-to-end (DB migration, API DTOs/validators/services, and client types/UI). - Improve investment valuation and display to handle non-USD listed securities (e.g., EUR quotes) and expose
nativeInvested/quoteCurrencyfor display. - Improve transfer editing behavior (preserve manually-entered received amount and price; allow updating investment-linked transfers with a stored share price).
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| client/src/hooks/useFinanceData.ts | Maps investment transactions and updates investment valuation to respect quote currency when converting to master currency. |
| client/src/components/investments-module/utils.ts | Adds quote-currency-aware valuation logic and new fields (nativeInvested, quoteCurrency) in calculated positions. |
| client/src/components/investments-module/utils.test.ts | Adds a vitest regression test for EUR-listed securities valuation/conversion behavior. |
| client/src/components/investments-module/types.ts | Extends client investment types to include quote_currency, price, and new position fields. |
| client/src/components/investments-module/Investments.tsx | Enhances investment transaction mapping to carry price and improve transfer description formatting. |
| client/src/components/investments-module/InvestmentDetailModal.tsx | Displays values/prices using the position’s quote currency; adjusts date formatting and transfer amount display. |
| client/src/components/investments-module/HoldingsList.tsx | Updates holdings display to show invested/value/price in the quote currency. |
| client/src/components/dashboard-module/TransactionList.tsx | Improves transfer auto-calculation/edit preservation and adds price support for investment transfers/edits. |
| client/src/components/dashboard-module/AccountList.tsx | Adds trading currency selection, persists quote_currency, and uses it when converting quote values to USD. |
| client/public/_headers | Prevents caching of site.webmanifest. |
| api/src/validators/transaction.validator.ts | Allows updating a transaction with an optional positive price. |
| api/src/validators/account.validator.ts | Adjusts account currency validation to support investment holding units and adds quote_currency validation. |
| api/src/tests/validators.test.ts | Adds validator coverage for SHARE/crypto units and invalid cash-account units. |
| api/src/services/transfer.service.ts | Improves transfer description generation for investment transfers to distinguish share price from FX rate. |
| api/src/services/transaction.service.ts | Adds support for updating transfers linked to investment transactions, including quantity/price updates and balance reconciliation. |
| api/src/services/account.service.ts | Persists and updates quote_currency consistently. |
| api/src/repositories/investment-transaction.repository.ts | Adds an update method for investment transactions. |
| api/src/models/Account.ts | Adds quote_currency to the Account model. |
| api/src/middlewares/cors.middleware.ts | Adds PATCH to allowed CORS methods. |
| api/src/mappers/account.mapper.ts | Maps quote_currency in both directions (model ↔ DTO). |
| api/src/dtos/transaction.dto.ts | Adds price to UpdateTransactionDto. |
| api/src/dtos/account.dto.ts | Adds quote_currency to account create/update/response DTOs. |
| api/migrations/008-investment-quote-currency.sql | Adds the quote_currency column to accounts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment on lines
26
to
33
| const account: Account = { | ||
| id, | ||
| name: dto.name, | ||
| type: dto.type, | ||
| balance: dto.balance, | ||
| currency: currency.toUpperCase(), | ||
| quote_currency: dto.quote_currency?.trim().toUpperCase(), | ||
| symbol: dto.symbol, |
| const sourceName = source.replace(/\s-\s.*$/, '').trim() | ||
| return `${sourceName} (${transaction.quantity} shares @ ${quoteCurrency} ${price.toFixed(2)}/share)${noteSuffix}` | ||
| } | ||
| return notes || `${transaction.quantity} shares @ ${quoteCurrency} ${price}` |
Comment on lines
55
to
65
| <div> | ||
| <div className="text-xs text-muted-foreground">Current Value</div> | ||
| <div className={`text-lg font-bold ${privacyMode === 'hidden' ? 'select-none' : ''}`}> | ||
| {privacyMode === 'hidden' ? '••••••' : formatValue(position.displayValue, account)} | ||
| {privacyMode === 'hidden' ? '••••••' : formatValue(position.displayValue, account, position.quoteCurrency)} | ||
| </div> | ||
| </div> | ||
| <div> | ||
| <div className="text-xs text-muted-foreground">Net Invested</div> | ||
| <div className={`text-lg font-bold ${privacyMode === 'hidden' ? 'select-none' : ''}`}> | ||
| {privacyMode === 'hidden' ? '••••••' : formatValue(position.netInvested, account)} | ||
| {privacyMode === 'hidden' ? '••••••' : formatValue(position.nativeInvested, account, position.quoteCurrency)} | ||
| </div> |
Comment on lines
94
to
106
| @@ -102,7 +102,7 @@ export function HoldingsList({ | |||
| </div> | |||
| )} | |||
| <div className={`text-sm text-muted-foreground ${privacyMode === 'hidden' ? 'select-none' : ''}`}> | |||
| {privacyMode === 'hidden' ? '•••• ' : `${position.account.symbol || position.account.name}`} {position.currentPrice > 0 && `@ $${position.currentPrice.toLocaleString()}`} | |||
| {privacyMode === 'hidden' ? '•••• ' : `${position.account.symbol || position.account.name}`} {position.currentPrice > 0 && `@ ${formatValue(position.currentPrice, undefined, position.quoteCurrency)}`} | |||
| </div> | |||
Comment on lines
+592
to
+596
| amount_to: amountTo, | ||
| description: formData.description, | ||
| date: formData.date, | ||
| price, | ||
| }), |
| const SUPPORTED_CURRENCIES = ['HUF', 'EUR', 'USD', 'GBP', 'CHF', 'PLN', 'CZK', 'RON'] | ||
|
|
||
| function isValidAccountCurrency(data: { type?: 'cash' | 'investment', currency?: string, asset_type?: 'stock' | 'crypto' | 'manual' }) { | ||
| if (!data.currency) return true |
Comment on lines
+144
to
+145
| nativeInvested = totalInvested - totalWithdrawn | ||
| netInvested = convertToUsd(nativeInvested, account.quote_currency || quoteCurrency) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.