Files
cbsnexora/src/app/sponsor-dd/sponsor-dd.service.ts
T
keshav 3cf01e2733 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.
2026-07-22 11:59:31 +05:30

268 lines
9.0 KiB
TypeScript

/**
* 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);
})
)
);
})
);
}
}