Business-Tax-US-Form_1040-Worksheets

 view release on metacpan or  search on metacpan

lib/Business/Tax/US/Form_1040/Worksheets.pm  view on Meta::CPAN


    $inputs = {
        box5    => 30000.00,    # Sum of box 5 on all Forms SSA-1099 and RRB-1099
        l1z     => 0,           # Form 1040, line 1z
        l2b     => 350.00,
        l3b     => 6000.00,
        l4b     => 0,
        l5b     => 8000.00,
        l7a     => 1600.00,     # Use 'l7' for tax years 2022 through 2024
        l8      => 1000.00,
        l2a     => 0,
        s1l11     => 0,         # Schedule 1 (Form 1040), line 11
        s1l12     => 0,
        s1l13     => 0,
        s1l14     => 0,
        s1l15     => 0,
        s1l16     => 0,
        s1l17     => 0,
        s1l18     => 0,
        s1l19     => 0,
        s1l20     => 0,
        s1l23     => 0,
        s1l25     => 0,
        status     => 'single', # Social Security Benefits Worksheet, line 8
        filing_year => 2025,
    };

Appropriate values for elements in C<$inputs>:

=over 4

=item * C<l7a>: Capital gain (or loss):  Number holding dollar amount (to
2-decimal maximum).  If value is C<0>, element may be omitted.

B<NOTE: For tax years up through 2024, use the value on Line 7 of IRS Form
1040.  Beginning with tax year 2025, this value is entered on Line 7a of IRS
Form 1040.>

=item * C<status>:  String holding one of C<married single married_sep>.

=item * C<filing_year>:  Filing year (4-digits).

=item *

All others:  Number holding dollar amount (to 2-decimal maximum).  If value is
C<0>, element may be omitted.

=back

=item * Return Value

Scalar holding taxable social security benefits in dollars and two cents,
I<e.g.,> C<289.73>.

=back

=cut

my %data_2022_ssb = (
    worksheet_line_count    => 18,
    ssa_percentage          => 0.5,
    percentage_a            => 0.85,
    percentage_b            => 0.85,
    percentage_c            => 0.85,
    married_amt_a           => 32000,
    single_amt_a            => 25000,
    married_amt_b           => 12000,
    single_amt_b            =>  9000,
    other_percentage        => 0.5,
);
# inspection of 2023 Soc Sec worksheet indicates no change in
# parameters
my %data_2023_ssb = map { $_ => $data_2022_ssb{$_} } keys %data_2022_ssb;
# inspection of 2024 Soc Sec worksheet indicates no change in
# parameters
my %data_2024_ssb = map { $_ => $data_2023_ssb{$_} } keys %data_2023_ssb;
# inspection of 2025 Soc Sec worksheet indicates no change in
# parameters
my %data_2025_ssb = map { $_ => $data_2024_ssb{$_} } keys %data_2024_ssb;

our %params = (
    ssb => {
        2022 => { %data_2022_ssb },
        2023 => { %data_2023_ssb },
        2024 => { %data_2024_ssb },
        2025 => { %data_2025_ssb },
    },
    qd => {
        2022 => {
            worksheet_line_count        => 21,
            single_or_married_sep_amt_a => 41675,
            married_amt_a               => 83350,
            head_of_household_amt_a     => 55800,
            single_amt_b                => 459750,
            married_sep_amt_b           => 258600,
            married_amt_b               => 517200,
            head_of_household_amt_b     => 488500,
            percentage_a                => 0.15,
            percentage_b                => 0.20,
        },
        2023 => {
            worksheet_line_count        => 21,
            single_or_married_sep_amt_a => 44625,
            married_amt_a               => 89250,
            head_of_household_amt_a     => 59750,
            single_amt_b                => 492300,
            married_sep_amt_b           => 276900,
            married_amt_b               => 553850,
            head_of_household_amt_b     => 523050,
            percentage_a                => 0.15,
            percentage_b                => 0.20,
        },
        2024 => {
            worksheet_line_count        => 21,
            single_or_married_sep_amt_a => 47025,
            married_amt_a               => 94050,
            head_of_household_amt_a     => 63000,
            single_amt_b                => 518900,
            married_sep_amt_b           => 291850,
            married_amt_b               => 583750,
            head_of_household_amt_b     => 551350,
            percentage_a                => 0.15,
            percentage_b                => 0.20,
        },
        2025 => {
            worksheet_line_count        => 21,
            single_or_married_sep_amt_a => 48350,
            married_amt_a               => 96700,
            head_of_household_amt_a     => 64750,
            single_amt_b                => 533400,
            married_sep_amt_b           => 300000,
            married_amt_b               => 600050,
            head_of_household_amt_b     => 566700,
            percentage_a                => 0.15,
            percentage_b                => 0.20,
        },
    },
);

