From 8d2e36474aa4134eafaf32921830b4428069720e Mon Sep 17 00:00:00 2001 From: Tharun Ravi Kumar Date: Tue, 15 Sep 2026 13:28:04 -0500 Subject: [PATCH] docs(tax): document transaction and dividend data model --- .../tax/transaction-dividend-data-model.md | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 project-docs/iteration-1/tax/transaction-dividend-data-model.md diff --git a/project-docs/iteration-1/tax/transaction-dividend-data-model.md b/project-docs/iteration-1/tax/transaction-dividend-data-model.md new file mode 100644 index 0000000000..13f5e2f646 --- /dev/null +++ b/project-docs/iteration-1/tax/transaction-dividend-data-model.md @@ -0,0 +1,330 @@ +# Tax Metrics — Existing Transaction and Dividend Data Model + +**Owner:** Tharun Swaminathan +**Iteration:** 1 — Design & Data Modelling +**Date:** September 15, 2026 + +## Objective + +The purpose of this review was to understand how Ghostfolio currently stores and +handles transactions, especially dividend transactions, before adding any +tax-related functionality. + +Since the tax feature will later include withholding tax, FIFO capital gains, +average-cost calculations, tax lots, yearly summaries, and exports, it is +important to first understand the existing data model instead of creating a +separate structure that does not match the rest of Ghostfolio. + +No implementation changes were made as part of this task. + +## Current Persistence Model + +Ghostfolio stores portfolio transactions using the Prisma `Order` model in +`prisma/schema.prisma`. + +Even though the database model is called `Order`, the rest of the application +usually refers to these records as activities. + +The `Order` model already contains the main information needed to describe a +portfolio transaction. + +| Field | Current purpose | +|---|---| +| `id` | Unique identifier for the activity | +| `accountId` | Account associated with the activity | +| `comment` | Optional user comment | +| `createdAt` | Time the database record was created | +| `currency` | Currency used by the activity | +| `date` | Date of the financial activity | +| `fee` | Fee associated with the activity | +| `quantity` | Number of units or shares | +| `symbolProfileId` | Asset associated with the activity | +| `tags` | Tags attached to the activity | +| `type` | Type of financial activity | +| `unitPrice` | Price or value per unit | +| `updatedAt` | Last time the record was updated | +| `userId` | User who owns the activity | + +The currently supported activity types are: + +- `BUY` +- `DIVIDEND` +- `FEE` +- `INTEREST` +- `LIABILITY` +- `SELL` + +This means the tax calculations should build on the existing activity model +where possible instead of introducing a completely separate transaction model. + +## Dividend Representation + +Ghostfolio does not currently have a separate database table or entity just for +dividends. + +A dividend is stored as a normal activity where: + +`type = DIVIDEND` + +The activity still uses fields such as `quantity`, `unitPrice`, `currency`, +`date`, and `fee`. + +For example, one of the existing Ghostfolio import samples contains the +following dividend activity: + +- Asset: Microsoft (`MSFT`) +- Quantity: 5 shares +- Unit price: 0.62 USD +- Type: `DIVIDEND` +- Fee: 0 +- Currency: USD +- Date: November 17, 2021 + +Using the existing quantity and unit-price values, the dividend amount +represented by this activity is: + +`5 × 0.62 = 3.10 USD` + +This gives the tax feature a natural starting point for determining the dividend +value. In the later withholding-tax design, this value can be used when defining +the relationship between gross dividend, withholding tax, and net dividend. + +## Existing Fee Field + +Ghostfolio already has a `fee` field on the activity model. + +The same field is available when an activity is created, and the current DTO +requires it to be a non-negative number. + +However, the current model does not define this field specifically as dividend +withholding tax. It is a general activity fee. + +Because of this, the tax extension should not simply reinterpret `fee` as +withholding tax. + +A brokerage fee and dividend withholding tax represent different pieces of +financial information and may both exist on the same activity. + +For the later withholding-tax design, these values should remain conceptually +separate: + +- Gross dividend +- Activity fee +- Withholding tax +- Net dividend + +The exact withholding fields will be designed in the Sep 16 task. This review +only establishes that the existing `fee` field should keep its current meaning. + +## API and DTO Flow + +The current Ghostfolio activity flow already provides a path that can be +extended later for tax-related information. + +At a high level, the flow is: + +`Prisma Order` → `CreateOrderDto / UpdateOrderDto` → Activities backend service → Activity API response + +The existing `CreateOrderDto` already validates important transaction fields +such as: + +- currency +- date +- fee +- quantity +- symbol +- activity type +- unit price + +The API activity interface then extends the stored `Order` with additional +calculated information used by the application. + +This means the tax feature should reuse this existing flow where possible. +Creating a second, independent dividend transaction API would add unnecessary +duplication and make the system harder to maintain. + +## Currency Handling + +Currency is already part of Ghostfolio's activity model. + +The activity response also includes calculated values such as: + +- `feeInAssetProfileCurrency` +- `feeInBaseCurrency` +- `unitPriceInAssetProfileCurrency` +- `value` +- `valueInBaseCurrency` + +This is important for the tax feature because tax values may eventually need +to be reported both in the activity currency and the user's base currency. + +Instead of creating a separate currency-conversion method only for tax, the +future implementation should follow Ghostfolio's existing conversion approach +where practical. + +The exact withholding-tax currency fields are not being decided in this task. +That will be part of the Sep 16 schema design. + +## Transaction Ordering + +Transaction ordering is especially important for the later FIFO capital-gains +calculation. + +During the code review, Ghostfolio's existing activity handling was found to +use the activity date as the main chronological value. + +This gives us the primary ordering value needed for tax calculations: + +`activity date` + +However, FIFO also needs a deterministic rule when two transactions have the +same date or timestamp. + +The activity model contains both an `id` and `createdAt`, but this review does +not yet define which should be treated as the official secondary tax ordering. + +This should be explicitly decided in the FIFO specification rather than leaving +the calculation dependent on accidental database ordering. + +For now, this is recorded as an open design question for the Sep 17 FIFO task. + +## Import Compatibility + +Ghostfolio already supports importing activities, including dividend +transactions. + +This matters because future tax functionality cannot assume that every dividend +was manually entered by the user. + +For example, an imported dividend may contain the basic dividend amount but may +not contain withholding-tax information. + +The tax design should therefore be able to handle at least two situations: + +1. A dividend where withholding information is available. +2. A dividend where withholding information is not available. + +Missing withholding information should not make an otherwise valid historical +dividend unusable. + +The exact validation and default behavior will be defined when the withholding +schema is designed. + +## Existing Tests + +Ghostfolio already contains tests and sample data involving dividend +transactions. + +These are useful because future tax tests should follow the project's existing +testing style instead of building a completely different testing approach. + +One useful example is +`portfolio-calculator-msft-buy-with-dividend.spec.ts`, which already covers a +Microsoft purchase together with a dividend. The existing import sample also +contains an MSFT dividend activity. + +For future tax calculations, similar tests can be extended to verify exact +numeric results for cases such as: + +- dividend withholding +- multiple purchases followed by a sale +- partial FIFO lot usage +- multiple tax lots +- same-date activities +- rounding +- missing withholding information + +No new tests were written during this task because Iteration 1 is currently +focused on design and data modelling. + +## Design Conclusions + +Based on the existing Ghostfolio implementation, the tax feature should extend +the current activity model rather than create a separate transaction system. + +The main conclusions from this review are: + +1. Dividends are already represented as normal activities using + `type = DIVIDEND`. + +2. The existing `Order` model provides most of the transaction information + needed by future tax calculations. + +3. The existing `fee` field should remain separate from withholding tax. + +4. Gross dividend, withholding amount, and net dividend should remain + distinguishable rather than being combined into one value. + +5. Existing currency-handling patterns should be reused where possible. + +6. Imported and historical dividends may not always contain withholding + information, so the future design needs to handle missing values safely. + +7. FIFO will need an explicitly documented ordering rule, especially when + multiple activities have the same date. + +These conclusions will be used as the starting point for the remaining tax +design tasks in Iteration 1. + +## Open Questions + +The following questions were identified during this review and will be resolved +in later Iteration 1 tax tasks. + +### Withholding Tax + +- Should the withholding amount be stored directly? +- Should a withholding rate also be stored? +- If both amount and rate are available, which one should be treated as the + source value? +- Should withholding be optional for older or imported dividend records? +- How should withholding amounts be represented across different currencies? + +### FIFO and Tax Lots + +- What secondary ordering rule should be used when activities share the same + timestamp? +- How should stock splits affect tax-lot quantities and cost basis? +- What information needs to be stored to trace a sale back to the acquisition + lots that were consumed? + +### Calculation Precision + +- Where should rounding occur? +- Should calculations use the activity currency first and convert afterward, + or use base-currency values during the calculation? + +These questions are intentionally left open here because they belong to the +specific design tasks scheduled later in Iteration 1. + +## Next Task + +The next tax task is scheduled for September 16: + +**Design the withholding-tax field schema for dividend transactions.** + +The Sep 15 review provides the baseline for that work. + +The next design will define the withholding-tax inputs and fields, including +their types, optional/required behavior, validation rules, currency meaning, +and relationship to gross and net dividend values. + +No withholding-tax implementation will be added yet because Iteration 1 is the +design and data-modelling phase. + +## Relevant Files Reviewed + +The main Ghostfolio files reviewed for this task were: + +- `prisma/schema.prisma` +- `libs/common/src/lib/dtos/create-order.dto.ts` +- `libs/common/src/lib/interfaces/activities.interface.ts` +- `apps/api/src/app/activities/activities.controller.ts` +- `apps/api/src/app/activities/activities.service.ts` +- `apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts` +- `apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts` +- `test/import/ok/sample.json` + +These files were used to understand the current transaction and dividend data +model, API flow, currency handling, and existing dividend behavior. No +source-code changes were made during this review.