DDS: Agent settlement (Agent Tran), Close Party account UI, reports UI, Party-Savings link field

This commit is contained in:
tarunmalviya
2026-08-25 10:56:03 +05:30
parent 7ef75c902d
commit 57a61b3057
78 changed files with 12711 additions and 2 deletions
@@ -0,0 +1,186 @@
/**
* 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/.
*/
/** Angular Imports */
import { Component, OnInit, inject } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/** Custom Services */
import { OrganizationService } from 'app/organization/organization.service';
import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
/**
* DDS Interest & Commission Batch Processing component.
*
* Six batch job types, each following the same deliberate two-step
* pattern from the legacy system: Calculate first (a dry-run preview),
* then Post as a separate, explicit confirmation — so a wrong calculation
* is never applied by accident. Modelled on the legacy Interest
* Calculation screen this replaces.
*
* Backed by the DDS Batch Run API (`/dds/batchruns`). Only the three
* "Calculation" job types can actually be generated - the three "Posting"
* entries exist in the dropdown for parity with the legacy menu, but Post
* always applies whatever run was most recently generated in this session,
* regardless of which dropdown entry is currently selected (matching how
* the Post button here is only ever gated on hasGenerated, not on jobType).
*/
@Component({
selector: 'mifosx-dds-batch-processing',
templateUrl: './dds-batch-processing.component.html',
styleUrls: ['./dds-batch-processing.component.scss'],
imports: [...STANDALONE_SHARED_IMPORTS]
})
export class DdsBatchProcessingComponent implements OnInit {
private formBuilder = inject(UntypedFormBuilder);
private router = inject(Router);
private organizationService = inject(OrganizationService);
private settingsService = inject(SettingsService);
private dateUtils = inject(Dates);
private static readonly JOB_TYPE_CODES: Record<string, string> = {
'Interest Calculation': 'INTEREST_CALCULATION',
'OD Interest Calculation': 'OD_INTEREST_CALCULATION',
'Commission Calculation': 'COMMISSION_CALCULATION'
};
private currentBatchRunId: number | null = null;
offices: any[] = [];
selectedOfficeNames = new Set<string>();
jobTypes = [
'Interest Calculation',
'Interest Posting',
'OD Interest Calculation',
'OD Interest Posting',
'Commission Calculation',
'Commission Posting'
];
branchScopes = [
'All',
'Selected'
];
hasGenerated = false;
previewRows: Array<{ agent: string; party: string; amount: number }> = [];
batchForm: UntypedFormGroup = this.formBuilder.group({
jobType: [
'Interest Calculation',
Validators.required
],
branchScope: [
'All',
Validators.required
],
jobDate: [
new Date(),
Validators.required
],
agentFrom: [
1,
Validators.required
],
agentTo: [
99999,
Validators.required
],
partyFrom: [
1,
Validators.required
],
partyTo: [
99999999,
Validators.required
]
});
get isPostingJob(): boolean {
return (this.batchForm.get('jobType').value as string).includes('Posting');
}
get isBranchSelected(): boolean {
return this.batchForm.get('branchScope')?.value === 'Selected';
}
ngOnInit(): void {
this.organizationService.getOffices().subscribe((offices: any[]) => {
this.offices = offices || [];
});
}
toggleOffice(officeName: string): void {
if (this.selectedOfficeNames.has(officeName)) {
this.selectedOfficeNames.delete(officeName);
} else {
this.selectedOfficeNames.add(officeName);
}
}
generate(): void {
const raw = this.batchForm.getRawValue();
const jobType = DdsBatchProcessingComponent.JOB_TYPE_CODES[raw.jobType];
if (!jobType) {
// A "Posting" entry was selected - there is nothing to calculate for
// it, only to post (see class-level note on how Post is wired).
this.hasGenerated = true;
this.previewRows = [];
return;
}
const dateFormat = this.settingsService.dateFormat;
const payload: any = {
jobType,
branchScope: raw.branchScope,
jobDate: this.dateUtils.formatDate(raw.jobDate, dateFormat),
agentFrom: raw.agentFrom,
agentTo: raw.agentTo,
partyFrom: raw.partyFrom,
partyTo: raw.partyTo,
dateFormat,
locale: this.settingsService.language.code
};
if (raw.branchScope === 'Selected') {
payload.officeNames = Array.from(this.selectedOfficeNames);
}
this.organizationService.generateDdsBatchRun(payload).subscribe((result: any) => {
this.currentBatchRunId = result.resourceId;
this.organizationService.getDdsBatchRun(this.currentBatchRunId).subscribe((batchRun: any) => {
this.previewRows = (batchRun.rows || []).map((row: any) => ({
agent: row.agentCode,
party: row.partyCode || '—',
amount: row.amount
}));
this.hasGenerated = true;
});
});
}
post(): void {
if (!this.currentBatchRunId) {
return;
}
this.organizationService.postDdsBatchRun(this.currentBatchRunId).subscribe(() => {
this.currentBatchRunId = null;
this.hasGenerated = false;
this.previewRows = [];
});
}
exit(): void {
this.router.navigate(['/organization']);
}
}