fix(accounting,deposits): allow future dates in premature close and GL search, fix FD/RD accounting mappings

This commit is contained in:
2026-10-04 16:02:15 +05:30
parent 9f7d9d28fd
commit 174a2e7b6b
4 changed files with 119 additions and 12 deletions
@@ -279,7 +279,7 @@
<div class="gl-fgroup"> <div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> From Date</label> <label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> From Date</label>
<mat-form-field appearance="outline" class="gl-ffield"> <mat-form-field appearance="outline" class="gl-ffield">
<input matInput [matDatepicker]="dpFrom" [min]="minDate" [max]="maxDate" [formControl]="transactionDateFrom" /> <input matInput [matDatepicker]="dpFrom" [min]="minDate" [formControl]="transactionDateFrom" />
<mat-datepicker-toggle matSuffix [for]="dpFrom"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="dpFrom"></mat-datepicker-toggle>
<mat-datepicker #dpFrom></mat-datepicker> <mat-datepicker #dpFrom></mat-datepicker>
</mat-form-field> </mat-form-field>
@@ -288,7 +288,7 @@
<div class="gl-fgroup"> <div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> To Date</label> <label class="gl-flabel"><fa-icon icon="calendar-alt"></fa-icon> To Date</label>
<mat-form-field appearance="outline" class="gl-ffield"> <mat-form-field appearance="outline" class="gl-ffield">
<input matInput [matDatepicker]="dpTo" [min]="minDate" [max]="maxDate" [formControl]="transactionDateTo" /> <input matInput [matDatepicker]="dpTo" [min]="minDate" [formControl]="transactionDateTo" />
<mat-datepicker-toggle matSuffix [for]="dpTo"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="dpTo"></mat-datepicker-toggle>
<mat-datepicker #dpTo></mat-datepicker> <mat-datepicker #dpTo></mat-datepicker>
</mat-form-field> </mat-form-field>
@@ -357,7 +357,6 @@
matInput matInput
[matDatepicker]="dpSubFrom" [matDatepicker]="dpSubFrom"
[min]="minDate" [min]="minDate"
[max]="maxDate"
[formControl]="submittedOnDateFrom" [formControl]="submittedOnDateFrom"
/> />
<mat-datepicker-toggle matSuffix [for]="dpSubFrom"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="dpSubFrom"></mat-datepicker-toggle>
@@ -368,7 +367,7 @@
<div class="gl-fgroup"> <div class="gl-fgroup">
<label class="gl-flabel"><fa-icon icon="calendar-check"></fa-icon> Submitted To</label> <label class="gl-flabel"><fa-icon icon="calendar-check"></fa-icon> Submitted To</label>
<mat-form-field appearance="outline" class="gl-ffield"> <mat-form-field appearance="outline" class="gl-ffield">
<input matInput [matDatepicker]="dpSubTo" [min]="minDate" [max]="maxDate" [formControl]="submittedOnDateTo" /> <input matInput [matDatepicker]="dpSubTo" [min]="minDate" [formControl]="submittedOnDateTo" />
<mat-datepicker-toggle matSuffix [for]="dpSubTo"></mat-datepicker-toggle> <mat-datepicker-toggle matSuffix [for]="dpSubTo"></mat-datepicker-toggle>
<mat-datepicker #dpSubTo></mat-datepicker> <mat-datepicker #dpSubTo></mat-datepicker>
</mat-form-field> </mat-form-field>
@@ -90,7 +90,7 @@ export class SearchJournalEntryComponent implements OnInit, OnDestroy, AfterView
// ── Misc ────────────────────────────────────────────────────────────────── // ── Misc ──────────────────────────────────────────────────────────────────
minDate = new Date(2000, 0, 1); minDate = new Date(2000, 0, 1);
maxDate = new Date(); maxDate: Date = null;
/** Accounting features switched off for this installation — see disabled-features.ts. */ /** Accounting features switched off for this installation — see disabled-features.ts. */
disabledFeatures = DISABLED_ACCOUNTING_FEATURES; disabledFeatures = DISABLED_ACCOUNTING_FEATURES;
@@ -208,7 +208,7 @@ export class SearchJournalEntryComponent implements OnInit, OnDestroy, AfterView
} }
ngOnInit() { ngOnInit() {
this.maxDate = this.settingsService.businessDate; this.maxDate = null;
this.paginator.pageSize = 10; this.paginator.pageSize = 10;
this.restoreState(); this.restoreState();
this.setFilteredOffices(); this.setFilteredOffices();
@@ -24,6 +24,10 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income'; type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income';
const SAVINGS_INTEREST_PAYABLE_ACCOUNT_NAME = 'Savings Interest Payable';
const FIXED_DEPOSIT_INTEREST_PAID_ACCOUNT_NAME = 'Fixed Deposit Interest Paid';
const SAVINGS_INTEREST_PAID_ACCOUNT_NAME = 'Savings Interest Paid';
type AccountingRowConfig = { type AccountingRowConfig = {
key: string; key: string;
label: string; label: string;
@@ -128,6 +132,7 @@ export class FixedDepositProductAccountingStepComponent implements OnInit, OnCha
feesReceivableAccountId: [''], feesReceivableAccountId: [''],
penaltiesReceivableAccountId: [''], penaltiesReceivableAccountId: [''],
interestPayableAccountId: [''], interestPayableAccountId: [''],
savingsInterestPaidAccountId: [''],
escheatLiabilityId: [''], escheatLiabilityId: [''],
assetsOtherGlMappings: this.formBuilder.array([]), assetsOtherGlMappings: this.formBuilder.array([]),
liabilitiesOtherGlMappings: this.formBuilder.array([]), liabilitiesOtherGlMappings: this.formBuilder.array([]),
@@ -158,6 +163,20 @@ export class FixedDepositProductAccountingStepComponent implements OnInit, OnCha
return ''; return '';
} }
private extractNamedGlMapping(
otherGlMappings: any[],
accountName: string,
controlName: string,
options: GLAccount[]
): any[] {
const match = (otherGlMappings || []).find((mapping) => mapping?.accountName === accountName);
if (match) {
const glAccountId = match.glAccountId || options.find((option) => option.glCode === match.glCode)?.id || '';
this.fixedDepositProductAccountingForm.get(controlName)?.patchValue(glAccountId);
}
return (otherGlMappings || []).filter((mapping) => mapping?.accountName !== accountName);
}
private patchAccountingValues(mappings: any) { private patchAccountingValues(mappings: any) {
this.fixedDepositProductAccountingForm.patchValue({ this.fixedDepositProductAccountingForm.patchValue({
savingsReferenceAccountId: this.extractAccountId( savingsReferenceAccountId: this.extractAccountId(
@@ -200,7 +219,18 @@ export class FixedDepositProductAccountingStepComponent implements OnInit, OnCha
glMappings?.liabilities || [], glMappings?.liabilities || [],
this.liabilityAccountOptions this.liabilityAccountOptions
); );
this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions); const fromExpenses = this.extractNamedGlMapping(
this.extractNamedGlMapping(
glMappings?.expenses || [],
FIXED_DEPOSIT_INTEREST_PAID_ACCOUNT_NAME,
'savingsInterestPaidAccountId',
this.expenseAccountOptions
),
SAVINGS_INTEREST_PAID_ACCOUNT_NAME,
'savingsInterestPaidAccountId',
this.expenseAccountOptions
);
this.setOtherGlMappings('expensesOtherGlMappings', fromExpenses, this.expenseAccountOptions);
this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions); this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions);
} }
} }
@@ -266,10 +296,26 @@ export class FixedDepositProductAccountingStepComponent implements OnInit, OnCha
})) }))
.filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode); .filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode);
const namedAsOtherGlMapping = (controlName: string, accountName: string, options: GLAccount[]) => {
const glAccountId = this.fixedDepositProductAccountingForm.get(controlName)?.value;
if (!glAccountId) {
return [];
}
const glCode = options.find((o) => o.id === glAccountId)?.glCode || '';
return glCode ? [{ accountName, glCode }] : [];
};
const otherGlMappings = { const otherGlMappings = {
assets: map('assetsOtherGlMappings', this.assetAccountOptions), assets: map('assetsOtherGlMappings', this.assetAccountOptions),
liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions), liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions),
expenses: map('expensesOtherGlMappings', this.expenseAccountOptions), expenses: [
...namedAsOtherGlMapping(
'savingsInterestPaidAccountId',
FIXED_DEPOSIT_INTEREST_PAID_ACCOUNT_NAME,
this.expenseAccountOptions
),
...map('expensesOtherGlMappings', this.expenseAccountOptions)
],
income: map('incomeOtherGlMappings', this.incomeAccountOptions) income: map('incomeOtherGlMappings', this.incomeAccountOptions)
}; };
return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null; return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null;
@@ -416,6 +462,13 @@ export class FixedDepositProductAccountingStepComponent implements OnInit, OnCha
controlName: 'interestOnSavingsAccountId', controlName: 'interestOnSavingsAccountId',
options: this.expenseAccountOptions, options: this.expenseAccountOptions,
required: true required: true
},
{
key: 'savingsInterestPaidAccountId',
label: 'Fixed Deposit Interest Paid',
inputLabel: 'Fixed deposit interest paid',
controlName: 'savingsInterestPaidAccountId',
options: this.expenseAccountOptions
} }
], ],
otherMappingsControlName: 'expensesOtherGlMappings', otherMappingsControlName: 'expensesOtherGlMappings',
@@ -456,6 +509,7 @@ export class FixedDepositProductAccountingStepComponent implements OnInit, OnCha
liabilitiesOtherGlMappings, liabilitiesOtherGlMappings,
expensesOtherGlMappings, expensesOtherGlMappings,
incomeOtherGlMappings, incomeOtherGlMappings,
savingsInterestPaidAccountId,
...accounting ...accounting
} = this.fixedDepositProductAccountingForm.value; } = this.fixedDepositProductAccountingForm.value;
const otherGlMappings = this.getOtherGlMappings(); const otherGlMappings = this.getOtherGlMappings();
@@ -24,6 +24,10 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income'; type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income';
const SAVINGS_INTEREST_PAYABLE_ACCOUNT_NAME = 'Savings Interest Payable';
const RECURRING_DEPOSIT_INTEREST_PAID_ACCOUNT_NAME = 'Recurring Deposit Interest Paid';
const SAVINGS_INTEREST_PAID_ACCOUNT_NAME = 'Savings Interest Paid';
type AccountingRowConfig = { type AccountingRowConfig = {
key: string; key: string;
label: string; label: string;
@@ -122,6 +126,7 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
feesReceivableAccountId: [''], feesReceivableAccountId: [''],
penaltiesReceivableAccountId: [''], penaltiesReceivableAccountId: [''],
interestPayableAccountId: [''], interestPayableAccountId: [''],
savingsInterestPaidAccountId: [''],
escheatLiabilityId: [''], escheatLiabilityId: [''],
assetsOtherGlMappings: this.formBuilder.array([]), assetsOtherGlMappings: this.formBuilder.array([]),
liabilitiesOtherGlMappings: this.formBuilder.array([]), liabilitiesOtherGlMappings: this.formBuilder.array([]),
@@ -136,6 +141,20 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
}); });
} }
private extractNamedGlMapping(
otherGlMappings: any[],
accountName: string,
controlName: string,
options: GLAccount[]
): any[] {
const match = (otherGlMappings || []).find((mapping) => mapping?.accountName === accountName);
if (match) {
const glAccountId = match.glAccountId || options.find((option) => option.glCode === match.glCode)?.id || '';
this.recurringDepositProductAccountingForm.get(controlName)?.patchValue(glAccountId);
}
return (otherGlMappings || []).filter((mapping) => mapping?.accountName !== accountName);
}
private patchAccountingValues(mappings: any) { private patchAccountingValues(mappings: any) {
this.recurringDepositProductAccountingForm.patchValue({ this.recurringDepositProductAccountingForm.patchValue({
savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '', savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '',
@@ -158,7 +177,18 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
glMappings?.liabilities || [], glMappings?.liabilities || [],
this.liabilityAccountOptions this.liabilityAccountOptions
); );
this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions); const fromExpenses = this.extractNamedGlMapping(
this.extractNamedGlMapping(
glMappings?.expenses || [],
RECURRING_DEPOSIT_INTEREST_PAID_ACCOUNT_NAME,
'savingsInterestPaidAccountId',
this.expenseAccountOptions
),
SAVINGS_INTEREST_PAID_ACCOUNT_NAME,
'savingsInterestPaidAccountId',
this.expenseAccountOptions
);
this.setOtherGlMappings('expensesOtherGlMappings', fromExpenses, this.expenseAccountOptions);
this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions); this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions);
} }
} }
@@ -224,10 +254,26 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
})) }))
.filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode); .filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode);
const namedAsOtherGlMapping = (controlName: string, accountName: string, options: GLAccount[]) => {
const glAccountId = this.recurringDepositProductAccountingForm.get(controlName)?.value;
if (!glAccountId) {
return [];
}
const glCode = options.find((o) => o.id === glAccountId)?.glCode || '';
return glCode ? [{ accountName, glCode }] : [];
};
const otherGlMappings = { const otherGlMappings = {
assets: map('assetsOtherGlMappings', this.assetAccountOptions), assets: map('assetsOtherGlMappings', this.assetAccountOptions),
liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions), liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions),
expenses: map('expensesOtherGlMappings', this.expenseAccountOptions), expenses: [
...namedAsOtherGlMapping(
'savingsInterestPaidAccountId',
RECURRING_DEPOSIT_INTEREST_PAID_ACCOUNT_NAME,
this.expenseAccountOptions
),
...map('expensesOtherGlMappings', this.expenseAccountOptions)
],
income: map('incomeOtherGlMappings', this.incomeAccountOptions) income: map('incomeOtherGlMappings', this.incomeAccountOptions)
}; };
return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null; return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null;
@@ -360,11 +406,18 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
rows: [ rows: [
{ {
key: 'interestOnSavingsAccountId', key: 'interestOnSavingsAccountId',
label: 'Interest On Savings Account', label: 'Interest On Recurring Deposits',
inputLabel: 'Interest on savings account', inputLabel: 'Interest on recurring deposits',
controlName: 'interestOnSavingsAccountId', controlName: 'interestOnSavingsAccountId',
options: this.expenseAccountOptions, options: this.expenseAccountOptions,
required: true required: true
},
{
key: 'savingsInterestPaidAccountId',
label: 'Recurring Deposit Interest Paid',
inputLabel: 'Recurring deposit interest paid',
controlName: 'savingsInterestPaidAccountId',
options: this.expenseAccountOptions
} }
], ],
otherMappingsControlName: 'expensesOtherGlMappings', otherMappingsControlName: 'expensesOtherGlMappings',
@@ -405,6 +458,7 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
liabilitiesOtherGlMappings, liabilitiesOtherGlMappings,
expensesOtherGlMappings, expensesOtherGlMappings,
incomeOtherGlMappings, incomeOtherGlMappings,
savingsInterestPaidAccountId,
...accounting ...accounting
} = this.recurringDepositProductAccountingForm.value; } = this.recurringDepositProductAccountingForm.value;
const otherGlMappings = this.getOtherGlMappings(); const otherGlMappings = this.getOtherGlMappings();