1+ /**
2+ * Computes all 18 lines of VA Form 26-8923 from raw inputs.
3+ * Returns an object with all computed line values.
4+ * All monetary values are rounded to 2 decimal places at each line.
5+ * Line 18 is always rounded DOWN (Math.floor to cents) per form note.
6+ *
7+ * @param {Object } inputs
8+ * @param {number } inputs.line1ExistingLoanBalance
9+ * @param {number } inputs.line2CashPaymentFromVeteran
10+ * @param {number } inputs.line5DiscountPercent
11+ * @param {number } inputs.line6OriginationFeePercent
12+ * @param {number } inputs.line7FundingFeePercent
13+ * @param {number } inputs.line8OtherClosingCosts
14+ * @returns {Object } computed line values
15+ */
16+ export function computeIRRRLWorksheet ( inputs ) {
17+ const {
18+ line1ExistingLoanBalance = 0 ,
19+ line2CashPaymentFromVeteran = 0 ,
20+ line5DiscountPercent = 0 ,
21+ line6OriginationFeePercent = 0 ,
22+ line7FundingFeePercent = 0.5 ,
23+ line8OtherClosingCosts = 0 ,
24+ } = inputs ;
25+
26+ // Section I
27+ const line3Total = round2 ( line1ExistingLoanBalance - line2CashPaymentFromVeteran ) ;
28+
29+ // Section II
30+ const line4CarryForward = line3Total ;
31+ const line5DollarAmount = round2 ( line4CarryForward * ( line5DiscountPercent / 100 ) ) ;
32+ const line6DollarAmount = round2 ( line4CarryForward * ( line6OriginationFeePercent / 100 ) ) ;
33+ const line7DollarAmount = round2 ( line4CarryForward * ( line7FundingFeePercent / 100 ) ) ;
34+ const line9PreliminaryTotal = round2 (
35+ line4CarryForward +
36+ line5DollarAmount +
37+ line6DollarAmount +
38+ line7DollarAmount +
39+ line8OtherClosingCosts ,
40+ ) ;
41+
42+ // Section III
43+ const line10CarryForward = line9PreliminaryTotal ;
44+ const line11DiscountOnLine10 = round2 (
45+ line10CarryForward * ( line5DiscountPercent / 100 ) ,
46+ ) ;
47+ const line12Subtotal = round2 ( line10CarryForward + line11DiscountOnLine10 ) ;
48+ const line13SubtractLine5 = line5DollarAmount ;
49+ const line14Subtotal = round2 ( line12Subtotal - line5DollarAmount ) ;
50+ const line15SubtractLine7 = line7DollarAmount ;
51+ const line16Subtotal = round2 ( line14Subtotal - line7DollarAmount ) ;
52+ const line17FinalFundingFee = round2 (
53+ line16Subtotal * ( line7FundingFeePercent / 100 ) ,
54+ ) ;
55+ const line18Raw = line16Subtotal + line17FinalFundingFee ;
56+
57+ // Round DOWN always — never round up. Math.floor to nearest cent.
58+ const line18MaxLoanAmount = floorToCents ( line18Raw ) ;
59+
60+ // Rounding differential for the $50 recomputation warning
61+ const roundingDifferential = round2 ( line18Raw - line18MaxLoanAmount ) ;
62+
63+ return {
64+ line3Total,
65+ line4CarryForward,
66+ line5DollarAmount,
67+ line6DollarAmount,
68+ line7DollarAmount,
69+ line9PreliminaryTotal,
70+ line10CarryForward,
71+ line11DiscountOnLine10,
72+ line12Subtotal,
73+ line13SubtractLine5,
74+ line14Subtotal,
75+ line15SubtractLine7,
76+ line16Subtotal,
77+ line17FinalFundingFee,
78+ line18MaxLoanAmount,
79+ roundingDifferential,
80+ } ;
81+ }
82+
83+ /**
84+ * Rounds to 2 decimal places using banker-safe method.
85+ * @param {number } value
86+ * @returns {number }
87+ */
88+ export function round2 ( value ) {
89+ return Math . round ( ( value + Number . EPSILON ) * 100 ) / 100 ;
90+ }
91+
92+ /**
93+ * Floors to nearest cent (always rounds DOWN per form note).
94+ * @param {number } value
95+ * @returns {number }
96+ */
97+ export function floorToCents ( value ) {
98+ return Math . floor ( value * 100 ) / 100 ;
99+ }
100+
101+ /**
102+ * Formats a number as a US dollar currency string.
103+ * @param {number|null } value
104+ * @returns {string }
105+ */
106+ export function formatCurrency ( value ) {
107+ if ( value === null || value === undefined || Number . isNaN ( value ) ) {
108+ return '$0.00' ;
109+ }
110+ return new Intl . NumberFormat ( 'en-US' , {
111+ style : 'currency' ,
112+ currency : 'USD' ,
113+ } ) . format ( value ) ;
114+ }
0 commit comments