[
https://issues.apache.org/jira/browse/FINERACT-2708?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Farooq Ayoade updated FINERACT-2708:
------------------------------------
Description:
Observed behavior
{{GET /v1/savingsaccounts/transactions/downloadtemplate}} returns HTTP 500 on a
tenant where a client has more than one savings account:
{noformat}
java.lang.IllegalArgumentException: The workbook already contains this name:
Account_maryam_yusuf_18_
at
org.apache.poi.hssf.usermodel.HSSFWorkbook.createName(HSSFWorkbook.java)
at
org.apache.fineract.infrastructure.bulkimport.populator.savings.SavingsTransactionsWorkbookPopulator.setNames(SavingsTransactionsWorkbookPopulator.java:240)
at ...SavingsTransactionsWorkbookPopulator.setRules(...)
at ...SavingsTransactionsWorkbookPopulator.populate(...)
{noformat}
The same defect is present in the recurring-deposit and fixed-deposit
transaction templates, which carry identical copies of the loop.
h3. Expected behavior
The template downloads successfully (HTTP 200). Each client contributes exactly
one {{Account_<client><id>}} Excel defined name (the dropdown source of that
client's accounts), regardless of how many accounts the client has.
h3. Steps to reproduce
Create client A with two or more savings accounts (e.g. "maryam yusuf").
Create client B whose display name differs from A's only in letter case (e.g.
"MARYAM YUSUF"), with at least one savings account. This is what triggers the
interleaving described below — two accounts on a single client are not
sufficient on their
own.
{{GET
/fineract-provider/api/v1/savingsaccounts/transactions/downloadtemplate?dateFormat=dd
MMMM yyyy}}
Observe HTTP 500 with {{{}The workbook already contains this name:
Account_<client><id>{}}}.
h3. Root cause
{{setNames()}} emits one {{Account_<client><id>}} defined name per client. It
collects the clients by walking the account lookup table and starting a new
"run" whenever the client name changes:
{code:java}
if (!clientName.equals(savingsAccounts.get(i).getClientName())) { //
case-SENSITIVE
...
clientsWithActiveSavings.add(clientName);
clientIdsWithActiveSavings.add(clientId);
}
{code}
That table was sorted immediately beforehand, in
{{{}populateSavingsTable(){}}}, with
{{SavingsAccountData.ClientNameComparator}} — which compares the names
case-insensitively:
{code:java}
String clientOfSavings1 =
savings1.getClientName().toUpperCase(Locale.ENGLISH);
String clientOfSavings2 =
savings2.getClientName().toUpperCase(Locale.ENGLISH);
return clientOfSavings1.compareTo(clientOfSavings2);
{code}
The two disagree. Names differing only in case compare equal to the comparator,
so the sort may interleave two such clients. The case-sensitive run detection
then sees the name change, change back, and starts a second run for the same
client,
appending it to {{clientsWithActiveSavings}} twice. The per-client loop that
follows asks POI to create a defined name that already exists:
{code:java}
for (int j = 0; j < clientsWithActiveSavings.size(); j++) {
Name name = workbook.createName();
setSanitized(name, "Account_" + clientsWithActiveSavings.get(j) + "" +
clientIdsWithActiveSavings.get(j) + "");
...
}
{code}
POI rejects the duplicate and the whole template download fails with HTTP 500.
This is why the symptom presents as "a client with more than one account" —
that is the necessary condition, but the trigger is the second client whose
name differs
only in case.
{{LoanRepaymentWorkbookPopulator.setNames()}} does not have this bug because it
guards the append:
{code:java}
if (!clientsWithActiveLoans.contains(clientName)) {
clientsWithActiveLoans.add(clientName);
clientIdsWithActiveLoans.add(clientId);
}
{code}
The guard is missing from the savings-transaction,
recurring-deposit-transaction and fixed-deposit-transaction populators.
h3. Proposed fix
Add the same guard to all three transaction populators, so a client name
already recorded is not added again and each client produces exactly one
{{Account_<client><id>}} defined name whatever its account count. Tenants that
never hit the
collision are unaffected — the guard only ever suppresses a duplicate.
Patch and tests: PR [#6180|https://github.com/apache/fineract/pull/6180].
was:
h3. Observed behavior
{{GET /v1/savingsaccounts/transactions/downloadtemplate}} returns HTTP 500 on a
tenant where a client has more than one savings account:
java.lang.IllegalArgumentException: The workbook already contains this name:
Account_maryam_yusuf_18_
at org.apache.poi.hssf.usermodel.HSSFWorkbook.createName(HSSFWorkbook.java)
at
org.apache.fineract.infrastructure.bulkimport.populator.savings.SavingsTransactionsWorkbookPopulator.setNames(SavingsTransactionsWorkbookPopulator.java:240)
at ...SavingsTransactionsWorkbookPopulator.setRules(...)
at ...SavingsTransactionsWorkbookPopulator.populate(...)
h3. Expected behavior
The template downloads successfully (HTTP 200). Each client contributes exactly
one {{Account_<client>_<id>_}} Excel defined name (the dropdown source of that
client's accounts), regardless of how many accounts the client has.
h3. Steps to reproduce
# Create a client with *two or more* savings accounts (e.g. client "Maryam
Yusuf", two active accounts).
# {{{}GET
/fineract-provider/api/v1/savingsaccounts/transactions/downloadtemplate?dateFormat=dd%20MMMM%20yyyy{}}}.
# Observe HTTP 500 with the {{The workbook already contains this name:
Account_<client>_<id>_}} exception.
h3. Root cause
{{SavingsTransactionsWorkbookPopulator.setNames()}} builds the per-client
account dropdown named ranges by walking the (client-name-sorted) account list
and, on each client-name transition, appending to {{clientsWithActiveSavings}}
{*}without a de-duplication guard{*}:
clientsWithActiveSavings.add(clientName); // no "already added?" check
clientIdsWithActiveSavings.add(clientId);
It then creates one Excel defined name per entry:
for (int j = 0; j < clientsWithActiveSavings.size(); j++) {
Name name = workbook.createName();
setSanitized(name, "Account_" + clientsWithActiveSavings.get(j) + "_" +
clientIdsWithActiveSavings.get(j) + "_");
...
}
When the same client name recurs in the account list, the client is added twice
and {{createName}} is called with the same {{Account_<client>_<id>_}} twice →
POI rejects the duplicate defined name → HTTP 500.
The *loan-repayment* template does not have this bug because
{{LoanRepaymentWorkbookPopulator.setNames()}} guards the append:
{{if (!clientsWithActiveLoans.contains(clientName)) \{
clientsWithActiveLoans.add(clientName);
clientIdsWithActiveLoans.add(clientId);
}}}
The same guard is missing from *savings-transaction,
recurring-deposit-transaction and fixed-deposit-transaction* populators.
h3. Proposed fix
Add the same de-duplication guard the loan-repayment populator already uses to
{{SavingsTransactionsWorkbookPopulator.setNames()}} (and the RD/FD-transaction
populators), so each client produces a single {{Account_<client>_<id>_}}
defined name.
> Bulk-import savings/recurring/fixed-deposit transaction download templates
> throw HTTP 500 (IllegalArgumentException: The workbook already contains this
> name) when a client has more than one account
> -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: FINERACT-2708
> URL: https://issues.apache.org/jira/browse/FINERACT-2708
> Project: Apache Fineract
> Issue Type: Bug
> Components: DataImportTool
> Reporter: Farooq Ayoade
> Priority: Minor
>
> Observed behavior
> {{GET /v1/savingsaccounts/transactions/downloadtemplate}} returns HTTP 500 on
> a tenant where a client has more than one savings account:
> {noformat}
> java.lang.IllegalArgumentException: The workbook already contains this
> name: Account_maryam_yusuf_18_
> at
> org.apache.poi.hssf.usermodel.HSSFWorkbook.createName(HSSFWorkbook.java)
> at
> org.apache.fineract.infrastructure.bulkimport.populator.savings.SavingsTransactionsWorkbookPopulator.setNames(SavingsTransactionsWorkbookPopulator.java:240)
> at ...SavingsTransactionsWorkbookPopulator.setRules(...)
> at ...SavingsTransactionsWorkbookPopulator.populate(...)
> {noformat}
> The same defect is present in the recurring-deposit and fixed-deposit
> transaction templates, which carry identical copies of the loop.
> h3. Expected behavior
> The template downloads successfully (HTTP 200). Each client contributes
> exactly one {{Account_<client><id>}} Excel defined name (the dropdown source
> of that client's accounts), regardless of how many accounts the client has.
> h3. Steps to reproduce
> Create client A with two or more savings accounts (e.g. "maryam yusuf").
> Create client B whose display name differs from A's only in letter case (e.g.
> "MARYAM YUSUF"), with at least one savings account. This is what triggers the
> interleaving described below — two accounts on a single client are not
> sufficient on their
> own.
> {{GET
> /fineract-provider/api/v1/savingsaccounts/transactions/downloadtemplate?dateFormat=dd
> MMMM yyyy}}
> Observe HTTP 500 with {{{}The workbook already contains this name:
> Account_<client><id>{}}}.
> h3. Root cause
> {{setNames()}} emits one {{Account_<client><id>}} defined name per client. It
> collects the clients by walking the account lookup table and starting a new
> "run" whenever the client name changes:
> {code:java}
> if (!clientName.equals(savingsAccounts.get(i).getClientName())) { //
> case-SENSITIVE
> ...
> clientsWithActiveSavings.add(clientName);
> clientIdsWithActiveSavings.add(clientId);
> }
> {code}
> That table was sorted immediately beforehand, in
> {{{}populateSavingsTable(){}}}, with
> {{SavingsAccountData.ClientNameComparator}} — which compares the names
> case-insensitively:
> {code:java}
> String clientOfSavings1 =
> savings1.getClientName().toUpperCase(Locale.ENGLISH);
> String clientOfSavings2 =
> savings2.getClientName().toUpperCase(Locale.ENGLISH);
> return clientOfSavings1.compareTo(clientOfSavings2);
> {code}
> The two disagree. Names differing only in case compare equal to the
> comparator, so the sort may interleave two such clients. The case-sensitive
> run detection then sees the name change, change back, and starts a second run
> for the same client,
> appending it to {{clientsWithActiveSavings}} twice. The per-client loop that
> follows asks POI to create a defined name that already exists:
> {code:java}
> for (int j = 0; j < clientsWithActiveSavings.size(); j++) {
> Name name = workbook.createName();
> setSanitized(name, "Account_" + clientsWithActiveSavings.get(j) + "" +
> clientIdsWithActiveSavings.get(j) + "");
> ...
> }
> {code}
> POI rejects the duplicate and the whole template download fails with HTTP
> 500. This is why the symptom presents as "a client with more than one
> account" — that is the necessary condition, but the trigger is the second
> client whose name differs
> only in case.
> {{LoanRepaymentWorkbookPopulator.setNames()}} does not have this bug because
> it guards the append:
> {code:java}
> if (!clientsWithActiveLoans.contains(clientName)) {
> clientsWithActiveLoans.add(clientName);
> clientIdsWithActiveLoans.add(clientId);
> }
> {code}
> The guard is missing from the savings-transaction,
> recurring-deposit-transaction and fixed-deposit-transaction populators.
> h3. Proposed fix
> Add the same guard to all three transaction populators, so a client name
> already recorded is not added again and each client produces exactly one
> {{Account_<client><id>}} defined name whatever its account count. Tenants
> that never hit the
> collision are unaffected — the guard only ever suppresses a duplicate.
> Patch and tests: PR [#6180|https://github.com/apache/fineract/pull/6180].
--
This message was sent by Atlassian Jira
(v8.20.10#820010)