packages feed

tax-ato-2025.1: src/Data/Tax/ATO/Common.hs

-- This file is part of hs-tax-ato
-- Copyright (C) 2018  Fraser Tweedale
--
-- hs-tax-ato is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

{-|

Common taxes and helpers.

-}

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.Tax.ATO.Common
  (
  -- * Tax tables
    TaxTables(..)

  -- * Classes
  , HasTaxableIncome(..)
  , HasTaxWithheld(..)

  -- * Medicare levy
  , MedicareLevyRatesAndThresholds(..)
  , medicareLevy'

  -- * Common taxes and helpers
  , medicareLevy
  , lowIncomeTaxOffset
  , lowIncomeTaxOffset2021
  , lamito
  , corporateTax

  -- * Deduction rates that vary by financial year
  , applyCentsPerKilometreMethod
  , HasCentsPerKilometreMethod

  , applyFixedRateMethod
  , HasFixedRateMethod
  , applyFixedRateMethodPre2023
  , HasFixedRateMethodPre2023
  , applyShortcutMethod
  , HasShortcutMethod

  -- * Convenience functions
  , thresholds'
  , marginal'
  ) where

import Control.Lens (Getter, review, to, view)
import Data.Bifunctor (first)
import Data.Proxy
import GHC.TypeLits

import Data.Tax
import Data.Tax.ATO.PrivateHealthInsuranceRebate
import Data.Tax.ATO.FY

-- | A set of tax tables for a particular financial year
data TaxTables y a = TaxTables
  { ttIndividualIncomeTax :: Tax (Money a) (Money a)
  , ttMedicareLevyRatesAndThresholds :: MedicareLevyRatesAndThresholds a
  , ttMedicareLevySurcharge :: Tax (Money a) (Money a)
  , ttHelp :: Tax (Money a) (Money a)
  , ttSfss :: Tax (Money a) (Money a)
  , ttAdditional :: Tax (Money a) (Money a)
  -- ^ Additional taxes and offsets that apply at EOY
  , ttPHIRebateRates :: PrivateHealthInsuranceRebateRates a
  }


data MedicareLevyRatesAndThresholds a = MedicareLevyRatesAndThresholds
  { medicareLevyRate :: a
  , medicareLevyThresholdIndividual :: Money a
  , medicareLevyThresholdIndividualSeniorAndPensioner :: Money a
  , medicareLevyThresholdFamily :: Money a
  , medicareLevyThresholdFamilySeniorAndPensioner :: Money a
  , medicareLevyThresholdDependentChildIncrease :: Money a
  }

medicareLevy'
  :: (Fractional a, Ord a)
  => MedicareLevyRatesAndThresholds a
  -> Money a          -- ^ taxable income
  -> Maybe (Money a)  -- ^ spouse taxable income
  -> Integer          -- ^ number of dependents
  -> Money a
medicareLevy' rates me mYou nDeps = case mYou of
  Nothing | nDeps <= 0 ->
    let
      t = medicareLevyThresholdIndividual rates
    in
      getTax (mk t) me
  _ ->
    let
      t = medicareLevyThresholdFamily rates
          $+$ fromIntegral nDeps *$ medicareLevyThresholdDependentChildIncrease rates
      combinedIncome = maybe me (me $+$) mYou
    in
      (me $/$ combinedIncome) *$ getTax (mk t) combinedIncome
  where
    mk l = lesserOf (above l 0.1) (flat (medicareLevyRate rates))


-- | The Medicare levy, incorporating the Medicare levy reduction.
-- The rate is 10% of the income above the given shade-in threshold
-- or 2% of the total income, whichever is less.
--
medicareLevy :: (Fractional a, Ord a) => Money a -> Tax (Money a) (Money a)
medicareLevy l = lesserOf (above l 0.1) (flat 0.02)

-- | /Low income tax offset (LITO)/.  $445, reduced by 1.5c for
-- every dollar earned over $37,000. The lump amount may change in
-- the future.
lowIncomeTaxOffset :: (Fractional a, Ord a) => Tax (Money a) (Money a)
lowIncomeTaxOffset =
  limit mempty
  (lump (review money (-445)) <> above (review money 37000) 0.015)

