Repository navigation
fix(forms): one request per Save, platform dates, the product's savings rate, and a reported load failure - #811
Conversation
🔄 What this change talks toFrom the 21 application TypeScript file(s) changed in this pull request. sequenceDiagram
autonumber
participant FineractBusinessDateApi as FineractBusinessDateApi (Service)
participant GlobalConfigurationService as GlobalConfigurationService (API client)
participant Fineract as Fineract (REST API)
participant BusinessDateManagementService as BusinessDateManagementService (API client)
participant of as of (Service)
participant ConfigService as ConfigService (Injected)
participant Injector as Injector (Injected)
participant CenterFormComponent as CenterFormComponent (Component)
participant PlatformDateService as PlatformDateService (Injected)
participant OfficesService as OfficesService (API client)
participant CentersService as CentersService (API client)
participant MutationOutcomeService as MutationOutcomeService (Injected)
FineractBusinessDateApi->>GlobalConfigurationService: getConfigurationsNameName()
GlobalConfigurationService->>Fineract: getConfigurationsNameName()
Fineract-->>GlobalConfigurationService: response
FineractBusinessDateApi->>BusinessDateManagementService: getBusinessdate()
BusinessDateManagementService->>Fineract: getBusinessdate()
Fineract-->>BusinessDateManagementService: response
of->>ConfigService: config()
of->>Injector: get()
CenterFormComponent->>PlatformDateService: resolveToday()
CenterFormComponent->>OfficesService: getOffices()
OfficesService->>Fineract: getOffices()
Fineract-->>OfficesService: response
CenterFormComponent->>CentersService: getCentersCenterId()
CentersService->>Fineract: getCentersCenterId()
Fineract-->>CentersService: response
CenterFormComponent->>CentersService: postCentersCenterId()
CentersService->>Fineract: postCentersCenterId()
Fineract-->>CentersService: response
CenterFormComponent->>CentersService: putCentersCenterId()
CentersService->>Fineract: putCentersCenterId()
Fineract-->>CentersService: response
CenterFormComponent->>MutationOutcomeService: complete()
CenterFormComponent->>CentersService: postCenters()
CentersService->>Fineract: postCenters()
Fineract-->>CentersService: response
Truncated for readability: 34 further participant(s) not shown.
Generated by run 39142625037 from |
E2E — mocked backend🎭 E2E Tests✅ All green — 423 passed · 0 failed · 1 skipped, across 40 spec files in 8m 56s. By spec file
All 424 tests — click to expand
Slowest 10 — what the shard counts should be tuned against
📼 Download the HTML report, videos and traces — see the Generated by run 39142625057 from |
E2E — real Fineract🎭 E2E Tests✅ All green — 171 passed · 0 failed · 0 skipped · 2 flaky, across 50 spec files in 13m 27s.
|
| Spec | ✅ | ❌ | ⏭️ | 🔒 | Time |
|---|---|---|---|---|---|
| backend.setup.ts | 3 | 0 | 0 | 0 | 6.4s |
| accounting-closure-backend.spec.ts | 2 | 0 | 0 | 0 | 11.6s |
| batch-api-operations.spec.ts | 5 | 0 | 0 | 0 | 21.6s |
| center-servicing.spec.ts | 2 | 0 | 0 | 0 | 16.1s |
| charge-servicing-backend.spec.ts | 6 | 0 | 0 | 0 | 24.4s |
| client-legal-form-search.spec.ts | 1 | 0 | 0 | 0 | 4.3s |
| client-transfer.spec.ts | 4 | 0 | 0 | 0 | 39.6s |
| collection-sheet.spec.ts | 1 | 0 | 0 | 0 | 5.5s |
| deposit-account-servicing.spec.ts | 3 | 0 | 0 | 0 | 25.0s |
| deposit-lifecycle-backend.spec.ts | 4 | 0 | 0 | 0 | 20.1s |
| deposit-product-configuration.spec.ts | 3 | 0 | 0 | 0 | 13.5s |
| double-submit-backend.spec.ts | 2 | 0 | 0 | 0 | 9.6s |
| form-layout-reflow.spec.ts | 6 | 0 | 0 | 0 | 27.0s |
| group-membership.spec.ts | 3 | 0 | 0 | 0 | 42.7s |
| loan-account-actions.spec.ts | 3 | 0 | 0 | 0 | 21.9s |
| loan-approved-amount-revision.spec.ts | 3 | 0 | 0 | 0 | 16.8s |
| loan-charge-off.spec.ts | 2 | 0 | 0 | 0 | 16.9s |
| loan-flows-non-admin-roles.spec.ts | 8 | 0 | 0 | 0 | 1m 25s |
| loan-product-accounting.spec.ts | 1 | 0 | 0 | 0 | 37.7s |
| loan-repayment-gating.spec.ts | 2 | 0 | 0 | 0 | 10.6s |
| loan-servicing-rbac-backend.spec.ts | 4 | 0 | 0 | 0 | 11.8s |
| loan-servicing.spec.ts | 2 | 0 | 0 | 0 | 20.7s |
| login.spec.ts | 4 | 0 | 0 | 0 | 6.8s |
| oidc-login-backend.spec.ts | 2 | 0 | 0 | 0 | 5.3s |
| parity-screens.spec.ts | 8 | 0 | 0 | 0 | 52.2s |
| rbac-backend-restricted-user.spec.ts | 7 | 0 | 0 | 0 | 39.7s |
| rbac-dead-end-controls.spec.ts | 7 | 0 | 0 | 0 | 37.4s |
| rbac-disabled-flag-backend.spec.ts | 6 | 0 | 0 | 0 | 30.7s |
| rbac-multi-permission.spec.ts | 9 | 0 | 0 | 0 | 1m 5s |
| rbac-office-scoped-user.spec.ts | 6 | 0 | 0 | 0 | 31.8s |
| bulk-import-entity-types.spec.ts | 1 | 0 | 0 | 0 | 7.1s |
| delinquency-configuration.spec.ts | 2 | 0 | 0 | 0 | 15.3s |
| full-demo.spec.ts | 1 | 0 | 0 | 0 | 49.5s |
| interest-rate-charts.spec.ts | 1 | 0 | 0 | 0 | 6.7s |
| loan-lifecycle.spec.ts | 4 | 0 | 0 | 0 | 1m 41s |
| loan-schedule-type.spec.ts | 3 | 0 | 0 | 0 | 26.9s |
| rbac-permissionless-user.spec.ts | 4 | 0 | 0 | 0 | 18.7s |
| report-mailing-job.spec.ts | 3 | 0 | 0 | 0 | 16.3s |
| report-parameter-backend.spec.ts | 4 | 0 | 0 | 0 | 18.9s |
| savings-flows-non-admin-roles.spec.ts | 7 | 0 | 0 | 0 | 1m 21s |
| savings-transaction-correction.spec.ts | 1 | 0 | 0 | 0 | 5.6s |
| savings-transaction-gating.spec.ts | 2 | 0 | 0 | 0 | 5.0s |
| share-account-servicing.spec.ts | 2 | 0 | 0 | 0 | 21.8s |
| share-product-accounting.spec.ts | 1 | 0 | 0 | 0 | 20.8s |
| teller-cash-management.spec.ts | 2 | 0 | 0 | 0 | 31.5s |
| tenant-date-defaults-backend.spec.ts | 1 | 0 | 0 | 0 | 5.6s |
| working-capital-loan-actions-backend.spec.ts | 3 | 0 | 0 | 0 | 17.7s |
| working-capital-product-accounting-backend.spec.ts | 2 | 0 | 0 | 0 | 18.6s |
| working-capital-rbac-backend.spec.ts | 2 | 0 | 0 | 0 | 7.1s |
| working-capital-servicing-backend.spec.ts | 6 | 0 | 0 | 0 | 29.9s |
All 171 tests — click to expand
backend.setup.ts
- ✅ seed backend reference data —
1.6s - ✅ seed backend reference data —
3.1s - ✅ seed backend reference data —
1.7s
accounting-closure-backend.spec.ts
- ✅ Accounting closures against Fineract › a closed period reads as Closed, and the platform refuses a posting inside it —
6.3s - ✅ Accounting closures against Fineract › the form keeps the user on the page and names the reason when Fineract refuses —
5.3s
batch-api-operations.spec.ts
- ✅ Batch API Operations against Fineract › runs the sample batch scenario — create client, create loan, add and read back a charge —
4.1s - ✅ Batch API Operations against Fineract › shows a parse error instead of submitting when the batch input is not valid JSON —
6.0s - ✅ Batch API enclosingTransaction semantics against Fineract › rolls back the earlier steps when enclosingTransaction is true and a later step fails —
3.8s - ✅ Batch API enclosingTransaction semantics against Fineract › does not roll back the earlier steps when enclosingTransaction is false and a later step fails —
4.0s - ✅ Batch API Operations on a mobile viewport against Fineract › the sample batch scenario is reachable and works by touch at mobile width —
3.8s
center-servicing.spec.ts
- ✅ Center servicing › a center is activated, staffed and given a group —
12.1s - ✅ Center servicing › notes are recorded against the center —
4.1s
charge-servicing-backend.spec.ts
- ✅ loan charge servicing › waives a loan charge, and the platform records the waiver —
5.6s - ✅ loan charge servicing › cannot reverse a waiver: the platform answers 500, not a business rule —
575ms - ✅ loan charge servicing › offers no Pay on a charge the platform will not let be paid —
3.8s - ✅ loan charge servicing › adjusts a loan charge to a smaller amount —
4.9s - ✅ savings charge servicing › pays a savings charge with the amount and due date the platform demands —
4.7s - ✅ savings charge servicing › carries an active flag, which is what gates Inactivate —
4.9s
client-legal-form-search.spec.ts
- ✅ Client list: legal form filtering › an entity client appears in the main client list —
4.3s
client-transfer.spec.ts
- ✅ Client transfer between offices › a proposed transfer is held until the destination accepts, and then the client moves —
9.9s - ✅ Client transfer between offices › a rejected transfer leaves the client on hold, and withdrawing is the way back —
10.7s - ✅ Client transfer between offices › a client can be transferred in one step when the user may act for both offices —
8.6s - ✅ Client staff assignment › an officer can be assigned and then removed —
10.4s
collection-sheet.spec.ts
- ✅ Collection Sheet › scopes staff to the office, sends the choice, and reports an empty sheet —
5.5s
deposit-account-servicing.spec.ts
- ✅ Term deposit account servicing › an account is approved, activated and closed before maturity —
11.0s - ✅ Term deposit account servicing › a deposit is recorded, listed, and reversed without leaving the list —
8.3s - ✅ Term deposit account servicing › an application can be rejected instead of approved —
5.7s
deposit-lifecycle-backend.spec.ts
- ✅ fixed deposit products › carry the rate the user typed, not a hidden default —
4.2s - ✅ an active fixed deposit account › shows its balance and maturity figures —
4.3s - ✅ an active fixed deposit account › can be closed into the client savings account —
6.4s - ✅ an active fixed deposit account › can have a charge added to it —
5.3s
deposit-product-configuration.spec.ts
- ✅ Deposit product configuration › a fixed deposit product survives being edited —
4.5s - ✅ Deposit product configuration › a recurring deposit product can be created at all —
4.4s - ✅ Deposit product configuration › a savings product carries its accounting configuration —
4.6s
double-submit-backend.spec.ts
- ✅ A double-click submits once › a repayment is recorded once, not twice —
4.7s - ✅ A double-click submits once › a client is created once, not twice —
4.9s
form-layout-reflow.spec.ts
- ✅ form layout at the narrow viewport › Create GL Account gives every field a usable width at 320px —
4.2s - ✅ form layout at the narrow viewport › Create Loan gives every field a usable width at 320px —
3.9s - ✅ form layout at the narrow viewport › Create Office gives every field a usable width at 320px —
4.0s - ✅ form layout at the narrow viewport › Create Staff gives every field a usable width at 320px —
3.6s - ✅ form layout at the narrow viewport › no page-level horizontal scroll at 320px —
7.7s - ✅ form layout at the wide viewport › Create GL Account still uses more than one column at 1280px —
3.5s
group-membership.spec.ts
- ✅ Group membership and lifecycle › a group is activated, staffed, given members and a committee, then emptied —
18.2s - ✅ Group membership and lifecycle › notes are recorded against the group and can be removed again —
7.2s - ✅ Group membership and lifecycle › an empty group is closed with a reason, and a group with members is refused —
17.2s
loan-account-actions.spec.ts
- ✅ Loan account lifecycle actions › new action menu items appear only for active loans —
5.8s - ✅ Loan account lifecycle actions › undo disbursal shows a confirm dialog and reverts the loan to Approved —
8.7s - ✅ Loan account lifecycle actions › write off requires confirmation and moves the loan out of Active status —
7.5s
loan-approved-amount-revision.spec.ts
- ✅ Loan approved and available-disbursement amount revision › revises the approved amount on an active, partially-disbursed loan —
5.5s - ✅ Loan approved and available-disbursement amount revision › revises the available disbursement amount, which also changes the approved amount —
6.2s - ✅ Loan approved and available-disbursement amount revision › shows the platform’s own refusal, not a generic validation message —
5.1s
loan-charge-off.spec.ts
- ✅ Loan servicing commands › charges a loan off through the UI and reverses it —
9.2s - ✅ Loan servicing commands › records a goodwill credit through the shared transaction form —
7.7s
loan-flows-non-admin-roles.spec.ts
- ✅ a loan taken through four separated duties, every account built in the UI › an administrator builds the four roles and accounts through the forms —
33.3s - ✅ a loan taken through four separated duties, every account built in the UI › the officer books the application and is not offered approval of their own loan —
12.0s - ✅ a loan taken through four separated duties, every account built in the UI › the approver, differing from the officer in one code, is offered it and approves —
4.8s - ✅ a loan taken through four separated duties, every account built in the UI › the approver is not offered disbursement of the loan they just approved —
3.9s - ✅ a loan taken through four separated duties, every account built in the UI › the disburser, holding only DISBURSE_LOAN, completes the payout end to end —
5.9s - ✅ a loan taken through four separated duties, every account built in the UI › and the same route now refuses an UPDATE_LOAN holder, as the platform does —
6.2s - ✅ a loan taken through four separated duties, every account built in the UI › a command with no mapped code is left to the platform rather than guessed at —
4.4s - ✅ a loan taken through four separated duties, every account built in the UI › a repayment duty reaches its own form, and records a repayment —
14.7s
loan-product-accounting.spec.ts
- ✅ Loan product accounting › a cash-accounting product is configured, round-trips on edit, and posts to the ledger —
37.7s
loan-repayment-gating.spec.ts
- ✅ Repayment is offered only where the platform allows it › withheld on a loan awaiting approval, offered once it is active —
10.2s - ✅ Repayment is offered only where the platform allows it › the platform refuses a repayment on a loan awaiting approval —
403ms
loan-servicing-rbac-backend.spec.ts
- ✅ loan servicing and charge command permissions, on the platform › each command is authorised by the code the UI declares for it —
5.8s - ✅ loan servicing and charge command permissions, on the platform › no portfolio write code authorises any of its entity commands —
5.7s - ✅ loan servicing and charge command permissions, on the platform › the deferred-income adjustments are not commands at loan level —
194ms - ✅ loan servicing and charge command permissions, on the platform › the deferred-income adjustments are commands on one transaction —
136ms
loan-servicing.spec.ts
- ✅ Loan servicing: notes and transaction adjustment › notes can be added and removed, with a confirm dialog on delete —
9.6s - ✅ Loan servicing: notes and transaction adjustment › a repayment transaction can be viewed and adjusted with a corrected amount —
11.1s
login.spec.ts
- ✅ Login › login page displays correctly —
1.4s - ✅ Login › login form has required fields —
2.1s - ✅ Login › submit button is disabled when form is empty —
1.4s - ✅ Login › submit button is enabled when form is filled —
1.8s
oidc-login-backend.spec.ts
- ✅ identity-provider route against a real Fineract › is offered beside the password form, and pressing it sends nothing to Fineract —
1.9s - ✅ identity-provider route against a real Fineract › a real password sign-in still gets a session with the route offered —
3.4s
parity-screens.spec.ts
- ✅ Screens added for platform parity › a manual journal entry can be read whole and reversed —
6.7s - ✅ Screens added for platform parity › an entry that is already reversed is not offered again —
5.7s - ✅ Screens added for platform parity › a report definition can be created, edited and deleted; a core one cannot —
7.4s - ✅ Screens added for platform parity › a core report opens read-only with only its in-use setting —
5.3s - ✅ Screens added for platform parity › a pending loan is approved from the queue, in a batch —
8.3s - ✅ Screens added for platform parity › a fixed deposit is listed as a deposit, not as a savings account —
5.7s - ✅ Screens added for platform parity › an office has a screen, and it carries its custom fields —
5.6s - ✅ Screens added for platform parity › a savings account carries notes, and the note survives a reload —
7.5s
rbac-backend-restricted-user.spec.ts
- ✅ a genuinely restricted Fineract user › holds exactly the permissions their role was granted —
170ms - ✅ a genuinely restricted Fineract user › reaches the screen their permission covers —
5.6s - ✅ a genuinely restricted Fineract user › is refused a screen their permission does not cover, by URL and by the backend —
5.5s - ✅ a genuinely restricted Fineract user › is refused a write screen they can read the list for, and the write itself —
6.9s - ✅ a genuinely restricted Fineract user › is not offered the actions it would be refused for —
5.7s - ✅ a genuinely restricted Fineract user › is shown an action it cannot take, disabled and saying what it needs —
6.3s - ✅ a genuinely restricted Fineract user › the superuser the rest of the suite uses is unaffected —
9.5s
rbac-dead-end-controls.spec.ts
- ✅ a client's account tables › withhold the link, but not the number, from a user who cannot open the account —
5.2s - ✅ a client's account tables › keep the link for a user who holds READ_LOAN —
5.5s - ✅ the office transactions list › withholds Create from a reader, and the route refuses it too —
5.4s - ✅ the office transactions list › offers Create to an administrator —
4.9s - ✅ the office transactions list › refuses the list at the door for the code the platform does not check —
4.7s - ✅ a group's member list › names the member without linking when the reader cannot open clients —
5.4s - ✅ a group's member list › keeps the link for a reader who holds READ_CLIENT —
6.2s
rbac-disabled-flag-backend.spec.ts
- ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is shown the whole navigation, including modules their role does not cover —
4.0s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is admitted by the route guard to a screen their role does not cover —
8.7s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is still refused by Fineract, which the flag does not reach —
603ms - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › is told the list is beyond their role, and not offered a retry that cannot succeed —
6.1s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › gets the same treatment on a second refused list, not a one-screen fix —
5.4s - ✅ rbacEnabled: false, with a user who holds only READ_CLIENT › still loads the list their role does cover, with no failure state at all —
6.0s
rbac-multi-permission.spec.ts
- ✅ a route declaring more than one permission code (OR semantics) › is admitted by either declared code alone —
5.6s - ✅ a route declaring more than one permission code (OR semantics) › is admitted by the other declared code alone —
5.7s - ✅ a route declaring more than one permission code (OR semantics) › is refused when holding neither declared code, by the router and by the backend —
5.8s - ✅ ALL_FUNCTIONS_READ, against the real Fineract permission catalogue › reaches read screens across modules it holds no specific code for —
10.3s - ✅ ALL_FUNCTIONS_READ, against the real Fineract permission catalogue › is refused every write screen, and the writes themselves —
7.9s - ✅ a restricted session across a real page reload › keeps the same permission boundary after reloading, not just after a fresh login —
11.6s - ✅ a second real action-level gate, distinct from loan repayment › is shown the Approve action disabled and naming what it needs, refused by the backend too —
5.8s - ✅ Security module writes (users, roles), against the real backend › reaches the list screens but is refused the write screens —
11.7s - ✅ Security module writes (users, roles), against the real backend › is refused creating a user and modifying a role, by the backend itself —
263ms
rbac-office-scoped-user.spec.ts
- ✅ an account scoped to a branch office › differs from a head-office account in office alone, not in permissions —
369ms - ✅ an account scoped to a branch office › is shown only its own office in the client list, and told which office it is in —
6.1s - ✅ an account scoped to a branch office › sees a shorter list than an identically-permissioned head-office account —
5.4s - ✅ an account scoped to a branch office › is refused another office’s client by the platform, and told so once —
5.6s - ✅ an account scoped to a branch office › can still open the record that is in its own office —
5.7s - ✅ an account scoped to a branch office › is not offered an edit it would be refused for on the Offices screen —
8.6s
bulk-import-entity-types.spec.ts
- ✅ every offered entity type is one the platform accepts —
7.1s
delinquency-configuration.spec.ts
- ✅ creates a delinquency range, then edits it —
8.1s - ✅ creates a delinquency bucket from the ranges the template offers —
7.2s
full-demo.spec.ts
- ✅ Full feature demo recording › walk through loan schedule type, lifecycle, custom fields, collateral, and disbursement —
49.5s
interest-rate-charts.spec.ts
- ✅ lists charts without a server error —
6.7s
loan-lifecycle.spec.ts
- ✅ Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Cumulative loan —
25.9s - ✅ Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Progressive loan —
26.6s - ✅ Loan lifecycle: creation, approval, disbursement › an approved loan can be returned to pending approval —
25.4s - ✅ Loan lifecycle: creation, approval, disbursement › the delinquency tab reads a real loan, and the empty data tabs stay hidden —
23.2s
loan-schedule-type.spec.ts
- ✅ Loan Schedule Type (Cumulative vs Progressive) › loan products list shows a schedule type chip per product —
7.0s - ✅ Loan Schedule Type (Cumulative vs Progressive) › create a Progressive loan product end-to-end and verify it round-trips —
11.6s - ✅ Loan Schedule Type (Cumulative vs Progressive) › loan creation shows the schedule type badge for a Progressive product —
8.2s
rbac-permissionless-user.spec.ts
- ✅ an account whose role grants nothing › is authenticated by Fineract and authorized by it for nothing —
713ms - ✅ an account whose role grants nothing › is signed in and landed on a screen rather than stranded at the login form —
4.3s - ✅ an account whose role grants nothing › is offered no permission-gated module, and refused each one by URL and by Fineract —
9.7s - ✅ an account whose role grants nothing › is left a navigation whose entries actually work —
4.1s
report-mailing-job.spec.ts
- ✅ report mailing job creation › is no longer refused for a missing parameter —
5.0s - ✅ report mailing job creation › the payload carries the locale and the schedule —
4.7s - ✅ report mailing job creation at the narrow viewport › every field is reachable and the job still saves at 320px —
6.6s
report-parameter-backend.spec.ts
- ✅ Dynamic report parameters against Fineract › keeps the parameter form available when a report has cascading lookups —
3.5s - ✅ Dynamic report parameters against Fineract › changing Office changes the Client Listing row set —
5.7s - ✅ Cascading report parameters against Fineract › sends the parent value to the child lookup and clears the child when it changes —
6.0s - ✅ Chart reports against Fineract › renders a chart report as a chart rather than a table —
3.7s
savings-flows-non-admin-roles.spec.ts
- ✅ a savings account taken through four separated duties, every account built in the UI › an administrator builds the five roles and accounts through the forms —
39.7s - ✅ a savings account taken through four separated duties, every account built in the UI › the officer books the application and is not offered approval of it —
7.0s - ✅ a savings account taken through four separated duties, every account built in the UI › the approver, differing in one code, is offered it and approves —
4.5s - ✅ a savings account taken through four separated duties, every account built in the UI › the activator activates, and cannot take a deposit —
4.0s - ✅ a savings account taken through four separated duties, every account built in the UI › the teller, holding only the transaction codes, records a deposit end to end —
5.6s - ✅ a savings account taken through four separated duties, every account built in the UI › the cash-withdrawal control asks for the cash code, not the application one —
14.9s - ✅ a savings account taken through four separated duties, every account built in the UI › the transaction route refuses an UPDATE_SAVINGSACCOUNT holder, as the platform does —
5.4s
savings-transaction-correction.spec.ts
- ✅ Savings transaction correction › a deposit is reversed and a hold is released —
5.6s
savings-transaction-gating.spec.ts
- ✅ Savings transactions are offered only on an active account › withheld while awaiting approval, offered once active —
4.8s - ✅ Savings transactions are offered only on an active account › the platform refuses a deposit on an account awaiting approval —
216ms
share-account-servicing.spec.ts
- ✅ Share account servicing › an account is approved, activated, traded and closed —
14.2s(retried 1×) - ✅ Share account servicing › an application can be rejected —
7.7s(retried 1×)
share-product-accounting.spec.ts
- ✅ Share product accounting › a share product is mapped to equity and round-trips on edit —
20.8s
teller-cash-management.spec.ts
- ✅ Teller cash management › a cashier is listed, receives an allocation, and settles cash back —
21.7s - ✅ Teller cash management › settling more than the cashier holds is refused and the form stays usable —
9.8s
tenant-date-defaults-backend.spec.ts
- ✅ Date defaults come from the platform, not the browser › a client can be created while the browser is a day ahead —
5.6s
working-capital-loan-actions-backend.spec.ts
- ✅ Working Capital loan actions, against a real Fineract › charges off an active loan, and then offers only the undo —
6.5s - ✅ Working Capital loan actions, against a real Fineract › credits the borrower with a goodwill credit, and the balance falls —
5.0s - ✅ Working Capital loan actions, against a real Fineract › writes the loan off, which closes it and opens the recovery path —
6.2s
working-capital-product-accounting-backend.spec.ts
- ✅ Working Capital product accounting, on the platform › configures the accrual rule and its twelve accounts, and the platform accepts it —
14.5s - ✅ Working Capital product accounting, on the platform › prefills a configured product and re-saves it without blanking anything —
4.1s
working-capital-rbac-backend.spec.ts
- ✅ Working Capital command permissions, on the platform › each command is authorised by the code the UI declares for it —
5.1s - ✅ Working Capital command permissions, on the platform › UPDATE_WORKINGCAPITALLOAN authorises none of them —
2.0s
working-capital-servicing-backend.spec.ts
- ✅ transaction corrections › undoes a repayment, and the platform marks it reversed —
4.7s - ✅ transaction corrections › offers no correction on the disbursement, which the platform refuses by type —
3.7s - ✅ transaction corrections › adjusts a repayment, which reverses it and books the new amount —
4.4s - ✅ charge adjustment › adjusts a charge, and the outstanding amount follows —
4.3s - ✅ custom datatables › adds, edits and deletes a row of a multi-row table on the loan —
6.1s - ✅ custom datatables › creates a loan that a create-time check would otherwise refuse —
6.6s
Slowest 10 — what the shard counts should be tuned against
| Test | Spec | Time |
|---|---|---|
| Full feature demo recording › walk through loan schedule type, lifecycle, custom fields, collateral, and disbursement | full-demo.spec.ts |
49.5s |
| a savings account taken through four separated duties, every account built in the UI › an administrator builds the five roles and accounts through the forms | savings-flows-non-admin-roles.spec.ts |
39.7s |
| Loan product accounting › a cash-accounting product is configured, round-trips on edit, and posts to the ledger | loan-product-accounting.spec.ts |
37.7s |
| a loan taken through four separated duties, every account built in the UI › an administrator builds the four roles and accounts through the forms | loan-flows-non-admin-roles.spec.ts |
33.3s |
| Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Progressive loan | loan-lifecycle.spec.ts |
26.6s |
| Loan lifecycle: creation, approval, disbursement › create, approve, and disburse a Cumulative loan | loan-lifecycle.spec.ts |
25.9s |
| Loan lifecycle: creation, approval, disbursement › an approved loan can be returned to pending approval | loan-lifecycle.spec.ts |
25.4s |
| Loan lifecycle: creation, approval, disbursement › the delinquency tab reads a real loan, and the empty data tabs stay hidden | loan-lifecycle.spec.ts |
23.2s |
| Teller cash management › a cashier is listed, receives an allocation, and settles cash back | teller-cash-management.spec.ts |
21.7s |
| Share product accounting › a share product is mapped to equity and round-trips on edit | share-product-accounting.spec.ts |
20.8s |
📼 Download the HTML report, videos and traces — see the playwright-report-backend artifact.
Generated by run 39142625057 from d29c708. The run executed a fork branch, so treat its contents as unverified.
…led load Two defects on the savings screens, and one of them put money in the wrong place. **apache#761 — every savings account opened through this form earned 0%.** The Interest Rate field started at `0` and never moved when a product was chosen, and the form sent it on every submit. Fineract reads a rate in the request as an account-level *override*, so a 5% product opened an account paying nothing, and nobody finds out until interest posting or a customer complaint. The rate now comes from the product the user picked, out of the `GET /savingsproducts` list the form has already loaded — measured, a product configured at 3.75% reports 3.75 in that list, so no extra request is needed. (The deposit product lists are the opposite case: they report `0` for every product, because a term deposit's rate lives in its chart. Same field name, opposite meaning.) With no rate known the field is now **omitted** rather than sent as zero, which is how the platform is asked to apply the product's own rate — and a rate the user types still stands, including a deliberate 0%, because that is a rate an institution may mean. **apache#763 — a failed account read rendered an empty page.** The savings account view wrapped its whole template in `@if (account())` and reported failure through a toast that had already gone by the time anyone looked, so an outage or a 5xx left the breadcrumb and nothing else: staff could not tell a missing account from a broken request. It now uses the shared load-error state the rest of the app has had since apache#223, with a Try again that re-reads, a separate branch for a 404 or 403 where retrying cannot succeed, and a spinner while the read is in flight. The deposit account view had the same gap and is fixed with it — it even held a `hasError` boolean that no part of its template read. Its toast stays, because that read goes through `DEPOSIT_ACCOUNT_API`, which takes no per-request context, and suppressing it in the adapter would also silence the deposit form, which reports nothing of its own.
apache#756. One double-click on the loan repayment form posted two repayments, both `200`: the borrower's balance and the GL were each wrong by the payment, and someone had to notice and reverse one. Create Client made two clients the same way. Approve and disburse escaped only because the platform refuses the second one once the state has moved — the monetary commands have no such accident to rely on, since each is valid on its own. The forms do set an `isSaving` flag and bind it to `[disabled]`. That is not a guard: the second click of a double-click, and a second Enter, both land before the flag reaches the DOM. Ninety- nine components carry such a flag and one of them re-checks it in the handler, so this is fixed once, in the HTTP layer, where every form already goes. `idempotencyInterceptor` does two things: **Collapses identical mutating requests that are in flight.** The second caller is handed the first request's response and no second request is issued. The window is the first request's flight, not a timed cache, so two *deliberate* identical submissions stay two requests — a customer paying two equal instalments in one visit is not a double-click, and cannot be made into one by clicking slower than the network. **Puts an idempotency key on every mutating request.** Fineract honours it, which was measured rather than assumed: two `POST /loans/{id}/transactions?command=repayment` carrying the same `Idempotency-Key` produced **one** transaction and returned the same `resourceId` twice, and two keyed `POST /clients` produced one client where the same pair without a key produced two (ids 107 and 108). So a duplicate that does leave the browser — a dropped connection, a proxy replay — cannot double-post either. This is the application's half of what the `IDEMPOTENT` context token already describes; nothing here opts commands into retrying, which stays a call-site decision. Registered after `loadingInterceptor`, so a collapsed double submit counts as the one request it becomes, and before `retryInterceptor`, so a retried attempt carries the key of the attempt it repeats. An upload is keyed but not collapsed: a `FormData` body cannot be compared by value, and two files of the same name are not the same upload. The e2e double-clicks Save against a real Fineract and asserts both halves — one POST left the browser, and the platform holds one transaction. Removing the interceptor makes it report 2, which is the bug as filed.
"the future" is by its own reckoning — the tenant's timezone, or the business date when that feature is on — so whenever the browser ran ahead, submitting the defaults was refused: `error.msg.clients.submittedOnDate.in.the.future`, and on the loan form a `403` that reads like a permission problem. Every client and loan entered during those hours failed on first submit, and the user was pushed into editing dates by trial and error, which invites back-dating mistakes. `PlatformDateService` already existed for this and was used by exactly one dialog, while five places read `GET /businessdate` for themselves. It is now the one source, and it is right about two things it was wrong about: **It asks whether the business date governs.** The rows behind `/businessdate` persist after the feature is switched off and stop being maintained — measured, a tenant with it disabled still reported `BUSINESS_DATE 2026-10-17` on the 18th, and `POST /businessdate` refused to move it with `business.date.is.not.enabled`. Every screen reading that row unasked was seeding a day in the past, the header included: it announced a business date that governed nothing. **It no longer invents a timezone.** The fallback assumed `Asia/Kolkata` whenever none was configured, which is a date up to a day away from the platform's for every tenant that is not in that zone — the same kind of guess the service exists to remove. Today now comes from the platform's own clock, read off the `Date` header that every response already carries (`serverClockInterceptor`), and a configured `tenantTimezone` is applied to *that* instant rather than to the browser's. Also measured, and the reason the client form cannot simply trust its template: `GET /loans/template` reports `expectedDisbursementDate` as the business date, while `GET /clients/template` reports `activationDate` as the *system* date — which under a business date of the 10th is the 18th, and is refused. The shared source is consulted either way. Resolved once and shared, with a five-minute window rather than forever: a back-office session stays open all day, and the business date moves when close-of-business runs. Seven forms now seed from it — client, loan, savings account, both deposit accounts, group and centre — and the header, two action dialogs and the delinquency tab read the business date through it instead of fetching it themselves. The e2e moves the browser clock a day forward with Playwright's `setFixedTime` and creates a client without touching a date. Removing the seeding leaves that test on `/clients/create`, which is the bug as filed.
CI's mocked shards caught three things the previous commit got wrong, all of them about what
"no business date" should mean.
**A tenant without the feature still needs the resume action.** Routing the delinquency tab
through the shared source made it withhold Pause's counterpart whenever no business date
governed — which is most tenants, and is a capability they had before. It now dates by the
platform's today: the business date when one governs, the platform's clock when none does. Both
halves matter, because the stored business date is *not* the current day on a tenant with the
feature off, and that was the bug being fixed.
**Two mocked specs had to be told about the second question.** The header shows a business date
only when one governs, so a spec that mocks `GET /businessdate` and asserts the label now has to
mock `enable-business-date` as well. Same for the delinquency specs, which are about a tenant
using the feature.
**The config read is now quiet and unretried.** It is asked on behalf of a date default and has
a safe answer when it fails, so three retries and an error toast per screen were waste. The flag
is also resolved once per session rather than once per five minutes — it is tenant
configuration, not a moving date.
And one thing worth recording, because it changed a test rather than code: the accessibility
scan of the client form had never evaluated colour contrast. The header used to throw on a
`GET /businessdate` payload it did not expect — `.find` on the mocked `{}` — which reached
`GlobalErrorHandler` and raised a toast, and an overlay puts `color-contrast` into axe's
*incomplete* bucket for every node it covers: fourteen of them here, nought violations, test
green. Going through the shared source stops that crash, so the scan can finally see the page —
and what it sees is the office select's placeholder at 3.69:1, measured identically on `main`.
Baselined with the mechanism written down, because the fix is Ionic's placeholder opacity across
every form in the application.
All four mocked shards, the mobile project and the unit suite verified with `--workers=1`, which
is what CI uses; locally Playwright defaults to eleven workers against one dev server, and the
failure counts that produces are noise.
… lands Rebasing onto main brought apache#805's post-mutation navigation, which returns to the affected record instead of the list. Two of these assertions named the list: the repayment now ends on `/loans/view/{id}` and a created client on `/clients/view/{id}`. Still assertions a form that never submitted would fail — the id segment is what rules out `/clients/create`, which is what the looser `/clients$` was there for.
63e2b37 to
9e403bf
Compare
`testTimeout` was raised to 30s because the first `createComponent()` in a file compiles the component and everything it imports, which runs past Vitest's default on the larger screens. That cost is usually paid in a `beforeEach`, and `hookTimeout` was left at 10s. So `client-form.component.test.ts` and `wc-loan-product-form.component.test.ts` — wizards built from several dozen Ionic components — time out in a hook that is itself synchronous, whenever the machine is busy. It reads as a flake in whichever file the suite was running, which is how it was first mistaken for one: the same signature appeared while chasing an unrelated failure in apache#804. Verified by reproducing the cause rather than the symptom: the full suite now passes with six spinning processes alongside it, at a load average of 30, where it failed twice at idle before.
Closes #756, #758, #761, #763.
Four defects that are one subject: what a form sends, and what the screen does while it is
sending or after it fails. Two of them move money to the wrong place. Everything below was
measured against a running
apache/fineract:latestbefore it was written, and both e2e testswere checked to fail without the fix.
#756 — a double-click posted two repayments
One double-click on the loan repayment form posted twice, both
200: the borrower's balance andthe GL each wrong by the payment, and someone had to notice and reverse one. Create Client made
two clients the same way. Approve and disburse escaped only because the platform refuses the
second one once the state has moved; a monetary command has no such accident to rely on.
The forms do set an
isSavingflag and bind it to[disabled], which is not a guard — thesecond click, and a second Enter, land before the flag reaches the DOM. Ninety-nine components
carry such a flag and one re-checks it in its handler, so this is fixed once, in the HTTP layer:
request's response and no second request is issued. The window is the first request's flight,
not a timed cache, so two deliberate identical submissions stay two requests;
two repayments with the same
Idempotency-Keyproduced one transaction and returned thesame
resourceIdtwice, and two keyedPOST /clientsproduced one client where the keylesspair produced two. So a duplicate that does leave the browser cannot double-post either.
This is the application's half of what the existing
IDEMPOTENTcontext token already describes.Nothing here opts commands into retrying — that stays a call-site decision.
#758 — dates defaulted to the browser's today, and were refused
PlatformDateServicealready existed for this, used by exactly one dialog, while five placesread
GET /businessdatefor themselves. It is now the one source, and right about two things ithad wrong:
off and stop being maintained — measured, a tenant with it disabled still reported
BUSINESS_DATE 2026-10-17on the 18th, andPOST /businessdaterefused to move it withbusiness.date.is.not.enabled. Screens reading it unasked seeded a day in the past; the headerannounced a business date that governed nothing;
Asia/Kolkatawhen none wasconfigured — a date up to a day away from the platform's for any other tenant. Today now comes
from the platform's own clock, read off the
Dateheader every response already carries, with aconfigured
tenantTimezoneapplied to that instant rather than the browser's.Worth recording:
GET /loans/templatereportsexpectedDisbursementDateas the business date,while
GET /clients/templatereportsactivationDateas the system date — which under abusiness date of the 10th is the 18th, and is refused. So the client form cannot simply trust its
own template, and the shared source is consulted either way.
Seven forms now seed from it (client, loan, savings account, both deposit accounts, group,
centre), and four screens read the business date through it instead of fetching it themselves.
#761 — every savings account was opened at 0%
The Interest Rate field started at
0and never moved when a product was chosen, and the formsent it on every submit. Fineract reads a rate in the request as an account-level override,
so a 5% product opened an account paying nothing and nobody finds out until interest posting.
The rate now comes from the product the user picked, out of the
GET /savingsproductslist theform already loads — measured, a product configured at 3.75% reports 3.75 there, so no extra
request is needed. With no rate known the field is omitted, which is how the platform is
asked to apply the product's own rate, and a rate the user types still stands, including a
deliberate 0%.
(The deposit product lists are the opposite case: they report
0for every product because aterm deposit's rate lives in its chart. Same field name, opposite meaning.)
#763 — a failed savings load rendered an empty page
The view wrapped its template in
@if (account())and reported failure through a toast that hadalready gone, so an outage left the breadcrumb and nothing else. It now uses the shared
load-error state with a Try again that re-reads, a separate branch for a 404 or 403 where
retrying cannot succeed, and a spinner while the read is in flight. The deposit account view had
the same gap — it even held a
hasErrorboolean no part of its template read — and is fixed withit.
Verification
America/New_York;test:mfegreentransaction; a client is created once; and with the browser clock a day ahead, Create Client
submits with untouched defaults. Each was run with its fix removed and fails then — 2 requests,
and a form that never leaves
/clients/createlint,lint:prune,prettier --check,check-api-surfaceand the translation check all exit 0Not covered
The remaining ~35
new Date()seeds are dialogs, report filters and charge due dates rather thanthe create and submit paths; they now have a shared source to move to.
client-form.component.test.tshas a pre-existing 10s
beforeEachbudget that its Ionic wizard exceeds when the machine isbusy — it fails on a loaded machine on
mainas well as here, and passes idle; worth its ownissue.