packages feed

tax-ato 2023.2 → 2024.1

raw patch · 17 files changed

+665/−175 lines, 17 filesdep +prettydep ~timePVP ok

version bump matches the API change (PVP)

Dependencies added: pretty

Dependency ranges changed: time

API changes (from Hackage documentation)

- Data.Tax.ATO: [dividendFrankedPortion] :: Dividend a -> Proportion a
- Data.Tax.ATO: [dividendNetPayment] :: Dividend a -> Money a
- Data.Tax.ATO: [dividendTaxWithheld] :: Dividend a -> Money a
- Data.Tax.ATO: dividendFrankingCredit :: RealFrac a => Dividend a -> Money a
- Data.Tax.ATO: income :: HasIncome a b c => Getter (a b) (Money c)
- Data.Tax.ATO: instance Data.Tax.ATO.Common.HasIncome Data.Tax.ATO.GrossAndWithheld a a
- Data.Tax.ATO: instance Data.Tax.ATO.Common.HasIncome Data.Tax.ATO.PaymentSummary a a
- Data.Tax.ATO: instance Data.Tax.ATO.Common.HasIncome Data.Tax.ATO.TaxAssessment a a
- Data.Tax.ATO: instance GHC.Num.Num a => Data.Tax.ATO.Common.HasIncome Data.Tax.ATO.ESSStatement a a
- Data.Tax.ATO: instance GHC.Real.RealFrac a => Data.Tax.ATO.Common.HasIncome (Data.Tax.ATO.TaxReturnInfo y) a a
- Data.Tax.ATO: instance GHC.Real.RealFrac a => Data.Tax.ATO.Common.HasIncome Data.Tax.ATO.Dividend a a
- Data.Tax.ATO: instance GHC.Show.Show a => GHC.Show.Show (Data.Tax.ATO.Dividend a)
- Data.Tax.ATO.Common: class HasIncome a b c
- Data.Tax.ATO.Common: income :: HasIncome a b c => Getter (a b) (Money c)
- Data.Tax.ATO.Common: instance (Data.Foldable.Foldable t, Data.Tax.ATO.Common.HasIncome x a a, GHC.Num.Num a) => Data.Tax.ATO.Common.HasIncome t (x a) a
- Data.Tax.ATO.Common: medicareLevySurcharge :: (Fractional a, Ord a) => Tax (Money a) (Money a)
- Data.Tax.ATO.Days: data Days (n :: Year)
- Data.Tax.ATO.Days: days :: forall a. DaysInYear a => Integer -> Days a
- Data.Tax.ATO.Days: daysAll :: forall a. DaysInYear a => Days a
- Data.Tax.ATO.Days: daysNone :: Days a
- Data.Tax.ATO.Days: getDays :: Days n -> Integer
- Data.Tax.ATO.Days: getFraction :: forall a frac. (DaysInYear a, Fractional frac) => Days a -> frac
- Data.Tax.ATO.Days: instance GHC.Show.Show (Data.Tax.ATO.Days.Days n)
- Data.Tax.ATO.Days: type DaysInYear = KnownNat
+ Data.Tax.ATO: [dividendGrossAndWithheld] :: Dividend a -> GrossAndWithheld a
+ Data.Tax.ATO: dividendFromGross :: String -> Day -> Money a -> Money a -> Dividend a
+ Data.Tax.ATO: dividendFromNet :: Num a => String -> Day -> Money a -> Money a -> Dividend a
+ Data.Tax.ATO: dividendFromNetFranked :: Fractional a => String -> Day -> Money a -> Proportion a -> Tax (Money a) (Money a) -> Dividend a
+ Data.Tax.ATO: dividendFromNetFranked30 :: Fractional a => String -> Day -> Money a -> Proportion a -> Dividend a
+ Data.Tax.ATO: instance Data.Tax.ATO.Common.HasTaxableIncome Data.Tax.ATO.GrossAndWithheld a a
+ Data.Tax.ATO: instance Data.Tax.ATO.Common.HasTaxableIncome Data.Tax.ATO.PaymentSummary a a
+ Data.Tax.ATO: instance Data.Tax.ATO.Common.HasTaxableIncome Data.Tax.ATO.TaxAssessment a a
+ Data.Tax.ATO: instance GHC.Num.Num a => Data.Tax.ATO.Common.HasTaxableIncome Data.Tax.ATO.ESSStatement a a
+ Data.Tax.ATO: instance GHC.Real.RealFrac a => Data.Tax.ATO.Common.HasTaxableIncome (Data.Tax.ATO.TaxReturnInfo y) a a
+ Data.Tax.ATO: instance GHC.Real.RealFrac a => Data.Tax.ATO.Common.HasTaxableIncome Data.Tax.ATO.Dividend a a
+ Data.Tax.ATO: paygInstalmentsCredit :: Lens' (TaxAssessment a) (Money a)
+ Data.Tax.ATO: studyAndTrainingLoanRepayment :: Lens' (TaxAssessment a) (Money a)
+ Data.Tax.ATO: taxableIncome :: HasTaxableIncome a b c => Getter (a b) (Money c)
+ Data.Tax.ATO.CGT: instance GHC.Base.Functor Data.Tax.ATO.CGT.CGTEvent
+ Data.Tax.ATO.Common: class HasTaxableIncome a b c
+ Data.Tax.ATO.Common: instance (Data.Foldable.Foldable t, Data.Tax.ATO.Common.HasTaxableIncome x a a, GHC.Num.Num a) => Data.Tax.ATO.Common.HasTaxableIncome t (x a) a
+ Data.Tax.ATO.Common: taxableIncome :: HasTaxableIncome a b c => Getter (a b) (Money c)
+ Data.Tax.ATO.FY: data Days (n :: Nat)
+ Data.Tax.ATO.FY: days :: forall a. FinancialYear a => Integer -> Days a
+ Data.Tax.ATO.FY: daysAll :: forall a. FinancialYear a => Days a
+ Data.Tax.ATO.FY: daysNone :: Days a
+ Data.Tax.ATO.FY: financialYear :: Day -> Year
+ Data.Tax.ATO.FY: financialYearRange :: Year -> (Day, Day)
+ Data.Tax.ATO.FY: financialYearRangeFromProxy :: forall n. FinancialYear n => Proxy n -> (Day, Day)
+ Data.Tax.ATO.FY: getDays :: Days n -> Integer
+ Data.Tax.ATO.FY: getFraction :: forall a frac. (FinancialYear a, Fractional frac) => Days a -> frac
+ Data.Tax.ATO.FY: instance GHC.Show.Show (Data.Tax.ATO.FY.Days n)
+ Data.Tax.ATO.FY: type FinancialYear = KnownNat
+ Data.Tax.ATO.FY.FY2018: fyProxy :: Proxy FY
+ Data.Tax.ATO.FY.FY2018: type FY = 2018
+ Data.Tax.ATO.FY.FY2019: fyProxy :: Proxy FY
+ Data.Tax.ATO.FY.FY2019: type FY = 2019
+ Data.Tax.ATO.FY.FY2020: fyProxy :: Proxy FY
+ Data.Tax.ATO.FY.FY2020: type FY = 2020
+ Data.Tax.ATO.FY.FY2021: fyProxy :: Proxy FY
+ Data.Tax.ATO.FY.FY2021: type FY = 2021
+ Data.Tax.ATO.FY.FY2022: fyProxy :: Proxy FY
+ Data.Tax.ATO.FY.FY2022: type FY = 2022
+ Data.Tax.ATO.FY.FY2023: fyProxy :: Proxy FY
+ Data.Tax.ATO.FY.FY2023: type FY = 2023
+ Data.Tax.ATO.FY.FY2024: fyProxy :: Proxy FY
+ Data.Tax.ATO.FY.FY2024: tables :: (Ord a, Fractional a) => TaxTables FY a
+ Data.Tax.ATO.FY.FY2024: type FY = 2024
+ Data.Tax.ATO.Pretty: summariseAssessment :: TaxAssessment Rational -> Doc
+ Data.Tax.ATO.Pretty: summariseCGTAssessment :: CGTAssessment Rational -> Doc
+ Data.Tax.ATO.Pretty: summariseTaxReturnInfo :: TaxReturnInfo y Rational -> Doc
+ Data.Tax.ATO.PrivateHealthInsuranceRebate: type HealthInsurerID = String
+ Data.Tax.ATO.PrivateHealthInsuranceRebate: type MembershipNumber = String
- Data.Tax.ATO: Dividend :: String -> String -> Money a -> Proportion a -> Money a -> Dividend a
+ Data.Tax.ATO: Dividend :: String -> Day -> GrossAndWithheld a -> Dividend a
- Data.Tax.ATO: [dividendDate] :: Dividend a -> String
+ Data.Tax.ATO: [dividendDate] :: Dividend a -> Day
- Data.Tax.ATO: assessTax :: (DaysInYear y, RealFrac a) => TaxTables y a -> TaxReturnInfo y a -> TaxAssessment a
+ Data.Tax.ATO: assessTax :: (FinancialYear y, RealFrac a) => TaxTables y a -> TaxReturnInfo y a -> TaxAssessment a
- Data.Tax.ATO: newTaxReturnInfo :: (DaysInYear y, Num a) => TaxReturnInfo y a
+ Data.Tax.ATO: newTaxReturnInfo :: (FinancialYear y, Num a) => TaxReturnInfo y a
- Data.Tax.ATO: newTaxReturnInfoForTables :: (DaysInYear y, Num a) => TaxTables y a -> TaxReturnInfo y a
+ Data.Tax.ATO: newTaxReturnInfoForTables :: (FinancialYear y, Num a) => TaxTables y a -> TaxReturnInfo y a
- Data.Tax.ATO.FY.FY2018: tables :: (Ord a, Fractional a) => TaxTables 2018 a
+ Data.Tax.ATO.FY.FY2018: tables :: (Ord a, Fractional a) => TaxTables FY a
- Data.Tax.ATO.FY.FY2019: tables :: (Ord a, Fractional a) => TaxTables 2019 a
+ Data.Tax.ATO.FY.FY2019: tables :: (Ord a, Fractional a) => TaxTables FY a
- Data.Tax.ATO.FY.FY2020: tables :: (Ord a, Fractional a) => TaxTables 2020 a
+ Data.Tax.ATO.FY.FY2020: tables :: (Ord a, Fractional a) => TaxTables FY a
- Data.Tax.ATO.FY.FY2021: tables :: (Ord a, Fractional a) => TaxTables 2021 a
+ Data.Tax.ATO.FY.FY2021: tables :: (Ord a, Fractional a) => TaxTables FY a
- Data.Tax.ATO.FY.FY2022: tables :: (Ord a, Fractional a) => TaxTables 2022 a
+ Data.Tax.ATO.FY.FY2022: tables :: (Ord a, Fractional a) => TaxTables FY a