-- | /Low income tax offset/, 2020–21 version.
lowIncomeTaxOffset2021 :: (Fractional a, Ord a) => Tax (Money a) (Money a)
lowIncomeTaxOffset2021 =
  limit mempty $
    lump (review money (-700))
    <> above (review money 37500) 0.05
    <> above (review money 45000) (-0.035)

-- | Low and middle income tax offset. FY2019, 2020, 2021.
--
lamito :: (Fractional a, Ord a) => Tax (Money a) (Money a)
lamito = limit mempty $
  greaterOf (lump (review money (-1080)))
    ( lump (review money (-255))
    <> above (review money 37000) (-0.075) )
  <> above (review money 90000) 0.03

-- | The corporate tax rate of 30%.  In the future, different rates may
-- be levied depending on business turnover/income.
corporateTax :: (Fractional a) => Tax (Money a) (Money a)
corporateTax = flat 0.3

thresholds', marginal'
  :: (Fractional a, Ord a) => [(a, a)] -> Tax (Money a) (Money a)
thresholds' = thresholds . fmap (first (review money))
-- ^ Convenience wrapper for 'thresholds'.  Turns the thresholds into 'Money'
marginal' = marginal . fmap (first (review money))
-- ^ Convenience wrapper for 'marginal'.  Turns the margins into 'Money'


-- | Types that may have a taxable income component.
class HasTaxableIncome a b c where
  taxableIncome :: Getter (a b) (Money c)

instance
    (Foldable t, HasTaxableIncome x a a, Num a)
    => HasTaxableIncome t (x a) a
    where
  taxableIncome = to (foldMap (view taxableIncome))

-- | Types that can have an amount of tax withheld.
class HasTaxWithheld a b c where
  taxWithheld :: Getter (a b) (Money c)

instance (Foldable t, HasTaxWithheld x a a, Num a)
            => HasTaxWithheld t (x a) a where
  taxWithheld = to (foldMap (view taxWithheld))


-- | See 'applyCentsPerKilometreMethod'.
class HasCentsPerKilometreMethod y where
  centsPerKilometre :: Num a => a

instance (2016 <= y, y <= 2025, FinancialYear y) => HasCentsPerKilometreMethod y where
  centsPerKilometre = case fromProxy (Proxy @y) of
    y | y == 2025           -> 88
      | y == 2024           -> 85
      | y == 2023           -> 78
      | y > 2020, y <= 2022 -> 72
      | y > 2018, y <= 2020 -> 68
      | y > 2015, y <= 2018 -> 66
      | otherwise           ->  0  -- can't happen

-- | Apply the single-rate cents per kilometre method (in use since
-- the 2015–16 FY).  Clamped (by law) at 5000 kms.  Use
-- @TypeApplications@ to specify the financial year, e.g.
--
-- @
-- import Data.Tax.ATO.FY.2025 (FY)
--
-- y = applyCentsPerKilometreMethod \@2025 1111  -- using type literal
-- x = applyCentsPerKilometreMethod \@FY   1111  -- same thing, using type synonym
-- @
--
applyCentsPerKilometreMethod
  :: forall y a. (HasCentsPerKilometreMethod y, Fractional a, Ord a)
  => a -> Money a
applyCentsPerKilometreMethod kms = Money $ centsPerKilometre @y * min kms 5000 / 100


-- | See 'applyFixedRateMethod'
class HasFixedRateMethod y where
  fixedRateMethodCentsPerHour :: Num a => a

instance HasFixedRateMethod 2023 where
  fixedRateMethodCentsPerHour = 67
instance HasFixedRateMethod 2024 where
  fixedRateMethodCentsPerHour = 67
instance HasFixedRateMethod 2025 where
  fixedRateMethodCentsPerHour = 70

