From 41540ad24ef4823b02bcc18997c9881f5eb83da3 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Thu, 17 Sep 2026 13:04:09 +0530 Subject: [PATCH] feat(share-products): retire the Share Reference mapping, remove dead Cashier Share Collection screen Share Products: - "Share Reference (Cash Vault or Suspense)" removed from the accounting step (create, edit, preview and the read-only view tab) -- every register now books a share transaction against the account the money actually reached, so no product needs a placeholder asset mapping any more. The field is now optional server-side, not required. Cashier: - Removed the Share Collection screen and its three service methods. It read m_share_account.cashier_collected, a column that has never existed in this schema, and no other screen ever called it -- it could not have produced a working entry. Co-Authored-By: Claude Opus 5 (1M context) --- src/app/cashier/cashier-routing.module.ts | 6 - src/app/cashier/cashier.module.ts | 4 +- src/app/cashier/cashier.service.ts | 13 - .../share-collection.component.html | 375 ---------- .../share-collection.component.scss | 701 ------------------ .../share-collection.component.ts | 282 ------- ...share-product-accounting-step.component.ts | 35 +- .../share-product-preview-step.component.html | 8 - .../share-product-general-tab.component.html | 10 - 9 files changed, 2 insertions(+), 1432 deletions(-) delete mode 100644 src/app/cashier/share-collection/share-collection.component.html delete mode 100644 src/app/cashier/share-collection/share-collection.component.scss delete mode 100644 src/app/cashier/share-collection/share-collection.component.ts diff --git a/src/app/cashier/cashier-routing.module.ts b/src/app/cashier/cashier-routing.module.ts index 32f24de9b..045252339 100644 --- a/src/app/cashier/cashier-routing.module.ts +++ b/src/app/cashier/cashier-routing.module.ts @@ -26,7 +26,6 @@ import { VaultPositionComponent } from './vault-position/vault-position.componen import { AccountRegisterComponent } from './account-register/account-register.component'; import { ClosingCashReportComponent } from './closing-cash-report/closing-cash-report.component'; import { ExcessTransactionReportComponent } from './excess-transaction-report/excess-transaction-report.component'; -import { ShareCollectionComponent } from './share-collection/share-collection.component'; const routes: Routes = [ Route.withShell([ @@ -345,11 +344,6 @@ const routes: Routes = [ component: ExcessTransactionReportComponent, data: { title: 'Excess Transaction Report', breadcrumb: 'Excess Transaction Report' } }, - { - path: 'share-collection', - component: ShareCollectionComponent, - data: { title: 'Share Collection', breadcrumb: 'Share Collection' } - }, { path: 'transaction-entry', canActivate: [CashierOnlyGuard], diff --git a/src/app/cashier/cashier.module.ts b/src/app/cashier/cashier.module.ts index 2cf93e092..ec6dcfeeb 100644 --- a/src/app/cashier/cashier.module.ts +++ b/src/app/cashier/cashier.module.ts @@ -20,7 +20,6 @@ import { ReconciliationComponent } from './reconciliation/reconciliation.compone import { CashHistoryComponent } from './cash-history/cash-history.component'; import { ClosingCashReportComponent } from './closing-cash-report/closing-cash-report.component'; import { ExcessTransactionReportComponent } from './excess-transaction-report/excess-transaction-report.component'; -import { ShareCollectionComponent } from './share-collection/share-collection.component'; @NgModule({ imports: [ @@ -35,8 +34,7 @@ import { ShareCollectionComponent } from './share-collection/share-collection.co ReconciliationComponent, CashHistoryComponent, ClosingCashReportComponent, - ExcessTransactionReportComponent, - ShareCollectionComponent + ExcessTransactionReportComponent ] }) export class CashierModule {} diff --git a/src/app/cashier/cashier.service.ts b/src/app/cashier/cashier.service.ts index 4ef40be49..ad24b948b 100644 --- a/src/app/cashier/cashier.service.ts +++ b/src/app/cashier/cashier.service.ts @@ -364,19 +364,6 @@ export class CashierService { return this.http.get('/paymenttypes'); } - // ── Share Collection ── - getShareAccountForCollection(accountId: string): Observable { - return this.http.get(`/accounts/share/${accountId}`); - } - - collectSharePayment(data: any): Observable { - return this.http.post('/cashier/sharecollection', data); - } - - getShareCollectionStatus(shareAccountId: number): Observable { - return this.http.get(`/cashier/sharecollection/${shareAccountId}`); - } - /** One unapplied Share Sundry receipt, found by the voucher number the member quotes. */ getShareAdvanceByVoucher(voucherNo: string, glAccountNo: string, clientId?: number): Observable { // The voucher travels as a query parameter, not a path segment: numbers are formatted diff --git a/src/app/cashier/share-collection/share-collection.component.html b/src/app/cashier/share-collection/share-collection.component.html deleted file mode 100644 index a17ed89f7..000000000 --- a/src/app/cashier/share-collection/share-collection.component.html +++ /dev/null @@ -1,375 +0,0 @@ - - -
- -
-
-
CASHIER · SHARE COLLECTION
-

Share Collection

-

- Collect cash from member for share purchase — amounts are held in Sundry GL until the account is activated. -

-
-
- - - @if (pageState !== 'success') { - - } - - - @if (errorMsg) { -
- error_outline - {{ errorMsg }} -
- } - - - @if (pageState === 'searching') { -
- hourglass_empty -

Looking up account…

-
- } - - - @if (pageState === 'loaded') { - -
-
- person -
-
-
{{ account.clientName }}
-
- - {{ account.productName }} - {{ account.status?.value }} -
-
- - -
-
-
- - -
-
×
-
- - -
-
=
-
-
Share Amount
-
{{ currencySymbol }}{{ shareAmount | number: '1.2-2' }}
-
-
-
-
- - -
- -
-
- receipt_long - Amount Breakdown -
- - - - - - - - - - - - - @for (charge of charges; track charge.id) { - - - - - } - @if (charges.length === 0) { - - - - } - - - - - - - -
DescriptionAmount
- Share Amount - - ({{ requestedSharesInput }} shares × {{ currencySymbol }}{{ pricePerShare | number: '1.2-2' }}) - - {{ currencySymbol }}{{ shareAmount | number: '1.2-2' }}
{{ charge.name }} - -
- No charges applicable -
Total Payable - {{ currencySymbol }}{{ totalAmount | number: '1.2-2' }} -
-
- - -
-
- account_tree - GL Entries Preview -
- - - - - - - - - - - - - - - - - - - - @if (totalChargeAmount > 0) { - - - - - - } - -
AccountDr / CrAmount
Cash Vault GLDr{{ currencySymbol }}{{ totalAmount | number: '1.2-2' }}
Share Sundry GLCr{{ currencySymbol }}{{ shareAmount | number: '1.2-2' }}
Charge Receivable GLCr{{ currencySymbol }}{{ totalChargeAmount | number: '1.2-2' }}
-
- info_outline - Funds move to Share GL & Income GL when account is activated. -
-
-
- - - @if (isCashPayment && allDenominations.length > 0) { -
-
- payments - Denomination Entry - @if (denominationTotal > 0) { - - ₹{{ denominationTotal | number: '1.2-2' }} - {{ denomMatchesTotal ? '✓ Matches' : '≠ Mismatch' }} - - } - @if (denominationTotal === 0) { - ⚠ Required for Cash - } -
-
- - - - - - - - - - @for (d of allDenominations; track d.id) { - - - - - - } - - - - - - - - -
DenominationCountAmount
- {{ d.label }} - -
- - - -
-
{{ subtotalFor(d) > 0 ? (subtotalFor(d) | number: '1.2-2') : '—' }}
Total - ₹{{ denominationTotal | number: '1.2-2' }} -
- @if (!denomMatchesTotal && denominationTotal > 0) { -
- warning - Denomination ₹{{ denominationTotal | number: '1.2-2' }} must equal Total Payable ₹{{ - totalAmount | number: '1.2-2' - }}. -
- } - @if (denominationTotal > 0) { -
- -
- } -
-
- } - - -
- - Transaction Date * - - - - - Payment Mode - - — Cash (Default) — - @for (pt of paymentTypes; track pt.id) { - {{ pt.name }} - } - - - - - Note (Optional) - - -
- - - - } - - - @if (pageState === 'success' && collectionResult) { -
-
- check_circle -
-

Collection Posted Successfully

-

- Cash of - {{ collectionResult.currencyCode }} {{ collectionResult.totalAmount | number: '1.2-2' }} - collected from {{ collectionResult.clientName }} on - {{ collectionResult.collectionDate }} by {{ collectionResult.collectedBy }}. -

- - -
- Transaction ID - {{ collectionResult.transactionId }} -
- - -
- @for (entry of collectionResult.glEntries; track entry.account) { -
- {{ entry.type }}. {{ entry.account }} - {{ entry.currency }} {{ entry.amount | number: '1.2-2' }} -
- } -
- -

- Funds are held in Sundry GL. They will move to Share Equity GL when the account is activated. -

- - -
- } -
diff --git a/src/app/cashier/share-collection/share-collection.component.scss b/src/app/cashier/share-collection/share-collection.component.scss deleted file mode 100644 index c3a6606a2..000000000 --- a/src/app/cashier/share-collection/share-collection.component.scss +++ /dev/null @@ -1,701 +0,0 @@ -/** - * 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/. - */ - -// ── Search Bar ─────────────────────────────────────────────────────────────── - -.sc-search-bar { - display: flex; - align-items: center; - gap: 10px; - background: #fff; - border: 1px solid #e2e8f0; - border-radius: 12px; - padding: 10px 14px; - - &__input-wrap { - display: flex; - align-items: center; - gap: 8px; - flex: 1 1 auto; - } - - &__icon { - color: #94a3b8; - font-size: 20px; - width: 20px; - height: 20px; - flex-shrink: 0; - } - - &__input { - flex: 1 1 auto; - border: none; - outline: none; - font-size: 14px; - font-family: inherit; - color: #0d1b2a; - background: transparent; - - &::placeholder { - color: #94a3b8; - } - } -} - -// ── Alert ──────────────────────────────────────────────────────────────────── - -.sc-alert { - display: flex; - align-items: center; - gap: 10px; - padding: 12px 16px; - border-radius: 10px; - font-size: 13px; - font-weight: 500; - - mat-icon { - font-size: 20px; - width: 20px; - height: 20px; - flex-shrink: 0; - } - - &--error { - background: #fef2f2; - border: 1px solid #fecaca; - color: #dc2626; - } -} - -// ── Spinner ────────────────────────────────────────────────────────────────── - -.sc-spin { - animation: sc-spin 1.2s linear infinite; - display: block; - margin: 0 auto 8px; - font-size: 32px !important; - width: 32px !important; - height: 32px !important; - color: #94a3b8; -} - -@keyframes sc-spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} - -// ── Member Card ────────────────────────────────────────────────────────────── - -.sc-member-card { - display: flex; - align-items: center; - gap: 16px; - background: #fff; - border: 1px solid #e2e8f0; - border-radius: 12px; - padding: 16px 20px; - - &__avatar { - display: flex; - align-items: center; - justify-content: center; - width: 48px; - height: 48px; - border-radius: 50%; - background: #eff6ff; - flex-shrink: 0; - - mat-icon { - color: #2563eb; - font-size: 24px; - width: 24px; - height: 24px; - } - } - - &__info { - flex: 1 1 auto; - min-width: 0; - } - - &__name { - font-size: 16px; - font-weight: 700; - color: #1e293b; - margin-bottom: 6px; - } - - &__meta { - display: flex; - flex-wrap: wrap; - gap: 6px; - } - - &__shares { - flex-shrink: 0; - display: flex; - align-items: center; - } -} - -// ── Badges ─────────────────────────────────────────────────────────────────── - -.sc-badge { - display: inline-flex; - align-items: center; - height: 22px; - padding: 0 8px; - border-radius: 5px; - font-size: 11px; - font-weight: 700; - background: #f1f5f9; - color: #475569; - border: 1px solid #e2e8f0; - - &--account { - background: #eff6ff; - color: #1d4ed8; - border-color: #bfdbfe; - font-family: 'Roboto Mono', monospace; - } - - &--product { - background: #f0fdf4; - color: #15803d; - border-color: #bbf7d0; - } - - &--pending { - background: #fffbeb; - color: #d97706; - border-color: #fde68a; - } - - &--active { - background: #f0fdf4; - color: #15803d; - border-color: #bbf7d0; - } -} - -// ── Editable Share Inputs ──────────────────────────────────────────────────── - -.sc-shares-inputs { - display: flex; - align-items: center; - gap: 10px; - flex-wrap: wrap; - justify-content: flex-end; -} - -.sc-shares-field { - display: flex; - flex-direction: column; - gap: 3px; -} - -.sc-shares-label { - font-size: 10px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.07em; - color: #94a3b8; -} - -.sc-shares-input { - width: 110px; - height: 38px; - border: 1.5px solid #e2e8f0; - border-radius: 8px; - padding: 0 10px; - font-size: 14px; - font-weight: 700; - font-family: inherit; - color: #1353a0; - text-align: right; - outline: none; - background: #f8fafc; - transition: border-color 0.15s; - - &:focus { - border-color: #1353a0; - background: #fff; - } - - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - appearance: none; - } -} - -.sc-shares-sep { - font-size: 20px; - font-weight: 300; - color: #94a3b8; - padding-top: 16px; -} - -.sc-shares-result { - text-align: right; -} - -.sc-shares-total { - font-size: 20px; - font-weight: 800; - color: #0f4f3c; - font-variant-numeric: tabular-nums; -} - -// ── Editable Charge Amount ─────────────────────────────────────────────────── - -.sc-charge-amount { - padding: 4px 14px !important; -} - -.sc-charge-input { - width: 100px; - height: 32px; - border: 1.5px solid #e2e8f0; - border-radius: 6px; - padding: 0 8px; - font-size: 13px; - font-weight: 700; - font-family: inherit; - color: #1353a0; - text-align: right; - outline: none; - background: #f8fafc; - transition: border-color 0.15s; - - &:focus { - border-color: #1353a0; - background: #fff; - } - - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - appearance: none; - } -} - -// ── Split Layout ───────────────────────────────────────────────────────────── - -.sc-split { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 16px; - - @media (width <= 900px) { - grid-template-columns: 1fr; - } -} - -// ── Breakdown Card ─────────────────────────────────────────────────────────── - -.sc-breakdown { - &__header { - display: flex; - align-items: center; - gap: 8px; - padding: 12px 16px; - background: #f8fafc; - border-bottom: 1px solid #e2e8f0; - font-size: 13px; - font-weight: 700; - color: #334155; - - mat-icon { - font-size: 18px; - width: 18px; - height: 18px; - color: #0f4f3c; - } - } -} - -.sc-gl-preview { - .sc-breakdown__header { - mat-icon { - color: #1d4ed8; - } - } -} - -.sc-gl-note { - display: flex; - align-items: flex-start; - gap: 6px; - padding: 10px 14px; - font-size: 12px; - color: #64748b; - background: #f8fafc; - border-top: 1px solid #e2e8f0; - - mat-icon { - font-size: 16px; - width: 16px; - height: 16px; - flex-shrink: 0; - margin-top: 1px; - color: #94a3b8; - } -} - -// ── Table helpers ──────────────────────────────────────────────────────────── - -.sc-dim { - color: #94a3b8; - font-size: 12px; -} - -.sc-total-row td { - background: #f8fafc; - border-top: 2px solid #e2e8f0; - font-size: 14px; -} - -.sc-total-amount { - color: #0f4f3c; -} - -// ── Dr / Cr badges ─────────────────────────────────────────────────────────── - -.sc-dr-badge, -.sc-cr-badge { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 20px; - border-radius: 4px; - font-size: 11px; - font-weight: 800; - letter-spacing: 0.04em; -} - -.sc-dr-badge { - background: #fef2f2; - color: #dc2626; - border: 1px solid #fecaca; -} - -.sc-cr-badge { - background: #f0fdf4; - color: #15803d; - border: 1px solid #bbf7d0; -} - -// ── Footer ─────────────────────────────────────────────────────────────────── - -.sc-footer { - justify-content: space-between !important; - - &__summary { - display: flex; - flex-direction: column; - gap: 2px; - } - - &__label { - font-size: 11px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.07em; - color: #64748b; - } - - &__amount { - font-size: 22px; - font-weight: 800; - color: #0f4f3c; - font-variant-numeric: tabular-nums; - } - - &__actions { - display: flex; - align-items: center; - gap: 10px; - } -} - -// ── Denomination Panel ──────────────────────────────────────────────────────── - -.sc-denom { - padding: 0; - overflow: hidden; -} - -.sc-denom-body { - padding: 14px 16px; -} - -.sc-denom-table { - width: 100%; - - th, - td { - padding: 7px 10px; - font-size: 13px; - } - - tfoot td { - border-top: 2px solid #e2e8f0; - background: #f8fafc; - font-size: 14px; - } -} - -.sc-denom-chip { - display: inline-flex; - align-items: center; - height: 24px; - padding: 0 10px; - border-radius: 5px; - font-size: 12px; - font-weight: 700; - background: #eff6ff; - color: #1d4ed8; - border: 1px solid #bfdbfe; - - &--coin { - background: #fffbeb; - color: #d97706; - border-color: #fde68a; - } -} - -.sc-denom-stepper { - display: flex; - align-items: center; - gap: 4px; - justify-content: flex-end; - - button { - width: 26px; - height: 26px; - border: 1.5px solid #e2e8f0; - border-radius: 5px; - background: #f8fafc; - color: #475569; - font-size: 16px; - line-height: 1; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - - &:hover:not(:disabled) { - background: #e0f2fe; - border-color: #7dd3fc; - color: #0369a1; - } - - &:disabled { - opacity: 0.35; - cursor: not-allowed; - } - } - - input { - width: 62px; - height: 28px; - border: 1.5px solid #e2e8f0; - border-radius: 6px; - padding: 0 6px; - font-size: 13px; - font-weight: 600; - font-family: inherit; - text-align: right; - outline: none; - background: #fff; - - &:focus { - border-color: #1353a0; - } - - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - appearance: none; - } - } -} - -.sc-denom-badge { - margin-left: auto; - display: inline-flex; - align-items: center; - height: 22px; - padding: 0 10px; - border-radius: 5px; - font-size: 12px; - font-weight: 700; - - &--match { - background: #f0fdf4; - color: #15803d; - border: 1px solid #bbf7d0; - } - - &--mismatch { - background: #fef2f2; - color: #dc2626; - border: 1px solid #fecaca; - } -} - -.sc-denom-required { - margin-left: auto; - font-size: 12px; - font-weight: 700; - color: #d97706; -} - -.sc-denom-mismatch { - display: flex; - align-items: center; - gap: 6px; - margin-top: 10px; - padding: 9px 12px; - background: #fef2f2; - border: 1px solid #fecaca; - border-radius: 8px; - font-size: 12px; - font-weight: 600; - color: #dc2626; - - mat-icon { - font-size: 16px; - width: 16px; - height: 16px; - flex-shrink: 0; - } -} - -.sc-denom-actions { - display: flex; - justify-content: flex-end; - margin-top: 10px; -} - -// ── Success ────────────────────────────────────────────────────────────────── - -.sc-success { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - padding: 40px 24px; - gap: 16px; - background: #fff; - border: 1px solid #e2e8f0; - border-radius: 16px; - - &__icon-wrap { - width: 72px; - height: 72px; - border-radius: 50%; - background: #f0fdf4; - display: flex; - align-items: center; - justify-content: center; - - mat-icon { - font-size: 40px; - width: 40px; - height: 40px; - color: #16a34a; - } - } - - &__title { - font-size: 22px; - font-weight: 700; - color: #1e293b; - margin: 0; - } - - &__sub { - font-size: 14px; - color: #64748b; - max-width: 480px; - margin: 0; - } - - &__ref { - display: flex; - align-items: center; - gap: 10px; - background: #f1f5f9; - border: 1px solid #e2e8f0; - border-radius: 8px; - padding: 8px 16px; - } - - &__ref-label { - font-size: 11px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.07em; - color: #94a3b8; - white-space: nowrap; - } - - &__ref-val { - font-family: 'Roboto Mono', 'Courier New', monospace; - font-size: 13px; - font-weight: 600; - color: #1353a0; - } - - &__note { - font-size: 12px; - color: #94a3b8; - max-width: 420px; - margin: 0; - text-align: center; - } - - &__gl { - display: flex; - flex-direction: column; - gap: 6px; - width: 100%; - max-width: 400px; - background: #f8fafc; - border: 1px solid #e2e8f0; - border-radius: 10px; - padding: 14px 16px; - } - - &__gl-row { - display: flex; - justify-content: space-between; - font-size: 13px; - font-weight: 600; - padding: 4px 0; - border-bottom: 1px dotted #e2e8f0; - - &:last-child { - border-bottom: none; - } - - &--dr { - color: #dc2626; - } - - &--cr { - color: #15803d; - } - } -} diff --git a/src/app/cashier/share-collection/share-collection.component.ts b/src/app/cashier/share-collection/share-collection.component.ts deleted file mode 100644 index e9d2119f3..000000000 --- a/src/app/cashier/share-collection/share-collection.component.ts +++ /dev/null @@ -1,282 +0,0 @@ -/** - * 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/. - */ - -import { Component, OnInit, inject } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; -import { MatIconModule } from '@angular/material/icon'; -import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatSelectModule } from '@angular/material/select'; -import { MatInputModule } from '@angular/material/input'; -import { catchError } from 'rxjs/operators'; -import { of } from 'rxjs'; -import { CashierService } from '../cashier.service'; - -type PageState = 'idle' | 'searching' | 'loaded' | 'submitting' | 'success'; - -interface Denomination { - id: number; - value: number; - kind: 'note' | 'coin'; - label: string; -} - -interface ShareCharge { - id: number; - name: string; - amount: number; - currencySymbol: string; -} - -@Component({ - selector: 'mifosx-share-collection', - templateUrl: './share-collection.component.html', - styleUrls: [ - '../cashier-shared.scss', - './share-collection.component.scss' - ], - standalone: true, - imports: [ - CommonModule, - FormsModule, - RouterModule, - MatIconModule, - MatSnackBarModule, - MatFormFieldModule, - MatSelectModule, - MatInputModule - ] -}) -export class ShareCollectionComponent implements OnInit { - private service = inject(CashierService); - private snackBar = inject(MatSnackBar); - - pageState: PageState = 'idle'; - accountInput = ''; - transactionDate = ''; - paymentTypeId: number | null = null; - paymentTypes: any[] = []; - note = ''; - errorMsg = ''; - saving = false; - - account: any = null; - charges: ShareCharge[] = []; - collectionResult: any = null; // filled after successful POST - - // Editable fields — cashier can override if API returns 0 - requestedSharesInput = 0; - pricePerShare = 0; - - // ── Denomination ──────────────────────────────────────────────────────────── - notes: Denomination[] = []; - coins: Denomination[] = []; - denominationCounts: Record = {}; - - get shareAmount(): number { - return this.requestedSharesInput * this.pricePerShare; - } - - get totalChargeAmount(): number { - return this.charges.reduce((s, c) => s + c.amount, 0); - } - - get totalAmount(): number { - return this.shareAmount + this.totalChargeAmount; - } - - get currencySymbol(): string { - return this.account?.currency?.displaySymbol ?? '₹'; - } - - get isCashPayment(): boolean { - if (this.paymentTypeId === null) return true; - const pt = this.paymentTypes.find((p: any) => p.id === this.paymentTypeId); - return !!pt?.isCashPayment; - } - - get allDenominations(): Denomination[] { - const desc = (a: Denomination, b: Denomination) => b.value - a.value; - return [ - ...[...this.notes].sort(desc), - ...[...this.coins].sort(desc) - ]; - } - - get denominationTotal(): number { - return this.allDenominations.reduce((s, d) => s + this.subtotalFor(d), 0); - } - - get denomMatchesTotal(): boolean { - return this.denominationTotal === this.totalAmount; - } - - get isValid(): boolean { - if (!this.account || this.totalAmount <= 0 || !this.transactionDate) return false; - if (this.isCashPayment && this.allDenominations.length > 0) { - return this.denomMatchesTotal; - } - return true; - } - - ngOnInit(): void { - const d = new Date(); - this.transactionDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; - this.service - .getPaymentTypes() - .pipe(catchError(() => of([]))) - .subscribe((types: any[]) => (this.paymentTypes = types || [])); - this.loadDenominations(); - } - - private loadDenominations(): void { - this.service - .getActiveDenominations() - .pipe(catchError(() => of([]))) - .subscribe((list: any[]) => { - const rows: Denomination[] = (list || []).map((d: any) => ({ - id: d.id, - value: d.denomination ?? d.value, - kind: d.type === 'coin' ? 'coin' : 'note', - label: '₹' + (d.denomination ?? d.value) - })); - const order = (a: Denomination, b: Denomination) => b.value - a.value; - this.notes = rows.filter((r) => r.kind === 'note').sort(order); - this.coins = rows.filter((r) => r.kind === 'coin').sort(order); - }); - } - - // ── 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 { - this.denominationCounts[this.denomKey(d)] = Math.max(0, Math.floor(Number(raw) || 0)); - } - - bumpDenom(d: Denomination, delta: number): void { - this.denominationCounts[this.denomKey(d)] = Math.max(0, this.countFor(d) + delta); - } - - clearDenominations(): void { - this.denominationCounts = {}; - } - - searchAccount(): void { - const query = this.accountInput.trim(); - if (!query) return; - this.pageState = 'searching'; - this.account = null; - this.charges = []; - this.errorMsg = ''; - - this.service - .getShareAccountForCollection(query) - .pipe( - catchError((err) => { - this.errorMsg = - err?.error?.errors?.[0]?.defaultUserMessage || - err?.error?.developerMessage || - 'Account not found. Please check the account ID and try again.'; - this.pageState = 'idle'; - return of(null); - }) - ) - .subscribe((acc: any) => { - if (!acc) return; - this.account = acc; - - // Auto-detect shares from summary or first pending purchasedShares entry - const pendingTx = (acc.purchasedShares || []).find( - (t: any) => - t.status?.code === 'purchasedSharesStatusType.applied' && t.type?.code === 'purchasedSharesType.purchased' - ); - this.requestedSharesInput = Number( - acc.summary?.totalPendingForApprovalShares || pendingTx?.numberOfShares || acc.requestedShares || 0 - ); - this.pricePerShare = Number(pendingTx?.purchasedPrice || acc.currentMarketPrice || acc.unitPrice || 0); - - // Show ALL charges on the account — no type filtering - this.charges = (acc.charges || []).map((c: any) => ({ - id: c.chargeId ?? c.id, - name: c.name, - amount: Number(c.amount ?? c.amountOutstanding ?? 0), - currencySymbol: c.currency?.displaySymbol ?? '₹' - })); - - this.pageState = 'loaded'; - }); - } - - recalc(): void { - // triggered by input changes — nothing extra needed, getters auto-recalculate - } - - submit(): void { - if (!this.isValid || this.saving) return; - this.saving = true; - const payload = { - shareAccountId: this.account.id, - accountNo: this.account.accountNo, - clientName: this.account.clientName, - transactionDate: this.transactionDate, - requestedShares: this.requestedSharesInput, - pricePerShare: this.pricePerShare, - shareAmount: this.shareAmount, - chargeAmount: this.totalChargeAmount, - totalAmount: this.totalAmount, - paymentTypeId: this.paymentTypeId || null, - note: this.note || null - }; - - this.service.collectSharePayment(payload).subscribe({ - next: (result: any) => { - this.collectionResult = result; - this.snackBar.open('Share collection posted successfully.', 'Close', { duration: 3000 }); - this.pageState = 'success'; - this.saving = false; - }, - error: (err: any) => { - const msg = - err?.error?.errors?.[0]?.defaultUserMessage || - err?.error?.developerMessage || - err?.error?.defaultUserMessage || - 'Collection failed. Please try again.'; - this.snackBar.open(msg, 'Close', { duration: 8000 }); - this.saving = false; - } - }); - } - - reset(): void { - this.pageState = 'idle'; - this.accountInput = ''; - this.account = null; - this.charges = []; - this.errorMsg = ''; - this.note = ''; - this.paymentTypeId = null; - this.requestedSharesInput = 0; - this.pricePerShare = 0; - this.collectionResult = null; - this.saving = false; - this.clearDenominations(); - } -} diff --git a/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.ts b/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.ts index 14693833d..4cb5e33d2 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.ts +++ b/src/app/products/share-products/share-product-stepper/share-product-accounting-step/share-product-accounting-step.component.ts @@ -70,16 +70,9 @@ export class ShareProductAccountingStepComponent implements OnInit { shareProductAccountingForm: UntypedFormGroup; accountingSectionConfigs: ShareAccountingSectionConfig[] = []; - assetAccountData: GLAccount[] = []; liabilityAccountData: GLAccount[] = []; incomeAccountData: GLAccount[] = []; equityAccountData: GLAccount[] = []; - /** "Share Reference" is where a redemption's credit side lands pending settlement — some banks - * model that as a receivable-like Asset (e.g. "Cash Vault", this field's original default), - * others as a payable-like Liability suspense account (e.g. an existing "Sundry" account - * already used elsewhere as Shares Suspense). Both are valid conventions, so this field accepts - * either instead of Asset-only. */ - referenceAccountData: GLAccount[] = []; constructor() { this.createShareProductAccountingForm(); @@ -87,18 +80,15 @@ export class ShareProductAccountingStepComponent implements OnInit { ngOnInit() { const opts = this.shareProductsTemplate.accountingMappingOptions || {}; - this.assetAccountData = opts.assetAccountOptions || []; this.liabilityAccountData = opts.liabilityAccountOptions || []; this.incomeAccountData = opts.incomeAccountOptions || []; this.equityAccountData = opts.equityAccountOptions || []; - this.referenceAccountData = this.assetAccountData.concat(this.liabilityAccountData); this.buildAccountingSectionConfigs(); if (this.shareProductsTemplate.accountingMappings) { const m = this.shareProductsTemplate.accountingMappings; this.shareProductAccountingForm.patchValue({ - shareReferenceId: m.shareReferenceId?.id || '', shareSuspenseId: m.shareSuspenseId?.id || '', incomeFromFeeAccountId: m.incomeFromFeeAccountId?.id || '', shareEquityId: m.shareEquityId?.id || '' @@ -109,10 +99,6 @@ export class ShareProductAccountingStepComponent implements OnInit { createShareProductAccountingForm() { this.shareProductAccountingForm = this.formBuilder.group({ accountingRule: [2], - shareReferenceId: [ - '', - Validators.required - ], shareSuspenseId: [ '', Validators.required @@ -125,7 +111,6 @@ export class ShareProductAccountingStepComponent implements OnInit { '', Validators.required ], - assetsOtherGlMappings: this.formBuilder.array([]), liabilitiesOtherGlMappings: this.formBuilder.array([]), incomeOtherGlMappings: this.formBuilder.array([]), equityOtherGlMappings: this.formBuilder.array([]) @@ -134,24 +119,6 @@ export class ShareProductAccountingStepComponent implements OnInit { buildAccountingSectionConfigs() { this.accountingSectionConfigs = [ - { - key: 'assets', - title: 'Assets', - pillLabel: 'Asset', - pillClass: 'green', - rows: [ - { - key: 'shareReferenceId', - label: 'Share Reference (Cash Vault or Suspense)', - inputLabel: 'Share reference', - controlName: 'shareReferenceId', - options: this.referenceAccountData, - required: true - } - ], - otherMappingsControlName: 'assetsOtherGlMappings', - otherAccountOptions: this.assetAccountData - }, { key: 'liabilities', title: 'Liabilities', @@ -239,7 +206,7 @@ export class ShareProductAccountingStepComponent implements OnInit { } get shareProductAccounting() { - const { assetsOtherGlMappings, liabilitiesOtherGlMappings, incomeOtherGlMappings, equityOtherGlMappings, ...rest } = + const { liabilitiesOtherGlMappings, incomeOtherGlMappings, equityOtherGlMappings, ...rest } = this.shareProductAccountingForm.value; return rest; } diff --git a/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.html b/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.html index 4bc274f94..4f1064ae8 100644 --- a/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.html +++ b/src/app/products/share-products/share-product-stepper/share-product-preview-step/share-product-preview-step.component.html @@ -213,14 +213,6 @@ @if (shareProduct.accountingRule === 2) { -
{{ 'labels.heading.Assets' | translate }}
-
- {{ 'labels.inputs.Share reference' | translate }}: - {{ - shareProduct.shareReferenceId - | find: shareProductsTemplate.accountingMappingOptions.assetAccountOptions : 'id' : 'name' - }} -
{{ 'labels.heading.Liabilities' | translate }}
{{ 'labels.inputs.Share suspense control' | translate }}: diff --git a/src/app/products/share-products/view-share-product/share-product-general-tab/share-product-general-tab.component.html b/src/app/products/share-products/view-share-product/share-product-general-tab/share-product-general-tab.component.html index 4538cc62a..2cd01ff86 100644 --- a/src/app/products/share-products/view-share-product/share-product-general-tab/share-product-general-tab.component.html +++ b/src/app/products/share-products/view-share-product/share-product-general-tab/share-product-general-tab.component.html @@ -208,16 +208,6 @@ @if (shareProduct.accountingRule.id === 2) {
-

{{ 'labels.heading.Assets' | translate }}

-
- - -

{{ 'labels.heading.Liabilities' | translate }}