1052 lines
49 KiB
HTML
1052 lines
49 KiB
HTML
<!--
|
|
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="pm-container">
|
|
<div class="pm-header">
|
|
<button mat-stroked-button [routerLink]="['/accounts/dds']" class="pm-back">
|
|
<i class="fas fa-arrow-left"></i> Party Master
|
|
</button>
|
|
<h2 class="pm-header__title">{{ partyId ? 'Edit Party' : 'Party Master Entry' }}</h2>
|
|
@if (isClosed) {
|
|
<span class="pm-closed-badge">Closed — view only</span>
|
|
}
|
|
</div>
|
|
|
|
<div class="pm-card">
|
|
<form [formGroup]="partyForm">
|
|
<mat-tab-group class="pm-tabs" [(selectedIndex)]="selectedTabIndex" animationDuration="200ms">
|
|
<mat-tab label="Main Detail">
|
|
<div class="pm-tab-content">
|
|
<div class="pm-section">
|
|
<div class="pm-section__header">
|
|
<i class="fas fa-id-card"></i>
|
|
<h3>Customer & Scheme</h3>
|
|
<span class="pm-section__sub"
|
|
>Who this account is for, which scheme it runs under, and which agent collects for it</span
|
|
>
|
|
</div>
|
|
<div class="pm-grid">
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Client ID or Name<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
formControlName="clientId"
|
|
[matAutocomplete]="clientAuto"
|
|
(focus)="onClientFocus()"
|
|
placeholder="Click to see customers, or type a name or ID…"
|
|
autocomplete="off"
|
|
/>
|
|
<fa-icon matSuffix icon="search" class="pm-search-ic"></fa-icon>
|
|
<mat-autocomplete
|
|
#clientAuto="matAutocomplete"
|
|
panelWidth="min(30rem, 92vw)"
|
|
[displayWith]="displayClient"
|
|
>
|
|
@for (client of clientOptions; track client.id) {
|
|
<mat-option [value]="client">
|
|
<div class="pm-opt">
|
|
<span class="pm-opt-id">{{ client.id }}</span>
|
|
<span class="pm-opt-main">{{ client.displayName }}</span>
|
|
<span class="pm-opt-sub">{{ client.officeName }}</span>
|
|
</div>
|
|
</mat-option>
|
|
}
|
|
<!-- Says so when the list is only the first page, so a customer missing from
|
|
it reads as "keep typing" rather than "not a customer here". -->
|
|
@if (clientListIsPartial) {
|
|
<mat-option disabled class="pm-opt-more">
|
|
Showing {{ clientOptions.length }} of {{ clientTotal }} — type a name to find the rest
|
|
</mat-option>
|
|
}
|
|
</mat-autocomplete>
|
|
@if (loadedClientName) {
|
|
<mat-hint class="pm-hint--found">{{ loadedClientName }}</mat-hint>
|
|
} @else if (clientLookupFailed) {
|
|
<mat-hint class="pm-hint--missing">No client with this ID</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Sub Type (Scheme)<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
formControlName="subType"
|
|
[matAutocomplete]="subTypeAuto"
|
|
(focus)="showAllSubTypes()"
|
|
placeholder="Type to search schemes…"
|
|
autocomplete="off"
|
|
/>
|
|
<fa-icon matSuffix icon="search" class="pm-search-ic"></fa-icon>
|
|
<mat-autocomplete
|
|
#subTypeAuto="matAutocomplete"
|
|
panelWidth="min(30rem, 92vw)"
|
|
[displayWith]="displaySubType"
|
|
>
|
|
@for (type of subTypeOptions; track type.id) {
|
|
<mat-option [value]="type.accountTypeCode">
|
|
<div class="pm-opt">
|
|
<span class="pm-opt-id">{{ type.accountTypeCode }}</span>
|
|
<span class="pm-opt-main">{{ type.accountTypeDescription }}</span>
|
|
<span class="pm-opt-sub">{{ type.accountTypeGroup }}</span>
|
|
</div>
|
|
</mat-option>
|
|
}
|
|
@if (subTypeOptions.length === 0) {
|
|
<mat-option disabled><em>No scheme matches that.</em></mat-option>
|
|
}
|
|
</mat-autocomplete>
|
|
@if (subTypes.length === 0) {
|
|
<mat-hint class="pm-hint--missing">No active account type — create one first</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Agent<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
formControlName="agentId"
|
|
[matAutocomplete]="agentAuto"
|
|
(focus)="onAgentFocus()"
|
|
placeholder="Click to see agents, or type a code or name…"
|
|
autocomplete="off"
|
|
/>
|
|
<fa-icon matSuffix icon="search" class="pm-search-ic"></fa-icon>
|
|
<mat-autocomplete
|
|
#agentAuto="matAutocomplete"
|
|
panelWidth="min(30rem, 92vw)"
|
|
[displayWith]="displayAgent"
|
|
>
|
|
@for (agent of agentOptions; track agent.id) {
|
|
<mat-option [value]="agent">
|
|
<div class="pm-opt">
|
|
<span class="pm-opt-id">{{ agent.agentCode }}</span>
|
|
<span class="pm-opt-main">{{ agent.agentNameId }}</span>
|
|
<!-- A stopped agent is still shown, marked: hiding one would silently
|
|
drop a party's own collector from the list. -->
|
|
<span class="pm-opt-sub">
|
|
{{ agent.active ? agent.homeBranch : 'not collecting' }}
|
|
</span>
|
|
</div>
|
|
</mat-option>
|
|
}
|
|
@if (agentOptions.length === 0) {
|
|
<mat-option disabled><em>No agent matches that.</em></mat-option>
|
|
}
|
|
</mat-autocomplete>
|
|
@if (loadedAgentName && !agentLookupFailed) {
|
|
<mat-hint class="pm-hint--found">{{ loadedAgentName }}</mat-hint>
|
|
} @else if (loadedAgentName && agentLookupFailed) {
|
|
<mat-hint class="pm-hint--missing">{{ loadedAgentName }} is deactivated</mat-hint>
|
|
} @else if (agentLookupFailed) {
|
|
<mat-hint class="pm-hint--missing">No agent with this ID</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!--
|
|
The branch this account belongs to. Asked for, not taken from the agent: an agent
|
|
working out of one branch can hold accounts for customers of another, and an
|
|
account that silently inherits its agent's branch is counted at the wrong one.
|
|
-->
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Branch<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select required formControlName="officeId">
|
|
@for (office of offices; track office.id) {
|
|
<mat-option [value]="office.id">{{ office.name }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
@if (officesError) {
|
|
<mat-hint class="pm-hint--missing">{{ officesError }}</mat-hint>
|
|
} @else {
|
|
<mat-hint>The branch this account belongs to</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!--
|
|
The customer's real deposit account. Three states, and the difference matters: an
|
|
account is open, this party predates DDS opening them, or the party is new and the
|
|
account is about to be opened. It used to read "Linked (id 36)", which is a number
|
|
only the database means anything by.
|
|
-->
|
|
@if (accountNo) {
|
|
<!-- The party is the DDS account now, so its number, scheme and balance are its own. -->
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">DDS Account</label>
|
|
<div class="pm-account">
|
|
<span class="pm-account-no">{{ accountNo }}</span>
|
|
<span class="pm-account-meta">
|
|
{{ accountSchemeName }} ·
|
|
<span [class.pm-account-live]="partyStatus === 'ACTIVE'">{{
|
|
partyStatus === 'ACTIVE' ? 'Active' : partyStatus
|
|
}}</span>
|
|
· {{ accountBalance | number: '1.2-2' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
} @else if (savingsAccountNo) {
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">DDS Account</label>
|
|
<div class="pm-account">
|
|
<span class="pm-account-no">{{ savingsAccountNo }}</span>
|
|
<span class="pm-account-meta">
|
|
{{ savingsProductName }} ·
|
|
<span [class.pm-account-live]="savingsAccountStatus === 'Active'">{{
|
|
savingsAccountStatus
|
|
}}</span>
|
|
· {{ savingsAccountBalance | number: '1.2-2' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
} @else if (partyId) {
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">DDS Account</label>
|
|
<div class="pm-account">
|
|
<span class="pm-account-missing">No account — opened before DDS started opening them</span>
|
|
</div>
|
|
</div>
|
|
} @else {
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">DDS Account</label>
|
|
<div class="pm-account">
|
|
<span class="pm-account-meta">Opens automatically when this party is saved</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Party Code</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="partyCode" placeholder="Auto-generated" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Last A/c</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="lastAccount" placeholder="—" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Nominal<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select required formControlName="nominal">
|
|
<mat-option value="Yes">Yes</mat-option>
|
|
<mat-option value="No">No</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f pm-f--wide">
|
|
<label class="pm-f-lbl">Party Name<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required formControlName="partyName" [readonly]="isFromClient('partyName')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f pm-f--wide">
|
|
<label class="pm-f-lbl">Party Name 1</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="partyName1" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f pm-f--wide">
|
|
<label class="pm-f-lbl">Address 1<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required formControlName="address1" [readonly]="isFromClient('address1')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f pm-f--wide">
|
|
<label class="pm-f-lbl">Address 2</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="address2" [readonly]="isFromClient('address2')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f pm-f--wide">
|
|
<label class="pm-f-lbl">Address 3</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="address3" [readonly]="isFromClient('address3')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">City<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required formControlName="city" [readonly]="isFromClient('city')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Pincode<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
formControlName="pincode"
|
|
[readonly]="isFromClient('pincode')"
|
|
mifosxRestrictInput="digits"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Gender</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select formControlName="gender">
|
|
<mat-option value="Male">Male</mat-option>
|
|
<mat-option value="Female">Female</mat-option>
|
|
<mat-option value="Other">Other</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Telephone</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
formControlName="telephone"
|
|
[readonly]="isFromClient('telephone')"
|
|
mifosxRestrictInput="phone"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Mobile No (SMS)<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
formControlName="mobileNoSms"
|
|
[readonly]="isFromClient('mobileNoSms')"
|
|
mifosxRestrictInput="digits"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Aadhar No</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
formControlName="aadharNo"
|
|
[readonly]="isFromClient('aadharNo')"
|
|
mifosxRestrictInput="aadhaar"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">PAN Card No</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
formControlName="panCardNo"
|
|
[readonly]="isFromClient('panCardNo')"
|
|
mifosxRestrictInput="pan"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">CKYC ID</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
formControlName="ckycId"
|
|
[readonly]="isFromClient('ckycId')"
|
|
mifosxRestrictInput="digits"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Operation<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select required formControlName="operation">
|
|
@for (op of operations; track op) {
|
|
<mat-option [value]="op">{{ op }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Birth Date</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
[matDatepicker]="birthPicker"
|
|
formControlName="birthDate"
|
|
[readonly]="isFromClient('birthDate')"
|
|
/>
|
|
<mat-datepicker-toggle matIconSuffix [for]="birthPicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #birthPicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Occupation</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select formControlName="occupation">
|
|
@for (occ of occupations; track occ) {
|
|
<mat-option [value]="occ">{{ occ }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Risk</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select formControlName="risk">
|
|
@for (r of riskLevels; track r) {
|
|
<mat-option [value]="r">{{ r }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pm-section">
|
|
<div class="pm-section__header">
|
|
<i class="fas fa-user-friends"></i>
|
|
<h3>Minor / Guardian</h3>
|
|
<span class="pm-section__sub"
|
|
>Only for a minor's account — the guardian who operates it until they come of age</span
|
|
>
|
|
</div>
|
|
<div class="pm-grid">
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Adult / Minor<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select required formControlName="adultMinor">
|
|
@for (opt of adultMinorOptions; track opt) {
|
|
<mat-option [value]="opt">{{ opt }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
@if (isMinorAccount) {
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Minor Date<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
[matDatepicker]="minorPicker"
|
|
formControlName="minorDate"
|
|
[readonly]="isFromClient('minorDate')"
|
|
/>
|
|
<mat-datepicker-toggle matIconSuffix [for]="minorPicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #minorPicker></mat-datepicker>
|
|
<mat-hint>Turns 18 on</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Guardian Client ID or Name<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
formControlName="guardianClientId"
|
|
[readonly]="isFromClient('guardianClientId')"
|
|
[matAutocomplete]="guardianAuto"
|
|
placeholder="Type an ID or search by name…"
|
|
autocomplete="off"
|
|
/>
|
|
<fa-icon matSuffix icon="search" class="pm-search-ic"></fa-icon>
|
|
<mat-autocomplete
|
|
#guardianAuto="matAutocomplete"
|
|
panelWidth="min(30rem, 92vw)"
|
|
[displayWith]="displayClient"
|
|
>
|
|
@for (client of guardianOptions; track client.id) {
|
|
<mat-option [value]="client">
|
|
<div class="pm-opt">
|
|
<span class="pm-opt-id">{{ client.id }}</span>
|
|
<span class="pm-opt-main">{{ client.displayName }}</span>
|
|
<span class="pm-opt-sub">{{ client.officeName }}</span>
|
|
</div>
|
|
</mat-option>
|
|
}
|
|
</mat-autocomplete>
|
|
@if (loadedGuardianName) {
|
|
<mat-hint class="pm-hint--found">{{ loadedGuardianName }}</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Min. A/c Opt</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="minAccountOpt" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Relation<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required formControlName="relation" [readonly]="isFromClient('relation')" />
|
|
<mat-hint>To the minor</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
}
|
|
</div>
|
|
@if (!isMinorAccount) {
|
|
<p class="pm-note">An adult's account needs no guardian details.</p>
|
|
}
|
|
</div>
|
|
|
|
<div class="pm-section">
|
|
<div class="pm-section__header">
|
|
<i class="fas fa-calendar-day"></i>
|
|
<h3>Scheme Terms</h3>
|
|
<span class="pm-section__sub">How much is collected daily, for how long, and what it matures to</span>
|
|
</div>
|
|
<div class="pm-grid">
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Opening Date<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<!--
|
|
A new account opens today, so yesterday is greyed out - a date in the past would
|
|
make the scheme look older than it is, with its lock-in further along and its
|
|
maturity falling before the money was collected for it. No floor while editing:
|
|
an account already on the books opened in the past by definition, and a floor of
|
|
today would make every one of them unsaveable.
|
|
-->
|
|
<input
|
|
matInput
|
|
required
|
|
[matDatepicker]="openingPicker"
|
|
[min]="minOpeningDate"
|
|
formControlName="openingDate"
|
|
/>
|
|
<mat-datepicker-toggle matIconSuffix [for]="openingPicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #openingPicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Month</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput type="number" formControlName="month" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Maturity Date</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput [matDatepicker]="maturityPicker" formControlName="maturityDate" />
|
|
<mat-datepicker #maturityPicker></mat-datepicker>
|
|
<mat-hint>Calculated</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Reopen Date</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput [matDatepicker]="reopenPicker" formControlName="reopenDate" />
|
|
<mat-datepicker-toggle matIconSuffix [for]="reopenPicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #reopenPicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Deposit Amt</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput type="number" formControlName="depositAmount" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Daily Deposit Amt<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required type="number" formControlName="dailyDepositAmount" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Days<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required type="number" formControlName="days" />
|
|
@if (partyForm.get('days')?.hasError('exceedsTerm')) {
|
|
<mat-error>Only {{ termDays }} days in this term</mat-error>
|
|
} @else if (termDays) {
|
|
<mat-hint>Term is {{ termDays }} days</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Ledger No</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="ledgerNo" />
|
|
<mat-hint>Collection book no.</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Mat. Amt</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput disabled class="pm-money" [value]="money('maturityAmount')" />
|
|
<mat-hint>Calculated</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!-- Interest Rate is the product's (Sub Type), not typed here. -->
|
|
<!-- Withdraw Type is decided when the account is paid out, not when it is opened. -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pm-section">
|
|
<div class="pm-section__header">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<h3>Balances & Status</h3>
|
|
<span class="pm-section__sub">Kept by collections and the nightly batch — shown here, never typed</span>
|
|
</div>
|
|
<div class="pm-grid">
|
|
<div class="pm-checkbox-field">
|
|
<mat-checkbox formControlName="od1Flag">OD1 Flag</mat-checkbox>
|
|
</div>
|
|
<div class="pm-checkbox-field">
|
|
<mat-checkbox formControlName="od2Flag">OD2 Flag</mat-checkbox>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Lien</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select formControlName="lien">
|
|
<mat-option value="Yes">Yes</mat-option>
|
|
<mat-option value="No">No</mat-option>
|
|
</mat-select>
|
|
@if (lienHeld) {
|
|
<mat-hint>Nothing can leave this account until it is released</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!--
|
|
Why the money is held. Asked for only when it is: the flag alone says an account is
|
|
frozen and never says what for, which is how a lien outlives the loan it secured -
|
|
months later nobody can tell whether it has been repaid, so nobody will release it
|
|
and nobody can justify keeping it.
|
|
-->
|
|
@if (lienHeld) {
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Lien Reason<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select required formControlName="lienReason">
|
|
@for (reason of lienReasons; track reason) {
|
|
<mat-option [value]="reason">{{ reason }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
<mat-hint>What this money is being held for</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!--
|
|
Only where the list genuinely does not cover it. Picking "Other" and leaving
|
|
this blank would say no more than leaving the whole reason blank did.
|
|
-->
|
|
@if (lienReasonIsOther) {
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Please specify<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
required
|
|
maxlength="200"
|
|
formControlName="lienReasonOther"
|
|
placeholder="e.g. Loan #1234"
|
|
/>
|
|
<mat-hint>Write the reason in your own words</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Pay Date</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput [matDatepicker]="payDatePicker" formControlName="payDate" />
|
|
<mat-datepicker-toggle matIconSuffix [for]="payDatePicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #payDatePicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Payment Amount</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput disabled class="pm-money" [value]="money('paymentAmount')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Current Bal</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput disabled class="pm-money" [value]="money('currentBalance')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Opening Bal</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput disabled class="pm-money" [value]="money('openingBalance')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-checkbox-field">
|
|
<mat-checkbox formControlName="isSmsActive">Is SMS Active</mat-checkbox>
|
|
</div>
|
|
<div class="pm-checkbox-field">
|
|
<mat-checkbox formControlName="isEmailActive">Is Email Active</mat-checkbox>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Int.Calc</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput disabled class="pm-money" [value]="money('interestCalc')" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Interest Date</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput [matDatepicker]="interestDatePicker" formControlName="interestDate" />
|
|
<mat-datepicker-toggle matIconSuffix [for]="interestDatePicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #interestDatePicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f pm-f--wide">
|
|
<label class="pm-f-lbl">Sp.Description</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="spDescription" />
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Fr'z CD</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select formControlName="freezeCode">
|
|
@for (code of freezeCodes; track code) {
|
|
<mat-option [value]="code">{{ code }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
<!-- Adv Code is decided when the account is paid out, not when it is opened. -->
|
|
</div>
|
|
</div>
|
|
|
|
@if (creditToRequired) {
|
|
<div class="pm-section">
|
|
<div class="pm-section__header">
|
|
<i class="fas fa-exchange-alt"></i>
|
|
<h3>Maturity Payout Routing</h3>
|
|
</div>
|
|
<p class="pm-note">Transfer To Saving — say where the maturity proceeds should land.</p>
|
|
<div class="pm-grid">
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Credit To Branch<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select required formControlName="creditToBranch">
|
|
@for (office of offices; track office.id) {
|
|
<mat-option [value]="office.name">{{ office.name }}</mat-option>
|
|
}
|
|
</mat-select>
|
|
@if (officesError) {
|
|
<mat-hint class="pm-hint--missing">{{ officesError }}</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!--
|
|
Picked from the customer's own savings accounts, never typed. The whole matured
|
|
amount lands here, and a number that exists only as text is a payout with
|
|
nowhere to go - which is what one party was already carrying.
|
|
-->
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Credit To A/C<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select
|
|
required
|
|
formControlName="creditToAccount"
|
|
panelWidth="min(30rem, 92vw)"
|
|
(selectionChange)="onCreditToAccountPicked($event.value)"
|
|
>
|
|
@for (account of creditToAccounts; track account.id) {
|
|
<mat-option [value]="account.accountNo">
|
|
{{ account.accountNo }} · {{ account.productName }}
|
|
</mat-option>
|
|
}
|
|
</mat-select>
|
|
@if (creditToAccounts.length === 0) {
|
|
<mat-hint class="pm-hint--missing">
|
|
{{ loadedClientName || 'This customer' }} has no other savings account to credit
|
|
</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
<!-- Read off the chosen account rather than asked for separately. -->
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Credit To A/C Type<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput readonly formControlName="creditToAccountType" />
|
|
</mat-form-field>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</mat-tab>
|
|
|
|
<mat-tab label="Nominee's Detail">
|
|
<div class="pm-tab-content">
|
|
<div class="pm-section">
|
|
<div class="pm-grid">
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Nominee</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select formControlName="nomineeApplicable">
|
|
<mat-option value="Yes">Yes</mat-option>
|
|
<mat-option value="No">No</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
|
|
@if (nomineesRequired) {
|
|
<mat-radio-group formControlName="nomineeSplitMode" class="pm-radio-group">
|
|
<mat-radio-button value="Percentage">Percentage</mat-radio-button>
|
|
<mat-radio-button value="Sequence">Sequence</mat-radio-button>
|
|
</mat-radio-group>
|
|
|
|
<button mat-stroked-button type="button" class="pm-add-nominee" (click)="addNominee()">
|
|
<i class="fas fa-plus"></i> Add Nominee
|
|
</button>
|
|
|
|
@if (splitByPercentage && nominees.length > 0) {
|
|
<span class="pm-share-total" [class.pm-share-total-bad]="nomineeShareError">
|
|
{{ nomineeShareTotal }}% of 100% allocated
|
|
@if (nomineeShareError) {
|
|
— {{ nomineeShareError }}
|
|
}
|
|
</span>
|
|
} @else if (splitBySequence && nominees.length > 0) {
|
|
<span class="pm-share-total" [class.pm-share-total-bad]="nomineeOrderError">
|
|
@if (nomineeOrderError) {
|
|
{{ nomineeOrderError }}
|
|
} @else {
|
|
Paid in the order numbered, 1 first
|
|
}
|
|
</span>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
@if (!nomineesRequired) {
|
|
<p class="pm-note">Set Nominee to Yes to nominate someone for this account.</p>
|
|
}
|
|
|
|
@for (nominee of nominees.controls; track nominee; let i = $index) {
|
|
<div class="pm-nominee-row" [formGroup]="$any(nominee)">
|
|
<div class="pm-f pm-f--date">
|
|
<label class="pm-f-lbl">Nom.Dt<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required [matDatepicker]="nomDatePicker" formControlName="nominationDate" />
|
|
<mat-datepicker-toggle matIconSuffix [for]="nomDatePicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #nomDatePicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Name<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required formControlName="name" />
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Mobile No</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput formControlName="mobileNo" mifosxRestrictInput="digits" />
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="pm-f pm-f--date">
|
|
<label class="pm-f-lbl">Birth Dt</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput [matDatepicker]="nomBirthPicker" formControlName="birthDate" />
|
|
<mat-datepicker-toggle matIconSuffix [for]="nomBirthPicker"></mat-datepicker-toggle>
|
|
<mat-datepicker #nomBirthPicker></mat-datepicker>
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="pm-f pm-f--tight">
|
|
<!-- Read from the date of birth, not asked: the two arriving separately is how
|
|
a ten-year-old came to be recorded as not a minor. -->
|
|
<label class="pm-f-lbl">Minor</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput readonly [value]="isNomineeMinor(nominee) ? 'Yes' : 'No'" />
|
|
<mat-hint>From date of birth</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">Relation<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput required formControlName="relation" />
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="pm-f pm-f--tight">
|
|
<label class="pm-f-lbl">Gender<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<mat-select required formControlName="gender">
|
|
<mat-option value="Male">Male</mat-option>
|
|
<mat-option value="Female">Female</mat-option>
|
|
<mat-option value="Other">Other</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
</div>
|
|
<div class="pm-f">
|
|
<label class="pm-f-lbl">
|
|
Guar. Name
|
|
@if (isNomineeMinor(nominee)) {
|
|
<b>*</b>
|
|
}
|
|
</label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput [required]="isNomineeMinor(nominee)" formControlName="guardianName" />
|
|
@if (isNomineeMinor(nominee)) {
|
|
<mat-hint>Required for a minor</mat-hint>
|
|
}
|
|
</mat-form-field>
|
|
</div>
|
|
@if (splitByPercentage) {
|
|
<div class="pm-f pm-f--tight">
|
|
<label class="pm-f-lbl">Share %<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input
|
|
matInput
|
|
type="number"
|
|
min="0.01"
|
|
max="100"
|
|
step="0.01"
|
|
required
|
|
formControlName="sharePercent"
|
|
/>
|
|
</mat-form-field>
|
|
</div>
|
|
} @else {
|
|
<div class="pm-f pm-f--tight">
|
|
<label class="pm-f-lbl">Order<b>*</b></label>
|
|
<mat-form-field appearance="outline" class="pm-field">
|
|
<input matInput type="number" min="1" step="1" required formControlName="sequenceNo" />
|
|
</mat-form-field>
|
|
</div>
|
|
}
|
|
<button
|
|
mat-icon-button
|
|
type="button"
|
|
class="pm-remove-nominee"
|
|
(click)="removeNominee(i)"
|
|
matTooltip="Remove nominee"
|
|
>
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</mat-tab>
|
|
</mat-tab-group>
|
|
|
|
@if (isClosed) {
|
|
<div class="pm-callout">
|
|
<i class="fas fa-lock"></i>
|
|
<span>This account is closed. No further collections or edits are possible.</span>
|
|
</div>
|
|
}
|
|
|
|
<!--
|
|
A minor who has come of age. The guardian's authority to operate the account ended on that
|
|
day, so this is something the branch has to act on rather than a detail on a tab - which is
|
|
why it sits beside the action bar where a closed account's notice does.
|
|
-->
|
|
@if (minorTurnedMajor) {
|
|
<div class="pm-callout pm-callout--action">
|
|
<i class="fas fa-user-clock"></i>
|
|
<span>
|
|
This account was opened for a minor who came of age on
|
|
<strong>{{ majorOn | dateFormat }}</strong
|
|
>. The guardian can no longer operate it — take fresh KYC, a signature and a new mandate from the account
|
|
holder.
|
|
</span>
|
|
</div>
|
|
}
|
|
|
|
<div class="pm-action-bar">
|
|
<button mat-raised-button type="button" (click)="exit()">Exit</button>
|
|
<button mat-stroked-button type="button" (click)="cancel()">Cancel</button>
|
|
<!--
|
|
Closing before the term is up. Kept here beside the ordinary close rather than on the party
|
|
list: it cannot be undone and it changes what the customer is owed, so it belongs on the
|
|
screen where the officer has the account open in front of them, not one click from a row.
|
|
Hidden once the account has matured - that one is paid out and closed on its own terms, and
|
|
the server refuses this on it.
|
|
-->
|
|
@if (partyId && !isClosed && canCloseEarly) {
|
|
<button
|
|
mat-stroked-button
|
|
type="button"
|
|
class="pm-early-btn"
|
|
matTooltip="Close this account before its term ends, settling the shortfall"
|
|
(click)="closeEarly()"
|
|
>
|
|
<i class="fas fa-times" style="margin-right: 6px"></i> Close Early
|
|
</button>
|
|
}
|
|
@if (partyId && !isClosed) {
|
|
<button
|
|
mat-stroked-button
|
|
type="button"
|
|
color="warn"
|
|
matTooltip="Balance must be zero before closing"
|
|
(click)="closeAccount()"
|
|
>
|
|
<i class="fas fa-lock" style="margin-right: 6px"></i> Close Account
|
|
</button>
|
|
}
|
|
<button
|
|
mat-raised-button
|
|
color="primary"
|
|
type="button"
|
|
[disabled]="partyForm.invalid || isClosed || !!nomineeShareError || !!nomineeOrderError"
|
|
(click)="save()"
|
|
>
|
|
Save
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|