sub social_security_benefits {
    my ($inputs) = @_;
    my $rv = _social_security_benefits_engine($inputs);
    return $rv->{taxable_benefits};
}

=head2 C<social_security_worksheet_data()>

=over 4

=item * Purpose

Calculate data needed for the purpose of completing all entries (except
checkboxes) on the Social Security Benefits Worksheet.

    my $worksheet_data = social_security_worksheet_data( $inputs );

=item * Arguments

The same, single hash reference which is supplied to
C<social_security_benefits()> (I<q.v.> above).

=item * Return Value

Reference to an array holding the data to be entered on lines 1-18 of the SSBW.
The indexes to the elements of that array correspond to those line numbers on the SSBW.

=back

=cut

sub social_security_worksheet_data {
    my ($inputs) = @_;
    my $rv = _social_security_benefits_engine($inputs);
    return $rv->{worksheet_data};
}

sub _social_security_benefits_engine {
    my ($inputs) = @_;
    croak "Argument to social_security_benefits() must be hashref"
        unless ref($inputs) eq 'HASH';

    croak "Must include 4-digit value for 'filing_year' in argument to social_security_benefits()"
        unless $inputs->{filing_year};
    my $filing_year = $inputs->{filing_year}; # TODO add test for numeric; required element
    croak "Must include 4-digit value for 'filing_year' in argument to social_security_benefits()"
        unless $inputs->{filing_year} =~ m/^\d{4}$/;
    croak "'filing_year' must be > 2000" unless $inputs->{filing_year} > 2000;

    my @numerics = (
        qw(
            box5
            l1z
            l2b
            l3b
            l4b
            l5b
        ),
        $filing_year <= 2024 ? 'l7' : 'l7a',
        qw(
            l8
            l2a
            s1l11 s1l12 s1l13 s1l14 s1l15
            s1l16 s1l17 s1l18 s1l19 s1l20
            s1l23
            s1l25
        ),
    );
    my %permitted = map { $_ => 1 } (@numerics, 'status', 'filing_year');
    for my $k (keys %{$inputs}) {
        croak "Invalid element in hashref passed to social_security_benefits()"
            unless $permitted{$k};
    }
    my %permitted_statuses = map { $_ => 1 } ( qw|
        married single married_sep
    | );
    croak "Invalid value for 'status' element"
        unless (defined $inputs->{status} and $permitted_statuses{$inputs->{status}});
    my $data = {};
    for my $el (@numerics) {
        $data->{$el} = $inputs->{$el} // 0;
    }
    $data->{status} = $inputs->{status};
    my @lines = (undef, (undef) x $params{ssb}{$filing_year}{worksheet_line_count});
    my $formatted_lines;
    $lines[1] = $data->{box5};
    $lines[2] = $lines[1] * $params{ssb}{$filing_year}{ssa_percentage};
    $lines[3] =
        $data->{l1z} +
        $data->{l2b} +
        $data->{l3b} +
        $data->{l4b} +
        $data->{l5b} +
        ($filing_year <= 2024 ? $data->{l7} : $data->{l7a}) +
        $data->{l8};
    $lines[4] = $data->{l2a};
    $lines[5] = $lines[2] + $lines[3] + $lines[4];
    # sum up some Adjustments to Income (Schedule 1, Part II)
    $lines[6] =
        $data->{s1l11} +
        $data->{s1l12} +
        $data->{s1l13} +
        $data->{s1l14} +
        $data->{s1l15} +
        $data->{s1l16} +
        $data->{s1l17} +
        $data->{s1l18} +
        $data->{s1l19} +
        $data->{s1l20} +
        $data->{s1l23} +
        $data->{s1l25};
    if (! ($lines[6] < $lines[5]) ) {
        $formatted_lines = decimal_lines(\@lines);
        return {
          taxable_benefits => 0,
          worksheet_data => $formatted_lines,
        };
    }
    $lines[7] = $lines[5] - $lines[6];
    if ($data->{status} eq 'married_sep') {
        $lines[16] = $lines[7] * $params{ssb}{$filing_year}{percentage_b};
        $lines[17] = $lines[1] * $params{ssb}{$filing_year}{percentage_c};
        $lines[18] = min($lines[16], $lines[17]);
        $formatted_lines = decimal_lines(\@lines);
        return {
            taxable_benefits => $lines[18],
            worksheet_data => $formatted_lines,
        };
    }
    $lines[8] = $data->{status} eq 'married'
        ? $params{ssb}{$filing_year}{married_amt_a}
        : $params{ssb}{$filing_year}{single_amt_a};
    unless ($lines[8] < $lines[7]) {
        $formatted_lines = decimal_lines(\@lines);
        return {
            taxable_benefits => 0,
            worksheet_data => $formatted_lines,
        };
    }
    $lines[9] = $lines[7] - $lines[8];
    $lines[10] = $data->{status} eq 'married'
        ? $params{ssb}{$filing_year}{married_amt_b}
        : $params{ssb}{$filing_year}{single_amt_b};
    my $diff = $lines[9] - $lines[10];
    $lines[11] = $diff > 0 ? $diff : 0;
    $lines[12] = min($lines[9], $lines[10]);
    $lines[13] = $lines[12] * $params{ssb}{$filing_year}{other_percentage};
    $lines[14] = min($lines[2], $lines[13]);
    my $x = $lines[11] * $params{ssb}{$filing_year}{percentage_a};
    $lines[15] = $x > 0 ? $x : 0;
    $lines[16] = $lines[14] + $lines[15];
    $lines[17] = $lines[1] * $params{ssb}{$filing_year}{percentage_c};
    $lines[18] = min($lines[16], $lines[17]);
    $formatted_lines = decimal_lines(\@lines);
    return {
        taxable_benefits => $lines[18],
        worksheet_data => $formatted_lines,
    };
}

