Hello Fineract developers,

I reproduced the following behavior in the actual
ShareProductDividendAssembler on develop
e4474a0f4a4c65a75e7758e47db1fd277e5fcfef (the source is identical in tag
1.15.0):

- 100 approved shares purchased on 2026-01-01;
- product minimum active period: 90 days;
- dividend window: 2026-07-01 through 2026-08-01;
- a single active account, USD 100.00 to distribute.

The purchase age is 212 days. The assembler first clips the purchase date
to July 1 and then tests 31 days against the 90-day minimum. It returns
null; the production caller maps this to eligible.shares.not.found.

I interpret the documented minimum active period as eligibility age, in
which case this account should qualify and receive 100.00. Please confirm
whether the intended policy instead counts only days inside each payout
window; that would exclude even longstanding holdings whenever the window
is shorter than the configured minimum.

I prepared a patch that checks age before clipping the share-day weighting
date. A 12-case native suite covers threshold equality, recent purchases
and equal in-window weights for accounts with different older purchase
dates. The original source fails seven cases under the holding-age
interpretation. Only database read interfaces are mocked; the assembler and
monetary calculations are real. No HTTP/database payout reproduction is
claimed.

I found FINERACT-148/329 about dividend total rounding and FINERACT-608
about allocation; this report concerns the eligibility date instead. If the
proposed interpretation is correct, could you create or identify a Jira
issue for the correction?

The proposed correction passes all 12 native cases. Spotless and Checkstyle
main/test checks pass.

Attachments: a detailed report with reproduction commands, a source-only
patch, a patch including the native JUnit tests, and JSON test vectors.

Regards,
Khamit

Attachment: dividend-test-vectors.json
Description: application/json

# Dividend eligibility clips the holding period to the payout window

ShareProductDividendAssembler replaces a purchase date before the dividend window with the window start, then uses that clipped date for minimumActivePeriod. Thus a 31-day dividend window with a 90-day minimum rejects every purchase, even shares held for years. When no positive share-days remain, calculateDividends returns null, and the production caller raises eligible.shares.not.found.

## Reproduction and proposed interpretation

Use an active account holding 100 approved shares purchased on 2026-01-01. Set the product's minimum active period to 90 days, the dividend window to 2026-07-01 through 2026-08-01, the currency to USD with two decimals, and the dividend pool to 100.00. Actual holding age at the window end is 212 days; the code tests 31 days instead.

Under the holding-age interpretation, the sole eligible account should receive 100.00. The [Mifos user documentation](https://mifosforge.jira.com/wiki/spaces/docs/pages/129531946/Share%2Bproducts) describes a minimum active period to receive dividends. This report interprets that setting as eligibility age. If the intended policy is instead a minimum entirely within each payout window, please confirm that policy; it would make any shorter window ineligible regardless of the actual holding age. The proposed patch depends on the holding-age interpretation.

## Test design and application reachability

ShareProductDividendMinimumActivePeriodTest invokes the real assembler and Money implementation with real product, account, transaction and payout objects. Only the two database read service interfaces are mocked. Tenant rounding is explicitly HALF_UP and restored after each test.

Inputs exercise minimum periods 0, 30, 31, 32, 90, 212 and 213; purchase ages 89, 90 and 91 days at a 90-day threshold; two older accounts with equal in-window holdings; and a recent ineligible purchase alongside an older account. The equal-holdings case ensures a fix does not add pre-window days to dividend weights.

ShareProductDataSerializer accepts a minimum active period in days without restricting it to the future dividend window. Its dividend validator requires dates in ascending order and a positive amount. ShareAccountReadPlatformServiceImpl retrieves approved purchases for active accounts without clipping their transaction dates. ShareProductWritePlatformServiceJpaRepositoryImpl.createShareProductDividend invokes the assembler and throws the no-eligible-shares exception for null. These paths were inspected; no HTTP or persisted payout execution is claimed.

## Proposed correction

Compute eligibility age from the original purchase date, then clip only the date used for in-window share-day weighting. Keep the threshold's existing inclusive behavior and retain exclusion of recent purchases.

## Related reports checked

FINERACT-148 and FINERACT-329 already report mismatches in rounded dividend totals. FINERACT-608 is a dividend distribution complaint; its example does not specify this clipped minimum-period case. This report does not claim a new rounding-total defect. No exact duplicate was identified in the recorded searches; intended policy still needs maintainer confirmation.

## Verified native results

Tested baseline: e4474a0f4a4c65a75e7758e47db1fd277e5fcfef, with the same assembler source in tag 1.15.0 (byte comparison only for the release tag). The original implementation fails 7 of the 12 cases; the proposed correction passes all 12. Module Spotless and Checkstyle main/test checks pass. These tests confirm implementation behavior under the stated holding-age interpretation; the expected business policy still warrants maintainer confirmation.

## Running the attached reproducer

Use JDK 25 and a separate checkout at the baseline commit. First apply dividend-eligibility-with-tests.patch. To reproduce the original failure while retaining the tests, reverse only dividend-eligibility.patch. Reapply that source-only patch to verify the correction.

```sh
git apply dividend-eligibility-with-tests.patch
git apply --reverse dividend-eligibility.patch
./gradlew --no-daemon --no-scan --max-workers=2 :fineract-provider:test --tests "*ShareProductDividendMinimumActivePeriodTest"
git apply dividend-eligibility.patch
./gradlew --no-daemon --no-scan --max-workers=2 :fineract-provider:test --tests "*ShareProductDividendMinimumActivePeriodTest"
```

The first test run is expected to fail; the second should pass. Only run these patch commands in an isolated checkout.

Reply via email to