fix: update sponsor DD approvals, cashier transaction entry, FIU and dividend declaration
This commit is contained in:
+63
-5
@@ -21,14 +21,31 @@
|
||||
<!-- The outcome, product by product. Which products were skipped and why matters as much as
|
||||
which succeeded, so both are shown rather than just a success count. -->
|
||||
<div class="pl-card dd-panel">
|
||||
<!-- A run that skipped a product is not a clean success: those members get nothing, and saying
|
||||
only "declared" is how that goes unnoticed. The heading states it either way. -->
|
||||
<div class="dd-result-head">
|
||||
<fa-icon icon="check-circle" class="dd-result-ic"></fa-icon>
|
||||
<fa-icon
|
||||
[icon]="hasSkipped ? 'exclamation-triangle' : 'check-circle'"
|
||||
class="dd-result-ic"
|
||||
[class.dd-result-ic--warn]="hasSkipped"
|
||||
></fa-icon>
|
||||
<div>
|
||||
<h3>Dividend declared for {{ result.financialYear }}</h3>
|
||||
<h3>
|
||||
@if (hasSkipped) {
|
||||
Declared for {{ result.financialYear }} — {{ result.skipped.length }}
|
||||
{{ result.skipped.length === 1 ? 'product was' : 'products were' }} skipped
|
||||
} @else {
|
||||
Dividend declared for {{ result.financialYear }}
|
||||
}
|
||||
</h3>
|
||||
<p>
|
||||
{{ result.declarationsCreated }}
|
||||
{{ result.declarationsCreated === 1 ? 'declaration' : 'declarations' }} created. Each still needs approval
|
||||
before members can claim.
|
||||
@if (hasSkipped) {
|
||||
Members of the skipped {{ result.skipped.length === 1 ? 'product' : 'products' }} below will receive
|
||||
nothing for this year — check the reason and declare separately if that is not intended.
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,7 +145,22 @@
|
||||
<span class="dd-amount">− {{ preview.totalAmount | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
|
||||
@if (isUnderfunded) {
|
||||
@if (exceedsMaximumRate) {
|
||||
<!-- Checked before the year and funding notes: a rate over the cap is not payable at
|
||||
all, so what it would cost is beside the point until it is lowered. -->
|
||||
<div class="dd-note dd-note--error">
|
||||
{{ declareForm.value.ratePercentage }}% is above the society's maximum of
|
||||
{{ preview.maximumRatePercentage }}%. Lower the rate, or raise the limit in Configurations if the board
|
||||
has resolved a higher one.
|
||||
</div>
|
||||
} @else if (isPeriodNotFinished) {
|
||||
<!-- Shown ahead of the funding check: a year still running cannot be declared at any
|
||||
rate, so the shortfall below it would be advice on a choice that is not available. -->
|
||||
<div class="dd-note dd-note--error">
|
||||
{{ preview.financialYear }} has not finished yet, so its profit is not final. A dividend can only be
|
||||
declared once the financial year is over.
|
||||
</div>
|
||||
} @else if (isUnderfunded) {
|
||||
<div class="dd-note dd-note--error">
|
||||
Short by {{ preview.fundingShortfall | number: '1.2-2' }}. There is not enough in
|
||||
{{ preview.profitGlCode }} to fund this dividend — lower the rate, or close the year's profit first.
|
||||
@@ -146,16 +178,42 @@
|
||||
{{ declareForm.value.ratePercentage || 0 }}% on each member’s share investment
|
||||
</div>
|
||||
|
||||
<!-- The member count sits beside each amount because an amount alone does not say who it
|
||||
reaches. A product paying a plausible total to one member is the case worth catching
|
||||
here, while the rate can still be changed. -->
|
||||
@for (product of eligibleProducts; track product.productId) {
|
||||
<div class="dd-preview-row">
|
||||
<span>{{ product.productName }}</span>
|
||||
<span>
|
||||
{{ product.productName }}
|
||||
<span class="dd-members">
|
||||
{{ product.memberCount }} {{ product.memberCount === 1 ? 'member' : 'members' }}
|
||||
@if (product.accountCount !== product.memberCount) {
|
||||
· {{ product.accountCount }} accounts
|
||||
}
|
||||
</span>
|
||||
</span>
|
||||
<span class="dd-amount">{{ product.amount | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (eligibleProducts.length > 0) {
|
||||
<div class="dd-preview-row dd-preview-row--total">
|
||||
<span>Total to declare</span>
|
||||
<span>
|
||||
Total to declare
|
||||
@if (preview.totalMemberCount) {
|
||||
<!-- Members are distinct people, accounts are what actually gets paid: someone
|
||||
holding shares in two products is one member with two entitlements, each
|
||||
claimed on its own draft. Both are shown so the difference reads as the
|
||||
split it is rather than a miscount. -->
|
||||
<span class="dd-members">
|
||||
{{ preview.totalMemberCount }}
|
||||
{{ preview.totalMemberCount === 1 ? 'member' : 'members' }}
|
||||
@if (preview.totalAccountCount !== preview.totalMemberCount) {
|
||||
· {{ preview.totalAccountCount }} accounts
|
||||
}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
<span class="dd-amount">{{ payableTotal | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
+13
@@ -148,6 +148,14 @@ form {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// Who a product's amount reaches, set quietly beside the product name so it reads as a
|
||||
// qualifier on the amount rather than competing with it.
|
||||
.dd-members {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.dd-note {
|
||||
margin-top: 12px;
|
||||
padding: 10px 14px;
|
||||
@@ -194,6 +202,11 @@ form {
|
||||
font-size: 26px;
|
||||
color: #16a34a;
|
||||
margin-top: 2px;
|
||||
|
||||
// A run that left a product out is amber, not green: it succeeded, but not for everyone.
|
||||
&--warn {
|
||||
color: #d97706;
|
||||
}
|
||||
}
|
||||
|
||||
.dd-table {
|
||||
|
||||
+47
-4
@@ -73,6 +73,17 @@ export class DeclareDividendComponent implements OnInit {
|
||||
/** Set once the declaration has gone through, so the outcome per product can be shown. */
|
||||
result: any = null;
|
||||
|
||||
/**
|
||||
* Whether any product was left out of the declaration that just ran.
|
||||
*
|
||||
* Drives the outcome heading. A run that skipped a product still returns success — one product
|
||||
* being ineligible is not a reason to lose the others — but those members receive nothing for the
|
||||
* year, so the screen has to say so rather than report a plain "declared".
|
||||
*/
|
||||
get hasSkipped(): boolean {
|
||||
return (this.result?.skipped?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.declareForm = this.formBuilder.group({
|
||||
financialYear: [
|
||||
@@ -96,9 +107,13 @@ export class DeclareDividendComponent implements OnInit {
|
||||
/**
|
||||
* The financial years a dividend can be declared for: the current one and the five before it.
|
||||
*
|
||||
* The current year is included because a society may declare an interim dividend part-way through,
|
||||
* and five back covers a declaration delayed by an AGM that ran late. Built from the business date
|
||||
* rather than the browser's clock, so the list matches the year the branch is operating in.
|
||||
* The year in progress is listed but cannot be declared — the server rejects it, and the screen
|
||||
* says so as soon as it is picked. It stays on the list so an officer looking for it finds it with
|
||||
* the reason attached, rather than wondering why the year they are in is missing. Five back covers
|
||||
* a declaration delayed by an AGM that ran late.
|
||||
*
|
||||
* Built from the business date rather than the browser's clock, so the list matches the year the
|
||||
* branch is operating in.
|
||||
*/
|
||||
private buildFinancialYears(): void {
|
||||
const businessDate: Date = this.settingsService.businessDate ?? new Date();
|
||||
@@ -171,8 +186,36 @@ export class DeclareDividendComponent implements OnInit {
|
||||
return !!this.preview?.exceedsDistributableProfit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the chosen year is still running.
|
||||
*
|
||||
* A dividend is a share of the year's profit, so it cannot be declared until the year is over and
|
||||
* that profit is final. The server enforces this; the flag simply lets the screen say so before the
|
||||
* officer commits to a rate.
|
||||
*/
|
||||
get isPeriodNotFinished(): boolean {
|
||||
return !!this.preview?.periodNotFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the entered rate is above the society's configured maximum.
|
||||
*
|
||||
* The bye-laws cap what may be paid in a year, so a rate above it needs a board resolution rather
|
||||
* than a typed figure. The server enforces the same limit.
|
||||
*/
|
||||
get exceedsMaximumRate(): boolean {
|
||||
return !!this.preview?.exceedsMaximumRate;
|
||||
}
|
||||
|
||||
get canSubmit(): boolean {
|
||||
return this.declareForm.valid && !this.submitting && this.eligibleProducts.length > 0 && !this.isUnderfunded;
|
||||
return (
|
||||
this.declareForm.valid &&
|
||||
!this.submitting &&
|
||||
this.eligibleProducts.length > 0 &&
|
||||
!this.isUnderfunded &&
|
||||
!this.isPeriodNotFinished &&
|
||||
!this.exceedsMaximumRate
|
||||
);
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
|
||||
Reference in New Issue
Block a user