=head2 C<pp_ssbw()>

=over 4

=item * Purpose

Pretty-print ('pp' for short) the results of
C<social_security_worksheet_data()> for easier transcription to printed
worksheet.

    pp_ssbw($results);

=item * Arguments

The array reference which is the return value of
C<social_security_worksheet_data()>.  Required.

=item * Return Value

Implicitly returns true value upon success.

=item * Comment

In a future version of this library, this function may take a second argument
which presumably will be a string holding the path to an output file.  For
now, the function simply prints to C<STDOUT>.

=back

=cut

sub _compose_worksheet_line {
    my ($line_number, $formatting, $text, $result) = @_;
    my $line = sprintf("$formatting" => (
        $line_number,
        $text,
        $line_number,
        $result,
    ) );
    return $line;
}

sub pp_ssbw {
    my ($results) = @_;
    croak "First argument to pp_ssbw() must be array reference"
        unless ref($results) eq 'ARRAY';
    my @output = ();
    my $line_number = 0;
    my $one_wide_format     = "% 2s. %-40s  % 2s. % 12.2f";
    my $two_wide_format     = "% 2s. %-52s        % 2s. % 12.2f";
    my @f = (undef, $one_wide_format, $two_wide_format);

lib/Business/Tax/US/Form_1040/Worksheets.pm  view on Meta::CPAN

        head_of_household
    | );
    croak "Invalid value for 'status1' element"
        unless (defined $inputs->{status1} and $permitted_statuses_1{$inputs->{status1}});

    my %permitted_statuses_2 = map { $_ => 1 } ( qw|
        single
        married_sep
        married
        head_of_household
    | );
    croak "Invalid value for 'status2' element"
        unless (defined $inputs->{status2} and $permitted_statuses_2{$inputs->{status2}});

    my %permitted = map { $_ => 1 } (@numerics, 'status1', 'status2', 'filing_year');
    for my $k (keys %{$inputs}) {
        croak "Invalid element in hashref passed to qualified_dividends_capital_gains_tax()"
            unless $permitted{$k};
    }

    my $data = {};
    for my $el (@numerics) {
        $inputs->{$el} //= 0;
        $data->{$el} = $inputs->{$el};
    }
    $data->{status1} = $inputs->{status1};
    $data->{status2} = $inputs->{status2};
    my $filing_year = $inputs->{filing_year}; # TODO add test for numeric; required element
    my @lines = (undef, (undef) x $params{qd}{$filing_year}{worksheet_line_count});
    # We will return after line 21 because thereafter we have to look up
    # things in the Tax Table.
    $lines[1] = $inputs->{l15};
    $lines[2] = $inputs->{l3a};
    $lines[3] = $inputs->{sD};
    $lines[4] = $lines[2] + $lines[3];
    my $diff = $lines[1] - $lines[4];
    $lines[5] = $diff > 0 ? $diff : 0;
    $lines[6] = $inputs->{status1} eq 'single_or_married_sep'
        ? $params{qd}{$filing_year}{single_or_married_sep_amt_a}
        : $inputs->{status1} eq 'married'
            ? $params{qd}{$filing_year}{married_amt_a}
            : $params{qd}{$filing_year}{head_of_household_amt_a};
    $lines[7] = min($lines[1], $lines[6]);
    $lines[8] = min($lines[5], $lines[7]);
    $lines[9] = $lines[7] - $lines[8];
    $lines[10] = min($lines[1], $lines[4]);
    $lines[11] = $lines[9];
    $lines[12] = $lines[10] - $lines[11];
    $lines[13] = $inputs->{status2} eq 'single'
        ? $params{qd}{$filing_year}{single_amt_b}
        : $inputs->{status2} eq 'married_sep'
            ? $params{qd}{$filing_year}{married_sep_amt_b}
            : $inputs->{status2} eq 'married'
                ? $params{qd}{$filing_year}{married_amt_b}
                : $params{qd}{$filing_year}{head_of_household_amt_b};
    $lines[14] = min($lines[1], $lines[13]);
    $lines[15] = $lines[5] + $lines[9];
    my $diff1 = $lines[14] - $lines[15];
    $lines[16] = $diff1 > 0 ? $diff1 : 0;
    $lines[17] = min($lines[12], $lines[16]);
    $lines[18] = $lines[17] * $params{qd}{$filing_year}{percentage_a};
    $lines[19] = $lines[9] + $lines[17];
    $lines[20] = $lines[10] - $lines[19];
    $lines[21] = $lines[20] * $params{qd}{$filing_year}{percentage_b};
    # We will need to use 5, 18, 21, 22 and 1
    my @formatted_lines = (
        $lines[0], # undef
        map { sprintf("%.2f" => $lines[$_]) } (1..$#lines),
    );
    return \@formatted_lines;
}

