{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoFieldSelectors #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -- | Common types and functions shared by multiple Canadian tax forms module Tax.Canada.Shared where import Control.Monad (guard, mfilter) import Data.Fixed (Centi) import Data.Text (Text) import Language.Haskell.TH qualified as TH import Rank2.TH qualified import Transformation.Shallow.TH qualified import Tax.FDF (FieldConst(Field), Entry(Amount)) import Tax.Util (fixEq, fractionOf, nonNegativeDifference) data TaxIncomeBracket line = TaxIncomeBracket { income :: line Centi, threshold :: line Centi, overThreshold :: line Centi, rate :: line Rational, timesRate :: line Centi, baseTax :: line Centi, equalsTax :: line Centi} data MedicalExpenses line = MedicalExpenses { expenses :: line Centi, netIncome :: line Centi, incomeRate :: line Rational, fraction :: line Centi, lesser :: line Centi, difference :: line Centi} -- | Used in several provincial forms to calculate a fixed amount of tax credit reduced by income. data BaseCredit line = BaseCredit { baseAmount :: line Centi, reduction :: line Centi, difference :: line Centi, cont :: line Centi} -- | A pair of form fields appearing next to each other at the same line, the right field value always a copy of the -- left one. data SubCalculation line = SubCalculation { calculation :: line Centi, result :: line Centi} $(foldMap (\t-> concat <$> sequenceA [ [d| deriving instance (Show (line Centi), Show (line Rational)) => Show ($(TH.conT t) line) deriving instance (Eq (line Centi), Eq (line Rational)) => Eq ($(TH.conT t) line) |], Rank2.TH.deriveAll t, Transformation.Shallow.TH.deriveAll t]) [''BaseCredit, ''MedicalExpenses, ''SubCalculation, ''TaxIncomeBracket]) fixTaxIncomeBracket :: Maybe Centi -> Maybe (TaxIncomeBracket Maybe) -> TaxIncomeBracket Maybe -> TaxIncomeBracket Maybe fixTaxIncomeBracket theIncome nextBracket = fixEq $ \bracket@TaxIncomeBracket{..} -> bracket{ income = do i <- theIncome floor <- threshold let ceiling = nextBracket >>= (.threshold) guard (floor <= i && all (i <) ceiling) Just i, overThreshold = liftA2 (-) income threshold, timesRate = fromRational <$> liftA2 (*) (toRational <$> overThreshold) rate, equalsTax = liftA2 (+) timesRate baseTax} fixBaseCredit :: BaseCredit Maybe -> BaseCredit Maybe fixBaseCredit = fixEq $ \credit@BaseCredit{..}-> credit{ difference = mfilter (> 0) $ liftA2 (-) baseAmount reduction, cont = difference} fixMedicalExpenses :: Centi -> MedicalExpenses Maybe -> MedicalExpenses Maybe fixMedicalExpenses ceiling = fixEq $ \part@MedicalExpenses{..} -> part{ fraction = incomeRate `fractionOf` netIncome, lesser = min ceiling <$> fraction, difference = nonNegativeDifference expenses lesser} fixSubCalculation :: Maybe Centi -> SubCalculation Maybe fixSubCalculation result = SubCalculation{ calculation = result, result = result} subCalculationFields :: Text -> [Text] -> [Text] -> SubCalculation FieldConst subCalculationFields parent calculationPath resultPath = SubCalculation{ calculation = Field (parent : calculationPath) Amount, result = Field (parent : resultPath) Amount}