+
+
+
+
+
+ {{ 'labels.buttons.Back' | translate }}
+
+
+
+
+
+
+ {{
+ transactionType.withdrawal
+ ? ('labels.heading.Withdraw Money From Current Account' | translate)
+ : ('labels.heading.Deposit Money To Current Account' | translate)
+ }}
+
+
+ {{
+ transactionType.deposit
+ ? 'Count the cash by denomination, then confirm the payment details.'
+ : 'Disburse cash by denomination and confirm the payment details.'
+ }}
+
+
+ @if (transactionType.withdrawal && clientId) {
+
+
+ Verify Identity
+
+ }
+
+ Counted Total
+ ₹{{ denominationTotal | number: '1.2-2' }}
+ @if (totalPieces > 0) {
+ {{ totalPieces }} piece(s)
+ }
+
+
-
diff --git a/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.scss b/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.scss
index b21a3cf9c..8d1c6a0cf 100644
--- a/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.scss
+++ b/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.scss
@@ -6,24 +6,842 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-.expandcollapsebutton {
- margin-top: -7px;
+/* Banking-grade transaction-entry screen. Two-column layout: cash
+ denomination breakdown on the left (the cashier's primary workspace),
+ transaction & payment details on the right. Hero header is blue for
+ deposits / teal for withdrawals. */
+
+$navy: #0d1b2a;
+$slate: #475569;
+$muted: #64748b;
+$border: #e2e8f0;
+$soft: #f8fafc;
+$blue: #1e40af;
+$green: #059669;
+$green-d: #047857;
+$amber: #d97706;
+$amber-d: #b45309;
+$red: #b91c1c;
+
+.sat {
+ width: 100%;
+ padding: 10px 16px 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
-.container {
- max-width: 37rem;
+/* ─── Hero ─────────────────────────────────────────────────────────── */
+
+.sat__hero {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px 16px 12px;
+ border-radius: 12px;
+ color: #fff;
+ background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #1e40af 100%);
+ box-shadow: 0 6px 18px -10px rgb(15 23 42 / 50%);
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
+ pointer-events: none;
+ }
}
-.right-input {
+.sat__hero-top {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ position: relative;
+ z-index: 1;
+}
+
+.sat__hero-back {
+ height: 30px !important;
+ border-radius: 8px !important;
+ padding: 0 11px !important;
+ font-weight: 700 !important;
+ font-size: 12px !important;
+ letter-spacing: 0.01em;
+ background: rgb(255 255 255 / 14%) !important;
+ color: #fff !important;
+ border: 1px solid rgb(255 255 255 / 28%) !important;
+ flex-shrink: 0;
+ transition:
+ background 0.15s ease,
+ border-color 0.15s ease;
+
+ &:hover {
+ background: rgb(255 255 255 / 26%) !important;
+ border-color: rgb(255 255 255 / 42%) !important;
+ }
+}
+
+.sat__hero-title-wrap {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+.sat--withdrawal .sat__hero {
+ background: linear-gradient(135deg, #134e4a 0%, #115e59 45%, #0f766e 100%);
+ box-shadow: 0 6px 18px -10px rgb(15 118 110 / 50%);
+}
+
+.sat__hero-ico {
+ width: 36px;
+ height: 36px;
+ border-radius: 9px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 28%);
+ color: #fff;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 15px;
+ flex-shrink: 0;
+}
+
+.sat__title {
+ margin: 0;
+ font-size: 14.5px;
+ font-weight: 800;
+ letter-spacing: 0.01em;
+ line-height: 1.15;
+}
+
+.sat__sub {
+ margin: 1px 0 0;
+ font-size: 11.5px;
+ color: rgb(255 255 255 / 80%);
+}
+
+.sat__hero-verify {
+ position: relative;
+ height: 30px !important;
+ border-radius: 8px !important;
+ padding: 0 12px !important;
+ font-weight: 700 !important;
+ font-size: 12px !important;
+ letter-spacing: 0.01em;
+ color: #fff !important;
+ background: rgb(255 255 255 / 16%) !important;
+ border: 1px solid rgb(255 255 255 / 30%) !important;
+ flex-shrink: 0;
+ transition:
+ background 0.15s ease,
+ border-color 0.15s ease;
+ overflow: visible !important;
+
+ &:hover {
+ background: rgb(255 255 255 / 28%) !important;
+ border-color: rgb(255 255 255 / 42%) !important;
+ }
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: -2px;
+ border-radius: 10px;
+ border: 2px solid rgb(255 255 255 / 70%);
+ opacity: 0;
+ pointer-events: none;
+ will-change: transform, opacity;
+ }
+
+ &--pulse::before {
+ animation: sat-verify-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+ }
+
+ &--pulse:hover::before {
+ animation-play-state: paused;
+ opacity: 0;
+ }
+}
+
+@keyframes sat-verify-pulse {
+ 0% {
+ transform: scale(1);
+ opacity: 0.85;
+ }
+
+ 70% {
+ transform: scale(1.18);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scale(1.18);
+ opacity: 0;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .sat__hero-verify--pulse::before {
+ animation: none;
+ opacity: 0.5;
+ }
+}
+
+.sat__hero-meta {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: flex-end;
+ padding: 5px 12px;
+ border-radius: 8px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 30%);
+ backdrop-filter: blur(8px);
+ min-width: 130px;
+ flex-shrink: 0;
+}
+
+.sat__hero-meta-label {
+ font-size: 9.5px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.07em;
+ color: rgb(255 255 255 / 78%);
+}
+
+.sat__hero-meta-value {
+ font-size: 15px;
+ font-weight: 800;
+ color: #fff;
+ font-variant-numeric: tabular-nums;
+ letter-spacing: -0.01em;
+}
+
+.sat__hero-meta-sub {
+ font-size: 10px;
+ color: rgb(255 255 255 / 75%);
+ margin-top: 0;
+}
+
+/* ─── Context strip ─────────────────────────────────────────────────── */
+.sat__ctx {
+ display: flex;
+ align-items: stretch;
+ flex-wrap: wrap;
+ gap: 0;
+ padding: 5px 8px;
+ border-radius: 8px;
+ background: rgb(255 255 255 / 12%);
+ border: 1px solid rgb(255 255 255 / 22%);
+ backdrop-filter: blur(8px);
+ position: relative;
+ z-index: 1;
+}
+
+.sat__ctx-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ flex: 1 1 110px;
+ padding: 2px 8px;
+ min-width: 0;
+}
+
+.sat__ctx-ico {
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 25%);
+ color: #fff;
+ font-size: 10px;
+ flex-shrink: 0;
+}
+
+.sat__ctx-text {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ gap: 0;
+}
+
+.sat__ctx-label {
+ font-size: 9px;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: rgb(255 255 255 / 70%);
+ font-weight: 700;
+}
+
+.sat__ctx-value {
+ font-size: 11.5px;
+ font-weight: 700;
+ color: #fff;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &--mono {
+ font-family: 'JetBrains Mono', 'Courier New', monospace;
+ font-size: 10.5px;
+ letter-spacing: 0.02em;
+ }
+
+ &--amount {
+ font-variant-numeric: tabular-nums;
+ font-size: 12px;
+ }
+
+ &--lg {
+ font-size: 13px;
+ }
+}
+
+.sat__ctx-item--balance {
+ background: rgb(255 255 255 / 12%);
+ border: 1px solid rgb(255 255 255 / 22%);
+ border-radius: 7px;
+ padding: 3px 8px;
+}
+
+.sat__ctx-divider {
+ width: 1px;
+ background: rgb(255 255 255 / 18%);
+ margin: 4px 0;
+ flex-shrink: 0;
+
+ @media (width <= 720px) {
+ display: none;
+ }
+}
+
+.sat__ctx-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ height: 22px;
+ padding: 0 9px;
+ border-radius: 999px;
+ font-size: 11.5px;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 30%);
+ color: #fff;
+ align-self: flex-start;
+ width: fit-content;
+
+ fa-icon {
+ font-size: 10px;
+ }
+
+ &--ok {
+ background: rgb(16 185 129 / 36%);
+ border-color: rgb(110 231 183 / 60%);
+ color: #d1fae5;
+ }
+
+ &--warn {
+ background: rgb(245 158 11 / 36%);
+ border-color: rgb(252 211 77 / 60%);
+ color: #fef3c7;
+ }
+
+ &--danger {
+ background: rgb(239 68 68 / 36%);
+ border-color: rgb(252 165 165 / 60%);
+ color: #fee2e2;
+ }
+}
+
+/* ─── Two-column layout ───────────────────────────────────────────── */
+.sat__form {
+ display: grid;
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
+ gap: 14px;
+ align-items: start;
+
+ @media (width <= 1100px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sat__panel {
+ background: #fff;
+ border: 1px solid $border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+}
+
+.sat__panel-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 14px;
+ background: $soft;
+ border-bottom: 1px solid $border;
+}
+
+.sat__panel-ico {
+ width: 24px;
+ height: 24px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 7px;
+ background: linear-gradient(135deg, $green 0%, $green-d 100%);
+ color: #fff;
+ font-size: 11px;
+}
+
+.sat--withdrawal .sat__panel-ico {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
+}
+
+.sat__panel-title {
+ margin: 0;
+ font-size: 12.5px;
+ font-weight: 700;
+ color: $navy;
+ letter-spacing: 0.01em;
+}
+
+.sat__panel-spacer {
+ flex: 1;
+}
+
+.sat__panel-action {
+ height: 26px !important;
+ border-radius: 7px !important;
+ font-size: 11.5px !important;
+ font-weight: 600 !important;
+ padding: 0 10px !important;
+}
+
+.sat__panel-body {
+ padding: 12px 14px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+/* ─── Denomination groups ─────────────────────────────────────────── */
+.sat__denom-group {
+ padding: 8px 14px 4px;
+
+ & + & {
+ border-top: 1px solid #f1f5f9;
+ }
+}
+
+.sat__denom-group-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin-bottom: 5px;
+}
+
+.sat__denom-group-title {
+ font-size: 10px;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 0.07em;
+ color: $slate;
+}
+
+.sat__denom-group-total {
+ font-size: 12px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__denom-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 6px;
+}
+
+.sat__denom {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ padding: 6px 9px;
+ border-radius: 8px;
+ background: #fff;
+ border: 1px solid $border;
+ transition:
+ border-color 0.15s ease,
+ background 0.15s ease,
+ box-shadow 0.15s ease;
+ min-width: 0;
+
+ &--active {
+ border-color: #86efac;
+ background: #f0fdf4;
+ box-shadow: 0 1px 3px -1px rgb(4 120 87 / 18%);
+ }
+}
+
+.sat__denom-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 6px;
+ min-width: 0;
+}
+
+.sat__denom-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 48px;
+ height: 22px;
+ padding: 0 9px;
+ border-radius: 5px;
+ font-size: 11.5px;
+ font-weight: 800;
+ letter-spacing: 0.02em;
+ font-variant-numeric: tabular-nums;
+ flex-shrink: 0;
+
+ &--note {
+ background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%);
+ color: #155e75;
+ border: 1px solid #a5f3fc;
+ }
+
+ &--coin {
+ background: linear-gradient(135deg, #fef9c3 0%, #fde68a 100%);
+ color: #854d0e;
+ border: 1px solid #fcd34d;
+ }
+}
+
+.sat__denom-amount {
+ font-size: 11.5px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 0;
}
-.right-label {
- padding-right: 25px !important;
+.sat__denom-stepper {
+ display: flex;
+ align-items: stretch;
+ height: 28px;
+ border-radius: 6px;
+ border: 1px solid $border;
+ background: #fff;
+ overflow: hidden;
}
-.form-heading {
- font-size: 1.25rem;
+.sat__denom-count {
+ flex: 1 1 auto;
+ width: 100%;
+ min-width: 0;
+ border: none;
+ outline: none;
+ background: transparent;
+ text-align: center;
+ font-size: 12.5px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+ padding: 0 4px;
+ appearance: textfield;
+
+ &::-webkit-outer-spin-button,
+ &::-webkit-inner-spin-button {
+ appearance: none;
+ margin: 0;
+ }
+}
+
+.sat__denom-step {
+ flex: 0 0 auto;
+ width: 28px;
+ background: $soft;
+ border: none;
+ border-right: 1px solid $border;
+ font-size: 15px;
+ font-weight: 700;
+ color: $slate;
+ cursor: pointer;
+ transition:
+ background 0.12s ease,
+ color 0.12s ease;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ line-height: 1;
+ padding: 0;
+
+ &:last-child {
+ border-right: none;
+ border-left: 1px solid $border;
+ }
+
+ &:hover:not(:disabled) {
+ background: #e2e8f0;
+ color: $navy;
+ }
+
+ &:active:not(:disabled) {
+ background: #cbd5e1;
+ }
+
+ &:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+ }
+}
+
+/* ─── Tally / apply ───────────────────────────────────────────────── */
+.sat__tally {
+ margin: 8px 14px 14px;
+ padding: 10px 12px;
+ border-radius: 10px;
+ background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
+ border: 1px solid $border;
+}
+
+.sat__tally-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 3px 0;
+ font-size: 12px;
+ color: $slate;
+
+ &--grand {
+ margin-top: 4px;
+ padding-top: 8px;
+ border-top: 1px dashed #cbd5e1;
+
+ .sat__tally-label {
+ font-weight: 800;
+ color: $navy;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-size: 11px;
+ }
+
+ .sat__tally-value {
+ font-size: 15.5px;
+ font-weight: 800;
+ color: $green-d;
+ }
+ }
+}
+
+.sat--withdrawal .sat__tally-row--grand .sat__tally-value {
+ color: $amber-d;
+}
+
+.sat__tally-value {
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__apply {
+ width: 100%;
+ margin-top: 8px;
+ height: 34px !important;
+ border-radius: 8px !important;
+ background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
+ color: #fff !important;
+ font-weight: 700 !important;
+ font-size: 12.5px !important;
+ letter-spacing: 0.01em;
+ box-shadow: 0 3px 10px -2px rgb(4 120 87 / 35%) !important;
+
+ &:disabled {
+ opacity: 0.5;
+ filter: grayscale(0.4);
+ box-shadow: none !important;
+ }
+}
+
+.sat--withdrawal .sat__apply {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
+ box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
+}
+
+/* ─── Form fields (right panel) ───────────────────────────────────── */
+.sat__field {
+ width: 100%;
+
+ ::ng-deep .mat-mdc-text-field-wrapper {
+ background: #fff;
+ border-radius: 10px;
+ }
+
+ ::ng-deep .mat-mdc-form-field-infix {
+ min-height: 48px;
+ padding-top: 12px !important;
+ padding-bottom: 12px !important;
+ }
+
+ ::ng-deep .mdc-notched-outline__leading,
+ ::ng-deep .mdc-notched-outline__notch,
+ ::ng-deep .mdc-notched-outline__trailing {
+ border-color: #cbd5e1 !important;
+ }
+}
+
+.sat__sub-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px 12px;
+
+ .sat__field--wide {
+ grid-column: 1 / -1;
+ }
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sat__toggle-row {
+ display: flex;
+ align-items: center;
+ padding: 6px 4px 10px;
+ cursor: pointer;
+ user-select: none;
+
+ .sat__toggle-label {
+ margin-left: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ color: $slate;
+ }
+}
+
+/* Match / mismatch banners */
+.sat__mismatch,
+.sat__match {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px;
+ border-radius: 10px;
+ font-size: 12.5px;
font-weight: 500;
- margin-bottom: 0.5rem;
+ margin: 2px 0 8px;
+}
+
+.sat__mismatch {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ color: #92400e;
+
+ &-ico {
+ color: $amber;
+ flex-shrink: 0;
+ }
+
+ strong {
+ font-weight: 800;
+ }
+}
+
+.sat__match {
+ background: #ecfdf5;
+ border: 1px solid #a7f3d0;
+ color: #065f46;
+
+ &-ico {
+ color: $green;
+ flex-shrink: 0;
+ }
+}
+
+/* ─── Actions ─────────────────────────────────────────────────────── */
+.sat__actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 10px;
+ padding-top: 12px;
+ border-top: 1px solid #f1f5f9;
+
+ button {
+ height: 42px !important;
+ border-radius: 10px !important;
+ font-weight: 700 !important;
+ padding: 0 22px !important;
+ letter-spacing: 0.01em;
+ }
+}
+
+.sat__cancel {
+ color: $slate !important;
+ border-color: $border !important;
+}
+
+.sat__submit {
+ background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
+ color: #fff !important;
+ box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
+
+ &:disabled {
+ opacity: 0.55;
+ filter: grayscale(0.3);
+ background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
+ box-shadow: none !important;
+ }
+}
+
+.sat--withdrawal .sat__submit {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
+ box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
+
+ &:disabled {
+ background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
+ }
+}
+
+.m-r-6 {
+ margin-right: 6px;
+}
+
+/* ─── Responsive ──────────────────────────────────────────────────── */
+@media (width <= 720px) {
+ .sat__hero-top {
+ flex-wrap: wrap;
+ }
+
+ .sat__hero-meta {
+ width: 100%;
+ }
+
+ .sat__ctx-item {
+ flex: 1 1 calc(50% - 14px);
+ }
+}
+
+@media (width <= 480px) {
+ .sat__ctx-item {
+ flex: 1 1 100%;
+ }
+
+ .sat__actions {
+ flex-direction: column-reverse;
+
+ button {
+ width: 100%;
+ }
+ }
}
diff --git a/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.ts b/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.ts
index c1ea4cee7..09cad551f 100644
--- a/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.ts
+++ b/src/app/current-accounts/current-account-actions/current-account-transactions/current-account-transactions.component.ts
@@ -19,16 +19,28 @@ import { Router, ActivatedRoute, RouterLink } from '@angular/router';
/** Custom Services */
import { CurrentAccountsService } from '../../current-accounts.service';
+import { ClientsService } from 'app/clients/clients.service';
import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
import { Currency } from 'app/shared/models/general.model';
import { InputAmountComponent } from '../../../shared/input-amount/input-amount.component';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import { MatDialog } from '@angular/material/dialog';
+import { TransactionConfirmDialogComponent } from '../../../savings/saving-account-actions/savings-account-transactions/transaction-confirm-dialog/transaction-confirm-dialog.component';
+import { IdentityVerifyDialogComponent } from '../../../savings/saving-account-actions/savings-account-transactions/identity-verify-dialog/identity-verify-dialog.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+/** Single Indian denomination row (note or coin). */
+interface Denomination {
+ value: number;
+ kind: 'note' | 'coin';
+ label: string;
+}
+
/**
- * Create current account transactions component.
+ * Create current account transactions component (Deposit / Withdraw).
*/
@Component({
selector: 'mifosx-current-account-transactions',
@@ -38,7 +50,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
InputAmountComponent,
MatSlideToggle,
- CdkTextareaAutosize
+ CdkTextareaAutosize,
+ FaIconComponent
]
})
export class CurrentAccountTransactionsComponent implements OnInit {
@@ -47,7 +60,9 @@ export class CurrentAccountTransactionsComponent implements OnInit {
private router = inject(Router);
private dateUtils = inject(Dates);
private currentAccountsService = inject(CurrentAccountsService);
+ private clientsService = inject(ClientsService);
private settingsService = inject(SettingsService);
+ private dialog = inject(MatDialog);
/** Minimum Due Date allowed. */
minDate = new Date(2000, 0, 1);
@@ -73,15 +88,38 @@ export class CurrentAccountTransactionsComponent implements OnInit {
currentAccountId: string;
currency: Currency | null = null;
- /**
- * Retrieves the Current Account transaction template data from `resolve`.
- * @param {FormBuilder} formBuilder Form Builder.
- * @param {CurrentAccountsService} currentAccountsService Current Accounts Service.
- * @param {ActivatedRoute} route Activated Route.
- * @param {Dates} dateUtils Date Utils.
- * @param {Router} router Router for navigation.
- * @param {SettingsService} settingsService Settings Service
- */
+ /** Indian banknote denominations (descending). */
+ readonly notes: Denomination[] = [
+ { value: 500, kind: 'note', label: '₹500' },
+ { value: 200, kind: 'note', label: '₹200' },
+ { value: 100, kind: 'note', label: '₹100' },
+ { value: 50, kind: 'note', label: '₹50' },
+ { value: 20, kind: 'note', label: '₹20' },
+ { value: 10, kind: 'note', label: '₹10' }
+ ];
+
+ /** Indian coin denominations (descending). */
+ readonly coins: Denomination[] = [
+ { value: 20, kind: 'coin', label: '₹20' },
+ { value: 10, kind: 'coin', label: '₹10' },
+ { value: 5, kind: 'coin', label: '₹5' },
+ { value: 2, kind: 'coin', label: '₹2' },
+ { value: 1, kind: 'coin', label: 'Re 1' }
+ ];
+
+ /** Quantity per denomination — keyed by `${kind}:${value}`. */
+ denominationCounts: Record
= {};
+
+ // ── Context strip data ──────────────────────────────────────────────
+ accountNo: string | null = null;
+ clientName: string | null = null;
+ clientId: number | null = null;
+ kycStatus: 'Verified' | 'Partial' | 'Pending' = 'Pending';
+ sameDayNeftRtgs = 0;
+ sameDayChequeClearing = 0;
+ availableBalance = 0;
+ identityVerified = false;
+
constructor() {
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
this.paymentTypeOptions = data.currentAccountActionData.paymentTypeOptions;
@@ -94,17 +132,93 @@ export class CurrentAccountTransactionsComponent implements OnInit {
this.currentAccountId = this.route.snapshot.params['currentAccountId'];
}
- /**
- * Creates the Current Account account transaction form when component loads.
- */
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
this.createCurrentAccountTransactionForm();
+ this.loadContextStrip();
+ }
+
+ private loadContextStrip(): void {
+ if (!this.currentAccountId) return;
+ this.currentAccountsService.getCurrentAccountData(this.currentAccountId).subscribe({
+ next: (account: any) => {
+ this.accountNo = account?.accountNo ?? null;
+ this.clientName = account?.clientName ?? account?.groupName ?? null;
+ this.clientId = account?.clientId ?? null;
+ if (!this.currency && account?.currency) this.currency = account.currency;
+ this.availableBalance = Number(account?.summary?.availableBalance ?? account?.summary?.accountBalance ?? 0);
+ this.computeSameDayNeftRtgs(account?.transactions);
+ this.computeSameDayChequeClearing(account?.transactions);
+ if (account?.clientId) {
+ this.fetchClientKyc(String(account.clientId));
+ }
+ },
+ error: () => {}
+ });
+ }
+
+ private fetchClientKyc(clientId: string): void {
+ this.clientsService.getClientKyc(clientId).subscribe({
+ next: (kyc: any) => {
+ const pan = !!kyc?.panVerified;
+ const aadhaar = !!kyc?.aadhaarVerified;
+ this.kycStatus = pan && aadhaar ? 'Verified' : pan || aadhaar ? 'Partial' : 'Pending';
+ },
+ error: () => {}
+ });
+ }
+
+ private computeSameDayNeftRtgs(transactions: any[] | undefined): void {
+ if (!Array.isArray(transactions) || transactions.length === 0) {
+ this.sameDayNeftRtgs = 0;
+ return;
+ }
+ const today = this.settingsService.businessDate ?? new Date();
+ const sameDay = (d: any) => {
+ if (!d) return false;
+ const t = Array.isArray(d) && d.length >= 3 ? new Date(d[0], d[1] - 1, d[2]) : new Date(d);
+ return (
+ t.getFullYear() === today.getFullYear() && t.getMonth() === today.getMonth() && t.getDate() === today.getDate()
+ );
+ };
+ const isNeftRtgs = (t: any) => {
+ const txnType = (t?.transactionType?.value ?? t?.transactionType?.code ?? '').toUpperCase();
+ const payType = (t?.paymentDetailData?.paymentType?.name ?? '').toUpperCase();
+ return /NEFT|RTGS/.test(txnType) || /NEFT|RTGS/.test(payType);
+ };
+ this.sameDayNeftRtgs = transactions
+ .filter((t: any) => !t.reversed && sameDay(t.date) && isNeftRtgs(t))
+ .reduce((sum, t: any) => sum + (Number(t.amount) || 0), 0);
+ }
+
+ private computeSameDayChequeClearing(transactions: any[] | undefined): void {
+ if (!Array.isArray(transactions) || transactions.length === 0) {
+ this.sameDayChequeClearing = 0;
+ return;
+ }
+ const today = this.settingsService.businessDate ?? new Date();
+ const sameDay = (d: any) => {
+ if (!d) return false;
+ const t = Array.isArray(d) && d.length >= 3 ? new Date(d[0], d[1] - 1, d[2]) : new Date(d);
+ return (
+ t.getFullYear() === today.getFullYear() && t.getMonth() === today.getMonth() && t.getDate() === today.getDate()
+ );
+ };
+ const isCheque = (t: any) => {
+ const txnType = (t?.transactionType?.value ?? t?.transactionType?.code ?? '').toUpperCase();
+ const payType = (t?.paymentDetailData?.paymentType?.name ?? '').toUpperCase();
+ const hasCheckNo = !!t?.paymentDetailData?.checkNumber;
+ return hasCheckNo || /CHEQUE|CHECK/.test(txnType) || /CHEQUE|CHECK/.test(payType);
+ };
+ const isPending = (t: any) => {
+ const cleared = t?.transactionType?.bankTransfer === true || t?.transactionType?.deposit === true;
+ return !cleared;
+ };
+ this.sameDayChequeClearing = transactions
+ .filter((t: any) => !t.reversed && sameDay(t.date) && isCheque(t) && isPending(t))
+ .reduce((sum, t: any) => sum + (Number(t.amount) || 0), 0);
}
- /**
- * Method to create the Current Account Transaction Form.
- */
createCurrentAccountTransactionForm() {
this.currentAccountTransactionForm = this.formBuilder.group({
transactionDate: [
@@ -123,6 +237,75 @@ export class CurrentAccountTransactionsComponent implements OnInit {
});
}
+ // ── Denomination helpers ────────────────────────────────────────────
+ denomKey(d: Denomination): string {
+ return `${d.kind}:${d.value}`;
+ }
+
+ countFor(d: Denomination): number {
+ return this.denominationCounts[this.denomKey(d)] || 0;
+ }
+
+ subtotalFor(d: Denomination): number {
+ return this.countFor(d) * d.value;
+ }
+
+ onDenomChange(d: Denomination, raw: any): void {
+ const n = Math.max(0, Math.floor(Number(raw) || 0));
+ this.denominationCounts[this.denomKey(d)] = n;
+ }
+
+ bumpDenom(d: Denomination, delta: number): void {
+ const next = Math.max(0, this.countFor(d) + delta);
+ this.denominationCounts[this.denomKey(d)] = next;
+ }
+
+ get notesTotal(): number {
+ return this.notes.reduce((s, d) => s + this.subtotalFor(d), 0);
+ }
+
+ get coinsTotal(): number {
+ return this.coins.reduce((s, d) => s + this.subtotalFor(d), 0);
+ }
+
+ get denominationTotal(): number {
+ return this.notesTotal + this.coinsTotal;
+ }
+
+ get totalPieces(): number {
+ return Object.values(this.denominationCounts).reduce((s, n) => s + (n || 0), 0);
+ }
+
+ get denominationMatchesForm(): boolean {
+ const formAmt = Number(this.currentAccountTransactionForm?.controls?.['transactionAmount']?.value || 0);
+ return this.denominationTotal > 0 && formAmt === this.denominationTotal;
+ }
+
+ applyDenominationTotal(): void {
+ this.currentAccountTransactionForm.controls['transactionAmount'].setValue(this.denominationTotal);
+ this.currentAccountTransactionForm.controls['transactionAmount'].markAsDirty();
+ }
+
+ clearDenominations(): void {
+ this.denominationCounts = {};
+ }
+
+ openIdentityVerify(): void {
+ if (!this.clientId) return;
+ this.identityVerified = true;
+ this.dialog.open(IdentityVerifyDialogComponent, {
+ width: '500px',
+ maxWidth: '92vw',
+ panelClass: 'idv-panel',
+ autoFocus: false,
+ data: {
+ clientId: this.clientId,
+ clientName: this.clientName,
+ accountNo: this.accountNo
+ }
+ });
+ }
+
/**
* Method to add payment detail fields to the UI.
*/
@@ -144,9 +327,35 @@ export class CurrentAccountTransactionsComponent implements OnInit {
}
/**
- * Method to submit the transaction details.
+ * Opens a confirmation dialog then posts the transaction.
*/
submit() {
+ const formAmount = Number(this.currentAccountTransactionForm.value.transactionAmount) || 0;
+ const paymentTypeId = this.currentAccountTransactionForm.value.paymentTypeId;
+ const paymentTypeLabel = this.paymentTypeOptions?.find((p) => p.id === paymentTypeId)?.name ?? null;
+
+ const ref = this.dialog.open(TransactionConfirmDialogComponent, {
+ width: '480px',
+ maxWidth: '92vw',
+ panelClass: 'tcd-panel',
+ autoFocus: false,
+ data: {
+ kind: this.transactionType.deposit ? 'deposit' : 'withdrawal',
+ clientName: this.clientName,
+ accountNo: this.accountNo,
+ currentBalance: this.availableBalance,
+ transactionAmount: formAmount,
+ paymentTypeLabel
+ }
+ });
+ ref.afterClosed().subscribe((result: any) => {
+ if (result?.confirm) {
+ this.postTransaction();
+ }
+ });
+ }
+
+ private postTransaction(): void {
const currentAccountTransactionFormData = this.currentAccountTransactionForm.value;
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
@@ -160,9 +369,23 @@ export class CurrentAccountTransactionsComponent implements OnInit {
locale
};
data['transactionAmount'] = data['transactionAmount'] * 1;
+ if (this.denominationTotal > 0) {
+ const breakdown = [
+ ...this.notes,
+ ...this.coins
+ ]
+ .map((d) => ({
+ denomination: d.value,
+ kind: d.kind,
+ count: this.countFor(d),
+ subtotal: this.subtotalFor(d)
+ }))
+ .filter((row) => row.count > 0);
+ data['denominationBreakdown'] = breakdown;
+ }
this.currentAccountsService
.executeCurrentAccountTransactionsCommand(this.currentAccountId, this.transactionCommand, data)
- .subscribe((res) => {
+ .subscribe(() => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
});
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts
index e7ec51bda..9365357c0 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts
@@ -215,6 +215,8 @@ export class FixedDepositAccountViewComponent implements OnInit {
case 'Add Charge':
case 'Withdrawn by Client':
case 'Premature Close':
+ case 'Deposit':
+ case 'Withdrawal':
this.router.navigate([`actions/${name}`], { relativeTo: this.route });
break;
case 'Modify Application':
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts
index 79e315a0e..ca1b56aee 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts
@@ -34,6 +34,14 @@ export class FixedDepositsButtonsConfiguration {
switch (status) {
case 'Active':
this.buttonsArray = [
+ {
+ name: 'Deposit',
+ icon: 'fa fa-arrow-down'
+ },
+ {
+ name: 'Withdrawal',
+ icon: 'fa fa-arrow-up'
+ },
{
name: 'Premature Close',
icon: 'fa fa-arrow-left'
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.html b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.html
index 6ccb3ba3b..f20e46af8 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.html
+++ b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.html
@@ -30,6 +30,6 @@
@if (actions['Close']) {
}
-@if (actions['Withdrawal']) {
+@if (actions['Withdrawal'] || actions['Deposit']) {
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.ts b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.ts
index 6b856bcad..e0b47a2f7 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-account-actions.component.ts
@@ -55,6 +55,7 @@ export class FixedDepositsAccountActionsComponent {
'Premature Close': boolean;
'Withdrawn by Client': boolean;
Withdrawal: boolean;
+ Deposit: boolean;
} = {
Approve: false,
Reject: false,
@@ -65,7 +66,8 @@ export class FixedDepositsAccountActionsComponent {
'Add Charge': false,
'Premature Close': false,
'Withdrawn by Client': false,
- Withdrawal: false
+ Withdrawal: false,
+ Deposit: false
};
/**
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.html b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.html
index 08f39216c..c9074136e 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.html
+++ b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.html
@@ -6,28 +6,240 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
-
-
- @if (transactionType.withdrawal) {
-
- {{ 'labels.menus.' + actionName | translate }} {{ 'labels.heading.Fixed Deposit Transactions' | translate }}
-
- }
+
+
+
+
+
+
+ {{ 'labels.buttons.Back' | translate }}
+
+
+
+
+
+
+ {{
+ transactionType.withdrawal
+ ? ('labels.heading.Withdraw Money From Fixed Deposit' | translate)
+ : ('labels.heading.Deposit Money To Fixed Deposit' | translate)
+ }}
+
+
+ {{
+ transactionType.deposit
+ ? 'Count the cash by denomination, then confirm the payment details.'
+ : 'Disburse cash by denomination and confirm the payment details.'
+ }}
+
+
+ @if (transactionType.withdrawal && clientId) {
+
+
+ Verify Identity
+
+ }
+
+ Counted Total
+ ₹{{ denominationTotal | number: '1.2-2' }}
+ @if (totalPieces > 0) {
+ {{ totalPieces }} piece(s)
+ }
+
+
-
-
-
+
+
+
+
+
+ Client
+ {{ clientName || '—' }}
+
+
+
+
+
+
+ Account No
+ {{ accountNo || '—' }}
+
+
+
+
+
+
+ Current Balance
+
+ ₹{{ availableBalance | number: '1.2-2' }}
+
+
+
+
+
+
+
+ KYC
+
+
+ {{ kycStatus }}
+
+
+
+
+
+
+
+
+
+
+
+ Cash Denomination Breakdown
+
+
+ Clear
+
+
+
+
+
+ Banknotes
+ ₹{{ notesTotal | number: '1.2-2' }}
+
+
+ @for (d of notes; track d.value) {
+
0">
+
+ {{ d.label }}
+ ₹{{ subtotalFor(d) | number: '1.2-2' }}
+
+
+
+ −
+
+
+ +
+
+
+ }
+
+
+
+
+
+ Coins
+ ₹{{ coinsTotal | number: '1.2-2' }}
+
+
+ @for (d of coins; track d.value) {
+
0">
+
+ {{ d.label }}
+ ₹{{ subtotalFor(d) | number: '1.2-2' }}
+
+
+
+ −
+
+
+ +
+
+
+ }
+
+
+
+
+
+ Banknotes
+ ₹{{ notesTotal | number: '1.2-2' }}
+
+
+ Coins
+ ₹{{ coinsTotal | number: '1.2-2' }}
+
+
+ Grand Total
+ ₹{{ denominationTotal | number: '1.2-2' }}
+
+
+
+ Use ₹{{ denominationTotal | number: '1.2-2' }} as Transaction Amount
+
+
+
+
+
+
+
+
+ Transaction Details
+
+
+
{{ 'labels.inputs.Transaction Date' | translate }}
-
-
+
+
@if (accountTransactionForm.controls.transactionDate.hasError('required')) {
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
@@ -44,76 +256,78 @@
>
-
+ @if (denominationTotal > 0 && !denominationMatchesForm) {
+
+
+
+ Counted cash ₹{{ denominationTotal | number: '1.2-2' }} doesn't match. Click
+ Use ₹{{ denominationTotal | number: '1.2-2' }} to sync.
+
+
+ }
+ @if (denominationMatchesForm) {
+
+
+ Cash count matches transaction amount.
+
+ }
+
+
{{ 'labels.inputs.Payment Type' | translate }}
-
+
@for (paymentType of paymentTypeOptions; track paymentType) {
-
- {{ paymentType.name }}
-
+ {{ paymentType.name }}
}
-
+
+
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.scss b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.scss
index 3795a54c2..d7810f153 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.scss
+++ b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.scss
@@ -6,18 +6,754 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-.expandcollapsebutton {
- margin-top: -7px;
+$navy: #0d1b2a;
+$slate: #475569;
+$border: #e2e8f0;
+$soft: #f8fafc;
+$green: #059669;
+$green-d: #047857;
+$amber: #d97706;
+$amber-d: #b45309;
+
+.sat {
+ width: 100%;
+ padding: 10px 16px 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
-.container {
- max-width: 37rem;
+.sat__hero {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px 16px 12px;
+ border-radius: 12px;
+ color: #fff;
+ background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #1e40af 100%);
+ box-shadow: 0 6px 18px -10px rgb(15 23 42 / 50%);
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
+ pointer-events: none;
+ }
}
-.right-input {
+.sat--withdrawal .sat__hero {
+ background: linear-gradient(135deg, #134e4a 0%, #115e59 45%, #0f766e 100%);
+ box-shadow: 0 6px 18px -10px rgb(15 118 110 / 50%);
+}
+
+.sat__hero-top {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ position: relative;
+ z-index: 1;
+}
+
+.sat__hero-back {
+ height: 30px !important;
+ border-radius: 8px !important;
+ padding: 0 11px !important;
+ font-weight: 700 !important;
+ font-size: 12px !important;
+ background: rgb(255 255 255 / 14%) !important;
+ color: #fff !important;
+ border: 1px solid rgb(255 255 255 / 28%) !important;
+ flex-shrink: 0;
+
+ &:hover {
+ background: rgb(255 255 255 / 26%) !important;
+ }
+}
+
+.sat__hero-ico {
+ width: 36px;
+ height: 36px;
+ border-radius: 9px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 28%);
+ color: #fff;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 15px;
+ flex-shrink: 0;
+}
+
+.sat__hero-title-wrap {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+.sat__title {
+ margin: 0;
+ font-size: 14.5px;
+ font-weight: 800;
+ letter-spacing: 0.01em;
+ line-height: 1.15;
+}
+
+.sat__sub {
+ margin: 1px 0 0;
+ font-size: 11.5px;
+ color: rgb(255 255 255 / 80%);
+}
+
+.sat__hero-verify {
+ position: relative;
+ height: 30px !important;
+ border-radius: 8px !important;
+ padding: 0 12px !important;
+ font-weight: 700 !important;
+ font-size: 12px !important;
+ color: #fff !important;
+ background: rgb(255 255 255 / 16%) !important;
+ border: 1px solid rgb(255 255 255 / 30%) !important;
+ flex-shrink: 0;
+ overflow: visible !important;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: -2px;
+ border-radius: 10px;
+ border: 2px solid rgb(255 255 255 / 70%);
+ opacity: 0;
+ pointer-events: none;
+ will-change: transform, opacity;
+ }
+
+ &--pulse::before {
+ animation: sat-verify-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+ }
+}
+
+@keyframes sat-verify-pulse {
+ 0% {
+ transform: scale(1);
+ opacity: 0.85;
+ }
+
+ 70% {
+ transform: scale(1.18);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scale(1.18);
+ opacity: 0;
+ }
+}
+
+.sat__hero-meta {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: flex-end;
+ padding: 5px 12px;
+ border-radius: 8px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 30%);
+ min-width: 130px;
+ flex-shrink: 0;
+}
+
+.sat__hero-meta-label {
+ font-size: 9.5px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.07em;
+ color: rgb(255 255 255 / 78%);
+}
+
+.sat__hero-meta-value {
+ font-size: 15px;
+ font-weight: 800;
+ color: #fff;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__hero-meta-sub {
+ font-size: 10px;
+ color: rgb(255 255 255 / 75%);
+}
+
+.sat__ctx {
+ display: flex;
+ align-items: stretch;
+ flex-wrap: wrap;
+ padding: 5px 8px;
+ border-radius: 8px;
+ background: rgb(255 255 255 / 12%);
+ border: 1px solid rgb(255 255 255 / 22%);
+ position: relative;
+ z-index: 1;
+}
+
+.sat__ctx-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ flex: 1 1 110px;
+ padding: 2px 8px;
+ min-width: 0;
+}
+
+.sat__ctx-ico {
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 25%);
+ color: #fff;
+ font-size: 10px;
+ flex-shrink: 0;
+}
+
+.sat__ctx-text {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+}
+
+.sat__ctx-label {
+ font-size: 9px;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: rgb(255 255 255 / 70%);
+ font-weight: 700;
+}
+
+.sat__ctx-value {
+ font-size: 11.5px;
+ font-weight: 700;
+ color: #fff;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &--mono {
+ font-family: 'Courier New', monospace;
+ font-size: 10.5px;
+ }
+
+ &--amount {
+ font-variant-numeric: tabular-nums;
+ font-size: 12px;
+ }
+
+ &--lg {
+ font-size: 13px;
+ }
+}
+
+.sat__ctx-item--balance {
+ background: rgb(255 255 255 / 12%);
+ border: 1px solid rgb(255 255 255 / 22%);
+ border-radius: 7px;
+ padding: 3px 8px;
+}
+
+.sat__ctx-divider {
+ width: 1px;
+ background: rgb(255 255 255 / 18%);
+ margin: 4px 0;
+ flex-shrink: 0;
+
+ @media (width <= 720px) {
+ display: none;
+ }
+}
+
+.sat__ctx-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ height: 22px;
+ padding: 0 9px;
+ border-radius: 999px;
+ font-size: 11.5px;
+ font-weight: 800;
+ text-transform: uppercase;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 30%);
+ color: #fff;
+ align-self: flex-start;
+ width: fit-content;
+
+ fa-icon {
+ font-size: 10px;
+ }
+
+ &--ok {
+ background: rgb(16 185 129 / 36%);
+ border-color: rgb(110 231 183 / 60%);
+ color: #d1fae5;
+ }
+
+ &--warn {
+ background: rgb(245 158 11 / 36%);
+ border-color: rgb(252 211 77 / 60%);
+ color: #fef3c7;
+ }
+
+ &--danger {
+ background: rgb(239 68 68 / 36%);
+ border-color: rgb(252 165 165 / 60%);
+ color: #fee2e2;
+ }
+}
+
+.sat__form {
+ display: grid;
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
+ gap: 14px;
+ align-items: start;
+
+ @media (width <= 1100px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sat__panel {
+ background: #fff;
+ border: 1px solid $border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+}
+
+.sat__panel-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 14px;
+ background: $soft;
+ border-bottom: 1px solid $border;
+}
+
+.sat__panel-ico {
+ width: 24px;
+ height: 24px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 7px;
+ background: linear-gradient(135deg, $green 0%, $green-d 100%);
+ color: #fff;
+ font-size: 11px;
+}
+
+.sat--withdrawal .sat__panel-ico {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
+}
+
+.sat__panel-title {
+ margin: 0;
+ font-size: 12.5px;
+ font-weight: 700;
+ color: $navy;
+}
+
+.sat__panel-spacer {
+ flex: 1;
+}
+
+.sat__panel-action {
+ height: 26px !important;
+ border-radius: 7px !important;
+ font-size: 11.5px !important;
+ font-weight: 600 !important;
+ padding: 0 10px !important;
+}
+
+.sat__panel-body {
+ padding: 12px 14px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+.sat__denom-group {
+ padding: 8px 14px 4px;
+
+ & + & {
+ border-top: 1px solid #f1f5f9;
+ }
+}
+
+.sat__denom-group-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin-bottom: 5px;
+}
+
+.sat__denom-group-title {
+ font-size: 10px;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 0.07em;
+ color: $slate;
+}
+
+.sat__denom-group-total {
+ font-size: 12px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__denom-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 6px;
+}
+
+.sat__denom {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ padding: 6px 9px;
+ border-radius: 8px;
+ background: #fff;
+ border: 1px solid $border;
+ transition:
+ border-color 0.15s ease,
+ background 0.15s ease;
+ min-width: 0;
+
+ &--active {
+ border-color: #86efac;
+ background: #f0fdf4;
+ box-shadow: 0 1px 3px -1px rgb(4 120 87 / 18%);
+ }
+}
+
+.sat__denom-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 6px;
+ min-width: 0;
+}
+
+.sat__denom-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 48px;
+ height: 22px;
+ padding: 0 9px;
+ border-radius: 5px;
+ font-size: 11.5px;
+ font-weight: 800;
+ flex-shrink: 0;
+
+ &--note {
+ background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%);
+ color: #155e75;
+ border: 1px solid #a5f3fc;
+ }
+
+ &--coin {
+ background: linear-gradient(135deg, #fef9c3 0%, #fde68a 100%);
+ color: #854d0e;
+ border: 1px solid #fcd34d;
+ }
+}
+
+.sat__denom-amount {
+ font-size: 11.5px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 0;
}
-.right-label {
- padding-right: 25px !important;
+.sat__denom-stepper {
+ display: flex;
+ align-items: stretch;
+ height: 28px;
+ border-radius: 6px;
+ border: 1px solid $border;
+ overflow: hidden;
+}
+
+.sat__denom-count {
+ flex: 1 1 auto;
+ width: 100%;
+ min-width: 0;
+ border: none;
+ outline: none;
+ background: transparent;
+ text-align: center;
+ font-size: 12.5px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+ padding: 0 4px;
+ appearance: textfield;
+
+ &::-webkit-outer-spin-button,
+ &::-webkit-inner-spin-button {
+ appearance: none;
+ margin: 0;
+ }
+}
+
+.sat__denom-step {
+ flex: 0 0 auto;
+ width: 28px;
+ background: $soft;
+ border: none;
+ border-right: 1px solid $border;
+ font-size: 15px;
+ font-weight: 700;
+ color: $slate;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+
+ &:last-child {
+ border-right: none;
+ border-left: 1px solid $border;
+ }
+
+ &:hover:not(:disabled) {
+ background: #e2e8f0;
+ color: $navy;
+ }
+
+ &:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+ }
+}
+
+.sat__tally {
+ margin: 8px 14px 14px;
+ padding: 10px 12px;
+ border-radius: 10px;
+ background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
+ border: 1px solid $border;
+}
+
+.sat__tally-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 3px 0;
+ font-size: 12px;
+ color: $slate;
+
+ &--grand {
+ margin-top: 4px;
+ padding-top: 8px;
+ border-top: 1px dashed #cbd5e1;
+
+ .sat__tally-label {
+ font-weight: 800;
+ color: $navy;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-size: 11px;
+ }
+
+ .sat__tally-value {
+ font-size: 15.5px;
+ font-weight: 800;
+ color: $green-d;
+ }
+ }
+}
+
+.sat--withdrawal .sat__tally-row--grand .sat__tally-value {
+ color: $amber-d;
+}
+
+.sat__tally-value {
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__apply {
+ width: 100%;
+ margin-top: 8px;
+ height: 34px !important;
+ border-radius: 8px !important;
+ background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
+ color: #fff !important;
+ font-weight: 700 !important;
+ font-size: 12.5px !important;
+ box-shadow: 0 3px 10px -2px rgb(4 120 87 / 35%) !important;
+
+ &:disabled {
+ opacity: 0.5;
+ filter: grayscale(0.4);
+ box-shadow: none !important;
+ }
+}
+
+.sat--withdrawal .sat__apply {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
+}
+
+.sat__field {
+ width: 100%;
+
+ ::ng-deep .mat-mdc-text-field-wrapper {
+ background: #fff;
+ border-radius: 10px;
+ }
+
+ ::ng-deep .mat-mdc-form-field-infix {
+ min-height: 48px;
+ padding-top: 12px !important;
+ padding-bottom: 12px !important;
+ }
+
+ ::ng-deep .mdc-notched-outline__leading,
+ ::ng-deep .mdc-notched-outline__notch,
+ ::ng-deep .mdc-notched-outline__trailing {
+ border-color: #cbd5e1 !important;
+ }
+}
+
+.sat__sub-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px 12px;
+
+ .sat__field--wide {
+ grid-column: 1 / -1;
+ }
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sat__toggle-row {
+ display: flex;
+ align-items: center;
+ padding: 6px 4px 10px;
+ cursor: pointer;
+ user-select: none;
+
+ .sat__toggle-label {
+ margin-left: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ color: $slate;
+ }
+}
+
+.sat__mismatch,
+.sat__match {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px;
+ border-radius: 10px;
+ font-size: 12.5px;
+ margin: 2px 0 8px;
+}
+
+.sat__mismatch {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ color: #92400e;
+
+ &-ico {
+ color: $amber;
+ flex-shrink: 0;
+ }
+
+ strong {
+ font-weight: 800;
+ }
+}
+
+.sat__match {
+ background: #ecfdf5;
+ border: 1px solid #a7f3d0;
+ color: #065f46;
+
+ &-ico {
+ color: $green;
+ flex-shrink: 0;
+ }
+}
+
+.sat__actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 10px;
+ padding-top: 12px;
+ border-top: 1px solid #f1f5f9;
+
+ button {
+ height: 42px !important;
+ border-radius: 10px !important;
+ font-weight: 700 !important;
+ padding: 0 22px !important;
+ }
+}
+
+.sat__cancel {
+ color: $slate !important;
+ border-color: $border !important;
+}
+
+.sat__submit {
+ background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
+ color: #fff !important;
+ box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
+
+ &:disabled {
+ opacity: 0.55;
+ background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
+ box-shadow: none !important;
+ }
+}
+
+.sat--withdrawal .sat__submit {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
+ box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
+}
+
+.m-r-6 {
+ margin-right: 6px;
+}
+
+@media (width <= 720px) {
+ .sat__hero-top {
+ flex-wrap: wrap;
+ }
+
+ .sat__hero-meta {
+ width: 100%;
+ }
+
+ .sat__ctx-item {
+ flex: 1 1 calc(50% - 14px);
+ }
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.ts b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.ts
index 607b26037..77a72c092 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposits-account-actions/fixed-deposits-cash-transaction/fixed-deposits-cash-transaction.component.ts
@@ -15,6 +15,7 @@ import {
ReactiveFormsModule
} from '@angular/forms';
import { FixedDepositsService } from '../../fixed-deposits.service';
+import { ClientsService } from 'app/clients/clients.service';
import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
@@ -23,8 +24,18 @@ import { TransactionCommand, TransactionTypeFlags } from '../../../transaction.m
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import { MatDialog } from '@angular/material/dialog';
+import { TransactionConfirmDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/transaction-confirm-dialog/transaction-confirm-dialog.component';
+import { IdentityVerifyDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/identity-verify-dialog/identity-verify-dialog.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+interface Denomination {
+ value: number;
+ kind: 'note' | 'coin';
+ label: string;
+}
+
@Component({
selector: 'mifosx-fixed-deposits-cash-transaction',
templateUrl: './fixed-deposits-cash-transaction.component.html',
@@ -33,7 +44,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
InputAmountComponent,
MatSlideToggle,
- CdkTextareaAutosize
+ CdkTextareaAutosize,
+ FaIconComponent
]
})
export class FixedDepositsCashTransactionComponent implements OnInit {
@@ -42,36 +54,47 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
private router = inject(Router);
private dateUtils = inject(Dates);
private fixedDepositsService = inject(FixedDepositsService);
+ private clientsService = inject(ClientsService);
private settingsService = inject(SettingsService);
+ private dialog = inject(MatDialog);
- /** Minimum Due Date allowed. */
minDate = new Date(2000, 0, 1);
- /** Maximum Due Date allowed. */
maxDate = new Date();
- /** Savings account transaction form. */
accountTransactionForm: UntypedFormGroup;
- /** savings account transaction payment options. */
paymentTypeOptions: PaymentType[] = [];
- /** Flag to enable payment details fields. */
addPaymentDetailsFlag: Boolean = false;
- /** transaction type flag to render required UI */
transactionType: TransactionTypeFlags = { deposit: false, withdrawal: false };
- /** transaction command for submit request */
transactionCommand: TransactionCommand;
actionName: string;
- /** saving account's Id */
accountId: string;
currency: Currency;
- /**
- * Retrieves the Saving Account transaction template data from `resolve`.
- * @param {FormBuilder} formBuilder Form Builder.
- * @param {FixedDepositsService} fixedDepositsService Fixed Deposit Service.
- * @param {ActivatedRoute} route Activated Route.
- * @param {Dates} dateUtils Date Utils.
- * @param {Router} router Router for navigation.
- * @param {SettingsService} settingsService Settings Service
- */
+ readonly notes: Denomination[] = [
+ { value: 500, kind: 'note', label: '₹500' },
+ { value: 200, kind: 'note', label: '₹200' },
+ { value: 100, kind: 'note', label: '₹100' },
+ { value: 50, kind: 'note', label: '₹50' },
+ { value: 20, kind: 'note', label: '₹20' },
+ { value: 10, kind: 'note', label: '₹10' }
+ ];
+
+ readonly coins: Denomination[] = [
+ { value: 20, kind: 'coin', label: '₹20' },
+ { value: 10, kind: 'coin', label: '₹10' },
+ { value: 5, kind: 'coin', label: '₹5' },
+ { value: 2, kind: 'coin', label: '₹2' },
+ { value: 1, kind: 'coin', label: 'Re 1' }
+ ];
+
+ denominationCounts: Record = {};
+
+ accountNo: string | null = null;
+ clientName: string | null = null;
+ clientId: number | null = null;
+ kycStatus: 'Verified' | 'Partial' | 'Pending' = 'Pending';
+ availableBalance = 0;
+ identityVerified = false;
+
constructor() {
this.route.data.subscribe((data: { fixedDepositsAccountActionData: any }) => {
this.currency = data.fixedDepositsAccountActionData.currency;
@@ -85,21 +108,43 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
} else {
throw new Error(`Invalid transaction action: ${this.actionName}`);
}
-
this.accountId = this.route.parent.snapshot.params['fixedDepositAccountId'];
}
- /**
- * Creates the Saving account transaction form when component loads.
- */
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
this.createSavingAccountTransactionForm();
+ this.loadContextStrip();
+ }
+
+ private loadContextStrip(): void {
+ if (!this.accountId) return;
+ this.fixedDepositsService.getFixedDepositsAccountData(this.accountId).subscribe({
+ next: (account: any) => {
+ this.accountNo = account?.accountNo ?? null;
+ this.clientName = account?.clientName ?? account?.groupName ?? null;
+ this.clientId = account?.clientId ?? null;
+ if (!this.currency && account?.currency) this.currency = account.currency;
+ this.availableBalance = Number(account?.summary?.accountBalance ?? account?.summary?.availableBalance ?? 0);
+ if (account?.clientId) {
+ this.fetchClientKyc(String(account.clientId));
+ }
+ },
+ error: () => {}
+ });
+ }
+
+ private fetchClientKyc(clientId: string): void {
+ this.clientsService.getClientKyc(clientId).subscribe({
+ next: (kyc: any) => {
+ const pan = !!kyc?.panVerified;
+ const aadhaar = !!kyc?.aadhaarVerified;
+ this.kycStatus = pan && aadhaar ? 'Verified' : pan || aadhaar ? 'Partial' : 'Pending';
+ },
+ error: () => {}
+ });
}
- /**
- * Method to create the Saving Account Transaction Form.
- */
createSavingAccountTransactionForm() {
this.accountTransactionForm = this.formBuilder.group({
transactionDate: [
@@ -115,9 +160,71 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
});
}
- /**
- * Method to add payment detail fields to the UI.
- */
+ // ── Denomination helpers ────────────────────────────────────────────
+ denomKey(d: Denomination): string {
+ return `${d.kind}:${d.value}`;
+ }
+
+ countFor(d: Denomination): number {
+ return this.denominationCounts[this.denomKey(d)] || 0;
+ }
+
+ subtotalFor(d: Denomination): number {
+ return this.countFor(d) * d.value;
+ }
+
+ onDenomChange(d: Denomination, raw: any): void {
+ const n = Math.max(0, Math.floor(Number(raw) || 0));
+ this.denominationCounts[this.denomKey(d)] = n;
+ }
+
+ bumpDenom(d: Denomination, delta: number): void {
+ const next = Math.max(0, this.countFor(d) + delta);
+ this.denominationCounts[this.denomKey(d)] = next;
+ }
+
+ get notesTotal(): number {
+ return this.notes.reduce((s, d) => s + this.subtotalFor(d), 0);
+ }
+
+ get coinsTotal(): number {
+ return this.coins.reduce((s, d) => s + this.subtotalFor(d), 0);
+ }
+
+ get denominationTotal(): number {
+ return this.notesTotal + this.coinsTotal;
+ }
+
+ get totalPieces(): number {
+ return Object.values(this.denominationCounts).reduce((s, n) => s + (n || 0), 0);
+ }
+
+ get denominationMatchesForm(): boolean {
+ const formAmt = Number(this.accountTransactionForm?.controls?.['transactionAmount']?.value || 0);
+ return this.denominationTotal > 0 && formAmt === this.denominationTotal;
+ }
+
+ applyDenominationTotal(): void {
+ this.accountTransactionForm.controls['transactionAmount'].setValue(this.denominationTotal);
+ this.accountTransactionForm.controls['transactionAmount'].markAsDirty();
+ }
+
+ clearDenominations(): void {
+ this.denominationCounts = {};
+ }
+
+ openIdentityVerify(): void {
+ if (!this.clientId) return;
+ this.identityVerified = true;
+ this.dialog.open(IdentityVerifyDialogComponent, {
+ width: '500px',
+ maxWidth: '92vw',
+ panelClass: 'idv-panel',
+ autoFocus: false,
+ data: { clientId: this.clientId, clientName: this.clientName, accountNo: this.accountNo }
+ });
+ }
+
addPaymentDetails() {
this.addPaymentDetailsFlag = !this.addPaymentDetailsFlag;
if (this.addPaymentDetailsFlag) {
@@ -135,27 +242,54 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
}
}
- /**
- * Method to submit the transaction details.
- */
submit() {
- const savingAccountTransactionFormData = this.accountTransactionForm.value;
+ const formAmount = Number(this.accountTransactionForm.value.transactionAmount) || 0;
+ const paymentTypeId = this.accountTransactionForm.value.paymentTypeId;
+ const paymentTypeLabel = this.paymentTypeOptions?.find((p: any) => p.id === paymentTypeId)?.name ?? null;
+
+ const ref = this.dialog.open(TransactionConfirmDialogComponent, {
+ width: '480px',
+ maxWidth: '92vw',
+ panelClass: 'tcd-panel',
+ autoFocus: false,
+ data: {
+ kind: this.transactionType.deposit ? 'deposit' : 'withdrawal',
+ clientName: this.clientName,
+ accountNo: this.accountNo,
+ currentBalance: this.availableBalance,
+ transactionAmount: formAmount,
+ paymentTypeLabel
+ }
+ });
+ ref.afterClosed().subscribe((result: any) => {
+ if (result?.confirm) {
+ this.postTransaction();
+ }
+ });
+ }
+
+ private postTransaction(): void {
+ const formData = this.accountTransactionForm.value;
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
- const prevTransactionDate: Date = this.accountTransactionForm.value.transactionDate;
- if (savingAccountTransactionFormData.transactionDate instanceof Date) {
- savingAccountTransactionFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
+ const prevDate: Date = this.accountTransactionForm.value.transactionDate;
+ if (formData.transactionDate instanceof Date) {
+ formData.transactionDate = this.dateUtils.formatDate(prevDate, dateFormat);
}
- const data = {
- ...savingAccountTransactionFormData,
- dateFormat,
- locale
- };
+ const data = { ...formData, dateFormat, locale };
delete data.note;
data['transactionAmount'] = data['transactionAmount'] * 1;
+ if (this.denominationTotal > 0) {
+ data['denominationBreakdown'] = [
+ ...this.notes,
+ ...this.coins
+ ]
+ .map((d) => ({ denomination: d.value, kind: d.kind, count: this.countFor(d), subtotal: this.subtotalFor(d) }))
+ .filter((row) => row.count > 0);
+ }
this.fixedDepositsService
.executeFixedDepositsAccountTransactionsCommand(this.accountId, this.transactionCommand, data)
- .subscribe((res) => {
+ .subscribe(() => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
});
}
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.html b/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.html
index b10752412..7f167a47d 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.html
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.html
@@ -6,110 +6,334 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
-
-
- @if (transactionType.withdrawal) {
-
- {{ 'labels.menus.' + actionName | translate }} {{ 'labels.heading.Recurring Deposit Transactions' | translate }}
-
- }
+
+
+
+
+
+
+ {{ 'labels.buttons.Back' | translate }}
+
+
+
+
+
+
+ {{
+ transactionType.withdrawal
+ ? ('labels.heading.Withdraw Money From Recurring Deposit' | translate)
+ : ('labels.heading.Deposit Money To Recurring Deposit' | translate)
+ }}
+
+
+ {{
+ transactionType.deposit
+ ? 'Count the cash by denomination, then confirm the payment details.'
+ : 'Disburse cash by denomination and confirm the payment details.'
+ }}
+
+
+ @if (transactionType.withdrawal && clientId) {
+
+
+ Verify Identity
+
+ }
+
+ Counted Total
+ ₹{{ denominationTotal | number: '1.2-2' }}
+ @if (totalPieces > 0) {
+ {{ totalPieces }} piece(s)
+ }
+
+
-
-
-
-
- {{ 'labels.inputs.Transaction Date' | translate }}
-
-
-
- @if (depositRecurringDepositForm.controls.transactionDate.hasError('required')) {
-
- {{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
- {{ 'labels.commons.required' | translate }}
-
- }
-
-
-
+
+
+
+
+ Client
+ {{ clientName || '—' }}
+
+
+
+
+
+
+ Account No
+ {{ accountNo || '—' }}
+
+
+
+
+
+
+ Current Balance
+
+ ₹{{ availableBalance | number: '1.2-2' }}
+
+
+
+
+
+
+
+ KYC
+
-
+
+ {{ kycStatus }}
+
+
+
+
+
-
- {{ 'labels.inputs.Payment Type Id' | translate }}
-
- @for (paymentType of paymentTypes; track paymentType) {
-
- {{ paymentType.name }}
-
- }
-
- @if (depositRecurringDepositForm.controls.paymentTypeId.hasError('required')) {
-
- {{ 'labels.inputs.Payment Type' | translate }} {{ 'labels.commons.is' | translate }}
- {{ 'labels.commons.required' | translate }}
-
- }
-
+
+
+
+
+
+ Cash Denomination Breakdown
+
+
+ Clear
+
+
-
-
-
-
- {{ 'labels.inputs.Show Payment Details' | translate }}
-
-
+
+
+ Banknotes
+ ₹{{ notesTotal | number: '1.2-2' }}
+
+
+ @for (d of notes; track d.value) {
+
0">
+
+ {{ d.label }}
+ ₹{{ subtotalFor(d) | number: '1.2-2' }}
+
+
+
+ −
+
+
+ +
+
+
+ }
+
+
+
+
+
+ Coins
+ ₹{{ coinsTotal | number: '1.2-2' }}
+
+
+ @for (d of coins; track d.value) {
+
0">
+
+ {{ d.label }}
+ ₹{{ subtotalFor(d) | number: '1.2-2' }}
+
+
+
+ −
+
+
+ +
+
+
+ }
+
+
+
+
+
+ Banknotes
+ ₹{{ notesTotal | number: '1.2-2' }}
+
+
+ Coins
+ ₹{{ coinsTotal | number: '1.2-2' }}
+
+
+ Grand Total
+ ₹{{ denominationTotal | number: '1.2-2' }}
+
+
+
+ Use ₹{{ denominationTotal | number: '1.2-2' }} as Transaction Amount
+
+
+
+
+
+
+
+
+ Transaction Details
+
+
+
+
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.scss b/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.scss
index d286d171f..d7810f153 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.scss
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.scss
@@ -6,19 +6,754 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-.container {
- max-width: 37rem;
+$navy: #0d1b2a;
+$slate: #475569;
+$border: #e2e8f0;
+$soft: #f8fafc;
+$green: #059669;
+$green-d: #047857;
+$amber: #d97706;
+$amber-d: #b45309;
+
+.sat {
+ width: 100%;
+ padding: 10px 16px 18px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
-.expandcollapsebutton {
- margin-top: -7px;
- margin-left: 2%;
+.sat__hero {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px 16px 12px;
+ border-radius: 12px;
+ color: #fff;
+ background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #1e40af 100%);
+ box-shadow: 0 6px 18px -10px rgb(15 23 42 / 50%);
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
+ pointer-events: none;
+ }
}
-.right-input {
+.sat--withdrawal .sat__hero {
+ background: linear-gradient(135deg, #134e4a 0%, #115e59 45%, #0f766e 100%);
+ box-shadow: 0 6px 18px -10px rgb(15 118 110 / 50%);
+}
+
+.sat__hero-top {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ position: relative;
+ z-index: 1;
+}
+
+.sat__hero-back {
+ height: 30px !important;
+ border-radius: 8px !important;
+ padding: 0 11px !important;
+ font-weight: 700 !important;
+ font-size: 12px !important;
+ background: rgb(255 255 255 / 14%) !important;
+ color: #fff !important;
+ border: 1px solid rgb(255 255 255 / 28%) !important;
+ flex-shrink: 0;
+
+ &:hover {
+ background: rgb(255 255 255 / 26%) !important;
+ }
+}
+
+.sat__hero-ico {
+ width: 36px;
+ height: 36px;
+ border-radius: 9px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 28%);
+ color: #fff;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 15px;
+ flex-shrink: 0;
+}
+
+.sat__hero-title-wrap {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
+.sat__title {
+ margin: 0;
+ font-size: 14.5px;
+ font-weight: 800;
+ letter-spacing: 0.01em;
+ line-height: 1.15;
+}
+
+.sat__sub {
+ margin: 1px 0 0;
+ font-size: 11.5px;
+ color: rgb(255 255 255 / 80%);
+}
+
+.sat__hero-verify {
+ position: relative;
+ height: 30px !important;
+ border-radius: 8px !important;
+ padding: 0 12px !important;
+ font-weight: 700 !important;
+ font-size: 12px !important;
+ color: #fff !important;
+ background: rgb(255 255 255 / 16%) !important;
+ border: 1px solid rgb(255 255 255 / 30%) !important;
+ flex-shrink: 0;
+ overflow: visible !important;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: -2px;
+ border-radius: 10px;
+ border: 2px solid rgb(255 255 255 / 70%);
+ opacity: 0;
+ pointer-events: none;
+ will-change: transform, opacity;
+ }
+
+ &--pulse::before {
+ animation: sat-verify-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+ }
+}
+
+@keyframes sat-verify-pulse {
+ 0% {
+ transform: scale(1);
+ opacity: 0.85;
+ }
+
+ 70% {
+ transform: scale(1.18);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scale(1.18);
+ opacity: 0;
+ }
+}
+
+.sat__hero-meta {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: flex-end;
+ padding: 5px 12px;
+ border-radius: 8px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 30%);
+ min-width: 130px;
+ flex-shrink: 0;
+}
+
+.sat__hero-meta-label {
+ font-size: 9.5px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.07em;
+ color: rgb(255 255 255 / 78%);
+}
+
+.sat__hero-meta-value {
+ font-size: 15px;
+ font-weight: 800;
+ color: #fff;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__hero-meta-sub {
+ font-size: 10px;
+ color: rgb(255 255 255 / 75%);
+}
+
+.sat__ctx {
+ display: flex;
+ align-items: stretch;
+ flex-wrap: wrap;
+ padding: 5px 8px;
+ border-radius: 8px;
+ background: rgb(255 255 255 / 12%);
+ border: 1px solid rgb(255 255 255 / 22%);
+ position: relative;
+ z-index: 1;
+}
+
+.sat__ctx-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ flex: 1 1 110px;
+ padding: 2px 8px;
+ min-width: 0;
+}
+
+.sat__ctx-ico {
+ width: 22px;
+ height: 22px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 6px;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 25%);
+ color: #fff;
+ font-size: 10px;
+ flex-shrink: 0;
+}
+
+.sat__ctx-text {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+}
+
+.sat__ctx-label {
+ font-size: 9px;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: rgb(255 255 255 / 70%);
+ font-weight: 700;
+}
+
+.sat__ctx-value {
+ font-size: 11.5px;
+ font-weight: 700;
+ color: #fff;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &--mono {
+ font-family: 'Courier New', monospace;
+ font-size: 10.5px;
+ }
+
+ &--amount {
+ font-variant-numeric: tabular-nums;
+ font-size: 12px;
+ }
+
+ &--lg {
+ font-size: 13px;
+ }
+}
+
+.sat__ctx-item--balance {
+ background: rgb(255 255 255 / 12%);
+ border: 1px solid rgb(255 255 255 / 22%);
+ border-radius: 7px;
+ padding: 3px 8px;
+}
+
+.sat__ctx-divider {
+ width: 1px;
+ background: rgb(255 255 255 / 18%);
+ margin: 4px 0;
+ flex-shrink: 0;
+
+ @media (width <= 720px) {
+ display: none;
+ }
+}
+
+.sat__ctx-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ height: 22px;
+ padding: 0 9px;
+ border-radius: 999px;
+ font-size: 11.5px;
+ font-weight: 800;
+ text-transform: uppercase;
+ background: rgb(255 255 255 / 18%);
+ border: 1px solid rgb(255 255 255 / 30%);
+ color: #fff;
+ align-self: flex-start;
+ width: fit-content;
+
+ fa-icon {
+ font-size: 10px;
+ }
+
+ &--ok {
+ background: rgb(16 185 129 / 36%);
+ border-color: rgb(110 231 183 / 60%);
+ color: #d1fae5;
+ }
+
+ &--warn {
+ background: rgb(245 158 11 / 36%);
+ border-color: rgb(252 211 77 / 60%);
+ color: #fef3c7;
+ }
+
+ &--danger {
+ background: rgb(239 68 68 / 36%);
+ border-color: rgb(252 165 165 / 60%);
+ color: #fee2e2;
+ }
+}
+
+.sat__form {
+ display: grid;
+ grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
+ gap: 14px;
+ align-items: start;
+
+ @media (width <= 1100px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sat__panel {
+ background: #fff;
+ border: 1px solid $border;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
+ overflow: hidden;
+}
+
+.sat__panel-head {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 14px;
+ background: $soft;
+ border-bottom: 1px solid $border;
+}
+
+.sat__panel-ico {
+ width: 24px;
+ height: 24px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 7px;
+ background: linear-gradient(135deg, $green 0%, $green-d 100%);
+ color: #fff;
+ font-size: 11px;
+}
+
+.sat--withdrawal .sat__panel-ico {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
+}
+
+.sat__panel-title {
+ margin: 0;
+ font-size: 12.5px;
+ font-weight: 700;
+ color: $navy;
+}
+
+.sat__panel-spacer {
+ flex: 1;
+}
+
+.sat__panel-action {
+ height: 26px !important;
+ border-radius: 7px !important;
+ font-size: 11.5px !important;
+ font-weight: 600 !important;
+ padding: 0 10px !important;
+}
+
+.sat__panel-body {
+ padding: 12px 14px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+.sat__denom-group {
+ padding: 8px 14px 4px;
+
+ & + & {
+ border-top: 1px solid #f1f5f9;
+ }
+}
+
+.sat__denom-group-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ margin-bottom: 5px;
+}
+
+.sat__denom-group-title {
+ font-size: 10px;
+ font-weight: 800;
+ text-transform: uppercase;
+ letter-spacing: 0.07em;
+ color: $slate;
+}
+
+.sat__denom-group-total {
+ font-size: 12px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__denom-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+ gap: 6px;
+}
+
+.sat__denom {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+ padding: 6px 9px;
+ border-radius: 8px;
+ background: #fff;
+ border: 1px solid $border;
+ transition:
+ border-color 0.15s ease,
+ background 0.15s ease;
+ min-width: 0;
+
+ &--active {
+ border-color: #86efac;
+ background: #f0fdf4;
+ box-shadow: 0 1px 3px -1px rgb(4 120 87 / 18%);
+ }
+}
+
+.sat__denom-top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 6px;
+ min-width: 0;
+}
+
+.sat__denom-chip {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 48px;
+ height: 22px;
+ padding: 0 9px;
+ border-radius: 5px;
+ font-size: 11.5px;
+ font-weight: 800;
+ flex-shrink: 0;
+
+ &--note {
+ background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%);
+ color: #155e75;
+ border: 1px solid #a5f3fc;
+ }
+
+ &--coin {
+ background: linear-gradient(135deg, #fef9c3 0%, #fde68a 100%);
+ color: #854d0e;
+ border: 1px solid #fcd34d;
+ }
+}
+
+.sat__denom-amount {
+ font-size: 11.5px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
text-align: right;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-width: 0;
}
-.right-label {
- padding-right: 25px !important;
+.sat__denom-stepper {
+ display: flex;
+ align-items: stretch;
+ height: 28px;
+ border-radius: 6px;
+ border: 1px solid $border;
+ overflow: hidden;
+}
+
+.sat__denom-count {
+ flex: 1 1 auto;
+ width: 100%;
+ min-width: 0;
+ border: none;
+ outline: none;
+ background: transparent;
+ text-align: center;
+ font-size: 12.5px;
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+ padding: 0 4px;
+ appearance: textfield;
+
+ &::-webkit-outer-spin-button,
+ &::-webkit-inner-spin-button {
+ appearance: none;
+ margin: 0;
+ }
+}
+
+.sat__denom-step {
+ flex: 0 0 auto;
+ width: 28px;
+ background: $soft;
+ border: none;
+ border-right: 1px solid $border;
+ font-size: 15px;
+ font-weight: 700;
+ color: $slate;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+
+ &:last-child {
+ border-right: none;
+ border-left: 1px solid $border;
+ }
+
+ &:hover:not(:disabled) {
+ background: #e2e8f0;
+ color: $navy;
+ }
+
+ &:disabled {
+ opacity: 0.35;
+ cursor: not-allowed;
+ }
+}
+
+.sat__tally {
+ margin: 8px 14px 14px;
+ padding: 10px 12px;
+ border-radius: 10px;
+ background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
+ border: 1px solid $border;
+}
+
+.sat__tally-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 3px 0;
+ font-size: 12px;
+ color: $slate;
+
+ &--grand {
+ margin-top: 4px;
+ padding-top: 8px;
+ border-top: 1px dashed #cbd5e1;
+
+ .sat__tally-label {
+ font-weight: 800;
+ color: $navy;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-size: 11px;
+ }
+
+ .sat__tally-value {
+ font-size: 15.5px;
+ font-weight: 800;
+ color: $green-d;
+ }
+ }
+}
+
+.sat--withdrawal .sat__tally-row--grand .sat__tally-value {
+ color: $amber-d;
+}
+
+.sat__tally-value {
+ font-weight: 700;
+ color: $navy;
+ font-variant-numeric: tabular-nums;
+}
+
+.sat__apply {
+ width: 100%;
+ margin-top: 8px;
+ height: 34px !important;
+ border-radius: 8px !important;
+ background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
+ color: #fff !important;
+ font-weight: 700 !important;
+ font-size: 12.5px !important;
+ box-shadow: 0 3px 10px -2px rgb(4 120 87 / 35%) !important;
+
+ &:disabled {
+ opacity: 0.5;
+ filter: grayscale(0.4);
+ box-shadow: none !important;
+ }
+}
+
+.sat--withdrawal .sat__apply {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
+}
+
+.sat__field {
+ width: 100%;
+
+ ::ng-deep .mat-mdc-text-field-wrapper {
+ background: #fff;
+ border-radius: 10px;
+ }
+
+ ::ng-deep .mat-mdc-form-field-infix {
+ min-height: 48px;
+ padding-top: 12px !important;
+ padding-bottom: 12px !important;
+ }
+
+ ::ng-deep .mdc-notched-outline__leading,
+ ::ng-deep .mdc-notched-outline__notch,
+ ::ng-deep .mdc-notched-outline__trailing {
+ border-color: #cbd5e1 !important;
+ }
+}
+
+.sat__sub-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 8px 12px;
+
+ .sat__field--wide {
+ grid-column: 1 / -1;
+ }
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sat__toggle-row {
+ display: flex;
+ align-items: center;
+ padding: 6px 4px 10px;
+ cursor: pointer;
+ user-select: none;
+
+ .sat__toggle-label {
+ margin-left: 8px;
+ font-size: 13px;
+ font-weight: 600;
+ color: $slate;
+ }
+}
+
+.sat__mismatch,
+.sat__match {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 12px;
+ border-radius: 10px;
+ font-size: 12.5px;
+ margin: 2px 0 8px;
+}
+
+.sat__mismatch {
+ background: #fffbeb;
+ border: 1px solid #fde68a;
+ color: #92400e;
+
+ &-ico {
+ color: $amber;
+ flex-shrink: 0;
+ }
+
+ strong {
+ font-weight: 800;
+ }
+}
+
+.sat__match {
+ background: #ecfdf5;
+ border: 1px solid #a7f3d0;
+ color: #065f46;
+
+ &-ico {
+ color: $green;
+ flex-shrink: 0;
+ }
+}
+
+.sat__actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 10px;
+ padding-top: 12px;
+ border-top: 1px solid #f1f5f9;
+
+ button {
+ height: 42px !important;
+ border-radius: 10px !important;
+ font-weight: 700 !important;
+ padding: 0 22px !important;
+ }
+}
+
+.sat__cancel {
+ color: $slate !important;
+ border-color: $border !important;
+}
+
+.sat__submit {
+ background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
+ color: #fff !important;
+ box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
+
+ &:disabled {
+ opacity: 0.55;
+ background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
+ box-shadow: none !important;
+ }
+}
+
+.sat--withdrawal .sat__submit {
+ background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
+ box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
+}
+
+.m-r-6 {
+ margin-right: 6px;
+}
+
+@media (width <= 720px) {
+ .sat__hero-top {
+ flex-wrap: wrap;
+ }
+
+ .sat__hero-meta {
+ width: 100%;
+ }
+
+ .sat__ctx-item {
+ flex: 1 1 calc(50% - 14px);
+ }
}
diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.ts
index 6c69d8628..3afdc3cd1 100644
--- a/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.ts
+++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.ts
@@ -19,6 +19,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
/** Custom Services */
import { RecurringDepositsService } from '../../recurring-deposits.service';
+import { ClientsService } from 'app/clients/clients.service';
import { SettingsService } from 'app/settings/settings.service';
import { Dates } from 'app/core/utils/dates';
import { Currency } from 'app/shared/models/general.model';
@@ -26,8 +27,18 @@ import { TransactionCommand, TransactionTypeFlags } from '../../../transaction.m
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
import { MatSlideToggle } from '@angular/material/slide-toggle';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import { MatDialog } from '@angular/material/dialog';
+import { TransactionConfirmDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/transaction-confirm-dialog/transaction-confirm-dialog.component';
+import { IdentityVerifyDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/identity-verify-dialog/identity-verify-dialog.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+interface Denomination {
+ value: number;
+ kind: 'note' | 'coin';
+ label: string;
+}
+
/**
* Deposits Recurring Deposits Account Component
*/
@@ -39,7 +50,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
...STANDALONE_SHARED_IMPORTS,
InputAmountComponent,
MatSlideToggle,
- CdkTextareaAutosize
+ CdkTextareaAutosize,
+ FaIconComponent
]
})
export class DepositRecurringDepositsAccountComponent implements OnInit {
@@ -48,25 +60,19 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
private router = inject(Router);
private dateUtils = inject(Dates);
private recurringDepositsService = inject(RecurringDepositsService);
+ private clientsService = inject(ClientsService);
private settingsService = inject(SettingsService);
+ private dialog = inject(MatDialog);
@Input() currency: Currency;
- /** Transactions Amount */
transactionAmount: any;
- /** Outstanding Charge Amount */
outstandingChargeAmount: any;
- /** Payment Types */
paymentTypes: any;
- /** Account Id */
accountId: string;
- /** Show payment details */
showPaymentDetails = false;
- /** Minimum Date allowed. */
minDate = new Date(2000, 0, 1);
- /** Maximum Date allowed. */
maxDate = new Date();
- /** Deposits Recurring Deposit Account form. */
depositRecurringDepositForm: UntypedFormGroup;
action: string;
@@ -74,14 +80,32 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
transactionCommand: TransactionCommand;
transactionType: TransactionTypeFlags = { deposit: false, withdrawal: false };
- /**
- * Retrieves action details transactions template data from `resolve`
- * @param {FormBuilder} formBuilder Form Builder
- * @param {ActivatedRoute} route Activated Route
- * @param {Router} router Router
- * @param {Dates} dateUtils Date Utils
- * @param {SettingsService} settingsService Settings Service
- */
+ readonly notes: Denomination[] = [
+ { value: 500, kind: 'note', label: '₹500' },
+ { value: 200, kind: 'note', label: '₹200' },
+ { value: 100, kind: 'note', label: '₹100' },
+ { value: 50, kind: 'note', label: '₹50' },
+ { value: 20, kind: 'note', label: '₹20' },
+ { value: 10, kind: 'note', label: '₹10' }
+ ];
+
+ readonly coins: Denomination[] = [
+ { value: 20, kind: 'coin', label: '₹20' },
+ { value: 10, kind: 'coin', label: '₹10' },
+ { value: 5, kind: 'coin', label: '₹5' },
+ { value: 2, kind: 'coin', label: '₹2' },
+ { value: 1, kind: 'coin', label: 'Re 1' }
+ ];
+
+ denominationCounts: Record = {};
+
+ accountNo: string | null = null;
+ clientName: string | null = null;
+ clientId: number | null = null;
+ kycStatus: 'Verified' | 'Partial' | 'Pending' = 'Pending';
+ availableBalance = 0;
+ identityVerified = false;
+
constructor() {
this.route.data.subscribe((data: { recurringDepositsAccountActionData: any }) => {
this.transactionAmount = data.recurringDepositsAccountActionData.amount;
@@ -102,18 +126,43 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
} else {
throw new Error(`Invalid transaction action: ${this.actionName}`);
}
-
this.accountId = this.route.parent.snapshot.params['recurringDepositAccountId'];
}
ngOnInit() {
this.maxDate = this.settingsService.businessDate;
this.createdepositRecurringDepositForm();
+ this.loadContextStrip();
+ }
+
+ private loadContextStrip(): void {
+ if (!this.accountId) return;
+ this.recurringDepositsService.getRecurringDepositsAccountData(this.accountId).subscribe({
+ next: (account: any) => {
+ this.accountNo = account?.accountNo ?? null;
+ this.clientName = account?.clientName ?? account?.groupName ?? null;
+ this.clientId = account?.clientId ?? null;
+ if (!this.currency && account?.currency) this.currency = account.currency;
+ this.availableBalance = Number(account?.summary?.accountBalance ?? account?.summary?.availableBalance ?? 0);
+ if (account?.clientId) {
+ this.fetchClientKyc(String(account.clientId));
+ }
+ },
+ error: () => {}
+ });
+ }
+
+ private fetchClientKyc(clientId: string): void {
+ this.clientsService.getClientKyc(clientId).subscribe({
+ next: (kyc: any) => {
+ const pan = !!kyc?.panVerified;
+ const aadhaar = !!kyc?.aadhaarVerified;
+ this.kycStatus = pan && aadhaar ? 'Verified' : pan || aadhaar ? 'Partial' : 'Pending';
+ },
+ error: () => {}
+ });
}
- /**
- * Creates the deposits form.
- */
createdepositRecurringDepositForm() {
this.depositRecurringDepositForm = this.formBuilder.group({
transactionDate: [
@@ -132,9 +181,71 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
});
}
- /**
- * Add payment detail fields to the UI.
- */
+ // ── Denomination helpers ────────────────────────────────────────────
+ denomKey(d: Denomination): string {
+ return `${d.kind}:${d.value}`;
+ }
+
+ countFor(d: Denomination): number {
+ return this.denominationCounts[this.denomKey(d)] || 0;
+ }
+
+ subtotalFor(d: Denomination): number {
+ return this.countFor(d) * d.value;
+ }
+
+ onDenomChange(d: Denomination, raw: any): void {
+ const n = Math.max(0, Math.floor(Number(raw) || 0));
+ this.denominationCounts[this.denomKey(d)] = n;
+ }
+
+ bumpDenom(d: Denomination, delta: number): void {
+ const next = Math.max(0, this.countFor(d) + delta);
+ this.denominationCounts[this.denomKey(d)] = next;
+ }
+
+ get notesTotal(): number {
+ return this.notes.reduce((s, d) => s + this.subtotalFor(d), 0);
+ }
+
+ get coinsTotal(): number {
+ return this.coins.reduce((s, d) => s + this.subtotalFor(d), 0);
+ }
+
+ get denominationTotal(): number {
+ return this.notesTotal + this.coinsTotal;
+ }
+
+ get totalPieces(): number {
+ return Object.values(this.denominationCounts).reduce((s, n) => s + (n || 0), 0);
+ }
+
+ get denominationMatchesForm(): boolean {
+ const formAmt = Number(this.depositRecurringDepositForm?.controls?.['transactionAmount']?.value || 0);
+ return this.denominationTotal > 0 && formAmt === this.denominationTotal;
+ }
+
+ applyDenominationTotal(): void {
+ this.depositRecurringDepositForm.controls['transactionAmount'].setValue(this.denominationTotal);
+ this.depositRecurringDepositForm.controls['transactionAmount'].markAsDirty();
+ }
+
+ clearDenominations(): void {
+ this.denominationCounts = {};
+ }
+
+ openIdentityVerify(): void {
+ if (!this.clientId) return;
+ this.identityVerified = true;
+ this.dialog.open(IdentityVerifyDialogComponent, {
+ width: '500px',
+ maxWidth: '92vw',
+ panelClass: 'idv-panel',
+ autoFocus: false,
+ data: { clientId: this.clientId, clientName: this.clientName, accountNo: this.accountNo }
+ });
+ }
+
addPaymentDetails() {
this.showPaymentDetails = !this.showPaymentDetails;
if (this.showPaymentDetails) {
@@ -152,32 +263,51 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
}
}
- /**
- * Toggles the display of payment details
- */
- toggleDisplay() {
- this.showPaymentDetails = !this.showPaymentDetails;
+ submit() {
+ const formAmount = Number(this.depositRecurringDepositForm.value.transactionAmount) || 0;
+ const paymentTypeId = this.depositRecurringDepositForm.value.paymentTypeId;
+ const paymentTypeLabel = this.paymentTypes?.find((p: any) => p.id === paymentTypeId)?.name ?? null;
+
+ const ref = this.dialog.open(TransactionConfirmDialogComponent, {
+ width: '480px',
+ maxWidth: '92vw',
+ panelClass: 'tcd-panel',
+ autoFocus: false,
+ data: {
+ kind: this.transactionType.deposit ? 'deposit' : 'withdrawal',
+ clientName: this.clientName,
+ accountNo: this.accountNo,
+ currentBalance: this.availableBalance,
+ transactionAmount: formAmount,
+ paymentTypeLabel
+ }
+ });
+ ref.afterClosed().subscribe((result: any) => {
+ if (result?.confirm) {
+ this.postTransaction();
+ }
+ });
}
- /**
- * Submits the deposits recurring deposit form
- */
- submit() {
- const depositRecurringDepositFormData = this.depositRecurringDepositForm.value;
+ private postTransaction(): void {
+ const formData = this.depositRecurringDepositForm.value;
const dateFormat = this.settingsService.dateFormat;
const locale = this.settingsService.language.code;
- const prevTransactionDate = this.depositRecurringDepositForm.value.transactionDate;
- if (depositRecurringDepositFormData.transactionDate instanceof Date) {
- depositRecurringDepositFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
+ const prevDate = this.depositRecurringDepositForm.value.transactionDate;
+ if (formData.transactionDate instanceof Date) {
+ formData.transactionDate = this.dateUtils.formatDate(prevDate, dateFormat);
}
- const data = {
- ...depositRecurringDepositFormData,
- dateFormat,
- locale
- };
-
+ const data = { ...formData, dateFormat, locale };
delete data['note'];
data['transactionAmount'] = data['transactionAmount'] * 1;
+ if (this.denominationTotal > 0) {
+ data['denominationBreakdown'] = [
+ ...this.notes,
+ ...this.coins
+ ]
+ .map((d) => ({ denomination: d.value, kind: d.kind, count: this.countFor(d), subtotal: this.subtotalFor(d) }))
+ .filter((row) => row.count > 0);
+ }
this.recurringDepositsService
.executeRecurringDepositsAccountCommand(this.accountId, this.action, data)
.subscribe(() => {