adamsaghy commented on PR #6187:
URL: https://github.com/apache/fineract/pull/6187#issuecomment-5760078938

   @AnvayKharb Please review the below concerns:
   
   **Blocking**
   
   1. transactionDate + transactionTime can describe the wrong instant 
(day-boundary bug)
   
   Every write path normalizes the time to UTC — 
fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java,
 
fineract-provider/src/main/java/org/apache/fineract/portfolio/account/domain/AccountTransferTransaction.java,
 
fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDTO.java,
 and again in JsonParserHelper.extractOffsetTimeNamed — but transactionDate is 
still taken verbatim from the client's local calendar.
   
   POST { "transactionDate": "27 July 2026", "transactionTime": 
"02:00:00+05:30" }
   stored:  date = 2026-07-27, time = 20:30:00Z
   
   Combining those, as your own review comment asked for ("transaction date + 
transaction time together can represent a more precise point in time"), gives 
2026-07-27T20:30Z. The actual instant was 2026-07-26T20:30Z — off by a full 
day. Same class of error for any time whose UTC conversion crosses midnight in 
either direction.
   
   The fix is to stop normalizing and persist the OffsetTime exactly as 
supplied. timetz keeps the offset, so 2026-07-27 + 02:00:00+05:30 reconstructs 
the correct instant. Normalizing is what breaks it, and it also makes the 
round-trip lossy (the client can no longer see the offset they sent).
   
   Caveat that pushes back on the design: MySQL's TIME(6) cannot store an 
offset at all, so "stored as OffsetTime, fetched as OffsetTime" only holds on 
PostgreSQL. On MySQL Hibernate falls back to NORMALIZE against the JVM default 
zone. Fineract forces -Duser.timezone=UTC in its run configs so it works today, 
but it's implicit — worth setting 
hibernate.timezone.default_storage=NORMALIZE_UTC if you keep MySQL support here.
   
   2. template=true silently drops the persisted time
   
   SavingsAccountTransactionData.templateOnTop() 
(fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java)
 rebuilds the object via createData(...) and never copies transactionTime. So 
GET /v1/savingsaccounts/{id}/transactions/{txnId}?template=true returns 
transactionTime: null for a transaction that has one 
(fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java).
 This is a direct consequence of the withTransactionTime mutator approach — a 
builder (as you already suggested) wouldn't have this hole.
   
   3. Transaction adjustment accepts transactionTime and ignores it
   
   SavingsAccountTransactionDataValidator.validate() now whitelists 
transactionTime, and adjustSavingsTransaction 
(fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java)
 calls that same validator — but builds its SavingsAccountTransactionDTO 
without the time. Adjusting a transaction therefore accepts the parameter, 
drops it, and returns 200. Either wire it through or reject it on that endpoint.
   
   **Unaddressed from your earlier review**
   - JdbcSupport.getOffsetTime still has all three branches (OffsetTime / 
LocalTime / java.sql.Time). You asked for direct OffsetTime reading and the 
author agreed to simplify it on 2026-09-07; it hasn't been done.
   - @Schema annotations are still on AccountTransferData.transferTime and 
SavingsAccountTransactionData.transactionTime, which you said weren't needed.
   
   **Non-blocking, but still should be review and address**
   - fields= filtering — transactionTime wasn't added to 
SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS 
(fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java),
 so ?fields=transactionTime throws UnsupportedParameterException. Normal 
responses are unaffected.
   - Validation style — both validators call extractOffsetTimeNamed(...) purely 
for its throwing side effect and discard the result. A malformed time throws 
PlatformApiDataValidationException immediately instead of accumulating into 
dataValidationErrors, so it masks every other validation error in the same 
request. Every neighbouring check uses baseDataValidator.reset().parameter(...).
   - Liquibase — the repo convention is one <addColumn> with a portable type 
(see TIMESTAMP WITH TIME ZONE in 0136/0162/0230); this splits into a Postgres 
<addColumn> plus a raw MySQL <sql> block with no rollback and no preconditions. 
Only 2 of 254 part files use dbms at all.
   - Deposit accounts — DepositAccountReadPlatformServiceImpl:1130 maps the 
same m_savings_account_transaction rows but wasn't updated, so fixed/recurring 
deposit transaction endpoints won't return transactionTime. Fine if 
intentionally out of scope, but worth stating.
   - Converter inconsistency — OffsetTimeJsonConverter.convertToObject does not 
normalize to UTC while JsonParserHelper.extractOffsetTimeNamed does. Harmless 
today (no Jackson-bound OffsetTime input exists), but a trap later.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to