Add customer search dialog and sponsor DD/reconciliation modules
Adds sponsor-dd and sponsor-dd-reconciliation feature modules, a shared customer search dialog, and wires them into routing/shell/ organization screens alongside cashier and deposit-request updates.
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SponsorDDService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
// ── Inventory ──
|
||||
getInventories(): Observable<any[]> {
|
||||
return this.http.get<any[]>('/sponsordd/inventory');
|
||||
}
|
||||
|
||||
getInventoryById(id: number): Observable<any> {
|
||||
return this.http.get<any>(`/sponsordd/inventory/${id}`);
|
||||
}
|
||||
|
||||
registerInventory(data: {
|
||||
sponsorBankId: number;
|
||||
branchId: number;
|
||||
seriesStart: number;
|
||||
seriesEnd: number;
|
||||
receivedDate: string; // yyyy-MM-dd
|
||||
}): Observable<any> {
|
||||
return this.http.post<any>('/sponsordd/inventory', data);
|
||||
}
|
||||
|
||||
// ── Leaflets ──
|
||||
getLeafletsByInventory(inventoryId: number): Observable<any[]> {
|
||||
return this.http.get<any[]>(`/sponsordd/inventory/${inventoryId}/leaflets`);
|
||||
}
|
||||
|
||||
getLeafletById(leafletId: number): Observable<any> {
|
||||
return this.http.get<any>(`/sponsordd/leaflets/${leafletId}`);
|
||||
}
|
||||
|
||||
getLeafletByDdNumber(ddNumber: string): Observable<any> {
|
||||
return this.http.get<any>(`/sponsordd/leaflets/by-number/${ddNumber}`);
|
||||
}
|
||||
|
||||
// ── Actions ──
|
||||
issueDemandDraft(
|
||||
leafletId: number,
|
||||
data: {
|
||||
clientId: number;
|
||||
beneficiaryName: string;
|
||||
faceValue: number;
|
||||
issuingBranchId: number;
|
||||
issuedDate: string; // yyyy-MM-dd
|
||||
savingsTransactionId?: number;
|
||||
currentTransactionId?: number;
|
||||
journalEntryId?: number;
|
||||
}
|
||||
): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/leaflets/${leafletId}/issue`, data);
|
||||
}
|
||||
|
||||
cancelDemandDraft(
|
||||
leafletId: number,
|
||||
data: {
|
||||
remarks: string;
|
||||
refundSavingsTransactionId?: number;
|
||||
refundCurrentTransactionId?: number;
|
||||
}
|
||||
): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/leaflets/${leafletId}/cancel`, data);
|
||||
}
|
||||
|
||||
stopPaymentDemandDraft(leafletId: number, data: { remarks: string }): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/leaflets/${leafletId}/stop`, data);
|
||||
}
|
||||
|
||||
// ── DD Issuance Requests (maker-checker) ──
|
||||
submitIssueRequest(
|
||||
leafletId: number,
|
||||
data: {
|
||||
clientId?: number;
|
||||
walkinPurchaserName?: string;
|
||||
walkinPurchaserAddress?: string;
|
||||
walkinPurchaserAadhaar?: string;
|
||||
beneficiaryName: string;
|
||||
payableAtBranch: string;
|
||||
payableAtCity: string;
|
||||
faceValue: number;
|
||||
chargesAmount?: number;
|
||||
issuingBranchId: number;
|
||||
issuedDate: string; // yyyy-MM-dd
|
||||
paymentMethod: 'ACCOUNT_DEBIT' | 'CHEQUE' | 'CASH';
|
||||
savingsAccountId?: number;
|
||||
currentAccountId?: number;
|
||||
chequeNumber?: string;
|
||||
chequeBankName?: string;
|
||||
cashierDepositRequestId?: number;
|
||||
}
|
||||
): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/leaflets/${leafletId}/issue-request`, data);
|
||||
}
|
||||
|
||||
getAvailableCashierDepositRequests(): Observable<any[]> {
|
||||
return this.http.get<any[]>('/sponsordd/cashier-deposit-requests/available');
|
||||
}
|
||||
|
||||
// ── Multi-DD slip (one account debit/cheque funds several drafts) ──
|
||||
submitIssueRequestBatch(
|
||||
inventoryId: number,
|
||||
data: {
|
||||
clientId?: number;
|
||||
issuingBranchId: number;
|
||||
issuedDate: string; // yyyy-MM-dd
|
||||
paymentMethod: 'ACCOUNT_DEBIT' | 'CHEQUE';
|
||||
savingsAccountId?: number;
|
||||
currentAccountId?: number;
|
||||
chequeNumber?: string;
|
||||
chequeBankName?: string;
|
||||
lines: {
|
||||
beneficiaryName: string;
|
||||
payableAtBranch: string;
|
||||
payableAtCity: string;
|
||||
faceValue: number;
|
||||
chargesAmount?: number;
|
||||
}[];
|
||||
}
|
||||
): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/inventory/${inventoryId}/issue-request-batch`, data);
|
||||
}
|
||||
|
||||
approveIssueRequestBatch(batchId: number): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/issue-requests/batch/${batchId}/approve`, {});
|
||||
}
|
||||
|
||||
rejectIssueRequestBatch(batchId: number, rejectionReason: string): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/issue-requests/batch/${batchId}/reject`, { rejectionReason });
|
||||
}
|
||||
|
||||
// ── Sponsor DD GL mapping (back-office configuration) ──
|
||||
getGlMappings(): Observable<any[]> {
|
||||
return this.http.get<any[]>('/sponsordd/gl-mapping');
|
||||
}
|
||||
|
||||
upsertGlMapping(data: {
|
||||
sponsorBankId: number;
|
||||
ddPayableGlAccountId: number;
|
||||
chequeClearingGlAccountId?: number;
|
||||
cashGlAccountId?: number;
|
||||
chargesIncomeGlAccountId?: number;
|
||||
cashSuspenseGlAccountId?: number;
|
||||
gstPercentage?: number;
|
||||
gstPayableGlAccountId?: number;
|
||||
ddLimitAmount?: number | null;
|
||||
}): Observable<any> {
|
||||
return this.http.post<any>('/sponsordd/gl-mapping', data);
|
||||
}
|
||||
|
||||
getAllIssueRequests(status?: string): Observable<any[]> {
|
||||
const params = status ? { status } : {};
|
||||
return this.http.get<any[]>('/sponsordd/issue-requests', { params });
|
||||
}
|
||||
|
||||
getMyIssueRequests(): Observable<any[]> {
|
||||
return this.http.get<any[]>('/sponsordd/issue-requests/mine');
|
||||
}
|
||||
|
||||
getIssueRequestById(id: number): Observable<any> {
|
||||
return this.http.get<any>(`/sponsordd/issue-requests/${id}`);
|
||||
}
|
||||
|
||||
approveIssueRequest(id: number): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/issue-requests/${id}/approve`, {});
|
||||
}
|
||||
|
||||
rejectIssueRequest(id: number, rejectionReason: string): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/issue-requests/${id}/reject`, { rejectionReason });
|
||||
}
|
||||
|
||||
// ── DD reprint requests (maker-checker) ──
|
||||
recordPrint(leafletId: number): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/leaflets/${leafletId}/print`, {});
|
||||
}
|
||||
|
||||
requestReprint(leafletId: number, reason: string): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/leaflets/${leafletId}/reprint-request`, { reason });
|
||||
}
|
||||
|
||||
getAllReprintRequests(status?: string): Observable<any[]> {
|
||||
const params = status ? { status } : {};
|
||||
return this.http.get<any[]>('/sponsordd/reprint-requests', { params });
|
||||
}
|
||||
|
||||
approveReprintRequest(id: number): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/reprint-requests/${id}/approve`, {});
|
||||
}
|
||||
|
||||
rejectReprintRequest(id: number, rejectionReason: string): Observable<any> {
|
||||
return this.http.post<any>(`/sponsordd/reprint-requests/${id}/reject`, { rejectionReason });
|
||||
}
|
||||
|
||||
// ── Approver signature (stamped onto the printed DD) ──
|
||||
// Reuses Fineract's generic document-attachment API (entityType is a free-form string with
|
||||
// no per-type backend wiring required) rather than building dedicated signature storage.
|
||||
private static readonly SIGNATURE_ENTITY_TYPE = 'users';
|
||||
private static readonly SIGNATURE_DOCUMENT_NAME = 'signature';
|
||||
|
||||
getUserDocuments(userId: number): Observable<any[]> {
|
||||
return this.http.get<any[]>(`/${SponsorDDService.SIGNATURE_ENTITY_TYPE}/${userId}/documents`);
|
||||
}
|
||||
|
||||
uploadSignature(userId: number, file: File): Observable<any> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, file.name);
|
||||
formData.append('name', SponsorDDService.SIGNATURE_DOCUMENT_NAME);
|
||||
formData.append('description', 'Approver signature, stamped onto printed demand drafts');
|
||||
return this.http.post<any>(`/${SponsorDDService.SIGNATURE_ENTITY_TYPE}/${userId}/documents`, formData);
|
||||
}
|
||||
|
||||
deleteSignature(userId: number, documentId: number): Observable<any> {
|
||||
return this.http.delete<any>(`/${SponsorDDService.SIGNATURE_ENTITY_TYPE}/${userId}/documents/${documentId}`);
|
||||
}
|
||||
|
||||
getSignatureBlob(userId: number, documentId: number): Observable<Blob> {
|
||||
return this.http.get(`/${SponsorDDService.SIGNATURE_ENTITY_TYPE}/${userId}/documents/${documentId}/attachment`, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
/** Finds this user's uploaded signature document, if any, from their full document list. */
|
||||
findSignatureDocument(userId: number): Observable<any> {
|
||||
return this.getUserDocuments(userId).pipe(
|
||||
map((docs) => (docs || []).find((d) => d.name === SponsorDDService.SIGNATURE_DOCUMENT_NAME) || null)
|
||||
);
|
||||
}
|
||||
|
||||
/** Fetches this user's signature (if any) as a data URI ready to use in an <img> src or print HTML. */
|
||||
getSignatureDataUri(userId: number): Observable<string | null> {
|
||||
return this.findSignatureDocument(userId).pipe(
|
||||
switchMap((doc) => {
|
||||
if (!doc) {
|
||||
return of(null);
|
||||
}
|
||||
return this.getSignatureBlob(userId, doc.id).pipe(
|
||||
switchMap(
|
||||
(blob) =>
|
||||
new Observable<string>((subscriber) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
subscriber.next(reader.result as string);
|
||||
subscriber.complete();
|
||||
};
|
||||
reader.onerror = () => subscriber.error(reader.error);
|
||||
reader.readAsDataURL(blob);
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user