feat(dds): DDS product creation in tabs with an Accounting tab
- DDS Product Creation split into Details / Interest & Maturity / Premature Closure / Accounting tabs, like the savings product - Accounting tab picks Customer Deposits (liability), Interest Expense and Agent Commission Expense (expense) GLs; product view shows them - Approval review shows the product's GLs, old -> new on edits - Agent master no longer reads the removed dds-deposit-product setting Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+61
-20
@@ -128,27 +128,68 @@
|
||||
<span class="ddr-chip ddr-chip--edit">{{ group.changed }} changed</span>
|
||||
}
|
||||
</header>
|
||||
<div class="ddr-grid">
|
||||
@for (field of group.fields; track field.key) {
|
||||
<div class="ddr-tile" [class.ddr-tile--changed]="field.changed">
|
||||
<span class="ddr-tile__label">
|
||||
{{ field.label }}
|
||||
@if (field.changed) {
|
||||
<span class="ddr-tile__flag">Changed</span>
|
||||
}
|
||||
</span>
|
||||
@if (field.changed) {
|
||||
<span class="ddr-tile__diff">
|
||||
<span class="ddr-tile__old">{{ field.current || '—' }}</span>
|
||||
<mat-icon>arrow_forward</mat-icon>
|
||||
<ng-container *ngTemplateOutlet="valueTpl; context: { field: field }"></ng-container>
|
||||
@if (group.name === 'Accounting') {
|
||||
<!--
|
||||
The ledger accounts the product posts through, laid out as the product's own Accounting
|
||||
tab lays them out: the kind of account, what it is for, and the account by name and code.
|
||||
-->
|
||||
<div class="ddr-acct">
|
||||
@for (field of group.fields; track field.key) {
|
||||
<div class="ddr-acct__row" [class.ddr-acct__row--changed]="field.changed">
|
||||
<div class="ddr-acct__meta">
|
||||
<span class="ddr-acct__kind" [ngClass]="'ddr-acct__kind--' + glKind(field.key).toLowerCase()">{{
|
||||
glKind(field.key)
|
||||
}}</span>
|
||||
<span class="ddr-acct__label">
|
||||
{{ field.label }}
|
||||
@if (field.changed) {
|
||||
<span class="ddr-tile__flag">Changed</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="ddr-acct__value">
|
||||
@if (field.changed) {
|
||||
<span class="ddr-acct__gl ddr-acct__gl--old">
|
||||
{{ glName(field.current) }}
|
||||
@if (glCode(field.current)) {
|
||||
<span class="ddr-acct__code">{{ glCode(field.current) }}</span>
|
||||
}
|
||||
</span>
|
||||
<mat-icon class="ddr-acct__arrow">arrow_forward</mat-icon>
|
||||
}
|
||||
<span class="ddr-acct__gl" [class.ddr-acct__gl--none]="!field.value">
|
||||
{{ glName(field.value) }}
|
||||
@if (glCode(field.value)) {
|
||||
<span class="ddr-acct__code">{{ glCode(field.value) }}</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="ddr-grid">
|
||||
@for (field of group.fields; track field.key) {
|
||||
<div class="ddr-tile" [class.ddr-tile--changed]="field.changed">
|
||||
<span class="ddr-tile__label">
|
||||
{{ field.label }}
|
||||
@if (field.changed) {
|
||||
<span class="ddr-tile__flag">Changed</span>
|
||||
}
|
||||
</span>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="valueTpl; context: { field: field }"></ng-container>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (field.changed) {
|
||||
<span class="ddr-tile__diff">
|
||||
<span class="ddr-tile__old">{{ field.current || '—' }}</span>
|
||||
<mat-icon>arrow_forward</mat-icon>
|
||||
<ng-container *ngTemplateOutlet="valueTpl; context: { field: field }"></ng-container>
|
||||
</span>
|
||||
} @else {
|
||||
<ng-container *ngTemplateOutlet="valueTpl; context: { field: field }"></ng-container>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
|
||||
|
||||
+110
@@ -451,3 +451,113 @@ $shadow-sm:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── A DDS product's Accounting, as its own Accounting tab shows it ───────────────────────────────────
|
||||
.ddr-acct {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 16px 20px 20px;
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
background: $bg-white;
|
||||
|
||||
&--changed {
|
||||
border-color: $warning-border;
|
||||
background: $warning-bg;
|
||||
}
|
||||
|
||||
@media (width <= 720px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__kind {
|
||||
flex: none;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
|
||||
&--liability {
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
&--expense {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
&__value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__gl {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $brand;
|
||||
|
||||
&--old {
|
||||
font-weight: 500;
|
||||
color: $text-soft;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
&--none {
|
||||
color: #b91c1c;
|
||||
}
|
||||
}
|
||||
|
||||
&__code {
|
||||
padding: 1px 8px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 6px;
|
||||
background: $bg-soft;
|
||||
font-family: ui-monospace, 'SF Mono', 'Cascadia Mono', Consolas, monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $text-mid;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 18px;
|
||||
color: $warning;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,26 @@ export class DdsApprovalReviewComponent implements OnInit {
|
||||
return this.fields.find((field: any) => field.key === key)?.value || '';
|
||||
}
|
||||
|
||||
/** The kind of ledger account a product's Accounting row names - customers' deposits are owed, the rest are costs. */
|
||||
glKind(key: string): string {
|
||||
return key === 'depositLiabilityGlId' ? 'Liability' : 'Expense';
|
||||
}
|
||||
|
||||
/** "DDS CUSTOMER DEPOSITS (200601)" → "DDS CUSTOMER DEPOSITS"; a missing account reads "Not set". */
|
||||
glName(value: string | null | undefined): string {
|
||||
if (!value) {
|
||||
return 'Not set';
|
||||
}
|
||||
const match = /^(.*) (([^()]+))$/.exec(value);
|
||||
return match ? match[1] : value;
|
||||
}
|
||||
|
||||
/** "DDS CUSTOMER DEPOSITS (200601)" → "200601". */
|
||||
glCode(value: string | null | undefined): string {
|
||||
const match = value ? /^(.*) (([^()]+))$/.exec(value) : null;
|
||||
return match ? match[2] : '';
|
||||
}
|
||||
|
||||
get changedCount(): number {
|
||||
return this.fields.filter((field: any) => field.changed).length;
|
||||
}
|
||||
@@ -131,6 +151,7 @@ export class DdsApprovalReviewComponent implements OnInit {
|
||||
Product: 'inventory_2',
|
||||
Interest: 'trending_up',
|
||||
'Maturity & Premature Closure': 'event_busy',
|
||||
Accounting: 'menu_book',
|
||||
'Customer & Scheme': 'groups',
|
||||
'Personal & Contact': 'contact_page',
|
||||
'Minor / Guardian': 'escalator_warning',
|
||||
|
||||
Reference in New Issue
Block a user