-- | The updated fixed rate method available from the 2022–23 income
-- year.  It covers:
--
-- * home and mobile internet or data expenses
-- * mobile and home phone usage expenses
-- * electricity and gas (energy expenses) for heating, cooling and lighting
-- * stationery and computer consumables, such as printer ink and paper.
--
-- You can separately claim deductions for work-related use of
-- technology and office furniture, and (in limited circumstances)
-- occupancy expenses and cleaning expenses.
--
-- Use @TypeApplications@ to specify the financial year, e.g.
--
-- @
-- import Data.Tax.ATO.FY.2025 (FY)
--
-- y = applyFixedRateMethod \@2025 1111  -- using type literal
-- x = applyFixedRateMethod \@FY   1111  -- same thing, using type synonym
-- @
--
-- For earlier income years use 'applyFixedRateMethodPre2023'.
--
applyFixedRateMethod
  :: forall y a. (HasFixedRateMethod y, Fractional a)
  => a -> Money a
applyFixedRateMethod hours = Money $ fixedRateMethodCentsPerHour @y * hours / 100


-- | See 'applyShortcutMethod'
class HasShortcutMethod y where
  shortcutMethodCentsPerHour :: Num a => a

instance (2020 <= y, y <= 2022) => HasShortcutMethod y where
  shortcutMethodCentsPerHour = 80

-- | The shortcut method for working from home deductions was
-- available from 1 March 2020 to 30 June 2020 in the 2019–20 income
-- year, and for the 2020–21 and 2021–22 income years.  It covers:
--
-- * phone and data expenses
-- * internet expenses
-- * the decline in value of equipment and furniture
-- * electricity and gas (energy expenses) for heating, cooling and lighting.
--
-- Use @TypeApplications@ to specify the financial year, e.g.
--
-- @
-- import Data.Tax.ATO.FY.2025 (FY)
--
-- y = applyShortcutMethod \@2025 1111  -- using type literal
-- x = applyShortcutMethod \@FY   1111  -- same thing, using type synonym
-- @
--
applyShortcutMethod
  :: forall y a. (HasShortcutMethod y, Fractional a)
  => a -> Money a
applyShortcutMethod hours = Money $ shortcutMethodCentsPerHour @y * hours / 100


-- | See 'applyFixedRateMethodPre2023'
class HasFixedRateMethodPre2023 y where
  fixedRateMethodPre2023CentsPerHour :: Num a => a

instance (2002 <= y, y <= 2022, FinancialYear y) => HasFixedRateMethodPre2023 y where
  fixedRateMethodPre2023CentsPerHour = case fromProxy (Proxy @y) of
    y | y > 2018, y <= 2022 -> 52
      | y > 2014, y <= 2018 -> 45
      | y > 2010, y <= 2014 -> 34
      | y > 2004, y <= 2010 -> 26
      | y > 2001, y <= 2004 -> 20
      | otherwise           ->  0  -- can't happen

-- | The fixed rate method available up to the 2021–22 income year.
-- It differs from the current fixed rate method (see
-- 'HasFixedRateMethod') in which categories of expenses are
-- included.  This method covers:
--
-- * the decline in value of home office furniture and
--   furnishings – for example, a desk
-- * electricity and gas (energy expenses) for heating,
--   cooling and lighting
-- * cleaning your dedicated home office.
--
-- You should separately claim for:
--
-- * home phone and internet expenses, including the decline in
--   value of the handset
-- * stationery and computer consumables, such as printer ink
--   and paper
-- * decline in value of depreciating assets other than
--   home office furniture and furnishings used for work
--   purposes – for example, computers and laptops.
--
-- Use @TypeApplications@ to specify the financial year, e.g.
--
-- @
-- import Data.Tax.ATO.FY.2025 (FY)
--
-- y = applyFixedRateMethodPre2023 \@2025 1111  -- using type literal
-- x = applyFixedRateMethodPre2023 \@FY   1111  -- same thing, using type synonym
-- @
--
-- For later income years, use 'applyFixedRateMethod'.
--
applyFixedRateMethodPre2023
  :: forall y a. (HasFixedRateMethodPre2023 y, Fractional a)
  => a -> Money a
applyFixedRateMethodPre2023 hours = Money $ fixedRateMethodPre2023CentsPerHour @y * hours / 100