packages feed

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

-- This file is part of hs-tax-ato
-- Copyright (C) 2018-2025  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/>.

{-|

Types and computations for taxes in Australia.

No guarantee that computations are correct, complete or current.

Lots of things are not implemented, including (but not limited to):
__ETPs__, income from __partnerships and trusts__,
__superannuation__ income streams and lump payments, tax losses from
previous years, Medicare levy exemptions, adjustments,
and variations based on family income and dependents.

-}

module Data.Tax.ATO
  (
  -- * Synopsis
  -- $synopsis

  -- * Individual tax returns
    TaxReturnInfo
  , newTaxReturnInfo
  , newTaxReturnInfoForTables

  -- ** Income

  -- *** PAYG Payment Summaries
  , paymentSummariesIndividualNonBusiness
  , paymentSummariesForeignEmployment
  , paymentSummariesBusinessAndPersonalServicesIncome
  , paymentSummariesWithholdingWhereABNNotQuoted
  , paymentSummaries

  -- *** Interest
  , interest

  -- *** Dividends
  , Dividend(..)
  , dividends
  , dividendFromGross
  , dividendFromNet
  , dividendFromNetFranked
  , dividendFromNetFranked30

  -- *** Capital gains tax (CGT)
  , HasCapitalLossCarryForward(..)
  , cgtEvents

  -- *** Employee share schemes
  , ESSStatement
  , newESSStatement
  , ess
  , essEmployerDetails
  , essTaxedUpfrontReduction
  , essTaxedUpfrontNoReduction
  , essDeferral
  , essPre2009
  , essTFNAmounts
  , essForeignSourceDiscounts

  -- *** Foreign income
  , foreignIncome

  -- ** Medicare Levy Surcharge and Private Health Insurance
  , mlsExemption
  , privateHealthInsurancePolicyDetails

  -- ** Student loan balances
  , helpBalance
  , sfssBalance

  -- ** Spouse details
  , SpouseDetails
  , spouseDetails
  , newSpouseDetails
  , spouseTaxableIncome

  -- ** Income Tests
  , IncomeTests
  , incomeTests
  , newIncomeTests
  , taxFreeGovernmentPensionsOrBenefits
  , targetForeignIncome
  , childSupportPaid
  , dependentChildren

  -- ** Deductions
  , Deductions
  , deductions
  , totalDeductions
  , workRelatedCarExpenses
  , workRelatedTravelExpenses
  , workRelatedClothingLaundryAndDryCleaningExpenses
  , workRelatedSelfEducationExpenses
  , otherWorkRelatedExpenses
  , lowValuePoolDeduction
  , interestDeductions
  , dividendDeductions
  , giftsOrDonations
  , costOfManagingTaxAffairs
  , deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity
  , personalSuperannuationContributions
  , deductionForProjectPool
  , forestryManagedInvestmentSchemeDeduction
  , otherDeductions
  , foreignIncomeDeductions

  -- *** Deduction methods
  , HasCentsPerKilometreMethod
  , applyCentsPerKilometreMethod
  , HasFixedRateMethod
  , applyFixedRateMethod
  , HasFixedRateMethodPre2023
  , applyFixedRateMethodPre2023
  , HasShortcutMethod
  , applyShortcutMethod

  -- ** Tax offsets
  , Offsets
  , offsets
  , spouseContributionOffset
  , foreignTaxOffset
  , paygInstalments

  -- ** Assessing tax
  , TaxAssessment
  , assessTax
  , taxBalance
  , taxDue
  , offsetForSuperannuationContributionsOnBehalfOfYourSpouse
  , foreignIncomeTaxOffsets
  , frankingCreditOffset
  , medicareLevyDue
  , medicareLevySurchargeDue
  , studyAndTrainingLoanRepayment
  , paygInstalmentsCredit
  , taxCGTAssessment
  , privateHealthInsuranceRebateAdjustment

  -- *** Division 293
  , division293Income

  -- *** PAYG instalments
  , paygInstalmentIncome

  -- * Corporate tax
  , corporateTax

  -- * Miscellaneous
  , GrossAndWithheld(..)
  , HasTaxableIncome(..)
  , HasTaxWithheld(..)
  , Proportion
  , getProportion
  , proportion
  , module Data.Tax
  , module Data.Tax.ATO.ABN
  , module Data.Tax.ATO.Depreciation
  , module Data.Tax.ATO.PaymentSummary
  , module Data.Tax.ATO.PrivateHealthInsuranceRebate
  , module Data.Tax.ATO.Rounding
  ) where

