/** * 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 { return this.http.get('/sponsordd/inventory'); } getInventoryById(id: number): Observable { return this.http.get(`/sponsordd/inventory/${id}`); } registerInventory(data: { sponsorBankId: number; branchId: number; seriesStart: number; seriesEnd: number; receivedDate: string; // yyyy-MM-dd }): Observable { return this.http.post('/sponsordd/inventory', data); } // ── Leaflets ── getLeafletsByInventory(inventoryId: number): Observable { return this.http.get(`/sponsordd/inventory/${inventoryId}/leaflets`); } getLeafletById(leafletId: number): Observable { return this.http.get(`/sponsordd/leaflets/${leafletId}`); } getLeafletByDdNumber(ddNumber: string): Observable { return this.http.get(`/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 { return this.http.post(`/sponsordd/leaflets/${leafletId}/issue`, data); } cancelDemandDraft( leafletId: number, data: { remarks: string; refundSavingsTransactionId?: number; refundCurrentTransactionId?: number; } ): Observable { return this.http.post(`/sponsordd/leaflets/${leafletId}/cancel`, data); } stopPaymentDemandDraft(leafletId: number, data: { remarks: string }): Observable { return this.http.post(`/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 { return this.http.post(`/sponsordd/leaflets/${leafletId}/issue-request`, data); } getAvailableCashierDepositRequests(): Observable { return this.http.get('/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 { return this.http.post(`/sponsordd/inventory/${inventoryId}/issue-request-batch`, data); } approveIssueRequestBatch(batchId: number): Observable { return this.http.post(`/sponsordd/issue-requests/batch/${batchId}/approve`, {}); } rejectIssueRequestBatch(batchId: number, rejectionReason: string): Observable { return this.http.post(`/sponsordd/issue-requests/batch/${batchId}/reject`, { rejectionReason }); } // ── Sponsor DD GL mapping (back-office configuration) ── getGlMappings(): Observable { return this.http.get('/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 { return this.http.post('/sponsordd/gl-mapping', data); } getAllIssueRequests(status?: string): Observable { const params = status ? { status } : {}; return this.http.get('/sponsordd/issue-requests', { params }); } getMyIssueRequests(): Observable { return this.http.get('/sponsordd/issue-requests/mine'); } getIssueRequestById(id: number): Observable { return this.http.get(`/sponsordd/issue-requests/${id}`); } approveIssueRequest(id: number): Observable { return this.http.post(`/sponsordd/issue-requests/${id}/approve`, {}); } rejectIssueRequest(id: number, rejectionReason: string): Observable { return this.http.post(`/sponsordd/issue-requests/${id}/reject`, { rejectionReason }); } // ── DD reprint requests (maker-checker) ── recordPrint(leafletId: number): Observable { return this.http.post(`/sponsordd/leaflets/${leafletId}/print`, {}); } requestReprint(leafletId: number, reason: string): Observable { return this.http.post(`/sponsordd/leaflets/${leafletId}/reprint-request`, { reason }); } getAllReprintRequests(status?: string): Observable { const params = status ? { status } : {}; return this.http.get('/sponsordd/reprint-requests', { params }); } approveReprintRequest(id: number): Observable { return this.http.post(`/sponsordd/reprint-requests/${id}/approve`, {}); } rejectReprintRequest(id: number, rejectionReason: string): Observable { return this.http.post(`/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 { return this.http.get(`/${SponsorDDService.SIGNATURE_ENTITY_TYPE}/${userId}/documents`); } uploadSignature(userId: number, file: File): Observable { 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(`/${SponsorDDService.SIGNATURE_ENTITY_TYPE}/${userId}/documents`, formData); } deleteSignature(userId: number, documentId: number): Observable { return this.http.delete(`/${SponsorDDService.SIGNATURE_ENTITY_TYPE}/${userId}/documents/${documentId}`); } getSignatureBlob(userId: number, documentId: number): Observable { 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 { 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 src or print HTML. */ getSignatureDataUri(userId: number): Observable { return this.findSignatureDocument(userId).pipe( switchMap((doc) => { if (!doc) { return of(null); } return this.getSignatureBlob(userId, doc.id).pipe( switchMap( (blob) => new Observable((subscriber) => { const reader = new FileReader(); reader.onload = () => { subscriber.next(reader.result as string); subscriber.complete(); }; reader.onerror = () => subscriber.error(reader.error); reader.readAsDataURL(blob); }) ) ); }) ); } }