diff --git a/project-docs/iteration-1/tax/sep25-tax-github-issues.md b/project-docs/iteration-1/tax/sep25-tax-github-issues.md new file mode 100644 index 0000000000..c62e61a34b --- /dev/null +++ b/project-docs/iteration-1/tax/sep25-tax-github-issues.md @@ -0,0 +1,535 @@ +# Issue 1 + +**Title:** [Tax][Iteration 2] Establish shared tax-domain contracts and source normalization + +## Goal +Create the shared backend tax-domain contracts and deterministic source-normalization pipeline used by FIFO, average cost, tax lots, yearly summaries, and exports. + +## Scope +- Define shared contracts such as `TaxCalculationMethod`, normalized `TaxActivity`, warning/error types, `TaxRealizedGainRecord`, and calculation-result envelopes. +- Reuse Ghostfolio activity retrieval instead of building a second transaction subsystem. +- Load persisted activities only; synthetic cash is excluded. +- Reuse Draft and Exclude from Analysis behavior. +- Preserve deterministic ordering: full activity date ascending, then activity id ascending. +- Reuse existing stock-split normalization. +- Preserve source ids, account id, symbolProfileId, transaction currency, asset currency, and base-currency values. +- Represent missing historical FX as unavailable + diagnostic, never zero or an assumed 1:1 rate. +- Initial calculation scope: `userId + accountId + symbolProfileId`; null account uses a separate `NO_ACCOUNT` scope. + +## Implementation Notes +Prefer a dedicated tax backend service/module that adapts existing Ghostfolio patterns. Do not modify the current ROAI/performance calculator for tax basis logic. Use `Big` internally. + +Likely touch: +- `apps/api/src/app/activities/activities.service.ts` +- `libs/common/src/lib/interfaces/` +- new tax-domain service/interface files +- existing stock-split and FX services + +## Acceptance Criteria +- One normalized input shape is usable by both FIFO and average cost. +- Source ids and exact dates remain traceable. +- Same-date ordering is deterministic. +- Split-adjusted quantities/prices preserve transaction value. +- Missing FX produces explicit diagnostics. +- Synthetic cash never enters tax calculations. +- No frontend changes. + +## Tests +Cover same-date ordering, split adjustment, null-account scope, missing FX, invalid/missing numeric inputs, and synthetic-cash exclusion. + +## Dependencies +Existing Ghostfolio activity, FX, and split services. + +## Out of Scope +FIFO matching, average-cost pooling, jurisdiction-specific tax rules, frontend work. + +## Design Sources +- `project-docs/iteration-1/tax/fifo-capital-gains-spec.md` +- `project-docs/iteration-1/tax/average-cost-capital-gains-spec.md` +- `project-docs/iteration-1/tax/tax-lot-data-model.md` +- `project-docs/iteration-1/tax/yearly-tax-summary-data-structure.md` + + +--- + +# Issue 2 + +**Title:** [Tax][Iteration 2] Add withholding-tax storage and activity API support + +## Goal +Persist dividend withholding tax on source activities and expose it through the existing activity create/update/import/export paths. + +## Scope +- Add nullable `withholdingTax` to `Order`. +- Preserve the semantic difference between `null` (unknown) and `0` (known no withholding). +- Accept the field in create/update DTOs. +- Preserve omitted values on update. +- Thread the field through import validation, dry-run preview, and actual import. +- Preserve the field in the existing Ghostfolio backup export/import round trip. +- Keep gross dividend, withholding amount/rate, net dividend, and activity fee separate. + +## Implementation Notes +`Order` remains the authoritative source. Do not create a separate dividend table. Dividend gross value remains based on source activity quantity × unit price. Derive rate/net values rather than persisting redundant totals. + +Likely touch: +- `prisma/schema.prisma` +- `libs/common/src/lib/dtos/create-order.dto.ts` +- `libs/common/src/lib/dtos/update-order.dto.ts` +- `apps/api/src/app/activities/` +- `apps/api/src/app/import/import.service.ts` +- existing export response/service + +## Acceptance Criteria +- Existing activities remain valid. +- `null` and `0` round-trip distinctly. +- Create, update, import dry-run, real import, read, and backup export all preserve the field. +- Activity fees remain separate from withholding. +- No jurisdiction-specific rate logic is added. + +## Tests +Positive withholding, zero withholding, null withholding, create/update omission, old import files, explicit import value, dry-run consistency, backup export/import round trip. + +## Dependencies +Shared activity model and import/export paths. + +## Out of Scope +Frontend controls, jurisdiction rules, automatic withholding-rate lookup. + +## Design Source +`project-docs/iteration-1/tax/withholding-tax-schema.md` + + +--- + +# Issue 3 + +**Title:** [Tax][Iteration 2] Add tax-relevant activity flag and filtering + +## Goal +Allow users to explicitly exclude a source activity from tax analytics without deleting it or changing normal portfolio behavior. + +## Scope +- Add `Order.isTaxRelevant Boolean @default(true)`. +- Add optional boolean support to create/update DTOs. +- Preserve omitted update values. +- Thread the flag through import dry-run and real import. +- Preserve the flag in generic backup export/import. +- Reuse existing Draft, activity Exclude from Analysis, and excluded-account behavior. +- Exclude synthetic cash from tax source data. +- Add mixed historical investment diagnostics when an excluded BUY/SELL may affect a later tax-relevant SELL. + +## Implementation Notes +Tax eligibility is: +`normal Ghostfolio eligibility AND isTaxRelevant AND operation-specific type`. + +Do not reuse the broad `Exclude from Analysis` tag as the tax-only flag. Do not cascade flag changes to related trades. + +Proposed diagnostic: +`TAX_SCOPE_HAS_EXCLUDED_INVESTMENT_HISTORY`. + +## Acceptance Criteria +- Existing rows/default imports resolve `true`. +- `false` excludes only tax analytics. +- Draft/excluded activity/excluded account still wins over `true`. +- BUY/SELL continuity problems are surfaced, not silently repaired. +- DIVIDEND filtering remains independent from unrelated BUY/SELL exclusions. +- Existing portfolio analytics do not change merely because the field exists. + +## Tests +Create/update/default behavior, old imports, explicit false import, excluded-account/tag interactions, BUY/SELL mixed-history diagnostics, DIVIDEND inclusion/exclusion, synthetic cash exclusion. + +## Dependencies +Shared tax source normalization. + +## Out of Scope +Frontend toggle, jurisdiction-specific taxable-event classification. + +## Design Source +`project-docs/iteration-1/tax/tax-relevant-filtering-spec.md` + + +--- + +# Issue 4 + +**Title:** [Tax][Iteration 2] Implement deterministic FIFO capital-gains engine + +## Goal +Implement the FIFO realized-capital-gains calculation exactly as specified in Iteration 1. + +## Scope +- Consume normalized eligible BUY/SELL activities in deterministic date/id order. +- Create acquisition lots chronologically. +- Support partial lot consumption and one sale consuming multiple lots. +- Preserve BUY/SELL source ids in trace output. +- Calculate gross proceeds, gross cost basis, gross realized gain, activity-fee allocation, and fee-adjusted realized gain. +- Produce asset-currency and base-currency values where available. +- Reuse stock-split-normalized source activities. +- Return open lots separately from realized results. +- Report insufficient holdings rather than inventing short-sale basis. + +## Implementation Notes +Use `Big` internally and do not round intermediate values for display. Keep FIFO separate from the existing ROAI/performance calculation. + +## Acceptance Criteria +- FIFO consumes oldest eligible acquisition quantity first. +- Partial lot balances remain correct. +- Multi-lot sales remain traceable. +- One source SELL produces one sale-level realized-gain record. +- Missing FX is not coerced to zero. +- Invalid/insufficient history returns deterministic diagnostics. + +## Tests +Single buy/sell, multiple buys, partial lot sale, sale across lots, same-date ordering, zero values, fractional quantities, fees, losses, stock splits, missing FX, insufficient lots. + +## Dependencies +Shared tax-domain contracts/source normalization; withholding/tax-relevant source fields as applicable. + +## Out of Scope +Average cost, short sales, wash-sale rules, jurisdiction-specific holding-period rules. + +## Design Source +`project-docs/iteration-1/tax/fifo-capital-gains-spec.md` + + +--- + +# Issue 5 + +**Title:** [Tax][Iteration 2] Implement average-cost capital-gains engine + +## Goal +Implement the average-cost realized-capital-gains strategy specified in Iteration 1. + +## Scope +- Maintain pooled quantity, gross cost, and acquisition-fee state. +- BUY increases quantity/cost/fee pools. +- SELL allocates pooled basis proportionally using disposal quantity / pool quantity. +- Partial sales reduce pool proportionally without changing average unit cost. +- Full close resets pool state exactly to zero. +- Produce the same common sale-level `TaxRealizedGainRecord` shape as FIFO. +- Preserve method-specific average-cost event/state trace data. + +## Core Formula +For SELL quantity `q` from pool quantity `Q`: +`disposalRatio = q / Q` + +Allocate gross basis and acquisition fees using that ratio before reducing the pool. + +## Acceptance Criteria +- Pool state is deterministic and traceable. +- Partial disposal leaves average unit cost unchanged. +- Full disposal closes the pool cleanly. +- Gross vs fee-adjusted results remain separate. +- Missing FX and insufficient quantity are explicit diagnostics. +- No FIFO lot matching is fabricated. + +## Tests +One buy/sell, multiple buys, partial disposal, repeated disposals, full close/reset, zero-cost buy, fractions, fees, losses, same-date ordering, missing FX, insufficient pool quantity. + +## Dependencies +Shared tax-domain contracts/source normalization. + +## Out of Scope +FIFO, jurisdiction-specific pooling periods/categories, short sales. + +## Design Source +`project-docs/iteration-1/tax/average-cost-capital-gains-spec.md` + + +--- + +# Issue 6 + +**Title:** [Tax][Iteration 2] Implement derived tax-lot and calculation trace tracking + +## Goal +Provide deterministic, traceable tax-lot and method-specific calculation state without persisting stale derived financial results as authoritative data. + +## Scope +- Derive FIFO tax lots from source BUY activities. +- Track initial quantity, remaining quantity, acquired date, currency, source activity id, and derived status. +- Produce deterministic FIFO match identifiers and BUY/SELL trace links. +- Produce average-cost pool/event trace state. +- Keep common realized-gain records method-independent. +- Recalculate derived state after relevant source activity edits/deletes/flag changes. + +## Implementation Notes +`Order` remains authoritative. Do not persist tax lots, matches, average-cost events, or yearly summaries as authoritative Prisma rows in the initial implementation. + +Safe initial invalidation may replay a full affected user or scope. + +## Acceptance Criteria +- Open/partially consumed/closed FIFO lot state is correct. +- FIFO match traces identify consumed BUY and SELL source ids. +- Average cost exposes pool/event trace without fake lots. +- Derived state updates after source changes. +- No stale persisted tax state becomes the source of truth. + +## Tests +Open lots, partial consumption, full close, multiple lots, source update/delete, tax-relevance change, null account scope, deterministic trace ids. + +## Dependencies +FIFO and average-cost engines; shared source normalization. + +## Out of Scope +Persistent authoritative lot tables, jurisdiction-specific lot-selection overrides. + +## Design Source +`project-docs/iteration-1/tax/tax-lot-data-model.md` + + +--- + +# Issue 7 + +**Title:** [Tax][Iteration 2] Implement yearly tax summary service and API + +## Goal +Build a yearly tax-summary service that aggregates dividend/withholding and realized-gain results for a requested year and selected cost-basis method. + +## Scope +- Accept year + method. +- Resolve user base currency from settings. +- Load eligible investment history from the beginning through the selected year end. +- Run FIFO or average cost over the required historical source set. +- Filter realized SELL records to the requested year. +- Build selected-year `DividendRecord` values. +- Aggregate dividend totals, withholding, proceeds, cost basis, gains, fees, and counts. +- Preserve nullable/incomplete aggregates and diagnostics. +- Expose a shared API response for frontend/dashboard use. + +## Critical Rule +Do **not** filter acquisition history to the requested year before calculating basis. A 2026 SELL may depend on BUY activity from earlier years. + +## Acceptance Criteria +- Empty year returns known zero totals and `isComplete = true`. +- Exactly one calculation method applies per summary. +- Portfolio-level totals aggregate across independent account/asset scopes. +- Null means unavailable; zero means known zero. +- Detail records reconcile with summary totals. +- Missing FX/unknown withholding/calculation errors propagate correctly. + +## Tests +Cross-year acquisitions, FIFO vs average-cost differences, empty year, multiple accounts/assets, gains/losses, zero/null withholding, fees, missing FX, insufficient history, year boundaries, reconciliation. + +## Dependencies +FIFO engine, average-cost engine, dividend/withholding source data, tax-relevant filtering, common contracts. + +## Out of Scope +Dashboard UI, tax filing rules. + +## Design Source +`project-docs/iteration-1/tax/yearly-tax-summary-data-structure.md` + + +--- + +# Issue 8 + +**Title:** [Tax][Iteration 2] Implement CSV and PDF tax exports + +## Goal +Generate CSV and PDF tax reports from the same yearly tax calculation results without duplicating financial logic. + +## Scope +- Accept year, method, and format. +- Use `YearlyTaxSummary` + method-specific trace data as the source. +- CSV record types: SUMMARY, DIVIDEND, REALIZED_GAIN, FIFO_MATCH, AVERAGE_COST_EVENT, WARNING, ERROR. +- Use fixed deterministic CSV column/row ordering. +- Preserve null vs zero and incomplete status. +- Include FIFO/average-cost trace detail without double counting sale totals. +- Generate human-readable PDF sections for summary, dividends, gains, trace detail, diagnostics, and report note. +- Return deterministic filename/content type/content. +- Keep tax report behavior separate from the current general Ghostfolio backup `GET /export`. + +## Implementation Notes +Evaluate the already-installed `papaparse` dependency for CSV. Select the smallest compatible PDF approach after checking NestJS/build/pagination/testability. + +Recommended names: +`ghostfolio-tax--.csv|pdf` + +## Acceptance Criteria +- CSV/PDF totals exactly match the source yearly summary. +- Unknown numeric values are not written as zero. +- Missing FX and incomplete-history diagnostics are visible. +- FIFO trace rows never increase annual disposal totals. +- Exports remain authorized to the authenticated user. +- Generic backup export/import preserves `isTaxRelevant`. + +## Tests +Empty year, dividend null/zero withholding, gain/loss, FIFO multi-lot, average-cost trace, missing FX, incomplete results, CSV escaping/injection safety, deterministic order, PDF content assertions, cross-format reconciliation. + +## Dependencies +Yearly summary service, FIFO/average-cost trace data. + +## Out of Scope +Official government forms, XLSX, electronic filing, email/cloud delivery. + +## Design Source +`project-docs/iteration-1/tax/tax-export-format-spec.md` + + +--- + +# Issue 9 + +**Title:** [Tax][Iteration 2] Add comprehensive tax backend calculation and integration tests + +## Goal +Provide numeric regression coverage for the tax backend so FIFO, average cost, withholding, filtering, yearly summaries, and exports remain deterministic and traceable. + +## Scope +Create focused unit/integration coverage across all tax-domain services, including: +- normal cases and zero values +- multiple purchases/sales +- partial lot sales +- same-date ordering +- missing/invalid inputs +- fractional quantities +- fee allocation +- stock splits +- multiple accounts/assets +- null-account scope +- historical FX gaps +- tax-relevant exclusions +- cross-year basis +- summary reconciliation +- export reconciliation + +## Acceptance Criteria +- Tests assert expected numeric values, not only successful execution. +- FIFO and average-cost test fixtures share normalized source inputs where practical. +- Existing Ghostfolio portfolio/performance behavior has regression protection. +- No expected value is changed solely to make a failing test pass. +- Incomplete/error cases assert specific diagnostics. + +## Dependencies +Implement alongside the relevant backend issues; complete before Iteration 2 closes. + +## Out of Scope +Frontend visual tests and jurisdiction-specific tax cases. + +## Design Sources +All Iteration 1 tax specifications under `project-docs/iteration-1/tax/`. + + +--- + +# Issue 10 + +**Title:** [Tax][Iteration 3] Build React activity controls for withholding and tax relevance + +## Goal +Add React UI controls for the tax source fields introduced in Iteration 2. + +## Scope +- Display/edit dividend withholding amount where applicable. +- Display/edit the `Tax relevant` activity flag. +- Preserve null vs zero withholding behavior. +- Explain that disabling tax relevance affects tax analytics without deleting the portfolio activity. +- Surface a warning for BUY/SELL when historical exclusion may make later tax calculations incomplete. +- Reuse shared React activity/form components before creating new ones. + +## Acceptance Criteria +- Existing activity values load correctly. +- Edit/save round trips through the backend. +- Null withholding remains distinguishable from zero. +- Flag changes do not modify unrelated activity fields. +- Validation/errors are visible and consistent with shared UI patterns. +- No tax calculations are performed in the frontend. + +## Tests +Component/form behavior, null/zero withholding, true/false flag, API error handling, regression for existing activity editing. + +## Dependencies +Iteration 2 withholding and tax-relevant backend fields. +Coordinate shared React/component work with Raniya. + +## Out of Scope +Capital-gain calculation views, export UI. + +## Design Sources +- `withholding-tax-schema.md` +- `tax-relevant-filtering-spec.md` + + +--- + +# Issue 11 + +**Title:** [Tax][Iteration 3] Build React capital-gains, tax-lot, and yearly-summary views + +## Goal +Provide React views for choosing a cost-basis method and reviewing realized gains, tax lots/trace data, and yearly tax summaries. + +## Scope +- Cost-basis selector: FIFO / AVERAGE_COST. +- Realized-gain table based on backend `TaxRealizedGainRecord`. +- FIFO tax-lot/match trace view. +- Average-cost pool/event trace view without fake FIFO lots. +- Year selector and yearly-summary cards/table. +- Display gross vs fee-adjusted gain distinctly. +- Display completeness, warnings, and errors. +- Reuse shared React table/card/empty/error/loading components. + +## Acceptance Criteria +- Method switch requests/re-renders backend-calculated results. +- Frontend does not recalculate basis. +- FIFO and average-cost traces are represented according to their actual method. +- Unknown values are not rendered as zero. +- Empty years render a valid empty state. +- Diagnostics are visible. +- Dashboard integration uses the shared tax API contract only after coordination with Raniya. + +## Tests +Method switching, empty year, gain/loss display, incomplete result, missing FX, FIFO trace, average-cost trace, API/loading/error states. + +## Dependencies +Iteration 2 engines, trace tracking, yearly-summary API. +Coordinate dashboard/shared React architecture with Raniya. + +## Out of Scope +Charts owned by Arthur; risk views owned by Sesha; jurisdiction-specific tax advice. + + +--- + +# Issue 12 + +**Title:** [Tax][Iteration 3] Build React CSV/PDF tax export UI and complete tax QA + +## Goal +Provide the user-facing CSV/PDF export controls and complete end-to-end tax feature QA/polish. + +## Scope +- Year selector and cost-basis method selection for export. +- CSV/PDF format controls. +- Trigger backend export and download the returned file. +- Show incomplete-report warning before/after export where appropriate. +- Verify filenames and MIME handling. +- Reuse shared download/UI components where available. +- Perform cross-feature QA for withholding, tax relevance, gains, lots, yearly summary, and exports. +- Capture confirmed issues/fixes for Iteration 3 report/demo notes. + +## Acceptance Criteria +- CSV and PDF download from the shared backend export contract. +- UI does not recalculate export values. +- Export options match the currently selected year/method. +- Incomplete diagnostics are visible. +- Download failures are handled clearly. +- End-to-end values reconcile with the yearly-summary view. +- No regression in shared dashboard/activity UI. + +## Tests +Successful CSV/PDF download, filename/content type, incomplete report, API failure, repeated exports, selected year/method, cross-view/export numeric reconciliation. + +## Dependencies +Iteration 2 tax export backend; React tax views. +Coordinate shared download/dashboard architecture with Raniya. + +## Out of Scope +Official tax filing submission, XLSX, email delivery. + + +---