Files
cbsnexora/src/app/loan-origination/application-detail/field-visit-details-dialog/field-visit-details-dialog.component.html
T
niteshandClaude Sonnet 5 c18219fce5
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
feat(loan-origination): persist the sanctioning pipeline to the backend
Replaces the in-memory mock (LOAN_APPLICATIONS array + seeded RNG, reset on
every refresh) with real Fineract-backed persistence: a new
LoanOriginationService for CRUD plus one API call per stage transition, a
loan-fineract-bridge that creates a real loan account (create + approve +
disburse) at disbursement for Existing Customers, and file uploads through
Fineract's generic document store keyed by a stable checklist slot so
"On file"/View controls survive a reload.

Also fixes several gaps found while verifying full-cycle persistence:
- CIC charge-account selection never saved (missing persist() call)
- Co-borrower relationship/monthly income lived in local-only component state
  instead of on the application, so they reset on refresh
- Field Investigation "View Full Details" showed local-session photos only;
  stored photos are now downloaded and shown as real thumbnails
- "Assign Field Officer" was free text; now a dropdown of real CBS loan
  officers pulled from /staff
- site photos and cheque/signature images now open in the same zoomable,
  pannable lightgallery viewer used elsewhere in the app

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 17:05:05 +05:30

82 lines
2.9 KiB
HTML

<!--
Copyright since 2025 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<h2 mat-dialog-title>{{ data.label }} — Visit Details</h2>
<mat-dialog-content class="fvd-content">
<span [class]="statusBadgeClass">{{ data.check.status }}</span>
<dl class="fvd-kv-grid">
<div class="fvd-kv-field">
<dt>Officer Assigned</dt>
<dd>{{ data.check.officerAssigned }}</dd>
</div>
<div class="fvd-kv-field">
<dt>Assigned On</dt>
<dd>{{ data.check.assignedDate }}</dd>
</div>
<div class="fvd-kv-field">
<dt>GPS Coordinates</dt>
<dd>{{ data.check.latitude || '—' }}, {{ data.check.longitude || '—' }}</dd>
</div>
<div class="fvd-kv-field">
<dt>{{ data.check.status === 'FAILED' ? 'Rejected By' : 'Verified By' }}</dt>
<dd>{{ data.check.verifiedBy }}</dd>
</div>
<div class="fvd-kv-field">
<dt>{{ data.check.status === 'FAILED' ? 'Rejected On' : 'Verified On' }}</dt>
<dd>{{ data.check.verifiedDate }}</dd>
</div>
<div class="fvd-kv-field">
<dt>Remarks</dt>
<dd>{{ data.check.remarks }}</dd>
</div>
</dl>
<p class="fvd-photos-title">Site Photos ({{ data.check.photoCount }})</p>
@if (data.photos.length > 0 || data.storedPhotos.length > 0) {
<div class="fvd-photo-grid">
@for (photo of data.photos; track $index) {
<button
type="button"
class="fvd-photo"
[class.fvd-photo--zoomable]="!!photo.previewUrl"
title="{{ photo.previewUrl ? 'Click to zoom' : photo.file.name }}"
(click)="openPhotoZoom(photo.previewUrl)"
>
@if (photo.previewUrl) {
<img [src]="photo.previewUrl" alt="Site photo {{ $index + 1 }}" />
} @else {
<span class="fvd-photo-label">{{ photo.file.name }}</span>
}
</button>
}
@for (stored of data.storedPhotos; track stored.id) {
<button
type="button"
class="fvd-photo fvd-photo--stored"
[class.fvd-photo--zoomable]="!!stored.previewUrl"
title="{{ stored.previewUrl ? 'Click to zoom' : stored.fileName }}"
(click)="stored.previewUrl ? openPhotoZoom(stored.previewUrl) : data.onViewStored(stored)"
>
@if (stored.previewUrl) {
<img [src]="stored.previewUrl" alt="{{ stored.fileName }}" />
} @else {
<span class="fvd-photo-label">View file</span>
}
</button>
}
</div>
} @else {
<p class="fvd-no-photos">No photos on file for this visit.</p>
}
<div #lightboxRoot class="fvd-lightbox-root"></div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button type="button" mat-button mat-dialog-close>Close</button>
</mat-dialog-actions>