Files

CHANGELOG.md view
@@ -1,3 +1,43 @@+## Version 2024.1++- The minimum supported version of GHC is now 9.2, due to a+  dependency on more recent versions of the *time* library.++- Update the `Dividend` type to use store gross payment amount and+  tax withheld, rather than net amount, franking portion and (other)+  tax withheld.  Add new helper functions for construction:+  - `dividendFromGross` takes gross amount and tax withheld+  - `dividendFromNet` takes net amount and tax withheld+  - `dividendFromNetFranked` takes net amount, franked proportion and+    applicable corporate tax rate for working out the franking credit.+  - `dividendFromNetFranked30` is a shortcut that uses the standard+    corporate tax rate of 30%.++- Change the type of `dividendDate` field from `String` to+  `Data.Time.Day`.++- Rename the `HasIncome` class to `HasTaxableIncome`, and its+  member function `income` to `taxableIncome`.++- Add functions for getting the `Day` range of a financial year,+  and a function for getting the financial year of a given day.++- Move the `Data.Tax.ATO.Days` module to `Data.Tax.ATO.FY` and+  rename the `DaysInYear` type synonym to `FinancialYear`, to+  reflect the additional behaviour.++- Each `FY.<YYYY>` module now exports an `FY` type synonym for its+  type-level `Nat` representing the financial year, and the `fyProxy+  :: Proxy FY` value.++- Add `Functor` instance for `CGTEvent`.++- Add `FY2024` module.++- Add `Data.Tax.ATO.Pretty` module, which provides pretty printers+  for `TaxReturnInfo`, `TaxAssessment`, and other data types.++ ## Version 2023.2  - Add support for PAYG Instalments, which are specified in aggregate
src/Data/Tax/ATO.hs view
@@ -39,7 +39,7 @@     TaxReturnInfo   , newTaxReturnInfo   , newTaxReturnInfoForTables-  , income+  , taxableIncome    -- ** Income @@ -51,10 +51,13 @@   -- *** Interest   , interest -  -- *** Dividends and franking credits+  -- *** Dividends   , Dividend(..)   , dividends-  , dividendFrankingCredit+  , dividendFromGross+  , dividendFromNet+  , dividendFromNetFranked+  , dividendFromNetFranked30    -- *** Capital gains tax (CGT)   , HasCapitalLossCarryForward(..)@@ -131,7 +134,9 @@   , taxBalance   , taxDue   , medicareLevyDue+  , studyAndTrainingLoanRepayment   , taxCreditsAndOffsets+  , paygInstalmentsCredit   , taxCGTAssessment   , privateHealthInsuranceRebateAdjustment @@ -150,11 +155,12 @@   ) where  import Control.Lens (Getter, Lens', (&), foldOf, lens, set, to, view, views)+import Data.Time (Day)  import Data.Tax import Data.Tax.ATO.CGT import Data.Tax.ATO.Common-import Data.Tax.ATO.Days+import Data.Tax.ATO.FY import Data.Tax.ATO.PrivateHealthInsuranceRebate import Data.Tax.ATO.Rounding @@ -217,13 +223,14 @@ -- +---------------------------------------+----------------------------------+ -- | 'mlsExemption'                        | Medicare levy exemption          | -- +---------------------------------------+----------------------------------+--- | 'helpBalance'                         | HELP account balance             |+-- | 'helpBalance'                         | HELP, VSL, SSL, ABSTUDY SSL,     |+-- |                                       | and TSL account balance          | -- +---------------------------------------+----------------------------------+ -- | 'sfssBalance'                         | SFSS account balance             | -- +---------------------------------------+----------------------------------+ -- | 'paymentSummaries'                    | PAYG payment summaries           | -- +---------------------------------------+----------------------------------+--- | 'interest'                            | Interest data                    |+-- | 'interest'                            | Interest income and tax withheld | -- +---------------------------------------+----------------------------------+ -- | 'dividends'                           | Dividend data                    | -- +---------------------------------------+----------------------------------+@@ -272,7 +279,7 @@ -- fully exempt). -- newTaxReturnInfo-  :: (DaysInYear y, Num a)+  :: (FinancialYear y, Num a)   => TaxReturnInfo y a newTaxReturnInfo = TaxReturnInfo   daysAll  -- MLS exemption@@ -296,7 +303,7 @@ -- argument (which is ignored). -- newTaxReturnInfoForTables-  :: (DaysInYear y, Num a)+  :: (FinancialYear y, Num a)   => TaxTables y a -> TaxReturnInfo y a newTaxReturnInfoForTables _ = newTaxReturnInfo @@ -304,9 +311,16 @@   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 }) @@ -358,11 +372,12 @@   , _taxCreditsAndOffsets :: Money a   , _taCGTAssessment :: CGTAssessment a   , _phiAdj :: Money a+  , _studyAndTrainingLoanRepayment :: Money a+  , _paygInstalmentsCredit :: Money a   } --- | Taxable income-instance HasIncome TaxAssessment a a where-  income = to _taxableIncome+instance HasTaxableIncome TaxAssessment a a where+  taxableIncome = to _taxableIncome  instance HasTaxWithheld TaxAssessment a a where   taxWithheld = to _taxWithheld@@ -379,9 +394,17 @@ 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 => Getter (TaxAssessment a) (Money a)@@ -389,8 +412,10 @@   view taxWithheld a   $-$ view taxDue a   $-$ view medicareLevyDue a+  $-$ view studyAndTrainingLoanRepayment a   $-$ view privateHealthInsuranceRebateAdjustment a   $+$ view taxCreditsAndOffsets a+  $+$ view paygInstalmentsCredit a  instance (Num a, Eq a) => HasCapitalLossCarryForward TaxAssessment a where   capitalLossCarryForward = taxCGTAssessment . capitalLossCarryForward@@ -408,18 +433,18 @@  -- | Tax to calculate compulsory study and training loan repayments -- (e.g. HELP, SFSS)-studyAndTrainingLoanRepayment+studyAndTrainingLoanRepaymentTax   :: (Fractional a, Ord a)   => TaxTables y a   -> TaxReturnInfo y a   -> Tax (Money a) (Money a)-studyAndTrainingLoanRepayment table info =+studyAndTrainingLoanRepaymentTax table info =   limit (view helpBalance info) (ttHelp table)   <> limit (view sfssBalance info) (ttSfss table)  -- | Medicare levy + surcharge medicareLevyTax-  :: (DaysInYear y, Fractional a)+  :: (FinancialYear y, Fractional a)   => TaxTables y a   -> TaxReturnInfo y a   -> Tax (Money a) (Money a)    -- grand unified individual income tax@@ -436,21 +461,23 @@     <> fmap ($* mlsFrac) mls  -- | Taxable income-instance (RealFrac a) => HasIncome (TaxReturnInfo y) a a where-  income = to $ \info ->+instance (RealFrac a) => HasTaxableIncome (TaxReturnInfo y) a a where+  taxableIncome = to $ \info ->     let       cf = view capitalLossCarryForward info       gross = foldMap wholeDollars-        [ view (paymentSummaries . income) info-        , view (interest . income) info-        , view (dividends . income) info-        , view (ess . income) info+        [ view (paymentSummaries . 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 or bank.+-- Does not include franking credits. instance (Num a) => HasTaxWithheld (TaxReturnInfo y) a a where   taxWithheld = to $ \info ->     view (paymentSummaries . taxWithheld) info@@ -458,15 +485,15 @@  -- | Assess a tax return, given tax tables and tax return info. assessTax-  :: (DaysInYear y, RealFrac a)+  :: (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 income info+    taxable = view taxableIncome info     due = getTax (individualTax tables) taxable-    studyRepayment = getTax (studyAndTrainingLoanRepayment tables info) taxable+    studyRepayment = getTax (studyAndTrainingLoanRepaymentTax tables info) taxable     mlAndMLS = getTax (medicareLevyTax tables info) taxable      incomeForSurchargePurposes =@@ -491,7 +518,9 @@         step2 =           let             info' = info & set foreignIncome mempty-            taxable' = view income info' <> view (deductions . foreignIncomeDeductions) info'+            taxable' =+              view taxableIncome info'+              <> view (deductions . foreignIncomeDeductions) info'             due' = getTax (individualTax tables) taxable'             mlAndMLS' = getTax (medicareLevyTax tables info') taxable'           in@@ -500,23 +529,22 @@       in         max (Money 1000) step3 -    frankingCredit =-      wholeDollars-      $ foldMap dividendFrankingCredit (view dividends info)+    frankingCredit = wholeDollars $ view (dividends . taxWithheld) info     off =       view (offsets . spouseContributionOffset) info       <> min (view (offsets . foreignTaxOffset) info) foreignIncomeTaxOffsetLimit-      <> view (offsets . paygInstalments) info    in     TaxAssessment       taxable-      (due <> studyRepayment)+      due       mlAndMLS       (view taxWithheld info)       (frankingCredit <> off)       cg       phiAdj+      studyRepayment+      (view (offsets . paygInstalments) info)  -- | Australian Business Number type ABN = String@@ -530,8 +558,8 @@   }  -- | Gross income-instance HasIncome PaymentSummary a a where-  income = to summaryGross+instance HasTaxableIncome PaymentSummary a a where+  taxableIncome = to summaryGross  instance HasTaxWithheld PaymentSummary a a where   taxWithheld = to summaryWithheld@@ -552,30 +580,83 @@ -- and amount of tax withheld. data Dividend a = Dividend   { dividendSource :: String-  , dividendDate :: String  -- FUTURE better type-  , dividendNetPayment :: Money a-  , dividendFrankedPortion :: Proportion a  -- ^ Franked ratio (@1@ = 100%)-  , dividendTaxWithheld :: Money a+  , dividendDate :: Day+  , dividendGrossAndWithheld :: GrossAndWithheld a   }-  deriving (Show) +-- | Rounds to whole cents instance (RealFrac a) => HasTaxWithheld Dividend a a where-  taxWithheld = to (roundCents . dividendTaxWithheld)+  taxWithheld = to dividendGrossAndWithheld . taxWithheld . to roundCents --- | Calculate the franking credit for a dividend+-- | 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). ---dividendFrankingCredit :: (RealFrac a) => Dividend a -> Money a-dividendFrankingCredit d = roundCents $-  (getProportion . dividendFrankedPortion) d-  *$ getTax corporateTax (dividendNetPayment d $* (1 / 0.7))+-- Does not perform rounding.+--+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 --- | Attributable income-instance (RealFrac a) => HasIncome Dividend a a where-  income = to $ \d ->-    roundCents (dividendNetPayment d)-    <> dividendFrankingCredit d-    <> view taxWithheld d+-- | Construct a dividend from a net payment, with a proportion+-- of the dividend franked at the 30% corporate tax rate.+--+-- Does not perform rounding.+--+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:@@ -615,10 +696,42 @@  -- | Deductions that individuals can claim. ----- The only "special case" field is 'foreignIncomeDeductions', which is--- the aggregate amount of /other/ deductions that pertains to foreign--- income.  It is used only for calculating the Foreign Income Tax Offset--- Limit.+-- +------------------------------------------------------------------------+----------------------------------------------------------------------------------------++-- | '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@@ -768,7 +881,9 @@   lens _foreignIncomeDeductions (\s b -> s { _foreignIncomeDeductions = b })  --- | A gross income (first argument) and amount of tax withheld (second argument)+-- | 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@@ -779,14 +894,34 @@   mempty = GrossAndWithheld mempty mempty   mappend = (<>) -instance HasIncome GrossAndWithheld a a where-  income = to $ \(GrossAndWithheld a _) -> a+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:+--+-- +------------------------------+--------------------------------------------++-- | '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   { _taxedUpfrontReduction :: Money a   , _taxedUpfrontNoReduction :: Money a@@ -836,8 +971,8 @@  -- | __Note:__ does not implement the reduction of taxed up front -- amounts eligible for reduction.-instance (Num a) => HasIncome ESSStatement a a where-  income = to $ \s ->+instance (Num a) => HasTaxableIncome ESSStatement a a where+  taxableIncome = to $ \s ->     view essTaxedUpfrontReduction s     <> view essTaxedUpfrontNoReduction s     <> view essDeferral s
src/Data/Tax/ATO/CGT.hs view
@@ -75,6 +75,13 @@   }   deriving (Show) +instance Functor CGTEvent where+  fmap f (CGTEvent k n t1 p1 b1 t2 p2 b2 cap own) =+    CGTEvent k (f n)+      t1 (f <$> p1) (f <$> b1)+      t2 (f <$> p2) (f <$> b2)+      (f <$> cap) (f <$> own)+ reducedCostBase :: Num a => CGTEvent a -> Money a reducedCostBase event =   (units event *$ acquisitionPrice event)
src/Data/Tax/ATO/Common.hs view
@@ -30,11 +30,10 @@     TaxTables(..)    -- * Classes-  , HasIncome(..)+  , HasTaxableIncome(..)    -- * Common taxes and helpers   , medicareLevy-  , medicareLevySurcharge   , lowIncomeTaxOffset   , lowIncomeTaxOffset2021   , lamito@@ -70,18 +69,6 @@ medicareLevy :: (Fractional a, Ord a) => Money a -> Tax (Money a) (Money a) medicareLevy l = lesserOf (above l 0.1) (flat 0.02) --- | /Medicare levy surcharge (MLS)/.  Certain exemptions are available.------ __Known issues__: the MLS is levied on taxable income + fringe--- benefits, but this is not implemented properly yet.  The--- thresholds are affected by family income and number of--- dependents; this also is not implemented.-medicareLevySurcharge :: (Fractional a, Ord a) => Tax (Money a) (Money a)-medicareLevySurcharge =-  threshold (review money 90000) 0.01-  <> threshold (review money 105000) 0.0025-  <> threshold (review money 140000) 0.0025- -- | /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.@@ -120,9 +107,12 @@ -- ^ Convenience wrapper for 'marginal'.  Turns the margins into 'Money'  --- | Types that have an income value.-class HasIncome a b c where-  income :: Getter (a b) (Money c)+-- | Types that may have a taxable income component.+class HasTaxableIncome a b c where+  taxableIncome :: Getter (a b) (Money c) -instance (Foldable t, HasIncome x a a, Num a) => HasIncome t (x a) a where-  income = to (foldMap (view income))+instance+    (Foldable t, HasTaxableIncome x a a, Num a)+    => HasTaxableIncome t (x a) a+    where+  taxableIncome = to (foldMap (view taxableIncome))
− src/Data/Tax/ATO/Days.hs
@@ -1,76 +0,0 @@--- 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/>.--{-|--Types for representing a number of days in a year.---}--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Data.Tax.ATO.Days-  ( Days-  , days-  , daysAll-  , daysNone-  , getDays-  , getFraction-  , DaysInYear-  )-  where--import GHC.TypeLits-import Data.Proxy-import Data.Ratio ((%))--import Data.Time.Calendar (isLeapYear)--type Year = Nat-type DaysInYear = KnownNat--daysInYear :: KnownNat n => Proxy n -> Integer-daysInYear proxy-  | isLeapYear (natVal proxy) = 366-  | otherwise                 = 365---- | Some number of days in a year.  Use 'days' to construct.-newtype Days (n :: Year) = Days-  { getDays :: Integer-  -- ^ Get the number of days, which is between 0 and 365/366 inclusive.-  }-  deriving (Show)---- | Construct a 'Days' value.  If out of range, the number of days--- is clamped to 0 or 365/366 (no runtime errors).-days :: forall a. (DaysInYear a) => Integer -> Days a-days = Days . max 0 . min (daysInYear (Proxy :: Proxy a))---- | Every day of the year-daysAll :: forall a. (DaysInYear a) => Days a-daysAll = Days (daysInYear (Proxy :: Proxy a))---- | Zero days of the year-daysNone :: Days a-daysNone = Days 0---- | Get the number of days as a fractional value.--- Information about the the year type is discarded.-getFraction :: forall a frac. (DaysInYear a, Fractional frac) => Days a -> frac-getFraction n = fromRational $ getDays n % daysInYear (Proxy :: Proxy a)
+ src/Data/Tax/ATO/FY.hs view
@@ -0,0 +1,100 @@+-- This file is part of hs-tax-ato+-- Copyright (C) 2018, 2023  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 functions related to financial years.++-}++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Data.Tax.ATO.FY+  ( Days+  , days+  , daysAll+  , daysNone+  , getDays+  , getFraction+  , FinancialYear+  , financialYear+  , financialYearRange+  , financialYearRangeFromProxy+  )+  where++import GHC.TypeLits+import Data.Proxy+import Data.Ratio ((%))++import Data.Time.Calendar (Day, Year, fromGregorian, isLeapYear, toGregorian)++type FinancialYear = KnownNat++daysInYear :: KnownNat n => Proxy n -> Integer+daysInYear proxy+  | isLeapYear (natVal proxy) = 366+  | otherwise                 = 365++-- | Some number of days in a year.  Use 'days' to construct.+newtype Days (n :: Nat) = Days+  { getDays :: Integer+  -- ^ Get the number of days, which is between 0 and 365/366 inclusive.+  }+  deriving (Show)++-- | Construct a 'Days' value.  If out of range, the number of days+-- is clamped to 0 or 365/366 (no runtime errors).+days :: forall a. (FinancialYear a) => Integer -> Days a+days = Days . max 0 . min (daysInYear (Proxy :: Proxy a))++-- | Every day of the year+daysAll :: forall a. (FinancialYear a) => Days a+daysAll = Days (daysInYear (Proxy :: Proxy a))++-- | Zero days of the year+daysNone :: Days a+daysNone = Days 0++-- | Get the number of days as a fractional value.+-- The denominator is determined by the year type.+--+getFraction :: forall a frac. (FinancialYear a, Fractional frac) => Days a -> frac+getFraction n = fromRational $ getDays n % daysInYear (Proxy :: Proxy a)++-- | The financial year in which the given day falls.+--+financialYear :: Day -> Year+financialYear d = case toGregorian d of+  (y, m, _)+    | m >= 7{-July-}  -> y + 1+    | otherwise       -> y++-- | Get the range of days (inclusive) for the financial year (July to June)+-- ending in the given year.+financialYearRange :: Year -> (Day, Day)+financialYearRange y =+  ( fromGregorian (y - 1) 7{-July-} 1+  , fromGregorian y       6{-June-} 30+  )++-- | Get the financial year range (inclusive) for the given type-level+-- financial year.  See also 'financialYearRange'.+financialYearRangeFromProxy :: forall n. (FinancialYear n) => Proxy n -> (Day, Day)+financialYearRangeFromProxy proxy = financialYearRange (natVal proxy)
src/Data/Tax/ATO/FY/FY2017.hs view
@@ -43,6 +43,19 @@   , (82551, 0.005), (86895, 0.005), (95627, 0.005), (101900, 0.005) ] sfss = thresholds' [(54869, 0.02), (67369, 0.01), (95627, 0.01)] +-- | /Medicare levy surcharge (MLS)/.  Certain exemptions are available.+--+-- __Known issues__: the MLS is levied on taxable income + fringe+-- benefits, but this is not implemented properly yet.  The+-- thresholds are affected by family income and number of+-- dependents; this also is not implemented.+--+medicareLevySurcharge :: (Fractional a, Ord a) => Tax (Money a) (Money a)+medicareLevySurcharge =+  threshold (Money 90000) 0.01+  <> threshold (Money 105000) 0.0025+  <> threshold (Money 140000) 0.0025+ -- | In FY2017 the 37% threshold was raised from $80,000 to $87,000 -- -- The Temporary Budget Repair Levy, 2% of income above $180,000,
src/Data/Tax/ATO/FY/FY2018.hs view
@@ -17,14 +17,19 @@ {-# LANGUAGE DataKinds #-}  -- | Tax tables for 2017–18 financial year.-module Data.Tax.ATO.FY.FY2018 (tables) where+module Data.Tax.ATO.FY.FY2018 (FY, fyProxy, tables) where +import Data.Proxy import Control.Lens (review) import Data.Tax import Data.Tax.ATO.Common import Data.Tax.ATO.PrivateHealthInsuranceRebate import qualified Data.Tax.ATO.FY.FY2017 as FY2017 +type FY = 2018+fyProxy :: Proxy FY+fyProxy = Proxy+ help, sfss :: (Fractional a, Ord a) => Tax (Money a) (Money a) help = thresholds'   [ (55874, 0.04)@@ -36,11 +41,11 @@ -- -- The /temporary budget repair levy/ no longer applies. ---tables :: (Ord a, Fractional a) => TaxTables 2018 a+tables :: (Ord a, Fractional a) => TaxTables FY a tables = TaxTables   FY2017.individualIncomeTax   (medicareLevy (review money 21980))-  medicareLevySurcharge+  (ttMedicareLevySurcharge FY2017.tables)   help   sfss   lowIncomeTaxOffset
src/Data/Tax/ATO/FY/FY2019.hs view
@@ -17,14 +17,20 @@ {-# LANGUAGE DataKinds #-}  -- | Tax tables for 2018–19 financial year.-module Data.Tax.ATO.FY.FY2019 (tables) where+module Data.Tax.ATO.FY.FY2019 (FY, fyProxy, tables) where +import Data.Proxy import Control.Lens (review)  import Data.Tax import Data.Tax.ATO.Common import Data.Tax.ATO.PrivateHealthInsuranceRebate+import qualified Data.Tax.ATO.FY.FY2018 as FY2018 +type FY = 2019+fyProxy :: Proxy FY+fyProxy = Proxy+ -- | In FY2019 the 37% threshold was increased from $87,000 to $90,000. individualIncomeTax :: (Fractional a, Ord a) => Tax (Money a) (Money a) individualIncomeTax = marginal'@@ -45,11 +51,11 @@ -- The new /low and middle income tax offset (LAMITO)/ was -- introduced, in addition to LITO. ---tables :: (Ord a, Fractional a) => TaxTables 2019 a+tables :: (Ord a, Fractional a) => TaxTables FY a tables = TaxTables   individualIncomeTax   (medicareLevy (review money 22398))-  medicareLevySurcharge+  (ttMedicareLevySurcharge FY2018.tables)   help   sfss   (lowIncomeTaxOffset <> lamito)
src/Data/Tax/ATO/FY/FY2020.hs view
@@ -17,8 +17,9 @@ {-# LANGUAGE DataKinds #-}  -- | Tax tables for 2019–20 financial year.-module Data.Tax.ATO.FY.FY2020 (tables) where+module Data.Tax.ATO.FY.FY2020 (FY, fyProxy, tables) where +import Data.Proxy import Control.Lens (review)  import Data.Tax@@ -26,6 +27,10 @@ import Data.Tax.ATO.PrivateHealthInsuranceRebate import qualified Data.Tax.ATO.FY.FY2019 as FY2019 +type FY = 2020+fyProxy :: Proxy FY+fyProxy = Proxy+ help :: (Fractional a, Ord a) => Tax (Money a) (Money a) help = thresholds'   [ (45881, 0.01)@@ -52,11 +57,11 @@ -- one set of thresholds and rates.  For backwards compatibility, -- 'ttHelp' and 'ttSfss' now refer to the same value. ---tables :: (Ord a, Fractional a) => TaxTables 2020 a+tables :: (Ord a, Fractional a) => TaxTables FY a tables = TaxTables   (ttIndividualIncomeTax FY2019.tables)   (medicareLevy (review money 22801))-  medicareLevySurcharge+  (ttMedicareLevySurcharge FY2019.tables)   help   help   (lowIncomeTaxOffset <> lamito)
src/Data/Tax/ATO/FY/FY2021.hs view
@@ -17,14 +17,20 @@ {-# LANGUAGE DataKinds #-}  -- | Tax tables for 2020–21 financial year.-module Data.Tax.ATO.FY.FY2021 (tables, individualIncomeTax) where+module Data.Tax.ATO.FY.FY2021 (FY, fyProxy, tables, individualIncomeTax) where +import Data.Proxy import Control.Lens (review)  import Data.Tax import Data.Tax.ATO.Common import Data.Tax.ATO.PrivateHealthInsuranceRebate+import qualified Data.Tax.ATO.FY.FY2020 as FY2020 +type FY = 2021+fyProxy :: Proxy FY+fyProxy = Proxy+ -- | In 2020–21 the 32.5% threshold was increased from $37,000 to -- $45,000, and the 37% threshold was increased from $90,000 to -- $120,000.@@ -57,11 +63,11 @@   , (136740, 0.005)   ] -tables :: (Ord a, Fractional a) => TaxTables 2021 a+tables :: (Ord a, Fractional a) => TaxTables FY a tables = TaxTables   individualIncomeTax   (medicareLevy (review money 23226))-  medicareLevySurcharge+  (ttMedicareLevySurcharge FY2020.tables)   help   help   (lowIncomeTaxOffset2021 <> lamito)
src/Data/Tax/ATO/FY/FY2022.hs view
@@ -17,8 +17,9 @@ {-# LANGUAGE DataKinds #-}  -- | Tax tables for 2021–22 financial year.-module Data.Tax.ATO.FY.FY2022 (tables) where+module Data.Tax.ATO.FY.FY2022 (FY, fyProxy, tables) where +import Data.Proxy import Control.Lens (review)  import Data.Tax@@ -26,6 +27,10 @@ import Data.Tax.ATO.PrivateHealthInsuranceRebate import qualified Data.Tax.ATO.FY.FY2021 as FY2021 +type FY = 2022+fyProxy :: Proxy FY+fyProxy = Proxy+ help :: (Fractional a, Ord a) => Tax (Money a) (Money a) help = thresholds'   [ (47014, 0.01)@@ -48,11 +53,11 @@   , (137898, 0.005)   ] -tables :: (Ord a, Fractional a) => TaxTables 2022 a+tables :: (Ord a, Fractional a) => TaxTables FY a tables = TaxTables   FY2021.individualIncomeTax   (medicareLevy (review money 23365))-  medicareLevySurcharge+  (ttMedicareLevySurcharge FY2021.tables)   help   help   (lowIncomeTaxOffset2021 <> lmito2022)
src/Data/Tax/ATO/FY/FY2023.hs view
@@ -17,12 +17,17 @@ {-# LANGUAGE DataKinds #-}  -- | Tax tables for 2022–23 financial year.-module Data.Tax.ATO.FY.FY2023 (tables) where+module Data.Tax.ATO.FY.FY2023 (FY, fyProxy, tables) where +import Data.Proxy import Data.Tax import Data.Tax.ATO.Common import qualified Data.Tax.ATO.FY.FY2022 as FY2022 +type FY = 2023+fyProxy :: Proxy FY+fyProxy = Proxy+ help :: (Fractional a, Ord a) => Tax (Money a) (Money a) help = thresholds'   [ (48361, 0.01)@@ -50,8 +55,8 @@   (ttIndividualIncomeTax FY2022.tables)    (medicareLevy (Money 24276))+  (ttMedicareLevySurcharge FY2022.tables) -  medicareLevySurcharge   help   help 
+ src/Data/Tax/ATO/FY/FY2024.hs view
@@ -0,0 +1,74 @@+-- This file is part of hs-tax-ato+-- Copyright (C) 2024  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/>.++{-# LANGUAGE DataKinds #-}++-- | Tax tables for 2023–24 financial year.+module Data.Tax.ATO.FY.FY2024 (FY, fyProxy, tables) where++import Data.Proxy+import Data.Tax+import Data.Tax.ATO.Common+import qualified Data.Tax.ATO.FY.FY2023 as FY2023++type FY = 2024+fyProxy :: Proxy FY+fyProxy = Proxy++help :: (Fractional a, Ord a) => Tax (Money a) (Money a)+help = thresholds'+  [ (51550, 0.01)+  , (59519, 0.01)+  , (63090, 0.005)+  , (66876, 0.005)+  , (70889, 0.005)+  , (75141, 0.005)+  , (79650, 0.005)+  , (84430, 0.005)+  , (89495, 0.005)+  , (94866, 0.005)+  , (100558, 0.005)+  , (106591, 0.005)+  , (112986, 0.005)+  , (119765, 0.005)+  , (126951, 0.005)+  , (134569, 0.005)+  , (142643, 0.005)+  , (151201, 0.005)+  ]++-- | Medicare levy surcharge thresholds changed for 2023–24+medicareLevySurcharge :: (Fractional a, Ord a) => Tax (Money a) (Money a)+medicareLevySurcharge =+  threshold (Money 93000) 0.01+  <> threshold (Money 108000) 0.0025+  <> threshold (Money 144000) 0.0025++tables :: (Ord a, Fractional a) => TaxTables FY a+tables = TaxTables+  (ttIndividualIncomeTax FY2023.tables)++  (medicareLevy (Money 26000))++  medicareLevySurcharge+  help+  help++  lowIncomeTaxOffset2021++  -- Rebate adjustment factor = 1.000 (no change)+  -- https://www.health.gov.au/news/phi-circulars/phi-1724-private-health-insurance-rebate-adjustment-factor-effective-1-april-2024+  (ttPHIRebateRates FY2023.tables)
+ src/Data/Tax/ATO/Pretty.hs view
@@ -0,0 +1,167 @@+-- This file is part of hs-tax-ato+-- Copyright (C) 2024  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/>.++{-|++Pretty-print tax data.++Monetary values are rounded to the nearest whole cent (half-up).++-}++{-# LANGUAGE OverloadedStrings #-}++module Data.Tax.ATO.Pretty+  ( summariseTaxReturnInfo+  , summariseAssessment+  , summariseCGTAssessment+  ) where++import Data.Function (on)+import Data.List (groupBy, sortOn)++import Control.Lens (ALens', cloneLens, view, views)+import qualified Text.PrettyPrint as P++import Data.Tax.ATO+import Data.Tax.ATO.CGT+++colWidthMoney, colWidthLabel :: Int+colWidthMoney = 16+colWidthLabel = 80 - colWidthMoney++-- | Format money for display.  Rounds to the nearest whole cent (half-up).+-- Does not prepend '$'.+formatMoney :: Money Rational -> P.Doc+formatMoney (Money x) =+  P.text (replicate (colWidthMoney - length amount) ' ' <> amount)+  where+    (iPart, fPart) = properFraction (x * 100) :: (Integer, Rational)+    iPart' = if fPart >= 0.5 then iPart + 1 else iPart+    digits = reverse (show iPart')+    cents = case reverse (take 2 digits) of [] -> "00" ; [c] -> c:"0" ; s -> s+    dollars = case drop 2 digits of "" -> "0" ; s -> reverse (putCommas s)+    amount = dollars <> "." <> cents+    putCommas (a:b:c:d:rest) | d /= '-' = a:b:c:',':putCommas (d:rest)+    putCommas rest                      = rest++twoCol :: (P.Doc, Money Rational) -> P.Doc+twoCol (label, value) = label P.$$ P.nest (80 - colWidthMoney) (formatMoney value)++threeCol :: (P.Doc, Money Rational, Money Rational) -> P.Doc+threeCol (label, v1, v2) =+  label+  P.$$ P.nest (80 - 2 * colWidthMoney) (formatMoney v1)+  P.<> formatMoney v2++-- | 3-column layout with rightmost column blank+threeColLeft :: (P.Doc, Money Rational) -> P.Doc+threeColLeft (label, v1) =+  label+  P.$$ P.nest (80 - 2 * colWidthMoney) (formatMoney v1)++vcatWith :: (a -> P.Doc) -> [a] -> P.Doc+vcatWith f = P.vcat . fmap f++summariseTaxReturnInfo :: TaxReturnInfo y Rational -> P.Doc+summariseTaxReturnInfo info =+  "Income"+  P.$+$ vcatWith threeCol+    [ ("  1   Salary or wages"  , view (paymentSummaries . taxWithheld) info, view (paymentSummaries . taxableIncome) info)+    , ("  10  Interest"         , view (interest . taxWithheld) info, view (interest . taxableIncome) info)+    ]+  P.$+$ "  11  Dividends"+  P.$+$ views dividends summariseDividends info+  P.$+$ vcatWith twoCol+    [ ("  12  Employee share schemes" , view (ess . taxableIncome) info)+    , ("  20M Other net foreign source income" , view foreignIncome info)+    ]+  P.$+$ "Deductions"+  P.$+$ P.vcat (uncurry (summariseDeduction (view deductions info)) <$> deductionsTable)+  P.$+$ "Tax offsets"+  P.$+$ vcatWith threeColLeft+    [ ("  20O Foreign income tax offset"  , view (offsets . foreignTaxOffset) info)+    ]++summariseDividends :: [Dividend Rational] -> P.Doc+summariseDividends =+  vcatWith (threeCol . prep)+  . groupBy ((==) `on` dividendSource)+  . sortOn dividendSource+  where+    prep :: [Dividend Rational] -> (P.Doc, Money Rational, Money Rational)+    prep l =+      ( P.text ("        " <> dividendSource (head l))+      , view taxWithheld l+      , view taxableIncome l+      )++deductionsTable :: [(ALens' (Deductions Rational) (Money Rational), String)]+deductionsTable =+  [ (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")+  ]++summariseDeduction+  :: Deductions Rational+  -> ALens' (Deductions Rational) (Money Rational)+  -> String+  -> P.Doc+summariseDeduction a l desc+  | amt > mempty+  = P.nest 2 (P.text desc) P.$$ P.nest colWidthLabel (formatMoney amt)+  | otherwise+  = P.empty+  where+    amt = view (cloneLens l) a+++summariseAssessment :: TaxAssessment Rational -> P.Doc+summariseAssessment assessment =+  "Your taxable income is $" P.<> formatMoney (view taxableIncome assessment)+  P.$+$ P.text (replicate 80 '-')+  P.$+$ vcatWith twoCol+    [ ("Tax on your taxable income"                 , view taxDue assessment)+    , ("Less credits and offsets"                   , views taxCreditsAndOffsets (fmap negate) assessment)+    , ("Medicare levy (and surcharge, if any)"      , view medicareLevyDue assessment)+    , ("Study and training loan repayment"          , view studyAndTrainingLoanRepayment assessment)+    , ("Excess private health reduction or refund"  , view privateHealthInsuranceRebateAdjustment assessment)+    , ("Less PAYG withholding"                      , views taxWithheld (fmap negate) assessment)+    , ("Less PAYG instalments"                      , views paygInstalmentsCredit (fmap negate) assessment)+    ]+  P.$+$ P.text (replicate 80 '-')+  P.$+$ "Result of this notice" P.$$ P.nest colWidthLabel (views taxBalance formatMoney assessment)+  P.$+$ "Net capital loss to carry forward" P.$$ P.nest colWidthLabel (views (taxCGTAssessment . capitalLossCarryForward) formatMoney assessment)++summariseCGTAssessment :: CGTAssessment Rational -> P.Doc+summariseCGTAssessment cgtAss@(CGTAssessment total _) =+  "Total FY capital gains" P.$$ (P.nest colWidthLabel . formatMoney) total+  P.$+$ "Net capital gain" P.$$ P.nest colWidthLabel (views cgtNetGain formatMoney cgtAss)+  P.$+$ "Net capital loss to carry forward" P.$$ P.nest colWidthLabel (views capitalLossCarryForward formatMoney cgtAss)
src/Data/Tax/ATO/PrivateHealthInsuranceRebate.hs view
@@ -27,6 +27,8 @@   , PrivateHealthInsurancePolicyDetail(..)   , BenefitCode(..)   , assessExcessPrivateHealthRebate+  , HealthInsurerID+  , MembershipNumber   ) where  import Data.List (find)@@ -40,12 +42,14 @@  data BenefitCode   = BenefitCode30 -- ^ Under 65, 1 July to 31 March-  | BenefitCode31 -- ^ Over 65, 1 April to 30 June+  | BenefitCode31 -- ^ Under 65, 1 April to 30 June   | BenefitCode35 -- ^ 65 to 69, 1 July to 31 March   | BenefitCode36 -- ^ 65 to 69, 1 April to 30 June   | BenefitCode40 -- ^ 70 or over, 1 July to 31 March   | BenefitCode41 -- ^ 70 or over, 1 April to 30 June +-- | Include these data in your tax return via the+-- 'Data.Tax.ATO.privateHealthInsurancePolicyDetails' field. data PrivateHealthInsurancePolicyDetail a =   PrivateHealthInsurancePolicyDetail     HealthInsurerID
tax-ato.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.2 name:                tax-ato-version:             2023.2+version:             2024.1 synopsis:            Tax types and computations for Australia description:   This library provides types and tax computations for tax@@ -14,10 +14,11 @@ copyright:           Copyright (C) 2018-2023 Fraser Tweedale category:            Finance build-type:          Simple-tested-with:         GHC ==8.10.7 || ==9.0.2 || ==9.2.7 || ==9.4.4 || ==9.6.1+tested-with:         GHC ==9.2.8 || ==9.4.7 || ==9.6.3 || ==9.8.1 extra-source-files:-  CHANGELOG.md   .hlint.yaml+extra-doc-files:+  CHANGELOG.md  homepage:            https://github.com/frasertweedale/hs-tax-ato bug-reports:         https://github.com/frasertweedale/hs-tax-ato/issues@@ -62,9 +63,10 @@     Data.Tax.ATO     Data.Tax.ATO.CGT     Data.Tax.ATO.Common-    Data.Tax.ATO.Days+    Data.Tax.ATO.Pretty     Data.Tax.ATO.PrivateHealthInsuranceRebate     Data.Tax.ATO.Rounding+    Data.Tax.ATO.FY     Data.Tax.ATO.FY.FY2017     Data.Tax.ATO.FY.FY2018     Data.Tax.ATO.FY.FY2019@@ -72,7 +74,9 @@     Data.Tax.ATO.FY.FY2021     Data.Tax.ATO.FY.FY2022     Data.Tax.ATO.FY.FY2023+    Data.Tax.ATO.FY.FY2024   build-depends:     , lens >= 4.12 && < 6-    , time >= 1.5 && < 1.13+    , time >= 1.11 && < 1.13     , tax >= 0.2 && < 0.3+    , pretty >= 1.1.3.6 && < 2