mirror of https://github.com/ghostfolio/ghostfolio
1 changed files with 168 additions and 0 deletions
@ -0,0 +1,168 @@ |
|||
# Tax Metrics — Withholding-Tax Field Schema |
|||
|
|||
**Owner:** Tharun Swaminathan |
|||
**Iteration:** 1 — Design & Data Modelling |
|||
**Date:** September 16, 2026 |
|||
|
|||
## Objective |
|||
|
|||
The purpose of this design is to define how dividend withholding tax should be |
|||
represented in Ghostfolio before implementation begins in Iteration 2. |
|||
|
|||
The design builds on the existing Ghostfolio activity model instead of creating |
|||
a separate dividend transaction structure. |
|||
|
|||
This task defines the proposed fields, calculation relationships, validation |
|||
rules, currency behavior, backward compatibility, and future API impact. |
|||
|
|||
No database or application code is changed as part of this task. |
|||
|
|||
## Existing Dividend Model |
|||
|
|||
Ghostfolio currently stores dividend transactions as normal `Order` activities |
|||
with: |
|||
|
|||
`type = DIVIDEND` |
|||
|
|||
The existing activity already contains the main values needed for a dividend, |
|||
including: |
|||
|
|||
- `quantity` |
|||
- `unitPrice` |
|||
- `currency` |
|||
- `date` |
|||
- `fee` |
|||
|
|||
The dividend value can be determined from: |
|||
|
|||
`grossDividend = quantity × unitPrice` |
|||
|
|||
The existing `fee` field is a general activity fee and should remain separate |
|||
from dividend withholding tax. |
|||
|
|||
## Proposed Stored Field |
|||
|
|||
The proposed new field on the existing Prisma `Order` model is: |
|||
|
|||
`withholdingTax Float?` |
|||
|
|||
### Meaning |
|||
|
|||
`withholdingTax` represents the actual tax amount withheld from a dividend |
|||
payment. |
|||
|
|||
The amount is stored in the activity's `currency`. |
|||
|
|||
Example: |
|||
|
|||
If the dividend activity uses USD, a value of: |
|||
|
|||
`withholdingTax = 15.00` |
|||
|
|||
means that 15.00 USD was withheld from that dividend payment. |
|||
|
|||
### Why the Amount Is Stored |
|||
|
|||
The withholding-tax amount should be treated as the authoritative value because |
|||
it represents the actual amount deducted from the dividend. |
|||
|
|||
This also provides better traceability for imported transactions and future tax |
|||
reports. |
|||
|
|||
The withholding percentage can be calculated from the stored amount when |
|||
needed, so storing both the amount and percentage would introduce the possibility |
|||
of inconsistent values. |
|||
|
|||
## Null and Zero Behavior |
|||
|
|||
The field should be nullable. |
|||
|
|||
`withholdingTax = null` |
|||
|
|||
means that withholding information is unknown or was not provided. |
|||
|
|||
`withholdingTax = 0` |
|||
|
|||
means that withholding information is known and no tax was withheld. |
|||
|
|||
This distinction is important for imported and historical transactions. |
|||
|
|||
Missing withholding information should not automatically be interpreted as zero. |
|||
|
|||
This allows existing Ghostfolio dividend records to remain valid after the new |
|||
field is introduced. |
|||
|
|||
## Derived Values |
|||
|
|||
The tax feature should keep gross dividend, withholding tax, withholding rate, |
|||
and net dividend as separate values. |
|||
|
|||
### Gross Dividend |
|||
|
|||
`grossDividend = quantity × unitPrice` |
|||
|
|||
The gross dividend is derived from the existing activity fields. |
|||
|
|||
### Withholding-Tax Amount |
|||
|
|||
`withholdingTax` |
|||
|
|||
This is the stored value. |
|||
|
|||
### Withholding-Tax Rate |
|||
|
|||
The withholding rate is derived rather than stored: |
|||
|
|||
`withholdingTaxRate = withholdingTax / grossDividend × 100` |
|||
|
|||
The rate can only be calculated when: |
|||
|
|||
- `grossDividend > 0` |
|||
- `withholdingTax` is not null |
|||
|
|||
Otherwise: |
|||
|
|||
`withholdingTaxRate = null` |
|||
|
|||
### Net Dividend |
|||
|
|||
The net dividend after withholding is: |
|||
|
|||
`netDividend = grossDividend - withholdingTax` |
|||
|
|||
If `withholdingTax` is unknown (`null`), the tax analytics layer should not |
|||
silently assume that withholding was zero. |
|||
|
|||
In that case: |
|||
|
|||
`netDividend = null` |
|||
|
|||
for tax reporting purposes. |
|||
|
|||
This preserves the difference between "no tax was withheld" and "we do not know |
|||
whether tax was withheld." |
|||
|
|||
## Activity Fee |
|||
|
|||
Activity fees and withholding tax should remain separate. |
|||
|
|||
For a dividend: |
|||
|
|||
`grossDividend = quantity × unitPrice` |
|||
|
|||
`netDividend = grossDividend - withholdingTax` |
|||
|
|||
If the dividend also has an activity fee, the cash effect can later be expressed |
|||
separately as: |
|||
|
|||
`netCashAmount = netDividend - fee` |
|||
|
|||
This prevents brokerage fees from being treated as tax. |
|||
|
|||
## Proposed Prisma Change |
|||
|
|||
The future Iteration 2 implementation is expected to extend the existing |
|||
`Order` model with: |
|||
|
|||
```prisma |
|||
withholdingTax Float? |
|||
Loading…
Reference in new issue