/** * 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 { Component, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { SponsorDDService } from '../sponsor-dd.service'; @Component({ selector: 'mifosx-gl-settings-dialog', standalone: true, imports: [ CommonModule, FormsModule, ReactiveFormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatButtonModule, MatIconModule, MatSnackBarModule ], template: `

Sponsor DD GST & GL Mapping Configuration

Configure the one-time General Ledger (GL) accounts for Demand Draft creation. When a walk-in or bank customer creates a DD, the system will automatically route the face value, charges, and GST to these mapped accounts.

account_balance
DD Payable is now set per sponsor bank.
Each bank credits its own payable account, so what is owed to each can be settled separately. Set it in Organization > Sponsor Banks.
DD Charges / Commission Income GL * {{ acc.glCode }} · {{ acc.name }} Credited for DD Issuance Charges/Commission Required GST Payable on Charges GL * {{ acc.glCode }} · {{ acc.name }} Credited for GST collected on charges Required Cash at Tellers / Vault GL Account * {{ acc.glCode }} · {{ acc.name }} Debited when cash is received from walk-in customer Required Cheque Clearing Suspense GL Account -- None -- {{ acc.glCode }} · {{ acc.name }} Debited when DD is paid via cheque
GST Percentage (%) * Required 0 - 100% DD Limit Amount (₹) Max limit per draft/day Cannot be negative

Loading GL accounts and settings...

`, styles: [ ` .dialog-form { display: flex; flex-direction: column; gap: 12px; padding-top: 8px; } .payable-moved-note { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 18px; padding: 11px 14px; background: #eff6ff; border: 1px solid #bfdbfe; border-left: 3px solid #1d4ed8; border-radius: 6px; font-size: 13px; color: #334155; } .payable-moved-note mat-icon { color: #1d4ed8; flex-shrink: 0; } .payable-moved-sub { margin-top: 3px; font-size: 11.5px; line-height: 1.5; color: #64748b; } .full-width { width: 100%; } .row-two-fields { display: flex; gap: 16px; } .half-width { flex: 1; } .hint-text { color: #64748b; font-size: 13px; margin: 0 0 16px 0; line-height: 1.4; } .loading-state { padding: 24px; text-align: center; color: #64748b; } ` ] }) export class GlSettingsDialogComponent implements OnInit { private fb = inject(FormBuilder); private http = inject(HttpClient); private sponsorDDService = inject(SponsorDDService); private dialogRef = inject(MatDialogRef); private snackBar = inject(MatSnackBar); form!: FormGroup; loading = true; saving = false; mapping: any = null; glAccounts: any[] = []; ngOnInit(): void { this.form = this.fb.group({ // No longer edited here — it lives on the sponsor bank. Kept in the form, unvalidated, so // saving this screen round-trips the stored value instead of clearing it: existing banks were // seeded from it on upgrade, and nothing should quietly erase what they were seeded from. ddPayableGlAccountId: [null], chargesIncomeGlAccountId: [ null, [Validators.required] ], gstPayableGlAccountId: [ null, [Validators.required] ], cashGlAccountId: [ null, [Validators.required] ], chequeClearingGlAccountId: [null], gstPercentage: [ 18, [ Validators.required, Validators.min(0), Validators.max(100) ] ], ddLimitAmount: [ null, [Validators.min(0)] ] }); this.loadGlAccountsAndMapping(); } loadGlAccountsAndMapping(): void { this.loading = true; // usage=1 (Detail only): these settings map real Sponsor DD transactions onto a GL account, and // a Header account can never receive one, so it must never be offered here. this.http.get('/glaccounts?manualEntriesAllowed=true&disabled=false&usage=1').subscribe({ next: (accounts) => { this.glAccounts = accounts || []; this.sponsorDDService.getGlMappings().subscribe({ next: (mappings) => { this.mapping = mappings && mappings.length ? mappings[0] : null; if (this.mapping) { this.form.patchValue({ ddPayableGlAccountId: this.mapping.ddPayableGlAccountId ?? null, chargesIncomeGlAccountId: this.mapping.chargesIncomeGlAccountId ?? null, gstPayableGlAccountId: this.mapping.gstPayableGlAccountId ?? null, cashGlAccountId: this.mapping.cashGlAccountId ?? null, chequeClearingGlAccountId: this.mapping.chequeClearingGlAccountId ?? null, gstPercentage: this.mapping.gstPercentage ?? 18, ddLimitAmount: this.mapping.ddLimitAmount ?? null }); } this.loading = false; }, error: (err) => { this.loading = false; this.snackBar.open('Error loading GL mapping: ' + (err.error?.message || err.message), 'Close', { duration: 5000 }); } }); }, error: (err) => { this.loading = false; this.snackBar.open('Error loading GL accounts: ' + (err.error?.message || err.message), 'Close', { duration: 5000 }); } }); } onSave(): void { if (this.form.invalid) return; this.saving = true; const val = this.form.value; this.sponsorDDService .upsertGlMapping({ sponsorBankId: 0, // DD Payable is set per sponsor bank (Organization > Sponsor Banks) and is the only source // DD posting reads, so this screen no longer owns it. We still round-trip whatever the row // already had -- so an existing value is preserved -- but a tenant that never set it (NULL) // now saves fine instead of hitting "Sponsor DD Payable GL account is required". ddPayableGlAccountId: this.mapping?.ddPayableGlAccountId ?? null, chequeClearingGlAccountId: val.chequeClearingGlAccountId, cashGlAccountId: val.cashGlAccountId, chargesIncomeGlAccountId: val.chargesIncomeGlAccountId, cashSuspenseGlAccountId: this.mapping?.cashSuspenseGlAccountId || val.cashGlAccountId, gstPercentage: val.gstPercentage, gstPayableGlAccountId: val.gstPayableGlAccountId, ddLimitAmount: val.ddLimitAmount }) .subscribe({ next: (res: any) => { // A save submitted for approval comes back HTTP 200 with rollbackTransaction: true - the // mapping has not actually been applied yet, so this must not claim success the same way an // immediate save does. const message = res?.rollbackTransaction ? 'Sponsor DD GL Mapping & GST Settings submitted for approval' : 'Sponsor DD GL Mapping & GST Settings saved successfully!'; this.snackBar.open(message, 'Close', { duration: res?.rollbackTransaction ? 4000 : 3000 }); this.dialogRef.close(true); }, error: (err) => { this.saving = false; this.snackBar.open('Error saving GL settings: ' + (err.error?.message || err.message), 'Close', { duration: 5000 }); } }); } onClose(): void { this.dialogRef.close(); } }