feat(investment): deal time, interest cycle and charges on Purchase Entry

As per Jitendra Sir's review:
- Deal Time is now a mandatory field next to Deal Date.
- New Interest Cycle section: the two coupon days, last and next coupon,
  next coupon amount and days to it.
- Stamp duty, brokerage and other charges sit on the main form. They are
  expenses on their own GLs (500601/500602/500603), so they are added to
  the settlement amount but no longer to the book value, and each shows
  as its own debit line in the accounting preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 11:54:33 +05:30
co-authored by Claude Opus 5.5
parent 88c4242692
commit 88f34f58e3
4 changed files with 140 additions and 17 deletions
+34
View File
@@ -303,6 +303,35 @@ export function lastHalfYearlyCouponDate(maturityStr: string, onStr: string): st
return best ? formatDDMMYYYY(best) : '';
}
/** First half-yearly coupon date after `onStr` (coupons fall on the maturity day/month and six months
* either side), never later than maturity. */
export function nextHalfYearlyCouponDate(maturityStr: string, onStr: string): string {
const last = lastHalfYearlyCouponDate(maturityStr, onStr);
const mat = parseDDMMYYYY(maturityStr);
if (!last || isNaN(mat.getTime())) {
return '';
}
const l = parseDDMMYYYY(last);
const year = l.getFullYear() + (l.getMonth() >= 6 ? 1 : 0);
const month = (l.getMonth() + 6) % 12;
const next = new Date(year, month, Math.min(mat.getDate(), new Date(year, month + 1, 0).getDate()));
return formatDDMMYYYY(next > mat ? mat : next);
}
/** The two coupon days of a half-yearly security, e.g. "14 Feb / 14 Aug". */
export function couponDatesLabel(maturityStr: string): string {
const mat = parseDDMMYYYY(maturityStr);
if (isNaN(mat.getTime())) {
return '';
}
const label = (month: number): string => {
const day = Math.min(mat.getDate(), new Date(2001, month + 1, 0).getDate());
return `${day} ${new Date(2001, month, 1).toLocaleString('en-US', { month: 'short' })}`;
};
const first = mat.getMonth() % 6;
return `${label(first)} / ${label(first + 6)}`;
}
export function formatDDMMYYYY(d: Date): string {
return `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
}
@@ -1480,6 +1509,11 @@ export const INVESTMENT_GL_BY_TYPE: Record<InstrumentType, string> = {
SHARE: '100706'
};
/** Charges on an investment deal — expenses on their own GLs, not part of the book value (Jitendra Sir). */
export const STAMP_DUTY_GL: InvestmentGlOption = { glCode: '500601', glName: 'STAMP DUTY ON INVESTMENTS' };
export const BROKERAGE_GL: InvestmentGlOption = { glCode: '500602', glName: 'BROKERAGE ON INVESTMENTS' };
export const OTHER_CHARGES_GL: InvestmentGlOption = { glCode: '500603', glName: 'OTHER CHARGES ON INVESTMENTS' };
/** Interest receivable (the senior's "Provision A/c") — debited for interest bought from the seller. */
export const INTEREST_RECEIVABLE_GL: InvestmentGlOption = {
glCode: '100603',