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 +40/−0
- src/Data/Tax/ATO.hs +192/−57
- src/Data/Tax/ATO/CGT.hs +7/−0
- src/Data/Tax/ATO/Common.hs +9/−19
- src/Data/Tax/ATO/Days.hs +0/−76
- src/Data/Tax/ATO/FY.hs +100/−0
- src/Data/Tax/ATO/FY/FY2017.hs +13/−0
- src/Data/Tax/ATO/FY/FY2018.hs +8/−3
- src/Data/Tax/ATO/FY/FY2019.hs +9/−3
- src/Data/Tax/ATO/FY/FY2020.hs +8/−3
- src/Data/Tax/ATO/FY/FY2021.hs +9/−3
- src/Data/Tax/ATO/FY/FY2022.hs +8/−3
- src/Data/Tax/ATO/FY/FY2023.hs +7/−2
- src/Data/Tax/ATO/FY/FY2024.hs +74/−0
- src/Data/Tax/ATO/Pretty.hs +167/−0
- src/Data/Tax/ATO/PrivateHealthInsuranceRebate.hs +5/−1
- tax-ato.cabal +9/−5
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