tax-ato-2025.1: src/Data/Tax/ATO/TFN.hs
-- This file is part of hs-tax-ato
-- Copyright (C) 2025 Fraser Tweedale
--
-- hs-tax-ato is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
{-|
Tax File Number (TFN) types and functions.
@
import qualified Data.Tax.ATO.TFN as TFN
main = do
let
tfnStr = "330 600 376"
Just tfn = TFN.parse tfnStr
tfnInt = TFN.asInt tfn
Just tfn' = TFN.fromInt tfnInt
putStrLn $ TFN.asString tfn' -- prints "330 600 376"
@
It is recommended to import this module qualified.
You can use the @OverloadedStrings@ language extension or the 'parse' function
to convert string to TFNs. Space characters are ignored, but other non-digits
will cause parse failure.
-}
module Data.Tax.ATO.TFN
(
TFN
, parse
, fromInt
, asString
, asInt
) where
import Data.Char (isDigit)
import Data.String
import Data.Maybe (fromJust)
-- | Tax File Number (TFN)
newtype TFN = TFN Int
deriving (Eq, Ord, Show)
-- | Parse a TFN, throwing an error if it is not valid.
instance IsString TFN where
fromString = fromJust . parse
-- | Construct a TFN, checking validity.
fromInt :: Int -> Maybe TFN
fromInt n | valid n = Just (TFN n)
fromInt _ = Nothing
-- | Parse a TFN. Spaces characters are allowed, other non-digit
-- characters are not.
parse :: String -> Maybe TFN
parse s
| all isDigit s' = fromInt (read s')
| otherwise = Nothing
where s' = filter (/= ' ') s
valid :: Int -> Bool
valid n =
(== 0) . (`mod` 11) . snd $ foldr step (n, 0) weights
where
weights = [1,4,3,7,5,8,6,9,10]
step weight (n',acc) = ((+ acc) . (* weight)) <$> divMod n' 10
asInt :: TFN -> Int
asInt (TFN n) = n
-- Format TFN in the standard way (XXX XXX XXX)
asString :: TFN -> String
asString (TFN n) =
case s' of
[a1,a2,a3,b1,b2,b3,c1,c2,c3] -> [a1,a2,a3,' ',b1,b2,b3,' ',c1,c2,c3]
_ -> show n -- can't happen, but just in case...
where
n' = abs n -- negative can't happen, but just in case...
s = show n'
s' = replicate (9 - length s) '0' <> s -- left-pad to 9 digits