DDS: Agent settlement (Agent Tran), Close Party account UI, reports UI, Party-Savings link field
This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="am-container">
|
||||
<div class="am-header">
|
||||
<button mat-stroked-button [routerLink]="['../']" class="am-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
|
||||
</button>
|
||||
<h2 class="am-header__title">Agent Master Entry</h2>
|
||||
</div>
|
||||
|
||||
<div class="am-card">
|
||||
<form [formGroup]="agentForm">
|
||||
<div class="am-section">
|
||||
<div class="am-section__header">
|
||||
<i class="fas fa-id-card"></i>
|
||||
<h3>Agent Details</h3>
|
||||
</div>
|
||||
<div class="am-grid">
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Client ID</mat-label>
|
||||
<input matInput required formControlName="clientId" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Code</mat-label>
|
||||
<input matInput formControlName="agentCode" placeholder="Auto-generated" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field am-field--wide">
|
||||
<mat-label>Agent Name ID</mat-label>
|
||||
<input matInput required formControlName="agentNameId" placeholder="Person or institution name" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field am-field--wide">
|
||||
<mat-label>Agent Address (Line 1)</mat-label>
|
||||
<input matInput required formControlName="agentAddress1" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field am-field--wide">
|
||||
<mat-label>Agent Address (Line 2)</mat-label>
|
||||
<input matInput formControlName="agentAddress2" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent City</mat-label>
|
||||
<input matInput required formControlName="agentCity" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent PinCode</mat-label>
|
||||
<input matInput required formControlName="agentPinCode" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Telephone</mat-label>
|
||||
<input matInput formControlName="agentTelephone" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Home Branch</mat-label>
|
||||
<input matInput required formControlName="homeBranch" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="am-section">
|
||||
<div class="am-section__header">
|
||||
<i class="fas fa-percent"></i>
|
||||
<h3>Collection & Commission Settings</h3>
|
||||
</div>
|
||||
<div class="am-grid">
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Com. Calculation</mat-label>
|
||||
<mat-select required formControlName="commissionCalculation">
|
||||
<mat-option value="Yes">Yes</mat-option>
|
||||
<mat-option value="No">No</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>TDS</mat-label>
|
||||
<mat-select required formControlName="tdsApplicable">
|
||||
<mat-option value="Yes">Yes</mat-option>
|
||||
<mat-option value="No">No</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>TDS %</mat-label>
|
||||
<input matInput type="number" formControlName="tdsPercent" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Cur.TDS</mat-label>
|
||||
<input matInput formControlName="currentTds" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>TDS Amt</mat-label>
|
||||
<input matInput formControlName="tdsAmount" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Limit</mat-label>
|
||||
<input matInput type="number" formControlName="limit" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Machine Rent</mat-label>
|
||||
<input matInput type="number" formControlName="machineRent" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="am-section">
|
||||
<div class="am-section__header">
|
||||
<i class="fas fa-piggy-bank"></i>
|
||||
<h3>Settlement & Security Accounts</h3>
|
||||
</div>
|
||||
<div class="am-grid">
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Saving Branch</mat-label>
|
||||
<input matInput required formControlName="savingBranch" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Saving A/C Type</mat-label>
|
||||
<mat-select required formControlName="agentSavingAccountType">
|
||||
@for (accountType of agentSavingAccountTypes; track accountType) {
|
||||
<mat-option [value]="accountType">{{ accountType }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Saving A/C No</mat-label>
|
||||
<input matInput required formControlName="agentSavingAccountNo" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Trf Branch</mat-label>
|
||||
<input matInput formControlName="transferBranch" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Trf.Amt A/C Type</mat-label>
|
||||
<input matInput formControlName="transferAmountAccountType" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Trf.Amt A/C No</mat-label>
|
||||
<input matInput formControlName="transferAccountNo" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Security Branch</mat-label>
|
||||
<input matInput formControlName="agentSecurityBranch" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Security A/C Type</mat-label>
|
||||
<mat-select formControlName="agentSecurityAccountType">
|
||||
@for (accountType of agentSavingAccountTypes; track accountType) {
|
||||
<mat-option [value]="accountType">{{ accountType }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Security A/C No</mat-label>
|
||||
<input matInput formControlName="agentSecurityAccountNo" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Opening Date</mat-label>
|
||||
<input matInput required [matDatepicker]="openingDatePicker" formControlName="agentOpeningDate" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="openingDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #openingDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Birth Date</mat-label>
|
||||
<input
|
||||
matInput
|
||||
required
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="birthDatePicker"
|
||||
formControlName="agentBirthDate"
|
||||
/>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="birthDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #birthDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Current Balance</mat-label>
|
||||
<input matInput formControlName="agentCurrentBalance" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Agent Opening Balance</mat-label>
|
||||
<input matInput formControlName="agentOpeningBalance" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="am-section">
|
||||
<div class="am-section__header">
|
||||
<i class="fas fa-clock"></i>
|
||||
<h3>Collection Limit History</h3>
|
||||
<button mat-stroked-button type="button" class="am-add-guarantor" (click)="addLimit()">
|
||||
<i class="fas fa-plus"></i> Add Limit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (limits.length === 0) {
|
||||
<p class="am-empty-guarantors">No limit periods added yet.</p>
|
||||
}
|
||||
|
||||
@for (limitRow of limits.controls; track limitRow; let i = $index) {
|
||||
<div class="am-limit-row" [formGroup]="$any(limitRow)">
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>From Date</mat-label>
|
||||
<input matInput required [matDatepicker]="fromPicker" formControlName="fromDate" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="fromPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #fromPicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>To Date</mat-label>
|
||||
<input matInput required [matDatepicker]="toPicker" formControlName="toDate" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="toPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #toPicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Limit</mat-label>
|
||||
<input matInput required type="number" formControlName="limit" />
|
||||
</mat-form-field>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="am-remove-guarantor"
|
||||
(click)="removeLimit(i)"
|
||||
matTooltip="Remove limit period"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="am-section">
|
||||
<div class="am-section__header">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
<h3>Guarantor's Detail</h3>
|
||||
<button mat-stroked-button type="button" class="am-add-guarantor" (click)="addGuarantor()">
|
||||
<i class="fas fa-plus"></i> Add Guarantor
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (guarantors.length === 0) {
|
||||
<p class="am-empty-guarantors">No guarantors added yet.</p>
|
||||
}
|
||||
|
||||
@for (guarantor of guarantors.controls; track guarantor; let i = $index) {
|
||||
<div class="am-guarantor-row" [formGroup]="$any(guarantor)">
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Client ID</mat-label>
|
||||
<input matInput required formControlName="clientId" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Guarantor Name</mat-label>
|
||||
<input matInput required formControlName="guarantorName" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Address 1</mat-label>
|
||||
<input matInput required formControlName="address1" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Address 2</mat-label>
|
||||
<input matInput formControlName="address2" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>City</mat-label>
|
||||
<input matInput required formControlName="city" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Pin Code</mat-label>
|
||||
<input matInput required formControlName="pinCode" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Telephone</mat-label>
|
||||
<input matInput formControlName="telephone" />
|
||||
</mat-form-field>
|
||||
<button
|
||||
mat-icon-button
|
||||
type="button"
|
||||
class="am-remove-guarantor"
|
||||
(click)="removeGuarantor(i)"
|
||||
matTooltip="Remove guarantor"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="am-section">
|
||||
<div class="am-section__header">
|
||||
<i class="fas fa-wallet"></i>
|
||||
<h3>Deductions & Deposits</h3>
|
||||
</div>
|
||||
<div class="am-grid">
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Comm</mat-label>
|
||||
<input matInput formControlName="commission" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Other Comm</mat-label>
|
||||
<input matInput type="number" formControlName="otherCommission" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Ag.Deposit</mat-label>
|
||||
<input matInput type="number" formControlName="agentDeposit" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Adv.Festival</mat-label>
|
||||
<input matInput type="number" formControlName="festivalAdvance" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="am-field">
|
||||
<mat-label>Other Deduction</mat-label>
|
||||
<input matInput type="number" formControlName="otherDeduction" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="am-action-bar">
|
||||
<button mat-raised-button type="button" (click)="exit()">Exit</button>
|
||||
<button mat-stroked-button type="button" (click)="cancel()">Cancel</button>
|
||||
<button mat-stroked-button type="button" (click)="retrieve()">Retrieve</button>
|
||||
<button mat-raised-button color="primary" type="button" [disabled]="agentForm.invalid" (click)="save()">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$primary: #0f766e;
|
||||
|
||||
.am-container {
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.am-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.am-back {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.am-card {
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||
padding: 8px 24px 24px;
|
||||
}
|
||||
|
||||
.am-section {
|
||||
padding: 20px 0;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
i {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.am-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
.am-field {
|
||||
width: 100%;
|
||||
|
||||
&--wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
.am-add-guarantor {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.am-empty-guarantors {
|
||||
color: rgb(0 0 0 / 50%);
|
||||
font-size: 14px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.am-guarantor-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr) auto;
|
||||
gap: 8px 12px;
|
||||
align-items: start;
|
||||
padding: 10px 0;
|
||||
border-top: 1px dashed rgb(0 0 0 / 10%);
|
||||
|
||||
&:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.am-limit-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr) auto;
|
||||
gap: 8px 12px;
|
||||
align-items: start;
|
||||
padding: 10px 0;
|
||||
border-top: 1px dashed rgb(0 0 0 / 10%);
|
||||
|
||||
&:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.am-remove-guarantor {
|
||||
color: #b91c1c;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.am-action-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@media (width <= 900px) {
|
||||
.am-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.am-guarantor-row,
|
||||
.am-limit-row {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/**
|
||||
* 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, inject } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, UntypedFormArray, 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';
|
||||
|
||||
/**
|
||||
* Agent Master Entry component.
|
||||
*
|
||||
* A DDS collection agent — either a person or an institution — registered
|
||||
* with its own settlement savings account and one or more guarantors. As of
|
||||
* 2026-08-24 the Agent also does the daily collection work directly (the
|
||||
* SubAgent tier was removed): it carries the collection-limit history,
|
||||
* TDS/commission settings and security account SubAgent used to own.
|
||||
*
|
||||
* Backed by its own DDS Agent API (`/dds/agents`) - independent of Staff/
|
||||
* Client CRUD, per the DDS agent/commission hierarchy being genuinely new
|
||||
* feature work.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-agent-master',
|
||||
templateUrl: './agent-master.component.html',
|
||||
styleUrls: ['./agent-master.component.scss'],
|
||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
||||
})
|
||||
export class AgentMasterComponent {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private router = inject(Router);
|
||||
private organizationService = inject(OrganizationService);
|
||||
private settingsService = inject(SettingsService);
|
||||
private dateUtils = inject(Dates);
|
||||
|
||||
maxDate = new Date();
|
||||
agentId: number | null = null;
|
||||
|
||||
agentSavingAccountTypes = [
|
||||
'Savings Deposit Individual A/C',
|
||||
'Savings Deposit Joint A/C',
|
||||
'Current Account'
|
||||
];
|
||||
|
||||
agentForm: UntypedFormGroup = this.formBuilder.group({
|
||||
clientId: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentCode: [
|
||||
{
|
||||
value: '',
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
agentNameId: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentAddress1: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentAddress2: [''],
|
||||
agentCity: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentPinCode: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentTelephone: [''],
|
||||
agentSavingAccountType: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentSavingAccountNo: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentOpeningDate: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentBirthDate: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
agentCurrentBalance: [
|
||||
{
|
||||
value: 0,
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
agentOpeningBalance: [
|
||||
{
|
||||
value: 0,
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
homeBranch: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
tdsApplicable: [
|
||||
'No',
|
||||
Validators.required
|
||||
],
|
||||
tdsPercent: [0],
|
||||
currentTds: [
|
||||
{
|
||||
value: 0,
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
tdsAmount: [
|
||||
{
|
||||
value: 0,
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
savingBranch: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
transferBranch: [''],
|
||||
transferAmountAccountType: [''],
|
||||
transferAccountNo: [''],
|
||||
commissionCalculation: [
|
||||
'Yes',
|
||||
Validators.required
|
||||
],
|
||||
limit: [0],
|
||||
machineRent: [0],
|
||||
agentSecurityBranch: [''],
|
||||
agentSecurityAccountType: [''],
|
||||
agentSecurityAccountNo: [''],
|
||||
commission: [
|
||||
{
|
||||
value: 0,
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
otherCommission: [0],
|
||||
agentDeposit: [0],
|
||||
festivalAdvance: [0],
|
||||
otherDeduction: [0],
|
||||
limits: this.formBuilder.array([]),
|
||||
guarantors: this.formBuilder.array([])
|
||||
});
|
||||
|
||||
get limits(): UntypedFormArray {
|
||||
return this.agentForm.get('limits') as UntypedFormArray;
|
||||
}
|
||||
|
||||
get guarantors(): UntypedFormArray {
|
||||
return this.agentForm.get('guarantors') as UntypedFormArray;
|
||||
}
|
||||
|
||||
private buildLimitGroup(): UntypedFormGroup {
|
||||
return this.formBuilder.group({
|
||||
fromDate: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
toDate: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
limit: [
|
||||
0,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
private buildGuarantorGroup(): UntypedFormGroup {
|
||||
return this.formBuilder.group({
|
||||
clientId: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
guarantorName: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
address1: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
address2: [''],
|
||||
city: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
pinCode: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
telephone: ['']
|
||||
});
|
||||
}
|
||||
|
||||
addLimit(): void {
|
||||
this.limits.push(this.buildLimitGroup());
|
||||
}
|
||||
|
||||
removeLimit(index: number): void {
|
||||
this.limits.removeAt(index);
|
||||
}
|
||||
|
||||
addGuarantor(): void {
|
||||
this.guarantors.push(this.buildGuarantorGroup());
|
||||
}
|
||||
|
||||
removeGuarantor(index: number): void {
|
||||
this.guarantors.removeAt(index);
|
||||
}
|
||||
|
||||
retrieve(): void {
|
||||
const clientId = this.agentForm.get('clientId')?.value;
|
||||
if (!clientId) {
|
||||
return;
|
||||
}
|
||||
this.organizationService.getAgents().subscribe((agents: any[]) => {
|
||||
const match = (agents || []).find((agent) => String(agent.clientId) === String(clientId));
|
||||
if (match) {
|
||||
this.patchFormFromAgent(match);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.agentId = null;
|
||||
this.agentForm.reset();
|
||||
this.agentForm.patchValue({
|
||||
agentCurrentBalance: 0,
|
||||
agentOpeningBalance: 0,
|
||||
tdsApplicable: 'No',
|
||||
commissionCalculation: 'Yes',
|
||||
currentTds: 0,
|
||||
tdsAmount: 0,
|
||||
commission: 0
|
||||
});
|
||||
this.limits.clear();
|
||||
this.guarantors.clear();
|
||||
}
|
||||
|
||||
exit(): void {
|
||||
this.router.navigate(['/organization']);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const raw = this.agentForm.getRawValue();
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const locale = this.settingsService.language.code;
|
||||
|
||||
const payload: any = {
|
||||
clientId: Number(raw.clientId),
|
||||
agentNameId: raw.agentNameId,
|
||||
agentAddress1: raw.agentAddress1,
|
||||
agentAddress2: raw.agentAddress2 || undefined,
|
||||
agentCity: raw.agentCity,
|
||||
agentPinCode: raw.agentPinCode,
|
||||
agentTelephone: raw.agentTelephone || undefined,
|
||||
agentSavingAccountType: raw.agentSavingAccountType,
|
||||
agentSavingAccountNo: raw.agentSavingAccountNo,
|
||||
agentOpeningDate: this.dateUtils.formatDate(raw.agentOpeningDate, dateFormat),
|
||||
agentBirthDate: this.dateUtils.formatDate(raw.agentBirthDate, dateFormat),
|
||||
agentOpeningBalance: raw.agentOpeningBalance || 0,
|
||||
homeBranch: raw.homeBranch,
|
||||
tdsApplicable: raw.tdsApplicable === 'Yes',
|
||||
tdsPercent: raw.tdsPercent || undefined,
|
||||
savingBranch: raw.savingBranch,
|
||||
transferBranch: raw.transferBranch || undefined,
|
||||
transferAmountAccountType: raw.transferAmountAccountType || undefined,
|
||||
transferAccountNo: raw.transferAccountNo || undefined,
|
||||
commissionCalculation: raw.commissionCalculation === 'Yes',
|
||||
limit: raw.limit || undefined,
|
||||
machineRent: raw.machineRent || undefined,
|
||||
agentSecurityBranch: raw.agentSecurityBranch || undefined,
|
||||
agentSecurityAccountType: raw.agentSecurityAccountType || undefined,
|
||||
agentSecurityAccountNo: raw.agentSecurityAccountNo || undefined,
|
||||
otherCommission: raw.otherCommission || undefined,
|
||||
agentDeposit: raw.agentDeposit || undefined,
|
||||
festivalAdvance: raw.festivalAdvance || undefined,
|
||||
otherDeduction: raw.otherDeduction || undefined,
|
||||
dateFormat,
|
||||
locale,
|
||||
limits: (raw.limits || []).map((limit: any) => ({
|
||||
fromDate: this.dateUtils.getDate(limit.fromDate),
|
||||
toDate: this.dateUtils.getDate(limit.toDate),
|
||||
limit: limit.limit
|
||||
})),
|
||||
guarantors: (raw.guarantors || []).map((guarantor: any) => ({
|
||||
clientId: Number(guarantor.clientId),
|
||||
guarantorName: guarantor.guarantorName,
|
||||
address1: guarantor.address1,
|
||||
address2: guarantor.address2 || undefined,
|
||||
city: guarantor.city,
|
||||
pinCode: guarantor.pinCode,
|
||||
telephone: guarantor.telephone || undefined
|
||||
}))
|
||||
};
|
||||
|
||||
if (this.agentId) {
|
||||
this.organizationService.updateAgent(this.agentId, payload).subscribe(() => this.exit());
|
||||
} else {
|
||||
this.organizationService.createAgent(payload).subscribe(() => this.exit());
|
||||
}
|
||||
}
|
||||
|
||||
private patchFormFromAgent(agent: any): void {
|
||||
this.agentId = agent.id;
|
||||
this.agentForm.patchValue({
|
||||
clientId: agent.clientId,
|
||||
agentCode: agent.agentCode,
|
||||
agentNameId: agent.agentNameId,
|
||||
agentAddress1: agent.agentAddress1,
|
||||
agentAddress2: agent.agentAddress2,
|
||||
agentCity: agent.agentCity,
|
||||
agentPinCode: agent.agentPinCode,
|
||||
agentTelephone: agent.agentTelephone,
|
||||
agentSavingAccountType: agent.agentSavingAccountType,
|
||||
agentSavingAccountNo: agent.agentSavingAccountNo,
|
||||
agentOpeningDate: agent.agentOpeningDate ? new Date(agent.agentOpeningDate) : null,
|
||||
agentBirthDate: agent.agentBirthDate ? new Date(agent.agentBirthDate) : null,
|
||||
agentCurrentBalance: agent.agentCurrentBalance,
|
||||
agentOpeningBalance: agent.agentOpeningBalance,
|
||||
homeBranch: agent.homeBranch,
|
||||
tdsApplicable: agent.tdsApplicable ? 'Yes' : 'No',
|
||||
tdsPercent: agent.tdsPercent,
|
||||
currentTds: agent.currentTds,
|
||||
tdsAmount: agent.tdsAmount,
|
||||
savingBranch: agent.savingBranch,
|
||||
transferBranch: agent.transferBranch,
|
||||
transferAmountAccountType: agent.transferAmountAccountType,
|
||||
transferAccountNo: agent.transferAccountNo,
|
||||
commissionCalculation: agent.commissionCalculation ? 'Yes' : 'No',
|
||||
limit: agent.limit,
|
||||
machineRent: agent.machineRent,
|
||||
agentSecurityBranch: agent.agentSecurityBranch,
|
||||
agentSecurityAccountType: agent.agentSecurityAccountType,
|
||||
agentSecurityAccountNo: agent.agentSecurityAccountNo,
|
||||
commission: agent.commission,
|
||||
otherCommission: agent.otherCommission,
|
||||
agentDeposit: agent.agentDeposit,
|
||||
festivalAdvance: agent.festivalAdvance,
|
||||
otherDeduction: agent.otherDeduction
|
||||
});
|
||||
|
||||
this.limits.clear();
|
||||
(agent.limits || []).forEach((limit: any) => {
|
||||
const group = this.buildLimitGroup();
|
||||
group.patchValue({
|
||||
fromDate: limit.fromDate ? new Date(limit.fromDate) : null,
|
||||
toDate: limit.toDate ? new Date(limit.toDate) : null,
|
||||
limit: limit.limit
|
||||
});
|
||||
this.limits.push(group);
|
||||
});
|
||||
|
||||
this.guarantors.clear();
|
||||
(agent.guarantors || []).forEach((guarantor: any) => {
|
||||
const group = this.buildGuarantorGroup();
|
||||
group.patchValue(guarantor);
|
||||
this.guarantors.push(group);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="csm-container">
|
||||
<div class="csm-header">
|
||||
<button mat-stroked-button [routerLink]="['../']" class="csm-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
|
||||
</button>
|
||||
<h2 class="csm-header__title">Commission Slab Master</h2>
|
||||
</div>
|
||||
|
||||
<div class="csm-card">
|
||||
<table class="csm-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slab Type</th>
|
||||
<th>Slab From</th>
|
||||
<th>Slab To</th>
|
||||
<th>Comm Per</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (slab of slabs; track slab; let i = $index) {
|
||||
<tr>
|
||||
<td>{{ slab.slabType }}</td>
|
||||
<td>{{ slab.slabFrom }}</td>
|
||||
<td>{{ slab.slabTo }}</td>
|
||||
<td>{{ slab.commissionPercent }}%</td>
|
||||
<td class="csm-row-actions">
|
||||
<button mat-icon-button type="button" (click)="removeSlab(i)" matTooltip="Remove slab">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
<tr [formGroup]="newSlabForm" class="csm-new-row">
|
||||
<td>
|
||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
||||
<mat-select required formControlName="slabType" placeholder="Slab Type">
|
||||
@for (type of slabTypes; track type) {
|
||||
<mat-option [value]="type">{{ type }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
<td>
|
||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
||||
<input matInput required type="number" formControlName="slabFrom" />
|
||||
</mat-form-field>
|
||||
</td>
|
||||
<td>
|
||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
||||
<input matInput required type="number" formControlName="slabTo" />
|
||||
</mat-form-field>
|
||||
</td>
|
||||
<td>
|
||||
<mat-form-field appearance="outline" class="csm-inline-field">
|
||||
<input matInput required type="number" formControlName="commissionPercent" />
|
||||
</mat-form-field>
|
||||
</td>
|
||||
<td class="csm-row-actions">
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
type="button"
|
||||
[disabled]="newSlabForm.invalid"
|
||||
(click)="addSlab()"
|
||||
matTooltip="Add slab"
|
||||
>
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@if (slabs.length === 0) {
|
||||
<p class="csm-empty">No commission slabs defined yet — add one using the row above.</p>
|
||||
}
|
||||
|
||||
<div class="csm-action-bar">
|
||||
<button mat-raised-button type="button" (click)="exit()">Exit</button>
|
||||
<button mat-raised-button color="primary" type="button" (click)="save()">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$primary: #0f766e;
|
||||
|
||||
.csm-container {
|
||||
max-width: 760px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.csm-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.csm-back {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.csm-card {
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||
padding: 16px 24px 24px;
|
||||
}
|
||||
|
||||
.csm-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: rgb(0 0 0 / 55%);
|
||||
padding: 8px 10px;
|
||||
border-bottom: 2px solid rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.csm-inline-field {
|
||||
width: 100%;
|
||||
|
||||
::ng-deep .mat-mdc-text-field-wrapper {
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.csm-row-actions {
|
||||
text-align: center;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.csm-new-row {
|
||||
background: rgb(15 118 110 / 4%);
|
||||
}
|
||||
|
||||
.csm-empty {
|
||||
color: rgb(0 0 0 / 50%);
|
||||
font-size: 14px;
|
||||
margin: 12px 0 0;
|
||||
}
|
||||
|
||||
.csm-action-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* Commission Slab Master component.
|
||||
*
|
||||
* Two kinds of slab: ACCOUNT (commission percentage on collection volume)
|
||||
* and COMMISSION (a senior-agent override on another agent's commission -
|
||||
* deferred: there is no agent hierarchy modelled yet, see the DDS punch
|
||||
* list). Modelled directly on the legacy Commission Slab Master screen this
|
||||
* replaces.
|
||||
*
|
||||
* Backed by its own DDS Commission Slab API (`/dds/commissionslabs`) - an
|
||||
* append-only chart, same as the legacy screen: existing rows load on open,
|
||||
* newly-added rows post on Save, and there is no edit/delete of a
|
||||
* previously-saved row.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-commission-slab-master',
|
||||
templateUrl: './commission-slab-master.component.html',
|
||||
styleUrls: ['./commission-slab-master.component.scss'],
|
||||
imports: [...STANDALONE_SHARED_IMPORTS]
|
||||
})
|
||||
export class CommissionSlabMasterComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private router = inject(Router);
|
||||
private organizationService = inject(OrganizationService);
|
||||
|
||||
private static readonly SLAB_TYPE_CODES: Record<string, string> = {
|
||||
Account: 'ACCOUNT',
|
||||
Commission: 'COMMISSION'
|
||||
};
|
||||
private static readonly SLAB_TYPE_LABELS: Record<string, string> = {
|
||||
ACCOUNT: 'Account',
|
||||
COMMISSION: 'Commission'
|
||||
};
|
||||
|
||||
slabTypes = [
|
||||
'Account',
|
||||
'Commission'
|
||||
];
|
||||
|
||||
displayedColumns: string[] = [
|
||||
'slabType',
|
||||
'slabFrom',
|
||||
'slabTo',
|
||||
'commissionPercent',
|
||||
'actions'
|
||||
];
|
||||
|
||||
slabs: Array<{ id?: number; slabType: string; slabFrom: number; slabTo: number; commissionPercent: number }> = [];
|
||||
|
||||
newSlabForm: UntypedFormGroup = this.formBuilder.group({
|
||||
slabType: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
slabFrom: [
|
||||
0,
|
||||
Validators.required
|
||||
],
|
||||
slabTo: [
|
||||
0,
|
||||
Validators.required
|
||||
],
|
||||
commissionPercent: [
|
||||
0,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadSlabs();
|
||||
}
|
||||
|
||||
private loadSlabs(): void {
|
||||
this.organizationService.getCommissionSlabs().subscribe((slabs: any[]) => {
|
||||
this.slabs = (slabs || []).map((slab) => ({
|
||||
id: slab.id,
|
||||
slabType: CommissionSlabMasterComponent.SLAB_TYPE_LABELS[slab.slabType] || slab.slabType,
|
||||
slabFrom: slab.slabFrom,
|
||||
slabTo: slab.slabTo,
|
||||
commissionPercent: slab.commissionPercent
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
addSlab(): void {
|
||||
if (this.newSlabForm.invalid) {
|
||||
return;
|
||||
}
|
||||
this.slabs.push(this.newSlabForm.getRawValue());
|
||||
this.newSlabForm.reset({
|
||||
slabType: '',
|
||||
slabFrom: 0,
|
||||
slabTo: 0,
|
||||
commissionPercent: 0
|
||||
});
|
||||
}
|
||||
|
||||
removeSlab(index: number): void {
|
||||
this.slabs.splice(index, 1);
|
||||
}
|
||||
|
||||
exit(): void {
|
||||
this.router.navigate(['/organization']);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const newSlabs = this.slabs.filter((slab) => !slab.id);
|
||||
if (newSlabs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = newSlabs.map((slab) => ({
|
||||
slabType: CommissionSlabMasterComponent.SLAB_TYPE_CODES[slab.slabType] || slab.slabType,
|
||||
slabFrom: slab.slabFrom,
|
||||
slabTo: slab.slabTo,
|
||||
commissionPercent: slab.commissionPercent
|
||||
}));
|
||||
|
||||
this.organizationService.createCommissionSlabs(payload).subscribe(() => this.loadSlabs());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<!--
|
||||
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/.
|
||||
-->
|
||||
|
||||
<div class="bp-container">
|
||||
<div class="bp-header">
|
||||
<button mat-stroked-button [routerLink]="['../']" class="bp-back">
|
||||
<i class="fas fa-arrow-left"></i> {{ 'labels.heading.Organization' | translate }}
|
||||
</button>
|
||||
<h2 class="bp-header__title">DDS Interest & Commission Batch Processing</h2>
|
||||
</div>
|
||||
|
||||
<div class="bp-card">
|
||||
<form [formGroup]="batchForm">
|
||||
<div class="bp-grid">
|
||||
<mat-form-field appearance="outline" class="bp-field bp-field--wide">
|
||||
<mat-label>Job Type</mat-label>
|
||||
<mat-select required formControlName="jobType">
|
||||
@for (type of jobTypes; track type) {
|
||||
<mat-option [value]="type">{{ type }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="bp-field">
|
||||
<mat-label>Branch</mat-label>
|
||||
<mat-select required formControlName="branchScope">
|
||||
@for (scope of branchScopes; track scope) {
|
||||
<mat-option [value]="scope">{{ scope }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@if (isBranchSelected) {
|
||||
<div class="bp-field bp-field--wide bp-branch-checklist">
|
||||
@for (office of offices; track office.id) {
|
||||
<mat-checkbox [checked]="selectedOfficeNames.has(office.name)" (change)="toggleOffice(office.name)">
|
||||
{{ office.name }}
|
||||
</mat-checkbox>
|
||||
}
|
||||
@if (offices.length === 0) {
|
||||
<span class="bp-empty">Loading branches...</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="bp-field">
|
||||
<mat-label>{{ isPostingJob ? 'Posting Date' : 'As-On Date' }}</mat-label>
|
||||
<input matInput required [matDatepicker]="jobDatePicker" formControlName="jobDate" />
|
||||
<mat-datepicker-toggle matIconSuffix [for]="jobDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #jobDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="bp-field">
|
||||
<mat-label>Agent From</mat-label>
|
||||
<input matInput required type="number" formControlName="agentFrom" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="bp-field">
|
||||
<mat-label>Agent To</mat-label>
|
||||
<input matInput required type="number" formControlName="agentTo" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="bp-field">
|
||||
<mat-label>Party From</mat-label>
|
||||
<input matInput required type="number" formControlName="partyFrom" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="bp-field">
|
||||
<mat-label>Party To</mat-label>
|
||||
<input matInput required type="number" formControlName="partyTo" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="bp-callout">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span>
|
||||
<strong>Generate</strong> runs a preview only — nothing is applied to any account. <strong>Post</strong> is a
|
||||
separate, explicit step that commits the previewed amounts.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (hasGenerated) {
|
||||
<div class="bp-section">
|
||||
<div class="bp-section__header">
|
||||
<i class="fas fa-table"></i>
|
||||
<h3>Preview</h3>
|
||||
</div>
|
||||
@if (previewRows.length === 0) {
|
||||
<p class="bp-empty">
|
||||
No calculation preview available yet — this appears once the backend batch-job endpoint for "{{
|
||||
batchForm.get('jobType').value
|
||||
}}" is connected.
|
||||
</p>
|
||||
} @else {
|
||||
<table class="bp-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Agent</th>
|
||||
<th>Party</th>
|
||||
<th>Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (row of previewRows; track row) {
|
||||
<tr>
|
||||
<td>{{ row.agent }}</td>
|
||||
<td>{{ row.party }}</td>
|
||||
<td>{{ row.amount | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="bp-action-bar">
|
||||
<button mat-raised-button type="button" (click)="exit()">Exit</button>
|
||||
<button mat-stroked-button type="button" [disabled]="batchForm.invalid" (click)="generate()">
|
||||
<i class="fas fa-play" style="margin-right: 6px"></i> Generate
|
||||
</button>
|
||||
<button mat-raised-button color="primary" type="button" [disabled]="!hasGenerated" (click)="post()">
|
||||
<i class="fas fa-check" style="margin-right: 6px"></i> Post
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$primary: #0f766e;
|
||||
|
||||
.bp-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.bp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.bp-back {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.bp-card {
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||
padding: 20px 24px 24px;
|
||||
}
|
||||
|
||||
.bp-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
.bp-field {
|
||||
width: 100%;
|
||||
|
||||
&--wide {
|
||||
grid-column: span 3;
|
||||
}
|
||||
}
|
||||
|
||||
.bp-branch-checklist {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 20px;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.bp-callout {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
background: rgb(15 118 110 / 6%);
|
||||
border-left: 3px solid $primary;
|
||||
border-radius: 0 8px 8px 0;
|
||||
padding: 12px 16px;
|
||||
margin: 16px 0;
|
||||
font-size: 13px;
|
||||
|
||||
i {
|
||||
color: $primary;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.bp-section {
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
i {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp-empty {
|
||||
color: rgb(0 0 0 / 50%);
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bp-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: rgb(0 0 0 / 55%);
|
||||
padding: 8px 10px;
|
||||
border-bottom: 2px solid rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid rgb(0 0 0 / 6%);
|
||||
}
|
||||
}
|
||||
|
||||
.bp-action-bar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (width <= 900px) {
|
||||
.bp-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.bp-field--wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,9 @@ import { Route } from '../core/route/route.service';
|
||||
import { OrganizationComponent } from './organization.component';
|
||||
import { LoanProvisioningCriteriaComponent } from './loan-provisioning-criteria/loan-provisioning-criteria.component';
|
||||
import { OfficesComponent } from './offices/offices.component';
|
||||
import { AgentMasterComponent } from './agent-master/agent-master.component';
|
||||
import { CommissionSlabMasterComponent } from './commission-slab-master/commission-slab-master.component';
|
||||
import { DdsBatchProcessingComponent } from './dds-batch-processing/dds-batch-processing.component';
|
||||
import { EmployeesComponent } from './employees/employees.component';
|
||||
import { CreateEmployeeComponent } from './employees/create-employee/create-employee.component';
|
||||
import { ViewEmployeeComponent } from './employees/view-employee/view-employee.component';
|
||||
@@ -237,6 +240,21 @@ const routes: Routes = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'agent-master',
|
||||
component: AgentMasterComponent,
|
||||
data: { title: 'Agent Master', breadcrumb: 'Agent Master' }
|
||||
},
|
||||
{
|
||||
path: 'commission-slab-master',
|
||||
component: CommissionSlabMasterComponent,
|
||||
data: { title: 'Commission Slab Master', breadcrumb: 'Commission Slab Master' }
|
||||
},
|
||||
{
|
||||
path: 'dds-batch-processing',
|
||||
component: DdsBatchProcessingComponent,
|
||||
data: { title: 'DDS Interest & Commission Batch', breadcrumb: 'DDS Batch Processing' }
|
||||
},
|
||||
{
|
||||
path: 'employees',
|
||||
data: { title: 'Manage Employees', breadcrumb: 'Manage Employees' },
|
||||
|
||||
@@ -90,6 +90,47 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div #agentMaster>
|
||||
<a class="org-card" [routerLink]="['agent-master']">
|
||||
<div class="org-card__icon org-card__icon--teal">
|
||||
<i class="fas fa-user-tie"></i>
|
||||
</div>
|
||||
<div class="org-card__body">
|
||||
<h3 class="org-card__title">Agent Master</h3>
|
||||
<p class="org-card__desc">
|
||||
Register Daily Deposit Scheme collection agents, their commission/TDS settings, limits and guarantors
|
||||
</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right org-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div #commissionSlabMaster>
|
||||
<a class="org-card" [routerLink]="['commission-slab-master']">
|
||||
<div class="org-card__icon org-card__icon--teal">
|
||||
<i class="fas fa-percentage"></i>
|
||||
</div>
|
||||
<div class="org-card__body">
|
||||
<h3 class="org-card__title">Commission Slab Master</h3>
|
||||
<p class="org-card__desc">Define collection-volume and override commission percentage slabs</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right org-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div #ddsBatchProcessing>
|
||||
<a class="org-card" [routerLink]="['dds-batch-processing']">
|
||||
<div class="org-card__icon org-card__icon--teal">
|
||||
<i class="fas fa-cogs"></i>
|
||||
</div>
|
||||
<div class="org-card__body">
|
||||
<h3 class="org-card__title">DDS Interest & Commission Batch</h3>
|
||||
<p class="org-card__desc">Calculate then post interest, OD interest, and commission for DDS accounts</p>
|
||||
</div>
|
||||
<i class="fas fa-chevron-right org-card__arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Manage Loan Originators -->
|
||||
<div #manageLoanOriginators>
|
||||
<a class="org-card" [routerLink]="['manage-loan-originators']" *mifosxHasPermission="'READ_LOAN_ORIGINATOR'">
|
||||
|
||||
@@ -233,6 +233,141 @@ export class OrganizationService {
|
||||
return this.http.get('/currencies');
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} DDS Agents data
|
||||
*/
|
||||
getAgents(): Observable<any> {
|
||||
return this.http.get('/dds/agents');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} agentId Agent ID.
|
||||
* @returns {Observable<any>} DDS Agent.
|
||||
*/
|
||||
getAgent(agentId: string | number): Observable<any> {
|
||||
return this.http.get(`/dds/agents/${agentId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} agent Agent to be created.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
createAgent(agent: any): Observable<any> {
|
||||
return this.http.post('/dds/agents', agent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} agentId Agent ID.
|
||||
* @param {any} agent Agent changes to apply.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
updateAgent(agentId: string | number, agent: any): Observable<any> {
|
||||
return this.http.put(`/dds/agents/${agentId}`, agent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} DDS Parties data
|
||||
*/
|
||||
getParties(): Observable<any> {
|
||||
return this.http.get('/dds/parties');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} partyId Party ID.
|
||||
* @returns {Observable<any>} DDS Party.
|
||||
*/
|
||||
getParty(partyId: string | number): Observable<any> {
|
||||
return this.http.get(`/dds/parties/${partyId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} party Party to be created.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
createParty(party: any): Observable<any> {
|
||||
return this.http.post('/dds/parties', party);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} partyId Party ID.
|
||||
* @param {any} party Party changes to apply.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
updateParty(partyId: string | number, party: any): Observable<any> {
|
||||
return this.http.put(`/dds/parties/${partyId}`, party);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} partyId Party ID.
|
||||
* @param {any} closeCommand { closeDate }
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
closeParty(partyId: string | number, closeCommand: any): Observable<any> {
|
||||
return this.http.post(`/dds/parties/${partyId}?command=close`, closeCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} transaction Daily transaction batch (PARTY_TRAN collections or a WITHDRAW_DEPOSITE transfer) to post.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
createPartyTransaction(transaction: any): Observable<any> {
|
||||
return this.http.post('/dds/partytransactions', transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} DDS Commission Slabs data
|
||||
*/
|
||||
getCommissionSlabs(): Observable<any> {
|
||||
return this.http.get('/dds/commissionslabs');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any[]} slabs New commission slab rows to add to the chart.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
createCommissionSlabs(slabs: any[]): Observable<any> {
|
||||
return this.http.post('/dds/commissionslabs', { slabs });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} batch Batch run filters (jobType, branchScope, jobDate, agentFrom/To, partyFrom/To) to generate a preview for.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
generateDdsBatchRun(batch: any): Observable<any> {
|
||||
return this.http.post('/dds/batchruns', batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} batchRunId Batch run ID.
|
||||
* @returns {Observable<any>} Batch run, including its previewed/posted rows.
|
||||
*/
|
||||
getDdsBatchRun(batchRunId: string | number): Observable<any> {
|
||||
return this.http.get(`/dds/batchruns/${batchRunId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | number} batchRunId Batch run ID to post - applies exactly the rows it previewed.
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
postDdsBatchRun(batchRunId: string | number): Observable<any> {
|
||||
return this.http.post(`/dds/batchruns/${batchRunId}`, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} reportKey One of the backend DDS report keys.
|
||||
* @param {any} filters fromDate, toDate (ISO strings), agentFrom/To, partyFrom/To.
|
||||
* @returns {Observable<any>} { reportKey, available, message, columns, rows }
|
||||
*/
|
||||
runDdsReport(reportKey: string, filters: any): Observable<any> {
|
||||
let httpParams = new HttpParams();
|
||||
Object.keys(filters || {}).forEach((key) => {
|
||||
if (filters[key] !== undefined && filters[key] !== null && filters[key] !== '') {
|
||||
httpParams = httpParams.set(key, filters[key]);
|
||||
}
|
||||
});
|
||||
return this.http.get(`/dds/reports/${reportKey}`, { params: httpParams });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any[]} currencies
|
||||
* @returns {Observable<any>} Currencies data
|
||||
|
||||
Reference in New Issue
Block a user