import Control.Lens (Getter, Lens', (&), foldOf, lens, preview, set, to, view, views)
import Data.Time (Day)

import Data.Tax
import Data.Tax.ATO.CGT
import Data.Tax.ATO.ABN (ABN)
import Data.Tax.ATO.Common
import Data.Tax.ATO.Depreciation
import Data.Tax.ATO.FY
import Data.Tax.ATO.PaymentSummary
import Data.Tax.ATO.PrivateHealthInsuranceRebate
import Data.Tax.ATO.Rounding

{- $synopsis

@
{-# LANGUAGE DataKinds #-}

import Control.Lens
import Data.Time
import Text.PrettyPrint (render)

import Data.Tax.ATO
import "Data.Tax.ATO.CGT"
import "Data.Tax.ATO.Pretty"

-- Import the tables for the financial year ending 30 June 2024
import "Data.Tax.ATO.FY.FY2025"

-- Convenience function for parsing a Data.Time.Day
day :: String -> Day
day = parseTimeOrError False defaultTimeLocale "%Y-%m-%d"

main :: IO ()
main = do
  let assessment = 'assessTax' tables taxReturn
  putStrLn . render $ 'Data.Tax.ATO.Pretty.summariseTaxReturnInfo' taxReturn
  putStrLn ""
  putStrLn . render $ 'Data.Tax.ATO.Pretty.summariseAssessment' assessment

taxReturn :: 'TaxReturnInfo' FY Rational
taxReturn = 'newTaxReturnInfo'
  & set 'paymentSummariesIndividualNonBusiness'
      [ 'newPaymentSummaryIndividualNonBusiness' \"53 004 085 616\"  -- ABN
          & set 'grossPayments'                                       (Money 180000)
          & set 'totalTaxWithheld'                                    (Money  50000)
          & set 'reportableEmployerSuperannuationContributions'       (Money   3000)
          & set 'reportableFringeBenefits' ('fringeBenefitsEmployerNotExempt' $ Money 7274)
      ]

  & set 'cgtEvents'
      [ 'CGTEvent'
          \"CODE\"             -- asset identifier (arbitrary string)
          10                 -- number of units (may be fractional)
          (day "2014-01-01") -- acquisition date
          (Money 30)         -- acquisition price
          (Money 20)         -- acquisition cost (brokerage)
          (day "2024-03-01") -- disposal date
          (Money 300)        -- disposal price
          (Money 20)         -- disposal cost (brokerage)
          mempty             -- capital costs
          mempty             -- costs of ownership
      ]

  & set 'dividends'
      [ 'dividendFromNetFranked30'
          \"CODE\"
          (day "2023-09-01")  -- payment date
          (Money 70)          -- net payment
          ('proportion' 1)      -- franking proportion (1 = 100%)
      ]

  & set 'helpBalance' (Money 10000)

  & set 'privateHealthInsurancePolicyDetails'
      [ 'PrivateHealthInsurancePolicyDetail'
          \"MBF\"
          "98765432"  -- policy number
          (Money 750) -- premiums eligible for rebate
          (Money 180) -- rebate received
          'BenefitCode30'
      , 'PrivateHealthInsurancePolicyDetail'
          "MBF" "98765432" (Money 250) (Money  60) 'BenefitCode31'
      ]

  & set ('deductions' . 'workRelatedCarExpenses') ('applyCentsPerKilometreMethod' @FY 3333)
  & set ('deductions' . 'personalSuperannuationContributions') (Money 5000)
@

-}

-- TODO part year spouse
newtype SpouseDetails a = SpouseDetails
  { _spouseTaxableIncome :: Money a
  -- TODO other fields
  }

newSpouseDetails :: (Num a) => SpouseDetails a
newSpouseDetails = SpouseDetails mempty

spouseTaxableIncome :: Lens' (SpouseDetails a) (Money a)
spouseTaxableIncome =
  lens _spouseTaxableIncome (\s b -> s { _spouseTaxableIncome = b })

data IncomeTests a = IncomeTests
  { _govBenefit :: Money a
  , _targetForeignIncome :: Money a
  , _childSupportPaid :: Money a
  , _dependents :: Integer
  }

newIncomeTests :: (Num a) => IncomeTests a
newIncomeTests = IncomeTests mempty mempty mempty 0

taxFreeGovernmentPensionsOrBenefits :: Lens' (IncomeTests a) (Money a)
taxFreeGovernmentPensionsOrBenefits =
  lens _govBenefit (\s b -> s { _govBenefit = b })

targetForeignIncome :: Lens' (IncomeTests a) (Money a)
targetForeignIncome =
  lens _targetForeignIncome (\s b -> s { _targetForeignIncome = b })

childSupportPaid :: Lens' (IncomeTests a) (Money a)
childSupportPaid =
  lens _childSupportPaid (\s b -> s { _childSupportPaid = b })

dependentChildren :: Lens' (IncomeTests a) Integer
dependentChildren =
  lens _dependents (\s b -> s { _dependents = b })


-- | Individual tax return information.
--
-- Use 'newTaxReturnInfo' to construct.  Alternatively,
-- 'newTaxReturnInfoForTables' can be used to coerce the type
-- parameters to be the same as some 'TaxTables'.
--
-- The following lenses are available:
--
-- +------------------------------------------------------+----------------------------------+
-- | 'mlsExemption'                                       | Medicare levy exemption          |
-- +------------------------------------------------------+----------------------------------+
-- | 'helpBalance'                                        | HELP, VSL, SSL, ABSTUDY SSL,     |
-- |                                                      | and AASL/TSL account balance     |
-- +------------------------------------------------------+----------------------------------+
-- | 'sfssBalance'                                        | SFSS account balance             |
-- +------------------------------------------------------+----------------------------------+
-- | 'paymentSummariesIndividualNonBusiness'              | PAYG payment summaries -         |
-- |                                                      | individual non-business          |
-- +------------------------------------------------------+----------------------------------+
-- | 'paymentSummariesForeignEmployment'                  | PAYG payment summaries -         |
-- |                                                      | foreign employment               |
-- +------------------------------------------------------+----------------------------------+
-- | 'paymentSummariesBusinessAndPersonalServicesIncome'  | PAYG payment summaries -         |
-- |                                                      | business and personal services   |
-- |                                                      | income                           |
-- +------------------------------------------------------+----------------------------------+
-- | 'paymentSummariesWithholdingWhereABNNotQuoted'       | PAYG payment summaries -         |
-- |                                                      | withholding where ABN not quoted |
-- +------------------------------------------------------+----------------------------------+
-- | 'interest'                                           | Interest income and tax withheld |
-- +------------------------------------------------------+----------------------------------+
-- | 'dividends'                                          | Dividend data                    |
-- +------------------------------------------------------+----------------------------------+
-- | 'ess'                                                | Employee Share Scheme statement  |
-- +------------------------------------------------------+----------------------------------+
-- | 'foreignIncome'                                      | Foreign income                   |
-- +------------------------------------------------------+----------------------------------+
-- | 'cgtEvents'                                          | Capital gains and losses         |
-- +------------------------------------------------------+----------------------------------+
-- | 'deductions'                                         | Deductions                       |
-- +------------------------------------------------------+----------------------------------+
-- | 'offsets'                                            | Tax offsets                      |
-- +------------------------------------------------------+----------------------------------+
-- | 'privateHealthInsurancePolicyDetails'                | Private health insurance         |
-- |                                                      | policy details                   |
-- +------------------------------------------------------+----------------------------------+
-- | 'spouseDetails'                                      | Spouse Details (or @Nothing@)    |
-- +------------------------------------------------------+----------------------------------+
-- | 'incomeTests'                                        | Income Tests                     |
-- +------------------------------------------------------+----------------------------------+
--
data TaxReturnInfo y a = TaxReturnInfo
  { _mlsExemption :: Days y
  , _helpBalance :: Money a
  , _sfssBalance :: Money a
  , _paymentSummariesIndividualNonBusiness :: [PaymentSummaryIndividualNonBusiness a]
  , _paymentSummariesForeignEmployment :: [PaymentSummaryForeignEmployment a]
  , _paymentSummariesBusinessAndPersonalServicesIncome :: [PaymentSummaryBusinessAndPersonalServicesIncome a]
  , _paymentSummariesWithholdingWhereABNNotQuoted :: [PaymentSummaryWithholdingWhereABNNotQuoted a]
  , _interest :: GrossAndWithheld a
  , _dividends :: [Dividend a]
  , _ess :: [ESSStatement a]
  , _foreignIncome :: Money a
  , _cgtEvents :: [CGTEvent a]
  , _deductions :: Deductions a
  , _offsets :: Offsets a
  , _triCapitalLossCarryForward :: Money a
  , _phi :: [PrivateHealthInsurancePolicyDetail a]
  , _spouseDetails :: Maybe (SpouseDetails a)
  , _incomeTests :: IncomeTests a
  }

-- | Construct a new 'TaxReturnInfo'.
--
-- All monetary fields and lists are initially empty.
--
-- The /Medicare levy surcharge exemption/ field is initially
-- set to the number of days in the year (i.e. the taxpayer is
-- fully exempt).
--
newTaxReturnInfo
  :: (FinancialYear y, Num a)
  => TaxReturnInfo y a
newTaxReturnInfo = TaxReturnInfo
  daysAll  -- MLS exemption
  mempty -- HELP
  mempty -- SFSS
  [] -- payment summaries - individual non-business
  [] -- payment summaries - foreign employment
  [] -- payment summaries - business and personal services income
  [] -- payment summaries - withholding where ABN not quoted
  mempty -- interest
  mempty -- dividends
  mempty -- ESS
  mempty -- foreign income
  mempty -- CGT events
  mempty -- deductions
  mempty -- offsets
  mempty -- cap loss carry forward
  mempty -- private health insurance policy details
  Nothing -- spouse details
  newIncomeTests

-- | Construct a 'TaxReturnInfo' per 'newTaxReturnInfo',
-- coercing the type parameters to match the 'TaxTables'
-- argument (which is ignored).
--
newTaxReturnInfoForTables
  :: (FinancialYear y, Num a)
  => TaxTables y a -> TaxReturnInfo y a
newTaxReturnInfoForTables _ = newTaxReturnInfo

instance HasCapitalLossCarryForward (TaxReturnInfo y) a where
  capitalLossCarryForward = lens _triCapitalLossCarryForward
      (\s b -> s { _triCapitalLossCarryForward = b })

-- | HELP, VSL, SSL, ABSTUDY SSL, and TSL account balance.
helpBalance :: Lens' (TaxReturnInfo y a) (Money a)
helpBalance = lens _helpBalance (\s b -> s { _helpBalance = b })

-- | SFSS account balance.  From 1 July 2019, all study and training
-- loans are covered by one set of threshold and rates.  Since then,
-- you can specify your entire study and training loan balance via
-- 'helpBalance'.  But it will still calculate correctly if you specify
-- your SFSS balance separately.
--
sfssBalance :: Lens' (TaxReturnInfo y a) (Money a)
sfssBalance = lens _sfssBalance (\s b -> s { _sfssBalance = b })

mlsExemption :: Lens' (TaxReturnInfo y a) (Days y)
mlsExemption = lens _mlsExemption (\s b -> s { _mlsExemption = b })

paymentSummariesIndividualNonBusiness :: Lens' (TaxReturnInfo y a) [PaymentSummaryIndividualNonBusiness a]
paymentSummariesIndividualNonBusiness =
  lens _paymentSummariesIndividualNonBusiness (\s b -> s { _paymentSummariesIndividualNonBusiness = b })

paymentSummariesForeignEmployment :: Lens' (TaxReturnInfo y a) [PaymentSummaryForeignEmployment a]
paymentSummariesForeignEmployment =
  lens _paymentSummariesForeignEmployment (\s b -> s { _paymentSummariesForeignEmployment = b })

paymentSummariesBusinessAndPersonalServicesIncome :: Lens' (TaxReturnInfo y a) [PaymentSummaryBusinessAndPersonalServicesIncome a]
paymentSummariesBusinessAndPersonalServicesIncome =
  lens _paymentSummariesBusinessAndPersonalServicesIncome (\s b -> s { _paymentSummariesBusinessAndPersonalServicesIncome = b })

paymentSummariesWithholdingWhereABNNotQuoted :: Lens' (TaxReturnInfo y a) [PaymentSummaryWithholdingWhereABNNotQuoted a]
paymentSummariesWithholdingWhereABNNotQuoted =
  lens _paymentSummariesWithholdingWhereABNNotQuoted (\s b -> s { _paymentSummariesWithholdingWhereABNNotQuoted = b })

-- | Deprecated synonym for 'paymentSummariesIndividualNonBusiness'
paymentSummaries :: Lens' (TaxReturnInfo y a) [PaymentSummaryIndividualNonBusiness a]
paymentSummaries = paymentSummariesIndividualNonBusiness
{-# DEPRECATED paymentSummaries "use 'paymentSummariesIndividualNonBusiness'" #-}

interest :: Lens' (TaxReturnInfo y a) (GrossAndWithheld a)
interest = lens _interest (\s b -> s { _interest = b })

dividends :: Lens' (TaxReturnInfo y a) [Dividend a]
dividends = lens _dividends (\s b -> s { _dividends = b })

ess :: Lens' (TaxReturnInfo y a) [ESSStatement a]
ess = lens _ess (\s b -> s { _ess = b })

foreignIncome :: Lens' (TaxReturnInfo y a) (Money a)
foreignIncome = lens _foreignIncome (\s b -> s { _foreignIncome = b })

cgtEvents :: Lens' (TaxReturnInfo y a) [CGTEvent a]
cgtEvents = lens _cgtEvents (\s b -> s { _cgtEvents = b })

deductions :: Lens' (TaxReturnInfo y a) (Deductions a)
deductions = lens _deductions (\s b -> s { _deductions = b })

offsets :: Lens' (TaxReturnInfo y a) (Offsets a)
offsets = lens _offsets (\s b -> s { _offsets = b })

privateHealthInsurancePolicyDetails
  :: Lens' (TaxReturnInfo y a) [PrivateHealthInsurancePolicyDetail a]
privateHealthInsurancePolicyDetails = lens _phi (\s b -> s { _phi = b })

spouseDetails :: Lens' (TaxReturnInfo y a) (Maybe (SpouseDetails a))
spouseDetails = lens _spouseDetails (\s b -> s { _spouseDetails = b })

incomeTests :: Lens' (TaxReturnInfo y a) (IncomeTests a)
incomeTests = lens _incomeTests (\s b -> s { _incomeTests = b })


-- | A tax assessment.  Use 'assessTax' to compute a
-- @TaxAssessment@.
data TaxAssessment a = TaxAssessment
  { _taxableIncome :: Money a
  , _taxDue :: Money a
  , _taSpouseContributionOffset :: Money a
  , _taForeignIncomeTaxOffset :: Money a
  , _taFrankingCreditOffset :: Money a
  , _medicareLevyDue :: Money a
  , _medicareLevySurchargeDue :: Money a
  , _taxWithheld :: Money a
  , _taCGTAssessment :: CGTAssessment a
  , _phiAdj :: Money a
  , _studyAndTrainingLoanRepayment :: Money a
  , _paygInstalmentsCredit :: Money a
  }

instance HasTaxableIncome TaxAssessment a a where
  taxableIncome = to _taxableIncome

instance HasTaxWithheld TaxAssessment a a where
  taxWithheld = to _taxWithheld

taxDue :: Lens' (TaxAssessment a) (Money a)
taxDue = lens _taxDue (\s b -> s { _taxDue = b })

offsetForSuperannuationContributionsOnBehalfOfYourSpouse :: Lens' (TaxAssessment a) (Money a)
offsetForSuperannuationContributionsOnBehalfOfYourSpouse =
  lens _taSpouseContributionOffset (\s b -> s { _taSpouseContributionOffset = b })

foreignIncomeTaxOffsets :: Lens' (TaxAssessment a) (Money a)
foreignIncomeTaxOffsets =
  lens _taForeignIncomeTaxOffset (\s b -> s { _taForeignIncomeTaxOffset = b })

frankingCreditOffset :: Lens' (TaxAssessment a) (Money a)
frankingCreditOffset =
  lens _taFrankingCreditOffset (\s b -> s { _taFrankingCreditOffset = b })

medicareLevyDue :: Lens' (TaxAssessment a) (Money a)
medicareLevyDue =
  lens _medicareLevyDue (\s b -> s { _medicareLevyDue = b })

medicareLevySurchargeDue :: Lens' (TaxAssessment a) (Money a)
medicareLevySurchargeDue =
  lens _medicareLevySurchargeDue (\s b -> s { _medicareLevySurchargeDue = b })

taxCGTAssessment :: Lens' (TaxAssessment a) (CGTAssessment a)
taxCGTAssessment = lens _taCGTAssessment (\s b -> s { _taCGTAssessment = b })

studyAndTrainingLoanRepayment :: Lens' (TaxAssessment a) (Money a)
studyAndTrainingLoanRepayment =
  lens _studyAndTrainingLoanRepayment (\s b -> s { _studyAndTrainingLoanRepayment = b })

privateHealthInsuranceRebateAdjustment :: Lens' (TaxAssessment a) (Money a)
privateHealthInsuranceRebateAdjustment = lens _phiAdj (\s b -> s { _phiAdj = b })

paygInstalmentsCredit :: Lens' (TaxAssessment a) (Money a)
paygInstalmentsCredit =
  lens _paygInstalmentsCredit (\s b -> s { _paygInstalmentsCredit = b })

-- | What is the balance of the assessment?  Positive means a
-- refund (tax withheld exceeds obligation), negative means a bill.
taxBalance :: (Num a, Ord a) => Getter (TaxAssessment a) (Money a)
taxBalance = to $ fmap negate . \a ->
  max (Money 0) (
    -- tax on taxable income
    view taxDue a
    -- less non-refundable offsets
    $-$ view offsetForSuperannuationContributionsOnBehalfOfYourSpouse a
    $-$ view foreignIncomeTaxOffsets a
  )
  -- less refundable tax offsets
  $-$ view frankingCreditOffset a
  -- plus other liabilities
  $+$ view medicareLevyDue a
  $+$ view medicareLevySurchargeDue a
  $+$ view privateHealthInsuranceRebateAdjustment a
  $+$ view studyAndTrainingLoanRepayment a
  -- less PAYG credits and other entitlements
  $-$ view taxWithheld a
  $-$ view paygInstalmentsCredit a

instance HasCapitalLossCarryForward TaxAssessment a where
  capitalLossCarryForward = taxCGTAssessment . capitalLossCarryForward


-- | Consolidated individual tax rate incorporating
-- HELP and SFSS repayments
-- (if applicable) and automatic offsets (e.g. LITO, LMITO).
individualTax
  :: (Fractional a, Ord a)
  => TaxTables y a
  -> Tax (Money a) (Money a)
individualTax table =
  greaterOf mempty (ttIndividualIncomeTax table <> ttAdditional table)

-- | Tax to calculate compulsory study and training loan repayments
-- (e.g. HELP, SFSS)
studyAndTrainingLoanRepaymentTax
  :: (Fractional a, Ord a)
  => TaxTables y a
  -> TaxReturnInfo y a
  -> Tax (Money a) (Money a)
studyAndTrainingLoanRepaymentTax table info =
  limit (view helpBalance info) (ttHelp table)
  <> limit (view sfssBalance info) (ttSfss table)

-- | Taxable income
instance (RealFrac a) => HasTaxableIncome (TaxReturnInfo y) a a where
  taxableIncome = to $ \info ->
    let
      cf = view capitalLossCarryForward info
      gross = foldMap wholeDollars
        [ view (paymentSummariesIndividualNonBusiness . taxableIncome) info
        , view (paymentSummariesForeignEmployment . taxableIncome) info
        , view (paymentSummariesBusinessAndPersonalServicesIncome . taxableIncome) info
        , view (paymentSummariesWithholdingWhereABNNotQuoted . taxableIncome) info
        , view (interest . taxableIncome) info
        , view (dividends . taxableIncome) info
        , view (ess . taxableIncome) info
        , view (cgtEvents . to (assessCGTEvents cf) . cgtNetGain) info
        , view foreignIncome info
        ]
    in
      wholeDollars (gross $-$ views deductions totalDeductions info)

-- | Includes PAYG withholding by employer, and TFN
-- amounts withheld e.g. from bank interest, ESS discounts, etc.
--
-- Does not include franking credits (which are a refundable offset).
--
instance (Num a) => HasTaxWithheld (TaxReturnInfo y) a a where
  taxWithheld = to $ \info ->
    view (paymentSummariesIndividualNonBusiness . taxWithheld) info
    <> view (paymentSummariesForeignEmployment . taxWithheld) info
    <> view (paymentSummariesBusinessAndPersonalServicesIncome . taxWithheld) info
    <> view (paymentSummariesWithholdingWhereABNNotQuoted . taxWithheld) info
    <> view (interest . taxWithheld) info
    <> view (ess . taxWithheld) info

fringeBenefits :: (Num a) => TaxReturnInfo y a -> Money a
fringeBenefits info =
  foldOf l'inb info
  <> foldOf l'foreign info
  where
    l'inb =
      paymentSummariesIndividualNonBusiness . traverse
      . reportableFringeBenefits . traverse
      . to (\(ReportableFringeBenefits amount _) -> amount)
    l'foreign =
      paymentSummariesForeignEmployment . traverse
      . reportableFringeBenefits . traverse
      . to (\(ReportableFringeBenefits amount _) -> amount)

reportableSuperContributions :: (Num a) => TaxReturnInfo y a -> Money a
reportableSuperContributions info =
  foldOf (paymentSummariesIndividualNonBusiness . traverse . reportableEmployerSuperannuationContributions) info
  <> foldOf (paymentSummariesForeignEmployment . traverse . reportableEmployerSuperannuationContributions) info
  <> foldOf (paymentSummariesBusinessAndPersonalServicesIncome . traverse . reportableEmployerSuperannuationContributions) info
  <> view (deductions . personalSuperannuationContributions) info

-- | Calculate Division 293 income
division293Income :: (RealFrac a) => TaxReturnInfo y a -> Money a
division293Income info =
  view taxableIncome info
  <> fringeBenefits info
  -- TODO net financial investment loss
  -- TODO net rental property loss
  -- TODO net amount on which family trust distribution has been paid
  -- TODO super lump sum taxed elements with zero tax rate
  -- TODO assessable FHSS released amount

paygInstalmentIncome :: (RealFrac a) => TaxReturnInfo y a -> Money a
paygInstalmentIncome info =
  -- TODO gross rent
  view (dividends . taxableIncome) info
  -- TODO royalties?
  -- TODO foreign pensions assessable in Australia
  -- TODO share of income from partnerships and trusts
  <> view foreignIncome info
  <> view (interest . taxableIncome) info
  -- TODO business income (incl PSI etc)
  <> view (paymentSummariesWithholdingWhereABNNotQuoted . taxableIncome) info
  -- TODO gross income where tax withheld due to not provide TFN
  -- TODO withdrawal from farm management deposits
  -- TODO fuel tax credits

-- | Assess a tax return, given tax tables and tax return info.
assessTax
  :: (FinancialYear y, RealFrac a)
  => TaxTables y a -> TaxReturnInfo y a -> TaxAssessment a
assessTax tables info =
  let
    cg = assessCGTEvents
          (view capitalLossCarryForward info) (view cgtEvents info)
    taxable = view taxableIncome info
    due = getTax (individualTax tables) taxable

    exemptForeignIncome =
      foldOf (paymentSummariesIndividualNonBusiness . traverse . exemptForeignEmploymentIncome) info

    repaymentIncome =
      taxable
      <> fringeBenefits info
      -- TODO net financial investment losses
      -- TODO net rental property losses
      <> reportableSuperContributions info
      <> exemptForeignIncome

    studyRepayment = getTax (studyAndTrainingLoanRepaymentTax tables info) repaymentIncome

    ml = medicareLevy'
          (ttMedicareLevyRatesAndThresholds tables)
          taxable
          (preview (spouseDetails . traverse . spouseTaxableIncome) info)
          (view (incomeTests . dependentChildren) info)

    surchargeIncome =
      taxable
      <> fringeBenefits info
      -- TODO net financial investment losses
      -- TODO net rental property losses
      <> reportableSuperContributions info
      -- TODO spouse's share of net income of a trust on which the trustee
      --      must pay tax, if not included in taxable income
      <> ( if taxable > mempty then exemptForeignIncome else mempty )
      -- TODO reduction by taxed element of super lump sum

    mls =
      let mlsFrac = 1 - getFraction (view mlsExemption info)
      -- TODO family thresholds apply
      in getTax (fmap ($* mlsFrac) (ttMedicareLevySurcharge tables)) surchargeIncome

    spouseIncomeForSurchargePurposes =
      fmap (view spouseTaxableIncome) (view spouseDetails info)

    phiAdj = assessExcessPrivateHealthRebate
      surchargeIncome
      spouseIncomeForSurchargePurposes
      (view (incomeTests . dependentChildren) info)
      (ttPHIRebateRates tables)
      (view privateHealthInsurancePolicyDetails info)

    foreignIncomeTaxOffsetLimit =
      let
        step1 = due <> ml <> mls
        step2 =
          let
            info' = info & set foreignIncome mempty
            taxable' =
              view taxableIncome info'
              <> view (deductions . foreignIncomeDeductions) info'
            due' = getTax (individualTax tables) taxable'
            ml' = medicareLevy'
                    (ttMedicareLevyRatesAndThresholds tables)
                    taxable
                    (preview (spouseDetails . traverse . spouseTaxableIncome) info)
                    (view (incomeTests . dependentChildren) info)
            mls' =
              let mlsFrac = 1 - getFraction (view mlsExemption info)
              -- FIXME income for MLS purposes includes fringe benefits; family thresholds apply
              in getTax (fmap ($* mlsFrac) (ttMedicareLevySurcharge tables)) taxable
          in
            due' <> ml' <> mls'
        step3 = step1 $-$ step2
      in
        max (Money 1000) step3

    frankingCredit = wholeDollars $ view (dividends . taxWithheld) info
    spouseContribOffset = view (offsets . spouseContributionOffset) info
    fito = min (view (offsets . foreignTaxOffset) info) foreignIncomeTaxOffsetLimit

  in
    TaxAssessment
      taxable
      due
      spouseContribOffset
      fito
      frankingCredit
      ml
      mls
      (view taxWithheld info)
      cg
      phiAdj
      studyRepayment
      (view (offsets . paygInstalments) info)


-- | A proportion is a non-negative number in interval @[0,1]@.
-- Use 'proportion' to construct.
newtype Proportion a = Proportion
  { getProportion :: a -- ^ Return underlying figure, which is in interval @[0,1]@
  }
  deriving (Show, Eq, Ord)

-- | Construct a proportion.  Out of range numbers are clamped
-- to @0@ or @1@ (no runtime errors).
proportion :: (Ord a, Num a) => a -> Proportion a
proportion = Proportion . max 0 . min 1

-- | Dividend payment.  Records net income, franked portion
-- and amount of tax withheld.
data Dividend a = Dividend
  { dividendSource :: String
  , dividendDate :: Day
  , dividendGrossAndWithheld :: GrossAndWithheld a
  }

-- | Rounds to whole cents
instance (RealFrac a) => HasTaxWithheld Dividend a a where
  taxWithheld = to dividendGrossAndWithheld . taxWithheld . to roundCents

-- | Rounds to whole cents
instance (RealFrac a) => HasTaxableIncome Dividend a a where
  taxableIncome = to dividendGrossAndWithheld . taxableIncome . to roundCents

-- | Construct a dividend from a net payment, with a proportion
-- of the dividend franked at the given corporate tax rate (must
-- be a flat rate).
--
-- Does not perform rounding.
--
-- For franking at the standard corporate tax rate of 30%, you can
-- use the convenience function 'dividendFromNetFranked30'.
--
dividendFromNetFranked
  :: (Fractional a)
  => String         -- ^ Source name (e.g. ticker)
  -> Day            -- ^ Dividend date
  -> Money a        -- ^ Net payment
  -> Proportion a   -- ^ Franked proportion
  -> Tax (Money a) (Money a)  -- ^ Corporate tax rate (must be a flat rate)
  -> Dividend a
dividendFromNetFranked src date net franked rate =
  dividendFromGross src date gross withheld
  where
    Money r = getTax rate (Money 1)  -- extract flat tax rate
    withheld = net $* ( getProportion franked * r / (1 - r) )
    gross = net <> withheld

-- | Construct a dividend from a net payment, with a proportion
-- of the dividend franked at the 30% corporate tax rate.
--
-- Does not perform rounding.
--
-- For franking at a tax rate other than 30%, use 'dividendFromNetFranked'.
--
dividendFromNetFranked30
  :: (Fractional a)
  => String         -- ^ Source name (e.g. ticker)
  -> Day            -- ^ Dividend date
  -> Money a        -- ^ Net payment
  -> Proportion a   -- ^ Franked proportion
  -> Dividend a
dividendFromNetFranked30 src date net franked =
  dividendFromNetFranked src date net franked corporateTax

-- | Construct a dividend from a net payment, with explicit
-- declaration of tax withheld.
--
-- Does not perform rounding.
--
dividendFromNet
  :: (Num a)
  => String         -- ^ Source name (e.g. ticker)
  -> Day            -- ^ Dividend date
  -> Money a        -- ^ Net payment
  -> Money a        -- ^ Tax withheld
  -> Dividend a
dividendFromNet src date net withheld =
  dividendFromGross src date (net <> withheld) withheld

-- | Construct a dividend from a gross payment, with explicit
-- declaration of tax withheld.
--
-- Does not perform rounding.
--
dividendFromGross
  :: String         -- ^ Source name (e.g. ticker)
  -> Day            -- ^ Dividend date
  -> Money a        -- ^ Gross payment
  -> Money a        -- ^ Tax withheld
  -> Dividend a
dividendFromGross src date gross withheld =
  Dividend src date (GrossAndWithheld gross withheld)

-- | Tax offsets that individuals can claim
--
-- The following lenses are available:
--
-- +---------------------------------------+----------------------------------+
-- | 'spouseContributionOffset'            | Spouse super contribution        |
-- +---------------------------------------+----------------------------------+
-- | 'foreignTaxOffset'                    | Foreign income tax offset        |
-- +---------------------------------------+----------------------------------+
-- | 'paygInstalments'                     | PAYG Instalments                 |
-- +---------------------------------------+----------------------------------+
--
data Offsets a = Offsets
  { _spouseOffset :: Money a
  , _foreignTaxOffset :: Money a
  , _paygInstalments :: Money a
  }

instance Num a => Semigroup (Offsets a) where
  Offsets a b c <> Offsets a' b' c' = Offsets (a <> a') (b <> b') (c <> c')

instance Num a => Monoid (Offsets a) where
  mempty = Offsets mempty mempty mempty
  mappend = (<>)

-- | Spouse contribution offset.  Maximum of /$540/ (not enforced).
spouseContributionOffset :: Lens' (Offsets a) (Money a)
spouseContributionOffset = lens _spouseOffset (\s b -> s { _spouseOffset = b })

-- | Offset for tax paid on foreign income.
foreignTaxOffset :: Lens' (Offsets a) (Money a)
foreignTaxOffset = lens _foreignTaxOffset (\s b -> s { _foreignTaxOffset = b })

paygInstalments :: Lens' (Offsets a) (Money a)
paygInstalments = lens _paygInstalments (\s b -> s { _paygInstalments = b })


-- | Deductions that individuals can claim.
--
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'workRelatedCarExpenses'                                               | __D1__ Work-related car expenses                                                       |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'workRelatedTravelExpenses'                                            | __D2__ Work-related travel expenses                                                    |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'workRelatedClothingLaundryAndDryCleaningExpenses'                     | __D3__ Work-related clothing, laundry and dry-cleaning expenses                        |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'workRelatedSelfEducationExpenses'                                     | __D4__ Work-related self-education expenses                                            |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'otherWorkRelatedExpenses'                                             | __D5__ Other work-related expenses                                                     |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'lowValuePoolDeduction'                                                | __D6__ Low-value pool deduction                                                        |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'interestDeductions'                                                   | __D7__ Interest deductions                                                             |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'dividendDeductions'                                                   | __D8__ Dividend deductions                                                             |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'giftsOrDonations'                                                     | __D9__ Gifts or donations                                                              |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'costOfManagingTaxAffairs'                                             | __D10__ Cost of managing tax affairs                                                   |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity'  | __D11__ Deductible amount of undeducted purchase price of a foreign pension or annuity |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'personalSuperannuationContributions'                                  | __D12__ Personal superannuation contributions                                          |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'deductionForProjectPool'                                              | __D13__ Deduction for project pool                                                     |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'forestryManagedInvestmentSchemeDeduction'                             | __D14__ Forestry managed investment scheme deduction                                   |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'otherDeductions'                                                      | __D15__ Other deductions — not claimable at __D1__ to __D14__ or elsewhere in your tax |
-- |                                                                        | return                                                                                 |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
-- | 'foreignIncomeDeductions'                                              | Aggregate of deductions related to foreign income.  The components making up this      |
-- |                                                                        | amount __must be included in the other fields__.  This field is only used in           |
-- |                                                                        | calculating the Foreign Income Tax Offset Limit.                                       |
-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------+
--
data Deductions a = Deductions
  { _workRelatedCarExpenses :: Money a
  , _workRelatedTravelExpenses :: Money a
  , _workRelatedClothingLaundryAndDryCleaningExpenses :: Money a
  , _workRelatedSelfEducationExpenses :: Money a
  , _otherWorkRelatedExpenses :: Money a
  , _lowValuePoolDeduction :: Money a
  , _interestDeductions :: Money a
  , _dividendDeductions :: Money a
  , _giftsOrDonations :: Money a
  , _costOfManagingTaxAffairs :: Money a
  , _deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity :: Money a
  , _personalSuperannuationContributions :: Money a
  , _deductionForProjectPool :: Money a
  , _forestryManagedInvestmentSchemeDeduction :: Money a
  , _otherDeductions :: Money a
  , _foreignIncomeDeductions :: Money a
  }

instance Num a => Semigroup (Deductions a) where
  Deductions a b c d e f g h i j k l m n o p
    <> Deductions a' b' c' d' e' f' g' h' i' j' k' l' m' n' o' p'
      = Deductions (a <> a') (b <> b') (c <> c') (d <> d') (e <> e') (f <> f') (g <> g') (h <> h')
                   (i <> i') (j <> j') (k <> k') (l <> l') (m <> m') (n <> n') (o <> o') (p <> p')

instance Num a => Monoid (Deductions a) where
  mempty = Deductions mempty mempty mempty mempty mempty mempty mempty mempty
                      mempty mempty mempty mempty mempty mempty mempty mempty

-- | Sum the deductions.  Negative components are ignored.
totalDeductions :: (Num a, Ord a) => Deductions a -> Money a
totalDeductions a =
  foldMap (max mempty)
    [ view workRelatedCarExpenses a
    , view workRelatedTravelExpenses a
    , view workRelatedClothingLaundryAndDryCleaningExpenses a
    , view workRelatedSelfEducationExpenses a
    , view otherWorkRelatedExpenses a
    , view lowValuePoolDeduction a
    , view interestDeductions a
    , view dividendDeductions a
    , view giftsOrDonations a
    , view costOfManagingTaxAffairs a
    , view deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity a
    , view personalSuperannuationContributions a
    , view deductionForProjectPool a
    , view forestryManagedInvestmentSchemeDeduction a
    , view otherDeductions a
    ]

-- | __D1__ Work-related car expenses
workRelatedCarExpenses :: Lens' (Deductions a) (Money a)
workRelatedCarExpenses =
  lens _workRelatedCarExpenses (\s b -> s { _workRelatedCarExpenses = b })

-- | __D2__ Work-related travel expenses
workRelatedTravelExpenses :: Lens' (Deductions a) (Money a)
workRelatedTravelExpenses =
  lens _workRelatedTravelExpenses (\s b -> s { _workRelatedTravelExpenses = b })

-- | __D3__ Work-related clothing, laundry and dry-cleaning expenses
workRelatedClothingLaundryAndDryCleaningExpenses :: Lens' (Deductions a) (Money a)
workRelatedClothingLaundryAndDryCleaningExpenses =
  lens
    _workRelatedClothingLaundryAndDryCleaningExpenses
    (\s b -> s { _workRelatedClothingLaundryAndDryCleaningExpenses = b })

-- | __D4__ Work-related self-education expenses
workRelatedSelfEducationExpenses :: Lens' (Deductions a) (Money a)
workRelatedSelfEducationExpenses =
  lens _workRelatedSelfEducationExpenses (\s b -> s { _workRelatedSelfEducationExpenses = b })

-- | __D5__ Other work-related expenses
otherWorkRelatedExpenses :: Lens' (Deductions a) (Money a)
otherWorkRelatedExpenses =
  lens _otherWorkRelatedExpenses (\s b -> s { _otherWorkRelatedExpenses = b })

-- | __D6__ Low-value pool deduction
lowValuePoolDeduction :: Lens' (Deductions a) (Money a)
lowValuePoolDeduction =
  lens _lowValuePoolDeduction (\s b -> s { _lowValuePoolDeduction = b })

-- | __D7__ Interest deductions
interestDeductions :: Lens' (Deductions a) (Money a)
interestDeductions =
  lens _interestDeductions (\s b -> s { _interestDeductions = b })

-- | __D8__ Dividend deductions
dividendDeductions :: Lens' (Deductions a) (Money a)
dividendDeductions =
  lens _dividendDeductions (\s b -> s { _dividendDeductions = b })

-- | __D9__ Gifts or donations
giftsOrDonations :: Lens' (Deductions a) (Money a)
giftsOrDonations =
  lens _giftsOrDonations (\s b -> s { _giftsOrDonations = b })

-- | __D10__ Cost of managing tax affairs
costOfManagingTaxAffairs :: Lens' (Deductions a) (Money a)
costOfManagingTaxAffairs =
  lens _costOfManagingTaxAffairs (\s b -> s { _costOfManagingTaxAffairs = b })

-- | __D11__ Deductible amount of undeducted purchase price of a foreign pension or annuity
deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity :: Lens' (Deductions a) (Money a)
deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity =
  lens
    _deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity 
    (\s b -> s { _deductibleAmountOfUndeductedPurchasePriceOfAForeignPensionOrAnnuity = b })

-- | __D12__ Personal superannuation contributions
personalSuperannuationContributions :: Lens' (Deductions a) (Money a)
personalSuperannuationContributions =
  lens _personalSuperannuationContributions (\s b -> s { _personalSuperannuationContributions = b })

-- | __D13__ Deduction for project pool
deductionForProjectPool :: Lens' (Deductions a) (Money a)
deductionForProjectPool =
  lens _deductionForProjectPool (\s b -> s { _deductionForProjectPool = b })

-- | __D14__ Forestry managed investment scheme deduction
forestryManagedInvestmentSchemeDeduction :: Lens' (Deductions a) (Money a)
forestryManagedInvestmentSchemeDeduction =
  lens
    _forestryManagedInvestmentSchemeDeduction
    (\s b -> s { _forestryManagedInvestmentSchemeDeduction = b })

-- | __D15__ Other deductions — not claimable at __D1__ to __D14__ or elsewhere
-- in your tax return
otherDeductions :: Lens' (Deductions a) (Money a)
otherDeductions =
  lens _otherDeductions (\s b -> s { _otherDeductions = b })

-- | Aggregate of deductions related to foreign income, including:
--
-- * Deductions that are reasonably related to amounts on which foreign
--   income tax has been paid
-- * Debt deductions attributable to your overseas permanent establishment
-- * Amount of the foreign loss component of one or more tax losses deducted
--   in the income year.
--
-- The components making up this amount __must be included in other fields__.
-- This field is only used in calculating the Foreign Income Tax Offset Limit.
--
foreignIncomeDeductions :: Lens' (Deductions a) (Money a)
foreignIncomeDeductions =
  lens _foreignIncomeDeductions (\s b -> s { _foreignIncomeDeductions = b })


-- | A gross income (first argument) and amount of tax withheld (second argument).
-- The whole gross amount is considered taxable income.
--
data GrossAndWithheld a = GrossAndWithheld (Money a) (Money a)

instance (Num a) => Semigroup (GrossAndWithheld a) where
  GrossAndWithheld a b <> GrossAndWithheld a' b' =
    GrossAndWithheld (a <> a') (b <> b')

instance (Num a) => Monoid (GrossAndWithheld a) where
  mempty = GrossAndWithheld mempty mempty
  mappend = (<>)

instance HasTaxableIncome GrossAndWithheld a a where
  taxableIncome = to $ \(GrossAndWithheld a _) -> a

instance HasTaxWithheld GrossAndWithheld a a where
  taxWithheld = to $ \(GrossAndWithheld _ a) -> a


-- | Employee share scheme statement.  Use 'newESSStatement' to construct.
-- The following lenses are available:
--
-- +------------------------------+--------------------------------------------+
-- | 'essEmployerDetails'         | 'PayerDetails' for employer.               |
-- +------------------------------+--------------------------------------------+
-- | 'essTaxedUpfrontReduction'   | __D__ Discount from taxed up front         |
-- |                              | schemes—eligible for reduction             |
-- +------------------------------+--------------------------------------------+
-- | 'essTaxedUpfrontNoReduction' | __E__ Discount from taxed up front         |
-- |                              | schemes—not eligible for reduction         |
-- +------------------------------+--------------------------------------------+
-- | 'essDeferral'                | __F__ Discount from taxed deferral schemes |
-- +------------------------------+--------------------------------------------+
-- | 'essPre2009'                 | __G__ Discounts on ESS interests acquired  |
-- |                              | pre 1 July 2009 and "cessation time"       |
-- |                              | occurred during the financial year.        |
-- +------------------------------+--------------------------------------------+
-- | 'essTFNAmounts'              | __C__ TFN amounts withheld from discounts  |
-- +------------------------------+--------------------------------------------+
-- | 'essForeignSourceDiscounts'  | __A__ ESS foreign source discounts         |
-- +------------------------------+--------------------------------------------+
--
data ESSStatement a = ESSStatement
  { _essPayer :: PayerDetails
  , _taxedUpfrontReduction :: Money a
  , _taxedUpfrontNoReduction :: Money a
  , _deferral :: Money a
  , _pre2009 :: Money a
  , _tfnAmounts :: Money a
  , _foreignSourceDiscounts :: Money a
  }
  deriving (Eq, Ord)

-- | Construct an 'ESSStatement' with all amounts at /zero/.
newESSStatement :: (Num a) => PayerDetails -> ESSStatement a
newESSStatement payer = ESSStatement payer mempty mempty mempty mempty mempty mempty

-- | Employer details
essEmployerDetails :: Lens' (ESSStatement a) PayerDetails
essEmployerDetails = lens _essPayer (\s b -> s { _essPayer = b })

-- | Discount from taxed up front schemes—eligible for reduction.
-- Item __D__ in /Employee share schemes/ section.
essTaxedUpfrontReduction :: Lens' (ESSStatement a) (Money a)
essTaxedUpfrontReduction =
  lens _taxedUpfrontReduction (\s b -> s { _taxedUpfrontReduction = b })

-- | Discount from taxed up front schemes—not eligible for reduction
-- Item __E__ in /Employee share schemes/ section.
essTaxedUpfrontNoReduction :: Lens' (ESSStatement a) (Money a)
essTaxedUpfrontNoReduction =
  lens _taxedUpfrontNoReduction (\s b -> s { _taxedUpfrontNoReduction = b })

-- | Discount from taxed deferral schemes.
-- Item __F__ in /Employee share schemes/ section.
essDeferral :: Lens' (ESSStatement a) (Money a)
essDeferral = lens _deferral (\s b -> s { _deferral = b })

-- | discounts on ESS interests acquired pre 1 July 2009 and
-- "cessation time" occurred during the finanical year.
-- Item __G__ in /Employee share schemes/ section.
essPre2009 :: Lens' (ESSStatement a) (Money a)
essPre2009 = lens _pre2009 (\s b -> s { _pre2009 = b })

-- | TFN amounts withheld from discounts.
-- Item __C__ in /Employee share schemes/ section.
essTFNAmounts :: Lens' (ESSStatement a) (Money a)
essTFNAmounts = lens _tfnAmounts (\s b -> s { _tfnAmounts = b })

-- | ESS foreign source discounts
-- Item __A__ in /Employee share schemes/ section.
essForeignSourceDiscounts :: Lens' (ESSStatement a) (Money a)
essForeignSourceDiscounts =
  lens _foreignSourceDiscounts (\s b -> s { _foreignSourceDiscounts = b })

-- | __Note:__ does not implement the reduction of taxed up front
-- amounts eligible for reduction.
instance (Num a) => HasTaxableIncome ESSStatement a a where
  taxableIncome = to $ \s ->
    view essTaxedUpfrontReduction s
    <> view essTaxedUpfrontNoReduction s
    <> view essDeferral s
    <> view essPre2009 s

instance HasTaxWithheld ESSStatement a a where
  taxWithheld = essTFNAmounts