feat: close nine daily branch-operations gaps, and unblock the spec sync #416 breaks - #418
Conversation
The nightly spec sync picked up a newer apache/fineract image, which adds `issuanceDate` and `expiryDate` to the document upload endpoint. Both land between existing parameters rather than after them, so every generated overload of `postEntityTypeEntityIdDocuments` shifts by two positions. The KYC document form is the only caller; it passes the new parameters as `undefined` so behaviour is unchanged, and the arguments are commented with the field each one carries so the next shift is easier to spot.
E2E — mocked backend🎭 E2E Tests✅ All green — 311 passed · 0 failed · 0 skipped, across 25 spec files in 6m 27s. By spec file
All 311 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 32584680931 from |
E2E — real Fineract🎭 E2E Tests✅ All green — 72 passed · 0 failed · 0 skipped, across 22 spec files in 6m 47s. By spec file
All 72 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 32584680931 from |
61f2f91 to
b4dc7ff
Compare
…rtcuts Two navigation gaps on every screen. The back office is several levels deep in places — a group's GLIM account, a loan's charge form — with nothing on the page saying where that is or how to get back up, and reaching a common section always meant a trip through the sidebar. The trail is derived from each activated route's `title`, the translation key `TranslatedTitleStrategy` already sets for the browser tab, rather than a second set of route data that could drift out of step with it. Route configs here commonly pair an outer titled segment with an identically titled empty-path child that lazy-loads the feature, so adjacent duplicate crumbs collapse into one. Shortcuts are Alt+letter, which leaves the browser's own Ctrl/Cmd bindings alone, and are ignored while focus is in a text field so they cannot eat what someone is typing.
The create-client form presented every field at once — legal form, office, staff, names, dates, address and contact details on one page — with no indication of which handful were actually required. Front-office staff opening an account are the least experienced users of this application and the ones under the most time pressure, so the long form is where they stall. Splitting it into Client Type, Personal Details and Contact Details lets each step gate its own required fields: `Next` stays disabled until the step is valid, so the user is told what is missing where it is missing rather than by a rejected submit at the end. The wizard applies to creation only. Editing reuses the same template flat, because someone correcting one field on an existing client is better served seeing all of them at once than being paced through steps again. The shared stepper is a new component rather than a form-local one — the loan and savings application forms have the same shape of problem.
Branch and portfolio managers had no way to answer "how much is outstanding, where, and how much of it is at risk" without exporting to a spreadsheet. The platform already answers this through the advanced search endpoint; nothing in the back office called it. The screen drives that endpoint: filter by status, product, office and a date range, optionally bounding outstanding amount or outstanding percentage, and get counts and totals grouped by office and product. The two outstanding filters are opt-in because the endpoint treats them as present-or-absent rather than as nullable — sending the field at all changes the query, so the payload omits the amount and percentage keys entirely unless their toggle is on, and sends min/max only for the `between` condition and a single value otherwise.
…try type The journal entries list was filterable by date alone. On a multi-branch tenant that is the one dimension that does not narrow anything useful — an accountant reconciling a single branch's cash account had to page through every entry the institution posted that day. Office and GL account come from their own endpoints rather than from the entries themselves, so the lists stay complete when the current filter returns nothing. The manual-entries toggle maps to the endpoint's `manualEntriesOnly` flag, which is how the platform distinguishes an accountant's correcting entry from one the portfolio posted. Filter changes are debounced through a subject: office, account, type and both dates are commonly changed in sequence, and each one otherwise fired its own request.
… rate chart Three gaps on the deposit side of the product screens. An approved savings account could not be returned to pending. Approval is routinely done in error — wrong product, wrong client — and without an undo the only remedy was to reject the account and re-key it, which loses the application date and the account number. `undoapproval` is sent without the usual `dateFormat`/`locale` pair: like the loan equivalent, it answers 400 for parameters it does not expect, and accepts an empty body or one carrying only a note. Deposit accounts showed neither their charges nor the interest rate chart the product was opened under. The rate chart is what a customer actually asks about — which rate applies at which term — and it was visible only on the product, not on the account, so an account opened before a product rate change could not be checked against the terms it was actually sold on. Both live under `?associations=all`, which neither generated method can send, so they go through the same `HttpClient` escape hatch as the transaction list. `ChartSlab` is declared locally rather than reusing the generated model: the generated type omits the amount range, description and incentive fields the real response carries, and this reads what is actually on the wire.
…uency tags The working-capital loan view showed the loan and its actions but none of the detail a reviewer needs to justify one. Four tabs, all backed by endpoints the API already exposed: - Charges, with the form to add and waive one. A working-capital facility is priced largely through its charges, so not being able to add one meant dropping to the generic loan screens or to the API. - Rate changes, showing the effective date and the rate before and after, including reversed ones — the audit trail for a repriced facility. - The projected amortization schedule, which is what the borrower is shown. - Delinquency range tags, so the reason a facility was flagged is visible next to the facility rather than only in the delinquency report. `mark as fraud` and `apply discount` join the action form for the same reason: both are decisions taken while looking at this screen.
…run jobs in bulk Bulk import offered a fraction of the entity types the platform supports. The transactional ones were the notable absence — loan repayments, savings, fixed and recurring deposit transactions — which are exactly the files an institution loads daily from a branch or an agent network. Guarantors and share accounts are added alongside them. The chart-of-accounts entry downloaded the wrong file: `glaccounts` called the journal-entries template. Every other entry names its own resource, so this was a copy-paste of the wrong service rather than a deliberate alias, and it gave anyone importing a chart of accounts a template that could not be imported. Journal entries are now their own entry, wired to the endpoint `glaccounts` had been borrowing. Guarantors are scoped to one loan rather than to the tenant, so that template needs a loan id before it can be downloaded; the field appears only for the entity types that require it. Scheduler jobs gain multi-select and a `Run Selected` action. Recovering from a failed nightly batch means re-running a specific set of jobs, and doing that one job at a time is both slow and easy to get wrong.
Two things made the bulk work queues awkward for the branch staff who use them. Everything arrived as one flat list across the whole institution, so a branch approver had to pick their own office's rows out of every other branch's; and the reschedule queue could only approve, even though a reschedule request is at least as often declined. Rows now group under the office heading the loan or client belongs to. The loan list answers `clientOfficeId` and no name, so office names are fetched once and cached — they are shared master data that does not change mid-session. The reschedule queue stays flat: its list response carries no office at all, so grouping it would mean a per-row lookup for a queue that is normally short. Reject reuses the same batch path as approve, differing only in the command and in the date field the platform expects (`rejectedOnDate` rather than `approvedOnDate`), and is gated on `REJECT_RESCHEDULELOAN`.
An audit log that can only be read on screen is of limited use to the people who need it — an internal auditor or a regulator asking what changed and who approved it wants the records out of the application, not a set of screenshots. The export covers the page currently loaded, matching what the table shows rather than silently re-querying the full result set: the audit endpoint is paginated and unbounded, and an export that quietly differed from the filtered view on screen would be worse than none. CSV escaping is its own utility with its own tests rather than an inline join. Audit records carry free text — usernames, entity names and the command JSON — so quotes, commas and newlines in the data are the normal case here, not an edge case.
Vitest's 5s default is measured against the test body, but the first `createComponent()` in a file also compiles the component and everything it imports. For the larger screens that runs past 5s on a cold Vite cache even though the test itself is synchronous, so a spec passes locally on a warm re-run and fails in CI, which is always cold. The compile is paid once per file rather than per test, so a higher ceiling costs nothing on the passing path.
b4dc7ff to
21273f9
Compare
Closes #419.
Supersedes #416.
Eleven commits, each taking a screen that already existed and making it do the one thing the role using it actually needs. Reviewable commit by commit.
Supersedes #416
#416 syncs the same image (
apache/fineract@sha256:6c331bbe…) and its build fails: upstream addedissuanceDate/expiryDatebetween existing parameters on the document upload endpoint, shifting every overload ofpostEntityTypeEntityIdDocumentsby two positions, so the sole caller no longer type-checks.The first commit here carries the identical sync and fixes both call sites, passing the new parameters as
undefined— behaviour unchanged.verify-api-clientagainstapache/fineract:latestconfirms the digest is still6c331bbe…and the regenerated client is byte-identical, so this is current with upstreammain.What changed
chore(api)feat(layout)feat(clients)feat(organization)feat(accounting)feat(products)feat(working-capital)feat(system)feat(tasks)feat(security)testNotes for review
titlerather than parallel route data, so they cannot drift from the tab title — but they inherit 285 routes in 21 feature files still inherit their tab title from the section above them #355, and those crumbs read wrong until it lands.undoapprovalomitsdateFormat/locale— like the loan equivalent, it answers 400 for parameters it does not expect.HttpClientdirectly: both need?associations=all, which no generated method can send.ChartSlabis local because the generated model omits fields the real response carries.createComponent()in a file compiles the component and its imports, exceeding the 5s default on a cold cache — so specs pass locally on a warm re-run and fail in CI, which is always cold. Reproduce withrm -rf node_modules/.vite && npm run test:unit.api-surface.jsonis recorded incrementally, soapi:surfacepasses at every commit, not just the tip. It also drops a staledeleteMakercheckersAuditIdentry, whose last call site went with the maker-checker rejection fix.Not in this branch
GLIM and GSIM screens are held back on
feat/glim-gsim-accounts: per #376 the platform refuses both on PostgreSQL, and the specs are mocked-only with no backend companion, so they are unverified against a real Fineract.Scoped out and left on their own issues: #197 (savings parity — undo-approval is taken here because it loses data when missing), #295 (term-deposit lifecycle), #355 (route titles).
Checks
All 14 CI checks pass locally, plus
verify-api-clientandbuild. 432 Vitest and 898 Karma tests pass, Vitest verified from a cold cache. All commits GPG-signed. The e2e suites need the Fineract stack and were left to CI.