=head2 C<pp_qdcgtw()>

=over 4

=item * Purpose

Pretty-print ('pp' for short) the results of
C<qualified_dividends_capital_gains_tax()> for easier transcription to printed
worksheet.

    pp_qdcgtw($results);

=item * Arguments

The array reference which is the return value of
C<qualified_dividends_capital_gains_tax()>.  Required.

=item * Return Value

Implicitly returns true value upon success.

=item * Comment

In a future version of this library, this function may take a second argument
which presumably will be a string holding the path to an output file.  For
now, the function simply prints to C<STDOUT>.

=back

=cut

sub pp_qdcgtw {
    my ($results) = @_;
    croak "First argument to pp_qdcgtw() must be array reference"
        unless ref($results) eq 'ARRAY';
    my @output = ();
    my $line_number = 0;
    my $one_wide_format     = "% 2s. %-28s  % 2s. % 12.2f";
    my $two_wide_format     = "% 2s. %-40s        % 2s. % 12.2f";
    my $three_wide_format   = "% 2s. %-52s              % 2s. % 12.2f";
    my @f = (undef, $one_wide_format, $two_wide_format, $three_wide_format);

    my $lines = [
        undef,
        { formatting => $f[2], text => "Enter Form 1040, line 15" },
        { formatting => $f[1], text => "Enter Form 1040, line 3a" },
        { formatting => $f[1], text => "Sche. D/Form 1040, line 7/7a" },
        { formatting => $f[1], text => "Line 2 - Line 3" },
        { formatting => $f[2], text => "Line 1 - Line 4" },
        { formatting => $f[2], text => "Filing status amount (1)" },
        { formatting => $f[2], text => "Smaller of lines 1 or 6" },
        { formatting => $f[2], text => "Smaller of lines 5 or 7" },



( run in 1.035 second using v1.01-cache-2.11-cpan-364913b4093 )