canadian-income-tax 2024.0 → 2024.0.0.1
raw patch · 45 files changed
+222/−158 lines, 45 filesdep +fast-loggerdep ~deep-transformations
Dependencies added: fast-logger
Dependency ranges changed: deep-transformations
Files
- CHANGELOG.md +4/−0
- app/Main.hs +4/−15
- canadian-income-tax.cabal +16/−7
- src/Tax/Canada.hs +1/−4
- src/Tax/Canada/Federal.hs +13/−12
- src/Tax/Canada/Federal/Schedule11.hs +1/−2
- src/Tax/Canada/Federal/Schedule6.hs +1/−2
- src/Tax/Canada/Federal/Schedule7.hs +5/−4
- src/Tax/Canada/Federal/Schedule8.hs +3/−6
- src/Tax/Canada/Federal/Schedule9.hs +1/−1
- src/Tax/Canada/Province/AB.hs +3/−5
- src/Tax/Canada/Province/AB/AB428/FieldNames.hs +11/−1
- src/Tax/Canada/Province/AB/AB428/Fix.hs +0/−3
- src/Tax/Canada/Province/BC.hs +1/−1
- src/Tax/Canada/Province/BC/BC428/FieldNames.hs +10/−1
- src/Tax/Canada/Province/BC/BC428/Fix.hs +0/−5
- src/Tax/Canada/Province/BC/BC479/FieldNames.hs +4/−1
- src/Tax/Canada/Province/MB.hs +3/−5
- src/Tax/Canada/Province/MB/MB428/FieldNames.hs +11/−1
- src/Tax/Canada/Province/MB/MB428/Fix.hs +0/−3
- src/Tax/Canada/Province/ON.hs +1/−1
- src/Tax/Canada/Province/ON/ON428/FieldNames.hs +13/−0
- src/Tax/Canada/Province/ON/ON428/Fix.hs +2/−2
- src/Tax/Canada/Province/ON/ON479/FieldNames.hs +3/−1
- src/Tax/Canada/Province/ON/ON479/Fix.hs +0/−5
- src/Tax/Canada/Shared.hs +2/−0
- src/Tax/Canada/T1/FieldNames/AB.hs +1/−2
- src/Tax/Canada/T1/FieldNames/BC.hs +5/−0
- src/Tax/Canada/T1/FieldNames/NB.hs +2/−0
- src/Tax/Canada/T1/FieldNames/NL.hs +3/−2
- src/Tax/Canada/T1/FieldNames/NT.hs +3/−2
- src/Tax/Canada/T1/FieldNames/NU.hs +1/−1
- src/Tax/Canada/T1/FieldNames/ON.hs +21/−1
- src/Tax/Canada/T1/FieldNames/PE.hs +1/−1
- src/Tax/Canada/T1/FieldNames/QC.hs +12/−1
- src/Tax/Canada/T1/FieldNames/YT.hs +6/−2
- src/Tax/Canada/T1/Fix.hs +4/−5
- src/Tax/Canada/T1/Types.hs +0/−1
- src/Tax/Canada/T4.hs +1/−1
- src/Tax/FDF.hs +6/−8
- src/Tax/Util.hs +3/−3
- test/Properties.hs +9/−12
- test/Regression.hs +8/−16
- test/Test/Transformations.hs +0/−3
- web/Main.hs +23/−9
CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for `canadian-income-tax` +## 2024.0.0.1++* Bumped the upper `deep-transformations` dependency bounds+ ## 2024.0 * Breaking changes to the library:
app/Main.hs view
@@ -10,10 +10,7 @@ import Codec.Archive.Tar qualified as Tar import Codec.Archive.Tar.Entry (fileEntry, toTarPath) import Control.Applicative ((<**>), many, optional)-import Control.Arrow ((&&&)) import Control.Monad (unless, void, when)-import Data.Bifunctor (bimap)-import Data.ByteString (ByteString) import Data.ByteString qualified as ByteString import Data.ByteString.Lazy qualified as Lazy import Data.ByteString.Lazy qualified as ByteString.Lazy@@ -23,18 +20,13 @@ import Data.Functor.Compose (Compose(Compose, getCompose)) import Data.List qualified as List import Data.Map.Lazy qualified as Map-import Data.Set qualified as Set import Data.Maybe (catMaybes)-import Data.Semigroup (Any (Any)) import Data.Semigroup.Cancellative (isPrefixOf, isSuffixOf)-import Data.Text (Text) import Data.Text qualified as Text import Options.Applicative (Parser, ReadM, long, metavar, short) import Options.Applicative qualified as OptsAp-import Rank2 qualified import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist) import System.FilePath (combine, replaceDirectory, takeFileName)-import System.IO (hPutStrLn, stderr) import Text.FDF (FDF, parse, serialize) import Paths_canadian_income_tax (getDataDir)@@ -42,8 +34,6 @@ import Tax.Canada.Federal (loadInputForms) import Tax.Canada.FormKey (FormKey) import Tax.Canada.FormKey qualified as FormKey-import Tax.FDF (FDFs)-import Tax.FDF qualified as FDF import Tax.PDFtk (fdf2pdf, pdf2fdf) main :: IO ()@@ -72,7 +62,7 @@ Options <$> OptsAp.argument readProvince (metavar "<two-letter province code>") <*> OptsAp.strOption (long "t1" <> metavar "<input T1 form file>")- <*> many (OptsAp.strOption (long "t4" <> metavar "<input t4 form file>"))+ <*> many (OptsAp.strOption (long "t4" <> metavar "<input t4 slip file>")) <*> optional (OptsAp.strOption (long "428" <> metavar "<input 428 form file>")) <*> optional (OptsAp.strOption (long "479" <> metavar "<input 479 form file>")) <*> optional (OptsAp.strOption (long "s6" <> metavar "<input Schedule 6 form file>"))@@ -81,8 +71,8 @@ <*> optional (OptsAp.strOption (long "s9" <> metavar "<input Schedule 9 form file>")) <*> optional (OptsAp.strOption (long "s11" <> metavar "<input Schedule 11 form file>")) <*> OptsAp.strOption (short 'o' <> long "output" <> OptsAp.value "-" <> metavar "<output file or directory>")- <*> OptsAp.switch (long "only-given")- <*> OptsAp.switch (long "keep-irrelevant")+ <*> OptsAp.switch (long "only-given" <> OptsAp.help "Complete only the forms given on the command line")+ <*> OptsAp.switch (long "keep-irrelevant" <> OptsAp.help "Complete the forms that have no effect on T1") <*> OptsAp.switch (short 'v' <> long "verbose") <**> OptsAp.helper @@ -110,7 +100,7 @@ process :: Options -> IO () process Options{province, t1InputPath, t4InputPaths, p428InputPath, p479InputPath, schedule6InputPath, schedule7InputPath, schedule8InputPath, schedule9InputPath, schedule11InputPath,- outputPath, onlyGivenForms, keepIrrelevantForms, verbose} = do+ outputPath, onlyGivenForms, keepIrrelevantForms} = do dataDir <- getDataDir let inputFiles :: [(FormKey, FilePath)] inputFiles = List.sortOn fst $@@ -143,7 +133,6 @@ then ByteString.writeFile (replaceDirectory inputPath outputPath) content' else ByteString.writeFile outputPath content' fdfs = getCompose <$> traverse (parse . Lazy.toStrict . snd) (Compose inputs) :: Either String [(FormKey, FDF)]- bytesMap = Lazy.toStrict . snd <$> Map.fromAscList inputs case do (inputFDFs, ioFDFs) <- List.partition ((FormKey.T4 ==) . fst) <$> fdfs inputForms <- loadInputForms inputFDFs let complete = if keepIrrelevantForms then completeForms else completeRelevantForms
canadian-income-tax.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: canadian-income-tax-version: 2024.0+version: 2024.0.0.1 synopsis: Canadian income tax calculation @@ -25,7 +25,12 @@ type: git location: https://github.com/blamario/canadian-income-tax +common stanzas+ default-language: Haskell2010+ GHC-options: -Wall+ library+ import: stanzas exposed-modules: Tax.Canada Tax.Canada.Federal@@ -89,13 +94,15 @@ -- other-extensions: build-depends: base == 4.*, text >= 1.0 && < 2.2, bytestring >= 0.10.4 && < 1, time == 1.*, containers >= 0.5 && < 0.9, bitwise-enum ^>= 1.0.0, bifunctors ^>= 5.6,- monoid-subclasses == 1.*, rank2classes ^>= 1.5.3, deep-transformations ^>= 0.2.2 || ^>= 0.3,+ monoid-subclasses == 1.*, rank2classes ^>= 1.5.3,+ deep-transformations ^>= 0.2.2 || ^>= 0.3 || ^>= 0.4, ca-province-codes == 1.0.*, forms-data-format ^>= 0.2, typed-process >= 0.1 && ^>=0.2, template-haskell == 2.* hs-source-dirs: src- default-language: Haskell2010+ GHC-options: -Wno-ambiguous-fields -Wno-simplifiable-class-constraints executable complete-canadian-taxes+ import: stanzas main-is: Main.hs -- Modules included in this executable, other than Main.@@ -113,9 +120,9 @@ other-modules: Paths_canadian_income_tax autogen-modules: Paths_canadian_income_tax hs-source-dirs: app- default-language: Haskell2010 executable serve-canadian-taxes+ import: stanzas main-is: Main.hs other-modules: Paths_canadian_income_tax autogen-modules: Paths_canadian_income_tax@@ -127,16 +134,17 @@ -- other-extensions: build-depends: base == 4.*, text, bytestring, containers, monoid-subclasses,+ fast-logger ^>= 3.0 || ^>= 3.1 || ^>= 3.2.1, filepath >= 1.3 && < 1.6, unix >= 2.6 && < 2.9, directory >= 1.2 && < 1.4, time >= 1.9.1 && < 1.15, zip-archive ^>= 0.4, aeson ^>= 2.2, http-types ^>= 0.12, scotty ^>= 0.22, wai-extra ^>= 3.1.10, wai-middleware-static ^>= 0.9.1, forms-data-format, canadian-income-tax hs-source-dirs: web- default-language: Haskell2010 ghc-options: -threaded test-suite properties+ import: stanzas type: exitcode-stdio-1.0 build-depends: base >= 4.7 && < 5, bytestring, directory ^>= 1.3, filepath,@@ -149,9 +157,10 @@ other-modules: Test.Transformations Paths_canadian_income_tax autogen-modules: Paths_canadian_income_tax- default-language: Haskell2010+ GHC-options: -Wno-incomplete-uni-patterns test-suite regression+ import: stanzas type: exitcode-stdio-1.0 build-depends: base >= 4.7 && < 5, bytestring, containers, directory ^>= 1.3, filepath,@@ -162,4 +171,4 @@ canadian-income-tax hs-source-dirs: test main-is: Regression.hs- default-language: Haskell2010+ GHC-options: -Wno-incomplete-uni-patterns
src/Tax/Canada.hs view
@@ -7,9 +7,6 @@ module Tax.Canada (completeForms, completeRelevantForms, formFileNames) where import Data.CAProvinceCodes qualified as Province-import Data.Functor.Const (Const)-import Data.Functor.Product (Product (Pair))-import Data.Kind (Type) import Data.Map (Map) import Data.Map qualified as Map import Data.Set qualified as Set@@ -24,7 +21,7 @@ import Tax.Canada.Province.BC qualified as BC import Tax.Canada.Province.MB qualified as MB import Tax.Canada.Province.ON qualified as ON-import Tax.Canada.T1 as T1 (T1, fixT1, t1FieldsForProvince, fileNameForProvince)+import Tax.Canada.T1 as T1 (T1, fileNameForProvince) import Tax.FDF (FDFs) import Tax.FDF qualified as FDF
src/Tax/Canada/Federal.hs view
@@ -19,12 +19,11 @@ formFieldsForProvince, formFileNames, relevantFormKeys) where import Control.Applicative ((<|>))-import Control.Monad ((=<<)) import Data.CAProvinceCodes qualified as Province import Data.Fixed (Centi) import Data.Foldable (find) import Data.Functor.Compose (Compose(Compose))-import Data.List.NonEmpty (NonEmpty((:|)), nonEmpty)+import Data.List.NonEmpty (NonEmpty, nonEmpty) import Data.Maybe (isJust) import Data.Map (Map, fromList) import Data.Set (Set)@@ -33,7 +32,6 @@ import Data.Text (Text) import Data.Text qualified as Text import Data.Time (Day)-import GHC.Stack (HasCallStack) import Rank2 qualified import Rank2.TH qualified import Transformation.Shallow.TH qualified@@ -71,10 +69,10 @@ Page8Step6(line_43700_Total_income_tax_ded, line_44800_CPPOverpayment, line_45300_CWB, line_45350_CTC), LanguageOfCorrespondence, MaritalStatus)-import Tax.Canada.T4 (T4, t4Fields, T4Slip(box16_employeeCPP, box26_pensionableEarnings))+import Tax.Canada.T4 (T4, T4Slip(box16_employeeCPP, box26_pensionableEarnings)) import Tax.Canada.T4 qualified as T4 import Tax.Canada.Shared (SubCalculation(result))-import Tax.FDF (Entry (Amount), FieldConst (Field), load, within)+import Tax.FDF (FieldConst, load, within) import Tax.Util (fixEq, totalOf) -- | All supported federal input forms, not to be filled in@@ -115,8 +113,9 @@ -- | Complete all the federal forms, also handling the inter-form field references. fixFederalForms :: Province.Code -> InputForms Maybe -> Forms Maybe -> Forms Maybe-fixFederalForms province InputForms{t4} = fixEq $ \Forms{t1, schedule6, schedule7, schedule8, schedule9, schedule11}->- let fromT4s' = fromT4s t4 in Forms{+fixFederalForms province InputForms{t4 = t4s} = fixEq $+ \Forms{t1, schedule6, schedule7, schedule8, schedule9, schedule11}->+ let fromT4s' = fromT4s t4s in Forms{ t1 = fixT1 t1{ page3 = fromT4s' (.slip1.box14_employmentIncome) (\amt pg-> pg{line_10100_EmploymentIncome = amt}) $ fromT4s' (additionalT4 ["42"]) (\amt pg-> pg{line_10120_Commissions = amt}) $@@ -124,7 +123,8 @@ page4 = fromT4s' (.slip1.box52_pensionAdjustment) (\amt pg-> pg{line_20600_PensionAdjustment = amt}) $ fromT4s' (.slip1.box20_employeeRPP) (\amt pg-> pg{line_20700_RPPDeduction = amt}) $ fromT4s' (.slip1.box44_unionDues) (\amt pg-> pg{line_21200_Dues = amt}) $- fromT4s' (additionalT4 ["77"]) (\amt step-> step{line_22900_OtherEmployExpenses = amt}) $+ fromT4s' (additionalT4 ["77"]) (\amt step-> step{line_22900_OtherEmployExpenses =+ amt <|> step.line_22900_OtherEmployExpenses}) $ t1.page4{line_20800_RRSPDeduction = schedule7.page3.partC.line20_deduction, line_22200_CPP_QPP_Contributions = schedule8.page6.line17_sum <|> schedule8.page10.line97_sum,@@ -143,12 +143,12 @@ fromT4s' (.slip1.box55_premiumPPIP) (\amt pg-> pg{line_31205 = amt}) t1.page6 _ -> fromT4s' (\t4-> totalOf [t4.slip1.box18_employeeEI, t4.slip1.box55_premiumPPIP]) (\amt pg-> pg{line_31200 = amt}) t1.page6)- {line_30800 = if any hasAnyField t4+ {line_30800 = if any hasAnyField t4s then schedule8.page5.part3a.line27_copy <|> schedule8.page5.part3b.line32_join <|> schedule8.page9.line60_least else t1.page6.line_30800,- line_31000 = if any hasAnyField t4+ line_31000 = if any hasAnyField t4s then schedule8.page6.line15_half.result <|> schedule8.page9.line78_half.result else t1.page6.line_31000,@@ -171,8 +171,9 @@ schedule8 = fixSchedule8 schedule8{ page4 = schedule8.page4{ line_50339_totalPensionableEarnings =- totalOf . (liftA2 (<|>) (.slip1.box26_pensionableEarnings) (.slip1.box14_employmentIncome) <$>) =<< t4,- line_50340_totalContributions = totalOf . fmap (.slip1.box16_employeeCPP) =<< t4},+ totalOf . (liftA2 (<|>) (.slip1.box26_pensionableEarnings) (.slip1.box14_employmentIncome) <$>) =<< t4s,+ line_50340_totalContributions = totalOf . fmap (.slip1.box16_employeeCPP) =<< t4s,+ line_50341_totalSecondContributions = totalOf . fmap (.slip1.box16a_employeeCPP) =<< t4s}, page6 = schedule8.page6{ line1_netSelfEmploymentEarnings = totalOf [t1.page3.line_12200_PartnershipIncome, t1.page3.line29_sum.result]},
src/Tax/Canada/Federal/Schedule11.hs view
@@ -18,7 +18,6 @@ module Tax.Canada.Federal.Schedule11 where import Data.Fixed (Centi)-import Data.Text (Text) import Language.Haskell.TH qualified as TH import Rank2 qualified import Rank2.TH qualified@@ -28,7 +27,7 @@ import Tax.Canada.T1.Types (T1) import Tax.Canada.T1.Types qualified import Tax.FDF (Entry (Amount, Checkbox, Count), FieldConst (Field), within)-import Tax.Util (fixEq, fractionOf, difference, nonNegativeDifference, totalOf)+import Tax.Util (fixEq, difference, nonNegativeDifference, totalOf) data Schedule11 line = Schedule11{ page1 :: Page1 line,
src/Tax/Canada/Federal/Schedule6.hs view
@@ -18,13 +18,12 @@ module Tax.Canada.Federal.Schedule6 where import Data.Fixed (Centi)-import Data.Text (Text) import Language.Haskell.TH qualified as TH import Rank2 qualified import Rank2.TH qualified import Transformation.Shallow.TH qualified -import Tax.Canada.Shared (SubCalculation(SubCalculation, calculation, result), fixSubCalculation, subCalculationFields)+import Tax.Canada.Shared (SubCalculation(result), fixSubCalculation, subCalculationFields) import Tax.Canada.T1.Types (T1) import Tax.Canada.T1.Types qualified as T1 import Tax.FDF (Entry (Amount, Constant, Percent, Switch'), FieldConst (Field), within)
src/Tax/Canada/Federal/Schedule7.hs view
@@ -17,18 +17,18 @@ module Tax.Canada.Federal.Schedule7 where +import Control.Applicative ((<|>)) import Data.Fixed (Centi)-import Data.Text (Text) import Language.Haskell.TH qualified as TH import Rank2 qualified import Rank2.TH qualified import Transformation.Shallow.TH qualified -import Tax.Canada.Shared (SubCalculation(SubCalculation, result), fixSubCalculation, subCalculationFields)+import Tax.Canada.Shared (SubCalculation(result), fixSubCalculation, subCalculationFields) import Tax.Canada.T1.Types (T1) import Tax.Canada.T1.Types qualified as T1 import Tax.FDF (Entry (Amount, Checkbox), FieldConst (Field), within)-import Tax.Util (fixEq, fractionOf, difference, nonNegativeDifference, totalOf)+import Tax.Util (fixEq, difference, nonNegativeDifference, totalOf) data Schedule7 line = Schedule7{ page2 :: Page2 line,@@ -102,7 +102,7 @@ partB = partB{ line6_contributions_copy = page2.line5_sum, line9_repayments_sum = fixSubCalculation id $ totalOf [line_24600_hbp, line_24620_llp],- line10_difference = difference line6_contributions_copy line9_repayments_sum.result},+ line10_difference = nonNegativeDifference line6_contributions_copy line9_repayments_sum.result}, partC = partC{ line12_prpp_copy = t1.page4.line_20810_PRPP, line13_difference = difference line11_deductionLimit line12_prpp_copy,@@ -110,6 +110,7 @@ line15_cont = line_24640_transfers, line16_difference = difference line14_copy line15_cont, line17_lesser = min line13_difference line16_difference,+ line18_deducting = line18_deducting <|> line17_lesser, line19_sum = totalOf [line15_cont, line18_deducting], line20_deduction = min line10_difference line19_sum}}, page4 = page4{
src/Tax/Canada/Federal/Schedule8.hs view
@@ -20,18 +20,15 @@ import Control.Applicative ((<|>)) import Data.Fixed (Centi)-import Data.Text (Text) import Data.Time.Calendar (MonthOfYear) import Language.Haskell.TH qualified as TH import Rank2 qualified import Rank2.TH qualified import Transformation.Shallow.TH qualified -import Tax.Canada.Shared (SubCalculation(SubCalculation, calculation, result), fixSubCalculation, subCalculationFields)-import Tax.Canada.T1.Types (T1)-import Tax.Canada.T1.Types qualified as T1-import Tax.FDF (Entry (Amount, Constant, Count, Month, Percent), FieldConst (Field), within)-import Tax.Util (fixEq, fractionOf, difference, nonNegativeDifference, totalOf)+import Tax.Canada.Shared (SubCalculation(result), fixSubCalculation, subCalculationFields)+import Tax.FDF (Entry (Amount, Count, Month), FieldConst (Field), within)+import Tax.Util (fixEq, difference, nonNegativeDifference, totalOf) data Schedule8 line = Schedule8{ page2 :: Page2 line,
src/Tax/Canada/Federal/Schedule9.hs view
@@ -27,7 +27,7 @@ import Tax.Canada.T1.Types (T1) import Tax.Canada.T1.Types qualified import Tax.FDF (Entry (Amount, Constant, Textual), FieldConst (Field), within)-import Tax.Util (fixEq, fractionOf, difference, nonNegativeDifference, totalOf)+import Tax.Util (fixEq, difference, nonNegativeDifference, totalOf) data Schedule9 line = Schedule9{ page1 :: Page1 line,
src/Tax/Canada/Province/AB.hs view
@@ -17,9 +17,8 @@ import Tax.Canada.Federal.Schedule9 qualified as Schedule9 import Tax.Canada.FormKey qualified as FormKey import Tax.Canada.FormKey (FormKey)-import Tax.Canada.T1.Types (T1 (T1, page7, page8), Page7(Page7, step6_RefundOrBalanceOwing), Page8(Page8))+import Tax.Canada.T1.Types (T1 (T1, page7), Page7(Page7, step6_RefundOrBalanceOwing)) import Tax.Canada.T1.Types qualified as T1-import Tax.Canada.T1.Types qualified as Page8 (Page8(..)) import Tax.Canada.T1.FieldNames.AB (t1Fields) import Tax.Canada.Province.AB.AB428.Types qualified as AB@@ -40,10 +39,9 @@ fixReturns :: Federal.InputForms Maybe -> Returns Maybe -> Returns Maybe fixReturns inputs = fixEq $ \(Pair- ff@Federal.Forms{t1 = t1@T1{page7 = page7@Page7{step6_RefundOrBalanceOwing},- page8 = page8@Page8{step6_RefundOrBalanceOwing = page8step6}},+ ff@Federal.Forms{t1 = t1@T1{page7 = page7@Page7{step6_RefundOrBalanceOwing}}, schedule9}- ab428@AB428{page1 = page1@AB.Page1{partA, partB = partB1@AB.Page1PartB{spouseAmount}},+ ab428@AB428{page1 = page1@AB.Page1{partB = partB1@AB.Page1PartB{spouseAmount}}, page2 = page2@AB.Page2{AB.partB = partB2@AB.Page2PartB{AB.medicalExpenses}}, page3 = page3@AB.Page3{AB.partC}}) -> (fixFederalForms AB inputs
src/Tax/Canada/Province/AB/AB428/FieldNames.hs view
@@ -12,19 +12,22 @@ import Tax.Canada.Province.AB.AB428.Types import Tax.Canada.Shared (BaseCredit(..), MedicalExpenses(..), TaxIncomeBracket (..), subCalculationFields)-import Tax.FDF (Entry (Count, Constant, Amount, Percent), FieldConst (Field, NoField), within)+import Tax.FDF (Entry (Constant, Amount, Percent), FieldConst (Field), within) +ab428Fields :: AB428 FieldConst ab428Fields = within "form1" Rank2.<$> AB428 { page1 = within "Page1" Rank2.<$> page1Fields, page2 = within "Page2" Rank2.<$> page2Fields, page3 = within "Page3" Rank2.<$> page3Fields} +page1Fields :: Page1 FieldConst page1Fields = Page1 { income = Field ["Line1", "Amount"] Amount, partA = within "Chart" Rank2.<$> page1PartAFields, partB = page1PartBFields} +page1PartAFields :: Page1PartA FieldConst page1PartAFields = Page1PartA { column1 = within "Column1" Rank2.<$> taxIncomeBracketFields "1" 0 0.10 0, column2 = within "Column2" Rank2.<$> taxIncomeBracketFields "2" 148_269 0.12 14_826.90,@@ -43,6 +46,7 @@ baseTax = Field ["Line7-C" <> column, "Amount"] $ Constant baseTax Amount, equalsTax = Field ["Line8-C" <> column, "Amount"] Amount} +page1PartBFields :: Page1PartB FieldConst page1PartBFields = Page1PartB { line9_basic = Field ["Line9", "Amount"] Amount, line10_age = Field ["Line10", "Amount"] Amount,@@ -66,9 +70,11 @@ line24_sum = subCalculationFields "Line24" ["Amount1"] ["Amount2"], line25 = Field ["Line25", "Amount"] Amount} +page2Fields :: Page2 FieldConst page2Fields = Page2 { partB = page2PartBFields} +page2PartBFields :: Page2PartB FieldConst page2PartBFields = Page2PartB { line26 = Field ["Line26", "Amount"] Amount, line27_pension = Field ["Line27", "Amount"] Amount,@@ -91,6 +97,7 @@ line50_sum = subCalculationFields "Line50" ["Amount1"] ["Amount2"], line51 = Field ["Line51", "Amount"] Amount} +medicalExpensesFields :: MedicalExpenses FieldConst medicalExpensesFields = MedicalExpenses { expenses = Field ["Line37", "Amount"] Amount, netIncome = Field ["Line38", "Amount"] Amount,@@ -99,15 +106,18 @@ lesser = Field ["Line41", "Amount"] Amount, difference = Field ["Line42", "Amount"] Amount} +donationFields :: Donations FieldConst donationFields = Donations { line48_base = Field ["Line48", "Amount1"] Amount, line48_fraction = Field ["Line48", "Amount2"] Amount, line49_base = Field ["Line49", "Amount1"] Amount, line49_fraction = Field ["Line49", "Amount2"] Amount} +page3Fields :: Page3 FieldConst page3Fields = Page3 { partC = within "PartC" Rank2.<$> partCFields} +partCFields :: PartC FieldConst partCFields = PartC { line52_tax = Field ["Line52", "Amount"] Amount, line53_splitIncomeTax = Field ["Line53", "Amount"] Amount,
src/Tax/Canada/Province/AB/AB428/Fix.hs view
@@ -8,10 +8,7 @@ module Tax.Canada.Province.AB.AB428.Fix (AB428, fixAB428) where -import Control.Applicative (liftA2)-import Control.Monad (guard) import Data.Fixed (Centi)-import Rank2 qualified import Tax.Canada.Province.AB.AB428.Types import Tax.Canada.Shared (fixBaseCredit, fixMedicalExpenses, fixSubCalculation, fixTaxIncomeBracket,
src/Tax/Canada/Province/BC.hs view
@@ -39,7 +39,7 @@ import Tax.Canada.Province.BC.BC428.Fix (fixBC428) import Tax.Canada.Province.BC.BC428.FieldNames (bc428Fields) import Tax.Canada.Province.BC.BC479.Types qualified as BCC-import Tax.Canada.Province.BC.BC479.Types (BC479 (BC479))+import Tax.Canada.Province.BC.BC479.Types (BC479) import Tax.Canada.Province.BC.BC479.Fix (fixBC479) import Tax.Canada.Province.BC.BC479.FieldNames (bc479Fields)
src/Tax/Canada/Province/BC/BC428/FieldNames.hs view
@@ -11,18 +11,21 @@ import Tax.Canada.Province.BC.BC428.Types import Tax.Canada.Shared (BaseCredit(..), MedicalExpenses(..), TaxIncomeBracket (..), subCalculationFields)-import Tax.FDF (Entry (Count, Constant, Amount, Percent, Textual), FieldConst (Field, NoField), within)+import Tax.FDF (Entry (Constant, Amount, Percent, Textual), FieldConst (Field), within) +bc428Fields :: BC428 FieldConst bc428Fields = within "form1" Rank2.<$> BC428 { page1 = within "Page1" Rank2.<$> page1Fields, page2 = within "Page2" Rank2.<$> page2Fields, page3 = within "Page3" Rank2.<$> page3Fields} +page1Fields :: Page1 FieldConst page1Fields = Page1 { partA = within "PartA" Rank2.<$> page1PartAFields, partB = within "PartB" Rank2.<$> page1PartBFields} +page1PartAFields :: Page1PartA FieldConst page1PartAFields = Page1PartA { income = Field ["Line1", "Amount"] Amount, column1 = within "Column1" Rank2.<$> taxIncomeBracketFields 0 0.0506 0,@@ -53,6 +56,7 @@ baseTax = Field ["Line14", "Amount_ReadOnly"] $ Constant baseTax Amount, equalsTax = Field ["Line15", "Amount"] Amount} +page1PartBFields :: Page1PartB FieldConst page1PartBFields = Page1PartB { line16_basic = Field ["Line16", "Amount"] Amount, line17_age = Field ["Line17", "Amount"] Amount,@@ -69,9 +73,11 @@ line24_caregiver = Field ["Line24", "Amount"] Amount, line25 = Field ["Line25", "Amount"] Amount} +page2Fields :: Page2 FieldConst page2Fields = Page2 { partB = page2PartBFields} +page2PartBFields :: Page2PartB FieldConst page2PartBFields = Page2PartB { line26 = Field ["Line26", "Amount"] Amount, line27_cppQpp = Field ["Line27", "Amount"] Amount,@@ -105,6 +111,7 @@ line59_fraction = Field ["Line59", "Amount"] Amount, line60 = Field ["Line60", "Amount"] Amount} +medicalExpensesFields :: MedicalExpenses FieldConst medicalExpensesFields = MedicalExpenses { expenses = Field ["Line46", "Amount"] Amount, netIncome = Field ["Line47", "Amount"] Amount,@@ -113,6 +120,7 @@ lesser = Field ["Line50", "Amount"] Amount, difference = Field ["Line51", "Amount"] Amount} +partCFields :: PartC FieldConst partCFields = PartC { line61_tax = Field ["Line61", "Amount"] Amount, line62_splitIncomeTax = Field ["Line62", "Amount"] Amount,@@ -129,6 +137,7 @@ line71_foreignCredit = Field ["Line71", "Amount"] Amount, line72 = Field ["Line72", "Amount"] Amount} +page3Fields :: Page3 FieldConst page3Fields = Page3 { partC = partCFields, line73_basicReduction = Field ["Line73", "Amount"] Amount,
src/Tax/Canada/Province/BC/BC428/Fix.hs view
@@ -8,11 +8,6 @@ module Tax.Canada.Province.BC.BC428.Fix (BC428, fixBC428) where -import Control.Applicative (liftA2)-import Control.Monad (guard, mfilter)-import Data.Fixed (Centi)-import Rank2 qualified- import Tax.Canada.Province.BC.BC428.Types import Tax.Canada.Shared (fixBaseCredit, fixMedicalExpenses, fixSubCalculation, fixTaxIncomeBracket, BaseCredit(cont), MedicalExpenses (difference),
src/Tax/Canada/Province/BC/BC479/FieldNames.hs view
@@ -4,18 +4,19 @@ module Tax.Canada.Province.BC.BC479.FieldNames (bc479Fields) where -import Data.Fixed (Centi) import Rank2 qualified import Tax.Canada.Province.BC.BC479.Types import Tax.Canada.Shared (subCalculationFields) import Tax.FDF (Entry (Amount, Checkbox, Constant, Count, Percent, Textual), FieldConst (Field), within) +bc479Fields :: BC479 FieldConst bc479Fields = within "form1" Rank2.<$> BC479 { page1 = within "Page1" Rank2.<$> page1Fields, page2 = within "Page2" Rank2.<$> page2Fields, page3 = within "Page3" Rank2.<$> page3Fields} +page1Fields :: Page1 FieldConst page1Fields = Page1 { line1_netIncome_self = Field ["Chart", "Line1", "NumWithoutCurrency"] Amount, line1_netIncome_spouse = Field ["Chart", "Line1", "NumWithoutCurrency2"] Amount,@@ -40,6 +41,7 @@ line14_fraction = Field ["BCSHRTC", "Line14", "Amount"] Amount, line15_sum = Field ["BCSHRTC", "Line15", "Amount"] Amount} +page2Fields :: Page2 FieldConst page2Fields = Page2 { line16_copy = Field ["Line16", "Amount"] Amount, line17_venture = Field ["BCVCTC", "Line17", "Amount"] Amount,@@ -59,6 +61,7 @@ line31_sum = subCalculationFields "BCTTC" ["Line31", "I1", "Amount1"] ["Line31", "I2", "Amount2"], line32_credits = Field ["BCTTC", "Line32", "Amount"] Amount} +page3Fields :: Page3 FieldConst page3Fields = Page3 { line33_copy = Field ["Line33", "Amount"] Amount, tenancy_months1 = Field ["Declaration-For-Renters", "Table", "Row1", "Numberofmonths"] Count,
src/Tax/Canada/Province/MB.hs view
@@ -18,10 +18,9 @@ import Tax.Canada.Federal.Schedule9 qualified as Schedule9 import Tax.Canada.FormKey (FormKey) import Tax.Canada.FormKey qualified as FormKey-import Tax.Canada.T1.Types (T1 (T1, page7, page8), Page7(Page7, step6_RefundOrBalanceOwing), Page8(Page8))+import Tax.Canada.T1.Types (T1 (T1, page7), Page7(Page7, step6_RefundOrBalanceOwing)) import Tax.Canada.T1.Types qualified as T1 import Tax.Canada.T1.FieldNames.AB (t1Fields) -- same T1 form as Alberta-import Tax.Canada.T1.Fix (fixT1) import Tax.Canada.Province.MB.MB428.Types qualified as MB import Tax.Canada.Province.MB.MB428.Types qualified as MB.Page1 (Page1(..))@@ -40,10 +39,9 @@ fixReturns :: Federal.InputForms Maybe -> Returns Maybe -> Returns Maybe fixReturns inputs =- fixEq $ \(Pair ff@Federal.Forms{t1 = t1@T1{page7 = page7@Page7{step6_RefundOrBalanceOwing},- page8 = page8@Page8{step6_RefundOrBalanceOwing = page8step6}},+ fixEq $ \(Pair ff@Federal.Forms{t1 = t1@T1{page7 = page7@Page7{step6_RefundOrBalanceOwing}}, schedule9}- mb428@MB428{page1 = page1@MB.Page1{partA, partB = partB1@MB.Page1PartB{spouseAmount}},+ mb428@MB428{page1 = page1@MB.Page1{partB = partB1@MB.Page1PartB{spouseAmount}}, page2 = page2@MB.Page2{MB.partB = partB2@MB.Page2PartB{MB.medicalExpenses}}, page3 = page3@MB.Page3{MB.partC}}) -> Pair (fixFederalForms MB inputs $
src/Tax/Canada/Province/MB/MB428/FieldNames.hs view
@@ -11,19 +11,22 @@ import Tax.Canada.Province.MB.MB428.Types import Tax.Canada.Shared (BaseCredit(..), MedicalExpenses(..), TaxIncomeBracket (..), subCalculationFields)-import Tax.FDF (Entry (Count, Constant, Amount, Percent), FieldConst (Field, NoField), within)+import Tax.FDF (Entry (Constant, Amount, Percent), FieldConst (Field), within) +mb428Fields :: MB428 FieldConst mb428Fields = within "form1" Rank2.<$> MB428 { page1 = within "Page1" Rank2.<$> page1Fields, page2 = within "Page2" Rank2.<$> page2Fields, page3 = within "Page3" Rank2.<$> page3Fields} +page1Fields :: Page1 FieldConst page1Fields = Page1 { income = Field ["Line1", "Amount"] Amount, partA = within "Table" Rank2.<$> page1PartAFields, partB = page1PartBFields} +page1PartAFields :: Page1PartA FieldConst page1PartAFields = Page1PartA { column1 = within "Column1" Rank2.<$> taxIncomeBracketFields 0 0.108 0, column2 = within "Column2" Rank2.<$> taxIncomeBracketFields 47_000 0.1275 5076,@@ -39,6 +42,7 @@ baseTax = Field ["Line7", "Amount"] $ Constant baseTax Amount, equalsTax = Field ["Line8", "Amount"] Amount} +page1PartBFields :: Page1PartB FieldConst page1PartBFields = Page1PartB { line9_basic = Field ["Line9", "Amount"] Amount, line10_age = Field ["Line10", "Amount"] Amount,@@ -66,9 +70,11 @@ line28_sum = subCalculationFields "Line28" ["Amount1"] ["Amount2"], line29 = Field ["Line29", "Amount"] Amount} +page2Fields :: Page2 FieldConst page2Fields = Page2 { partB = within "PartB" Rank2.<$> page2PartBFields} +page2PartBFields :: Page2PartB FieldConst page2PartBFields = Page2PartB { line30 = Field ["Line30", "Amount"] Amount, line31_pension = Field ["Line31", "Amount"] Amount,@@ -93,6 +99,7 @@ line56_sum = subCalculationFields "Line56" ["Amount1"] ["Amount2"], line57 = Field ["Line57", "Amount"] Amount} +medicalExpensesFields :: MedicalExpenses FieldConst medicalExpensesFields = MedicalExpenses { expenses = Field ["Line43", "Amount"] Amount, netIncome = Field ["Line44", "Amount"] Amount,@@ -101,15 +108,18 @@ lesser = Field ["Line47", "Amount"] Amount, difference = Field ["Line48", "Amount"] Amount} +donationFields :: Donations FieldConst donationFields = Donations { line54_base = Field ["Line54", "Amount1"] Amount, line54_fraction = Field ["Line54", "Amount2"] Amount, line55_base = Field ["Line55", "Amount1"] Amount, line55_fraction = Field ["Line55", "Amount2"] Amount} +page3Fields :: Page3 FieldConst page3Fields = Page3 { partC = within "PartC" Rank2.<$> partCFields} +partCFields :: PartC FieldConst partCFields = PartC { line58_tax = Field ["Line58", "Amount"] Amount, line59_splitIncomeTax = Field ["Line59", "Amount"] Amount,
src/Tax/Canada/Province/MB/MB428/Fix.hs view
@@ -8,10 +8,7 @@ module Tax.Canada.Province.MB.MB428.Fix (MB428, fixMB428) where -import Control.Applicative (liftA2)-import Control.Monad (guard) import Data.Fixed (Centi)-import Rank2 qualified import Tax.Canada.Province.MB.MB428.Types import Tax.Canada.Shared (fixBaseCredit, fixMedicalExpenses, fixSubCalculation, fixTaxIncomeBracket,
src/Tax/Canada/Province/ON.hs view
@@ -39,7 +39,7 @@ import Tax.Canada.Province.ON.ON428.Types (ON428 (ON428)) import Tax.Canada.Province.ON.ON428.Fix (fixON428) import Tax.Canada.Province.ON.ON428.FieldNames (on428Fields)-import Tax.Canada.Province.ON.ON479.Types (ON479 (ON479, page2), Page2(line23_credits))+import Tax.Canada.Province.ON.ON479.Types (ON479 (page2), Page2(line23_credits)) import Tax.Canada.Province.ON.ON479.Fix (fixON479) import Tax.Canada.Province.ON.ON479.FieldNames (on479Fields)
src/Tax/Canada/Province/ON/ON428/FieldNames.hs view
@@ -14,6 +14,7 @@ import Tax.Canada.Shared (BaseCredit(..), MedicalExpenses(..), TaxIncomeBracket (..), subCalculationFields) import Tax.FDF (Entry (Count, Constant, Amount, Percent), FieldConst (Field, NoField), within) +on428Fields :: ON428 FieldConst on428Fields = within "form1" Rank2.<$> ON428 { page1 = within "Page1" Rank2.<$> page1Fields, page2 = within "Page2" Rank2.<$> page2Fields,@@ -21,11 +22,13 @@ page4 = within "Page4" Rank2.<$> page4Fields} +page1Fields :: Page1 FieldConst page1Fields = Page1 { line1 = Field ["Line1", "Amount"] Amount, partA = within "Chart" Rank2.<$> page1PartAFields, partB = within "Part_B" Rank2.<$> page1PartBFields} +page1PartAFields :: Page1PartA FieldConst page1PartAFields = Page1PartA { column1 = within "Column1" Rank2.<$> taxIncomeBracketFields 0 0.0505 0, column2 = within "Column2" Rank2.<$> taxIncomeBracketFields 51_446.00 0.0915 2_598.02,@@ -43,6 +46,7 @@ baseTax = Field ["Line7", "Amount"] $ Constant baseTax Amount, equalsTax = Field ["Line8", "Amount"] Amount} +page1PartBFields :: Page1PartB FieldConst page1PartBFields = Page1PartB { line9_basic = Field ["Line9", "Amount"] Amount, line10_age = Field ["Line10", "Amount"] Amount,@@ -66,10 +70,12 @@ line24_sum = subCalculationFields "Line24" ["Amount1"] ["Amount2"], line25 = Field ["Line25", "Amount"] Amount} +page2Fields :: Page2 FieldConst page2Fields = Page2 { partB = page2PartBFields, partC = page2PartCFields} +page2PartBFields :: Page2PartB FieldConst page2PartBFields = Page2PartB { line26 = Field ["Line26", "Amount"] Amount, line27_pension = Field ["Line27", "Amount"] Amount,@@ -90,6 +96,7 @@ donations = within "Donations" Rank2.<$> donationsFields, line50 = Field ["Line50", "Amount"] Amount} +medicalExpensesFields :: MedicalExpenses FieldConst medicalExpensesFields = MedicalExpenses { expenses = Field ["Line36", "Amount"] Amount, netIncome = Field ["Line37", "Amount"] Amount,@@ -98,6 +105,7 @@ lesser = Field ["Line40", "Amount"] Amount, difference = Field ["Line41", "Amount"] Amount} +donationsFields :: Donations FieldConst donationsFields = Donations { line47_base = Field ["Line47", "Amount1"] Amount, line47_fraction = Field ["Line47", "Amount2"] Amount,@@ -105,6 +113,7 @@ line48_fraction = Field ["Line48", "Amount2"] Amount, line49_sum = subCalculationFields "Line49" ["Amount1"] ["Amount2"]} +page2PartCFields :: Page2PartC FieldConst page2PartCFields = Page2PartC { line51_tax = Field ["Line51", "Amount"] Amount, line52_credits = Field ["Line52", "Amount"] Amount,@@ -119,6 +128,7 @@ line60_lesser = Field ["Min-Tax-Carryover", "Line60", "Amount"] Amount, line61 = Field ["Line61", "Amount"] Amount} +page3Fields :: Page3 FieldConst page3Fields = Page3 { line62 = Field ["Line62", "Amount"] Amount, line63 = Field ["ON-Surtax", "Line63", "Amount"] Amount,@@ -145,6 +155,7 @@ line82 = Field ["Line82", "Amount"] Amount, line83 = Field ["Line83", "Amount"] Amount} +page4Fields :: Page4 FieldConst page4Fields = Page4 { line84 = Field ["Line84", "Amount"] Amount, line85_lift = Field ["Line85", "Amount"] Amount,@@ -156,6 +167,7 @@ line90 = Field ["Line90", "Amount"] Amount, healthPremium = within "ON_Health_Prenium-worksheet" . within "Chart_ON_Health_Prenium" Rank2.<$> healthPremiumFields} +healthPremiumFields :: HealthPremium FieldConst healthPremiumFields = HealthPremium { row1 = within "Taxable_Line2" Rank2.<$> healthPremiumBracketFields{HealthPremiumBracket.equalsTax = NoField}, row2 = within "Taxable_Line4" Rank2.<$> healthPremiumBracketFields,@@ -163,6 +175,7 @@ row4 = within "Taxable_Line8" Rank2.<$> healthPremiumBracketFields, row5 = within "Taxable_Line10" Rank2.<$> healthPremiumBracketFields} +healthPremiumBracketFields :: HealthPremiumBracket FieldConst healthPremiumBracketFields = HealthPremiumBracket { taxableIncome = Field ["Amount1"] Amount, overThreshold = Field ["Amount2"] Amount,
src/Tax/Canada/Province/ON/ON428/Fix.hs view
@@ -9,8 +9,6 @@ module Tax.Canada.Province.ON.ON428.Fix (ON428, fixON428) where -import Control.Applicative (liftA2)-import Control.Monad (guard, mfilter) import Data.Fixed (Centi) import Rank2 qualified @@ -19,6 +17,8 @@ BaseCredit(cont), MedicalExpenses (difference), SubCalculation (result), TaxIncomeBracket (equalsTax)) import Tax.Util (fixEq, fractionOf, nonNegativeDifference, totalOf)++import Prelude hiding (floor, ceiling) fixON428 :: ON428 Maybe -> ON428 Maybe fixON428 = fixEq $ \on428@ON428{..}-> ON428{page1 = fixPage1 page1,
src/Tax/Canada/Province/ON/ON479/FieldNames.hs view
@@ -6,18 +6,19 @@ module Tax.Canada.Province.ON.ON479.FieldNames (on479Fields) where -import Data.Fixed (Centi) import Rank2 qualified import Tax.Canada.Province.ON.ON479.Types import Tax.Canada.Shared (subCalculationFields) import Tax.FDF (Entry (Count, Constant, Amount, Percent, RadioButton, Textual), FieldConst (Field), within) +on479Fields :: ON479 FieldConst on479Fields = within "form1" Rank2.<$> ON479 { page1 = within "Page1" Rank2.<$> page1Fields, page2 = within "Page2" Rank2.<$> page2Fields} +page1Fields :: Page1 FieldConst page1Fields = Page1 { line_63050_childcare = Field ["Line1", "Amount"] Amount, line4_homecare_copy = Field ["Line2", "Amount"] Amount,@@ -35,6 +36,7 @@ line_63100_fraction = Field ["Line13", "Amount"] Amount, line16_sum = Field ["Line14", "Amount"] Amount} +page2Fields :: Page2 FieldConst page2Fields = Page2 { line17_copy = Field ["Line15", "Amount"] Amount, line_63110_contributions = Field ["ON-Political-Contribution", "Line16", "Amount"] Amount,
src/Tax/Canada/Province/ON/ON479/Fix.hs view
@@ -8,11 +8,6 @@ module Tax.Canada.Province.ON.ON479.Fix (ON479, fixON479) where -import Control.Applicative (liftA2)-import Control.Monad (guard, mfilter)-import Data.Fixed (Centi)-import Rank2 qualified- import Tax.Canada.Province.ON.ON479.Types import Tax.Canada.Shared (fixSubCalculation, SubCalculation(result)) import Tax.Util (fixEq, fractionOf, nonNegativeDifference, totalOf)
@@ -26,6 +26,8 @@ import Tax.FDF (FieldConst(Field), Entry(Amount)) import Tax.Util (fixEq, fractionOf, nonNegativeDifference) +import Prelude hiding (floor, ceiling)+ data TaxIncomeBracket line = TaxIncomeBracket { income :: line Centi, threshold :: line Centi,
src/Tax/Canada/T1/FieldNames/AB.hs view
@@ -8,9 +8,7 @@ import Rank2 qualified import Tax.FDF (FieldConst (Field, NoField), Entry (..), within)-import Tax.Canada.Shared (subCalculationFields) import Tax.Canada.T1.Types-import Tax.Canada.T1.Types qualified as Page8 (Page8(..)) import Tax.Canada.T1.FieldNames.BC qualified as BC import Tax.Canada.T1.FieldNames.ON qualified as ON @@ -25,6 +23,7 @@ page7 = within "Page7" . within "Return-pg7" Rank2.<$> ON.page7Fields, page8 = within "Page8" . within "Return-pg8" Rank2.<$> BC.page8Fields} +page2Fields :: Page2 FieldConst page2Fields = ON.page2Fields { cai = Field ["CAI", "AB_CAI", "Tick_box"] Checkbox, organ_donor = NoField}
src/Tax/Canada/T1/FieldNames/BC.hs view
@@ -17,12 +17,15 @@ page2 = within "form1" . within "Page2" Rank2.<$> page2Fields, page8 = within "form1" . within "Page8" . within "Return-pg8" Rank2.<$> page8Fields} +page2Fields :: Page2 FieldConst page2Fields = ON.page2Fields { cai = NoField, organ_donor = Field ["Organ_donor", "Authori"] $ Switch "Option1" "Option2" "OrganDonor_CheckBox"} +page3Fields :: Page3 FieldConst page3Fields = ON.page3Fields +page8Fields :: Page8 FieldConst page8Fields = ON.page8Fields { Page8.step6_RefundOrBalanceOwing = within "Step6-Cont" Rank2.<$> page8step6Fields, taxPreparer = within "Efile" Rank2.<$> taxPreparerFields,@@ -30,11 +33,13 @@ line_46500 = NoField, line_46600 = NoField} +page8step6Fields :: Page8Step6 FieldConst page8step6Fields = ON.page8step6Fields { line_45350_CTC = Field ["Line45350", "Line_45300_Amount"] Amount, line_45355_MHRTC = Field ["Line45355", "Line_45300_Amount"] Amount, line_47555_TaxPaid = Field ["Line47555", "Line_47555_Amount"] Amount} +taxPreparerFields :: TaxPreparer FieldConst taxPreparerFields = ON.taxPreparerFields { eFileNumber = Field ["Line48900", "EFile"] Textual, line_49000_WasAFeeCharged = Field ["Line49000", "Line49000_CheckBoxGroup"] $ Switch' "Line49000_CheckBox_EN"}
src/Tax/Canada/T1/FieldNames/NB.hs view
@@ -15,9 +15,11 @@ page2 = within "form1" . within "Page2" Rank2.<$> page2Fields, page8 = within "form1" . within "Page8" . within "Return-pg8" Rank2.<$> page8Fields} +page2Fields :: Page2 FieldConst page2Fields = PE.page2Fields{ cai = Field ["CAI", "CAI_2024", "Tick_box"] Checkbox} +page8Fields :: Page8 FieldConst page8Fields = PE.page8Fields { line_48400_Refund = Field ["Refund_or_Balance-Owing", "Line48400", "Line_48400_Amount"] Amount, line_48500_BalanceOwing = Field ["Refund_or_Balance-Owing", "Line48500", "Line_48500_Amount"] Amount}
src/Tax/Canada/T1/FieldNames/NL.hs view
@@ -8,8 +8,7 @@ import Rank2 qualified -import Tax.FDF (FieldConst (Field, NoField), Entry (..), within)-import Tax.Canada.Shared (subCalculationFields, TaxIncomeBracket (timesRate))+import Tax.FDF (FieldConst (Field), Entry (..), within) import Tax.Canada.T1.Types import Tax.Canada.T1.Types qualified as Page8 (Page8(..)) import Tax.Canada.T1.FieldNames.ON qualified as ON@@ -27,10 +26,12 @@ page7 = within "Page7" . within "Return-pg7" Rank2.<$> ON.page7Fields, page8 = within "Page8" . within "Return-pg8" Rank2.<$> page8Fields} +page2Fields :: Page2 FieldConst page2Fields = AB.page2Fields { foreign_property = Field ["Foreign_property", "Line26600"] $ Switch' "ForeignProperty_CheckBox", tax_exempt = Field ["Indian_Act", "IndianAct_Question", "Checkbox"] Checkbox} +page8Fields :: Page8 FieldConst page8Fields = BC.page8Fields { Page8.step6_RefundOrBalanceOwing = within "Step6-Cont" Rank2.<$> BC.page8step6Fields, telephone = Field ["Certification", "Telephone"] Amount,
src/Tax/Canada/T1/FieldNames/NT.hs view
@@ -8,8 +8,7 @@ import Rank2 qualified -import Tax.FDF (FieldConst (Field, NoField), Entry (..), within)-import Tax.Canada.Shared (subCalculationFields)+import Tax.FDF (FieldConst (NoField), within) import Tax.Canada.T1.Types import Tax.Canada.T1.Types qualified as Page8 (Page8(..)) import Tax.Canada.T1.FieldNames.ON@@ -25,8 +24,10 @@ page2 = within "form1" . within "Page2" Rank2.<$> page2Fields, page8 = within "form1" . within "Page8" . within "Return-pg8" Rank2.<$> page8Fields} +page2Fields :: Page2 FieldConst page2Fields = BC.page2Fields{ organ_donor = NoField} +page8Fields :: Page8 FieldConst page8Fields = BC.page8Fields { Page8.step6_RefundOrBalanceOwing = within "Step6-Cont" Rank2.<$> BC.page8step6Fields}
src/Tax/Canada/T1/FieldNames/NU.hs view
@@ -7,7 +7,7 @@ import Rank2 qualified -import Tax.FDF (FieldConst (NoField), within)+import Tax.FDF (FieldConst, within) import Tax.Canada.T1.Types import Tax.Canada.T1.FieldNames.PE qualified as PE import Tax.Canada.T1.FieldNames.ON qualified as ON
src/Tax/Canada/T1/FieldNames/ON.hs view
@@ -10,7 +10,7 @@ import Data.Text (Text) import Data.Text.Lazy (toStrict)-import Data.Text.Lazy.Builder (fromText, toLazyText)+import Data.Text.Lazy.Builder (toLazyText) import Data.Text.Lazy.Builder.Int (decimal) import Tax.FDF (FieldConst (Field, NoField), Entry (..), within) import Tax.Canada.Shared (TaxIncomeBracket (..), subCalculationFields)@@ -27,11 +27,13 @@ page7 = within "Page7" . within "Return-pg7" Rank2.<$> page7Fields, page8 = within "Page8" Rank2.<$> page8Fields} +page1Fields :: Page1 FieldConst page1Fields = Page1 { identification = within "Identification" Rank2.<$> page1IdentificationFields, residence = within "Residence_Info" Rank2.<$> page1ResidenceFields, spouse = within "Info_Spouse_CLP" Rank2.<$> page1SpouseFields} +page1IdentificationFields :: Identification FieldConst page1IdentificationFields = Identification { emailAddress = Field ["EmailAddress"] Textual, dateDeath = Field ["DateDeath_Comb_BordersAll", "DateDeath_Comb"] Date,@@ -48,6 +50,7 @@ id_POBox = Field ["ID_POBox"] Textual, prov_DropDown = Field ["Prov_DropDown"] Province} +page1ResidenceFields :: Residence FieldConst page1ResidenceFields = Residence { prov_DropDown_Business = Field ["Prov_DropDown-Business"] Province, prov_DropDown_Residence = Field ["Prov_DropDown-Residence"] Province,@@ -55,6 +58,7 @@ date_Entry = Field ["Date_Entry", "DateMMDD_Comb_BordersAll_Std", "DateMMDD_Comb"] Date, prov_DropDown = Field ["Prov_DropDown"] Textual} +page1SpouseFields :: Spouse FieldConst page1SpouseFields = Spouse { line_23600 = Field ["Line23600", "Amount"] Amount, self_employment = Field ["Self-employment", "Checkbox"] Checkbox,@@ -63,6 +67,7 @@ line_21300 = Field ["Line21300", "Amount"] Amount, sin = Field ["SIN_Comb_BordersAll", "SIN_Comb"] Textual} +page2Fields :: Page2 FieldConst page2Fields = Page2 { foreign_property = Field ["Foreign_property", "Line26600"] $ Switch "Option1" "Option2" "ForeignProperty_CheckBox", tax_exempt = Field ["Tax_exempt", "Exempt", "Spouse_SelfEmployed"] Checkbox,@@ -71,10 +76,12 @@ organ_donor = Field ["Organ_donor", "Question"] $ Switch "Option1" "Option2" "OrganDonor_CheckBox" } +page2ElectionsCanadaFields :: ElectionsCanada FieldConst page2ElectionsCanadaFields = ElectionsCanada { citizenship = Field ["LineA"] $ Switch "Option1" "Option2" "A_CheckBox", authorization = Field ["LineB"] $ Switch "Option1" "Option2" "B_Authorize_CheckBox"} +page3Fields :: Page3 FieldConst page3Fields = Page3 { line_10100_EmploymentIncome = Field ["Line1", "Line_10100_Amount"] Amount, line_10105_Taxexemptamount = Field ["Line10105", "Line_10105_Amount"] Amount,@@ -118,6 +125,7 @@ line_14700_sum = subCalculationFields "Line14700" ["Line_14700_Amount1"] ["Line_14700_Amount2"], line_15000_TotalIncome = Field ["Line15000", "Line_15000_Amount"] Amount} +selfEmploymentFields :: SelfEmploymentIncome FieldConst selfEmploymentFields = SelfEmploymentIncome { line_13499_Amount = Field ["Line13500", "Line13499", "Line_13499_Amount"] Amount, line_13500_Amount = Field ["Line13500", "Line_13500_Amount"] Amount,@@ -130,6 +138,7 @@ line_14299_Amount = Field ["Line14300", "Line14299", "Line_14299_Amount"] Amount, line_14300_Amount = Field ["Line14300", "Line_14300_Amount"] Amount} +page4Fields :: Page4 FieldConst page4Fields = Page4 { line_15000_TotalIncome_2 = Field ["Line36", "Amount"] Amount, line_20600_PensionAdjustment = Field ["Line20600", "Line_20600_Amount"] Amount,@@ -162,11 +171,13 @@ line_23500_SocialBenefits = Field ["Line23500", "Line_23500_Amount"] Amount, line_23600_NetIncome = Field ["Line23600", "Line_23600_Amount"] Amount} +page5Fields :: Page5 FieldConst page5Fields = Page5 { step4_TaxableIncome = within "Step4" Rank2.<$> step4Fields, partA_FederalTax = within "PartA" Rank2.<$> partAFields "Column" 36, partB_FederalTaxCredits = within "PartB" Rank2.<$> partBFields} +step4Fields :: Step4 FieldConst step4Fields = Step4 { line_23600_NetIncome_2 = Field ["Line59", "Amount"] Amount, line_24400_MilitaryPoliceDeduction = Field ["Line24400", "Line_24400_Amount"] Amount,@@ -209,6 +220,7 @@ equalsTax = Field [fieldNameAt (startLine + 6) n False] Amount} toText = toStrict . toLazyText +partBFields :: Page5PartB FieldConst partBFields = Page5PartB { line_30000 = Field ["Line30000", "Line_30000_Amount"] Amount, line_30100 = Field ["Line30100", "Line_30100_Amount"] Amount,@@ -220,6 +232,7 @@ line_30500 = Field ["Line30500", "Line_30500_Amount"] Amount, pageBreakSummary = Field ["Line89", "Amount"] Amount} +page6Fields :: Page6 FieldConst page6Fields = Page6 { pageBreakCarry = Field ["Line90", "Amount"] Amount, line_30800 = Field ["Line30800", "Line_30800_Amount"] Amount,@@ -255,6 +268,7 @@ line_34900 = Field ["Line34900", "Line_34900_Amount"] Amount, line_35000 = Field ["Line35000", "Line_35000_Amount"] Amount} +page6MedicalExpensesFields :: MedicalExpenses FieldConst page6MedicalExpensesFields = MedicalExpenses { familyExpenses = Field ["Line33099", "Line_33099_Amount"] Amount, taxableIncome = Field ["Line114", "Amount1"] Amount,@@ -263,10 +277,12 @@ difference = Field ["Line116", "Amount"] Amount, otherDependants = Field ["Line33199", "Line_33199_Amount"] Amount} +page7Fields :: Page7 FieldConst page7Fields = Page7 { partC_NetFederalTax = within "PartC" Rank2.<$> partCFields, step6_RefundOrBalanceOwing = within "Step6" Rank2.<$> page7step6Fields} +partCFields :: Page7PartC FieldConst partCFields = Page7PartC { tax_copy = Field ["Line124", "Amount"] Amount, line_40424 = Field ["Line40424", "Line_40424_Amount"] Amount,@@ -295,6 +311,7 @@ line_41800 = Field ["Line41800", "Line_41800_Amount"] Amount, line_42000 = Field ["Line42000", "Line_42000_Amount"] Amount} +page7step6Fields :: Page7Step6 FieldConst page7step6Fields = Page7Step6 { tax_copy = Field ["Line148", "Amount"] Amount, line_42100_CPPContributions = Field ["Line42100", "Line_42100_Amount"] Amount,@@ -304,6 +321,7 @@ line_43200_FirstNationsTax = NoField, line_43500_TotalPayable = Field ["Line43500", "Line_43500_Amount"] Amount} +page8Fields :: Page8 FieldConst page8Fields = Page8 { step6_RefundOrBalanceOwing = within "Step6-Continued" Rank2.<$> page8step6Fields, line_48400_Refund = Field ["Refund_or_Balance-owing", "Line48400", "Line_48400_Amount"] Amount,@@ -315,6 +333,7 @@ line_46500 = Field ["ONOpportunitiesFund2", "Line_2", "Amount"] Amount, line_46600 = Field ["ONOpportunitiesFund2", "Line_3", "Amount"] Amount} +page8step6Fields :: Page8Step6 FieldConst page8step6Fields = Page8Step6 { line_43500_totalpayable = Field ["Line154", "Amount"] Amount, line_43700_Total_income_tax_ded = Field ["Line43700", "Line_43700_Amount"] Amount,@@ -344,6 +363,7 @@ line_48200_sum = subCalculationFields "Line48200" ["Line_48200_Amount1"] ["Line_48200_Amount2"], line164_Refund_or_BalanceOwing = Field ["Line172", "Amount"] Amount} +taxPreparerFields :: TaxPreparer FieldConst taxPreparerFields = TaxPreparer { eFileNumber = Field ["EFileNumber_Comb", "EFile"] Textual, nameOfPreparer = Field ["NameOfPreparer"] Textual,
src/Tax/Canada/T1/FieldNames/PE.hs view
@@ -6,7 +6,7 @@ import Rank2 qualified -import Tax.FDF (FieldConst (Field), Entry (..), within)+import Tax.FDF (FieldConst, within) import Tax.Canada.T1.Types import Tax.Canada.T1.FieldNames.AB qualified as AB import Tax.Canada.T1.FieldNames.BC qualified as BC
src/Tax/Canada/T1/FieldNames/QC.hs view
@@ -18,7 +18,6 @@ import Tax.Canada.T1.Types qualified as Page8 (Page8(..)) import Tax.Canada.T1.Types qualified as MedicalExpenses (MedicalExpenses(..)) import Tax.Canada.T1.FieldNames.ON qualified as ON-import Tax.Canada.T1.FieldNames.BC qualified as BC t1Fields :: T1 FieldConst t1Fields = within "form1" Rank2.<$> T1 {@@ -31,15 +30,18 @@ page7 = within "Page7" Rank2.<$> page7Fields, page8 = within "Page8" Rank2.<$> page8Fields} +page2Fields :: Page2 FieldConst page2Fields = ON.page2Fields { cai = NoField, organ_donor = NoField} +page4Fields :: Page4 FieldConst page4Fields = ON.page4Fields{ line_20810_PRPP = Field ["Line20810", "Amount"] Amount, line_21698_Amount = Field ["Line45", "Line21698", "Line_21699_Amount"] Amount, line_22300_DeductionPPIP = Field ["Line22300", "Line_22300_Amount"] Amount} +page5Fields :: Page5 FieldConst page5Fields = Page5 { step4_TaxableIncome = within "Step4" Rank2.<$> step4Fields, partA_FederalTax = within "Part_A" Rank2.<$> partA1{column4 = column4, column5 = partA2.column5},@@ -55,6 +57,7 @@ toText $ "Line" <> decimal line <> (if isRate then "Rate" else "Amount") <> decimal column toText = toStrict . toLazyText +step4Fields :: Step4 FieldConst step4Fields = ON.step4Fields { line_23600_NetIncome_2 = Field ["Line60", "Amount"] Amount, line_24901_SecurityDeductions = Field ["Line24901", "Line_24901_Amount"] Amount,@@ -62,10 +65,12 @@ line72_difference = Field ["Line73", "Line_26000_Amount"] Amount, line_25999_CapitalGainsReductionAddBack = Field ["Line25999", "Line_25999_Amount"] Amount} +partBFields :: Page5PartB FieldConst partBFields = ON.partBFields { line_30500 = Field ["Line30500", "Line_30499_Amount"] Amount, pageBreakSummary = Field ["Line90", "Amount"] Amount} +page6Fields :: Page6 FieldConst page6Fields = ON.page6Fields { pageBreakCarry = Field ["Line91", "Amount"] Amount, line_31205 = Field ["Line31205", "Line_31205_Amount"] Amount,@@ -80,16 +85,19 @@ line_33200_sum = subCalculationFields "Line33200" ["Line_33200_Amount1"] ["Line_33200_Line32Amount2"], line120_taxCreditRate = Field ["Line124", "Percent"] $ Constant 0.15 Percent} +page6MedicalExpensesFields :: MedicalExpenses FieldConst page6MedicalExpensesFields = ON.page6MedicalExpensesFields { taxableIncome = Field ["Line118", "Amount1"] Amount, taxableIncomeFraction = Field ["Line118", "Amount2"] Amount, MedicalExpenses.threshold = Field ["Line119", "Amount"] Amount, difference = Field ["Line120", "Amount"] Amount} +page7Fields :: Page7 FieldConst page7Fields = Page7 { partC_NetFederalTax = within "PartC" Rank2.<$> partCFields, step6_RefundOrBalanceOwing = within "Step6" Rank2.<$> page7step6Fields} +partCFields :: Page7PartC FieldConst partCFields = ON.partCFields { tax_copy = Field ["Line128", "Amount"] Amount, credits_copy = Field ["Line131", "Amount"] Amount,@@ -101,10 +109,12 @@ line137_sum = Field ["Line141", "Amount"] Amount, line138_logging = Field ["Line142", "Amount"] Amount} +page7step6Fields :: Page7Step6 FieldConst page7step6Fields = ON.page7step6Fields { tax_copy = Field ["Line152", "Amount"] Amount, line_42100_CPPContributions = NoField} +page8Fields :: Page8 FieldConst page8Fields = ON.page8Fields { Page8.step6_RefundOrBalanceOwing = within "Step6-Continued" Rank2.<$> page8step6Fields, line1_ONOpportunitiesFund = NoField,@@ -113,6 +123,7 @@ line_48400_Refund = Field ["Refund_or_Balancing-owing", "Line48400", "Line_48400_Amount"] Amount, line_48500_BalanceOwing = Field ["Refund_or_Balancing-owing", "Line48500", "Line_48500_Amount"] Amount} +page8step6Fields :: Page8Step6 FieldConst page8step6Fields = ON.page8step6Fields { line_43500_totalpayable = Field ["Line157", "Amount"] Amount, line_43800_TaxTransferQC = Field ["Line43800", "Line_43800_Amount"] Amount,
src/Tax/Canada/T1/FieldNames/YT.hs view
@@ -7,8 +7,7 @@ import Rank2 qualified -import Tax.FDF (FieldConst (Field, NoField), Entry (..), within)-import Tax.Canada.Shared (subCalculationFields)+import Tax.FDF (FieldConst (Field), Entry (..), within) import Tax.Canada.T1.Types import Tax.Canada.T1.Types qualified as Page8 (Page8(..)) import Tax.Canada.T1.FieldNames.BC qualified as BC@@ -21,21 +20,26 @@ page7 = within "form1" . within "Page7" Rank2.<$> page7Fields, page8 = within "form1" . within "Page8" Rank2.<$> page8Fields} +page7Fields :: Page7 FieldConst page7Fields = Page7 { partC_NetFederalTax = within "PartC" Rank2.<$> partCFields, step6_RefundOrBalanceOwing = within "Step6" Rank2.<$> page7step6Fields} +partCFields :: Page7PartC FieldConst partCFields = ON.partCFields { line_40900 = Field ["Line4100", "Line40900", "Line_40900_Amount"] Amount, line_41000 = Field ["Line4100", "Line_41000_Amount"] Amount} +page7step6Fields :: Page7Step6 FieldConst page7step6Fields = ON.page7step6Fields { line_43200_FirstNationsTax = Field ["Line43200", "Line_43200_Amount"] Amount} +page8Fields :: Page8 FieldConst page8Fields = BC.page8Fields { Page8.step6_RefundOrBalanceOwing = within "Step6-Cont" Rank2.<$> page8step6Fields, taxPreparer = within "Line_49000_IfFeeWasCharged" Rank2.<$> BC.taxPreparerFields} +page8step6Fields :: Page8Step6 FieldConst page8step6Fields = ON.page8step6Fields { line_43500_totalpayable = Field ["Line155", "Amount"] Amount, line_44100 = Field ["Line44100", "Line_44000_Amount"] Amount,
src/Tax/Canada/T1/Fix.hs view
@@ -10,17 +10,16 @@ module Tax.Canada.T1.Fix (T1, fixT1) where import Control.Applicative ((<|>))-import Data.Fixed (Centi) import Data.Maybe (fromMaybe)-import Data.Text (Text) import Data.Time.Calendar (Year, dayPeriod) import GHC.Stack (HasCallStack)-import Rank2 qualified import Tax.Canada.T1.Types import Tax.Canada.Shared (fixSubCalculation, fixTaxIncomeBracket, SubCalculation(result), TaxIncomeBracket (equalsTax)) import Tax.Util (difference, fixEq, fractionOf, nonNegativeDifference, totalOf) +import Prelude hiding (floor, ceiling)+ fixT1 :: HasCallStack => T1 Maybe -> T1 Maybe fixT1 = fixEq $ \t1@T1{..}-> T1{page1 = fixPage1 page1, page2 = fixPage2 page2,@@ -141,7 +140,7 @@ step6_RefundOrBalanceOwing = fixPage7Step6 t1 step6_RefundOrBalanceOwing} fixPage8 :: T1 Maybe -> Page8 Maybe -> Page8 Maybe-fixPage8 t1 = fixEq $ \page@Page8{..}-> Page8{+fixPage8 t1 = fixEq $ \Page8{..}-> Page8{ step6_RefundOrBalanceOwing = fixPage8Step6 t1 step6_RefundOrBalanceOwing, line_48400_Refund = step6_RefundOrBalanceOwing.line164_Refund_or_BalanceOwing >>= \x-> if x < 0 then Just (negate x) else Nothing,@@ -169,7 +168,7 @@ line_26000_TaxableIncome = totalOf [line72_difference, line_25999_CapitalGainsReductionAddBack]} fixPage5PartA :: HasCallStack => T1 Maybe -> Page5PartA Maybe -> Page5PartA Maybe-fixPage5PartA t1 = fixEq $ \part@Page5PartA{..}-> part{+fixPage5PartA t1 = fixEq $ \part-> Page5PartA{ column1 = fixTaxIncomeBracket income (Just part.column2) part.column1, column2 = fixTaxIncomeBracket income (Just part.column3) part.column2, column3 = fixTaxIncomeBracket income (Just part.column4) part.column3,
src/Tax/Canada/T1/Types.hs view
@@ -17,7 +17,6 @@ module Tax.Canada.T1.Types where import Data.Fixed (Centi)-import Data.Monoid (Ap(Ap, getAp)) import Data.Text (Text) import Data.Time (Day) import Data.CAProvinceCodes qualified as Province
src/Tax/Canada/T4.hs view
@@ -20,7 +20,7 @@ import Rank2.TH qualified import Transformation.Shallow.TH qualified -import Tax.FDF (FieldConst (Field, NoField), Entry (Amount, Province, Textual, Year), within)+import Tax.FDF (FieldConst (Field), Entry (Amount, Province, Textual, Year), within) data T4 line = T4{ slip1 :: T4Slip line,
src/Tax/FDF.hs view
@@ -11,10 +11,9 @@ module Tax.FDF (FDFs, FieldConst(..), Entry(..), mapForm, mapForm2, mapForms, load, loadAll, store, storeAll, update, updateAll, formKeys, within) where -import Control.Monad (join) import Data.Biapplicative (biliftA2, biliftA3) import Data.Bifunctor (bimap)-import Data.Bitraversable (bisequence, bitraverse)+import Data.Bitraversable (bisequence) import Data.CAProvinceCodes qualified as Province import Data.Char (isDigit, isSpace) import Data.Fixed (Centi)@@ -28,9 +27,8 @@ import Data.Text (Text) import Data.Text qualified as Text import Data.Time (Day, MonthOfYear, defaultTimeLocale, formatTime, parseTimeM)-import Data.Void (Void) import Rank2 qualified-import Text.FDF (FDF (FDF, body), Field, foldMapWithKey, mapWithKey, parse, serialize, traverseWithKey)+import Text.FDF (FDF, foldMapWithKey, mapWithKey) import Text.Read (readEither, readMaybe) -- | A form field path serves to uniquely identify and locate the field inside a form@@ -162,7 +160,7 @@ pairKey Field {path, entry = Switch' leaf} (Just True) = Const $ Just (addIndex <$> (path ++ [leaf]), Right "1") pairKey Field {path, entry = Switch' leaf} (Just False) = Const $ Just (map addIndex path ++ [leaf <> "[1]"], Right "1")- pairKey Field {path, entry = Constant c e} (Just v)+ pairKey Field {path, entry = Constant c _} (Just v) | c == v = Const Nothing | otherwise = Const $ Just (path, Left ("Trying to replace constant field " ++ show (path, c) ++ " with " ++ show v)) pairKey Field {path, entry} v = Const $ Just (addIndex <$> path, maybe (Right "") (fromEntry entry) v)@@ -188,7 +186,7 @@ fromFieldMap fieldForm fieldMap = Rank2.traverse (fill fieldMap) fieldForm fill :: forall a. Map [Text] Text -> FieldConst a -> Either String (Maybe a)-fill fieldValues NoField = Right Nothing+fill _ NoField = Right Nothing fill fieldValues Field {path, entry} | Just v <- Map.lookup path fieldValues = toEntry entry v | Just v <- Map.lookup (addIndex <$> path) fieldValues = toEntry entry v@@ -256,8 +254,8 @@ | Right n <- readEither $ Text.unpack v, n > 0, x:_ <- drop (n - 1) values = Right $ Just x | otherwise = Left ("Bad radio button value: " <> show (e, v)) toEntry e@RadioButtons{} v = Left (show (e, v))-toEntry e@(Switch a b leaf) v = Left (show (e, v))-toEntry e@(Switch' leaf) v = Left (show (e, v))+toEntry e@Switch{} v = Left (show (e, v))+toEntry e@Switch'{} v = Left (show (e, v)) addIndex :: Text -> Text addIndex key
src/Tax/Util.hs view
@@ -2,8 +2,7 @@ module Tax.Util where import Data.Fixed (Centi)-import Data.List.NonEmpty (nonEmpty)-import Data.Maybe (fromMaybe, mapMaybe)+import Data.Maybe (fromMaybe) -- | Repeatedly apply the function to the argument until it reaches the fixed point. fixEq :: Eq a => (a -> a) -> a -> a@@ -27,7 +26,8 @@ -- | Non-negative subtraction under 'Maybe', returning @Just 0@ instead of negative results nonNegativeDifference :: Maybe Centi -> Maybe Centi -> Maybe Centi nonNegativeDifference Nothing Nothing = Nothing-nonNegativeDifference a b = Just (max 0 $ fromMaybe 0 a - fromMaybe 0 b)+nonNegativeDifference Nothing (Just x) | x >= 0 = Nothing+nonNegativeDifference x y = Just (max 0 $ fromMaybe 0 x - fromMaybe 0 y) -- | Multiplication under 'Maybe' fractionOf :: Maybe Rational -> Maybe Centi -> Maybe Centi
test/Properties.hs view
@@ -11,7 +11,6 @@ import Tax.Canada.Province.AB qualified as AB import Tax.Canada.Province.BC qualified as BC import Tax.Canada.Province.MB qualified as MB-import Tax.Canada.Province.NB qualified as NB import Tax.Canada.Province.NL qualified as NL import Tax.Canada.Province.ON qualified as ON import Tax.Canada.Province.PE qualified as PE@@ -19,7 +18,7 @@ import Tax.Canada.Territory.NT qualified as NT import Tax.Canada.Territory.NU qualified as NU import Tax.Canada.Territory.YT qualified as YT-import Tax.Canada.T1 (T1, fixT1)+import Tax.Canada.T1 (fixT1) import Tax.Canada.T4 (t4Fields) import Tax.Canada.Federal qualified as Federal import Tax.Canada.Federal.Schedule6 (schedule6Fields)@@ -27,7 +26,7 @@ import Tax.Canada.Federal.Schedule8 (schedule8Fields) import Tax.Canada.Federal.Schedule9 (schedule9Fields) import Tax.Canada.Federal.Schedule11 (schedule11Fields)-import Tax.FDF as FDF+import Tax.FDF qualified as FDF import Paths_canadian_income_tax (getDataDir) import Test.Transformations qualified as Transformations@@ -35,26 +34,24 @@ import Data.ByteString qualified as ByteString import Data.Char qualified as Char import Data.Either (isLeft, isRight)-import Data.Functor.Const (Const (Const, getConst)) import Data.List qualified as List import Data.Maybe (fromMaybe) import Data.Monoid.Textual qualified as Textual import Data.Semigroup.Cancellative (isSuffixOf, stripPrefix)-import Data.Semigroup (All (All, getAll)) import Data.Text (Text, isInfixOf, stripSuffix) import Rank2 qualified import System.Directory (listDirectory) import System.Exit (die)-import System.FilePath.Posix (combine, isExtensionOf)+import System.FilePath.Posix (combine) import Transformation.Shallow qualified as Shallow-import Text.FDF (FDF (body), Field, parse)+import Text.FDF (FDF, parse) import Text.FDF qualified import Hedgehog (Gen, Property, (===), annotateShow, assert, forAll, property)-import Hedgehog.Gen qualified as Gen import Test.Tasty import Test.Tasty.Hedgehog +main :: IO () main = do dataDir <- getDataDir fdfMaps <- traverse (readFDFs . combine dataDir) ["fdf", "fdf/T1", "fdf/428", "fdf/479"]@@ -129,17 +126,17 @@ checkFormIdempotent :: (Eq (g Maybe), Show (g Maybe), Rank2.Applicative g, Shallow.Traversable Transformations.Gen g)- => g FieldConst -> (g Maybe -> g Maybe) -> Property+ => g FDF.FieldConst -> (g Maybe -> g Maybe) -> Property checkFormIdempotent fields f = checkIdempotent (generateForm fields) f checkFederalFormIdempotent :: (Eq (g Maybe), Show (g Maybe), Rank2.Applicative g, Shallow.Traversable Transformations.Gen g)- => g FieldConst -> (Federal.InputForms Maybe -> g Maybe -> g Maybe) -> Property+ => g FDF.FieldConst -> (Federal.InputForms Maybe -> g Maybe -> g Maybe) -> Property checkFederalFormIdempotent fields f = checkIdempotent (generateForm fields) (f mempty) checkFormFields :: (Eq (g Maybe), Show (g Maybe), Rank2.Applicative g, Shallow.Traversable Transformations.Gen g)- => g FieldConst -> Maybe FDF -> Property+ => g FDF.FieldConst -> Maybe FDF -> Property checkFormFields _ Nothing = error "Missing FDF template" checkFormFields fields (Just fdf) = property $ do annotateShow $ FDF.load fields fdf@@ -159,7 +156,7 @@ FDF.load fields fdf' === Right form List.sort (noCheckbox formKeys) === List.sort (noCheckbox $ filter (\x-> any (`List.isPrefixOf` x) keyHeads) fdfKeys) -generateForm :: (Rank2.Applicative g, Shallow.Traversable Transformations.Gen g) => g FieldConst -> Gen (g Maybe)+generateForm :: (Rank2.Applicative g, Shallow.Traversable Transformations.Gen g) => g FDF.FieldConst -> Gen (g Maybe) generateForm = Shallow.traverse Transformations.Gen checkIdempotent :: (Eq a, Show a) => Gen a -> (a -> a) -> Property
test/Regression.hs view
@@ -12,32 +12,24 @@ import Tax.Canada.FormKey (FormKey) import Tax.Canada.FormKey qualified as FormKey import Tax.Canada.Province.ON qualified as ON-import Tax.Canada.Province.AB qualified as AB (t1Fields)-import Tax.Canada.Province.BC qualified as BC (t1Fields)-import Tax.Canada.T1 (T1, fixT1)-import Tax.FDF as FDF+import Tax.Canada.T1 (fixT1)+import Tax.FDF qualified as FDF import Control.Monad.IO.Class (liftIO) import Data.ByteString qualified as ByteString import Data.ByteString.Lazy (fromStrict) import Data.Foldable (toList)-import Data.Functor.Const (Const (Const, getConst)) import Data.List qualified as List import Data.Map qualified as Map-import Data.Maybe (fromMaybe)-import Data.Semigroup (All (All, getAll))-import Data.Text (Text)-import Data.Text qualified as Text-import Rank2 qualified import System.Directory (doesDirectoryExist, listDirectory) import System.Exit (die) import System.FilePath.Posix (combine)-import Text.FDF (FDF, parse, serialize)-import Text.FDF qualified+import Text.FDF (parse, serialize) import Test.Tasty import Test.Tasty.Golden +main :: IO () main = listDirectory inputDir >>= traverse test >>= defaultMain . testGroup "Regression" rootDir, inputDir, outputDir, referenceDir :: FilePath@@ -81,11 +73,11 @@ Right filled | let fdfOutputs = fromStrict . serialize <$> filled diff ref new = ["diff", "-uw", ref, new]- compare key fdfOutput+ verify key fdfOutput | let Just fileName = List.find ((key ==) . formKey) fdfFileNames- = pure $ goldenVsStringDiff fileName diff (combine referenceDir $ combine path fileName)- $ pure fdfOutput- -> testGroup path . toList <$> Map.traverseWithKey compare fdfOutputs+ = pure $ goldenVsStringDiff fileName diff (combine referenceDir $ combine path fileName)+ $ pure fdfOutput+ -> testGroup path . toList <$> Map.traverseWithKey verify fdfOutputs formKey :: FilePath -> FormKey
test/Test/Transformations.hs view
@@ -15,7 +15,6 @@ import Data.Functor.Compose import Data.Void (Void)-import Data.Word (Word) import Data.Text (Text) import Data.Time (Day) import Data.Time.Calendar (fromGregorian)@@ -27,8 +26,6 @@ import Transformation (Transformation (Domain, Codomain), At) import Transformation qualified-import Transformation.Shallow qualified as Shallow-import Transformation.Shallow.TH qualified as Shallow.TH import Tax.FDF (FieldConst (Field, NoField), Entry (Constant)) import Tax.Canada.T1 (LanguageOfCorrespondence, MaritalStatus)
web/Main.hs view
@@ -1,15 +1,15 @@ {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Codec.Archive.Zip (addEntryToArchive, emptyArchive, fromArchive, toEntry) import Control.Category ((>>>))-import Control.Monad (forM, join)+import Control.Monad (forM) import Control.Monad.IO.Class (liftIO)-import Control.Exception (assert) import Data.Aeson (decode) import Data.Bifunctor (first) import Data.ByteString (ByteString)@@ -18,7 +18,6 @@ import Data.ByteString.Lazy qualified as ByteString.Lazy import Data.Fixed (Centi) import Data.Foldable (fold, toList)-import Data.Functor.Compose (Compose(..)) import Data.List qualified as List import Data.Map.Lazy (Map) import Data.Map.Lazy qualified as Map@@ -30,13 +29,15 @@ import Data.Text (Text) import Data.Text qualified as Text import Data.Text.Lazy qualified as Text.Lazy-import Network.HTTP.Types.Status (ok200, internalServerError500,+import Network.HTTP.Types.Status (statusCode, ok200, internalServerError500, notFound404, unsupportedMediaType415, unprocessableEntity422) import Network.Wai.Middleware.RequestLogger import Network.Wai.Middleware.Static import Network.Wai.Parse (FileInfo (..)) import System.Directory (removeDirectoryRecursive) import System.FilePath.Posix ((</>))+import System.Log.FastLogger (LogType'(LogFile), FileLogSpec(FileLogSpec), toLogStr, withTimedFastLogger)+import System.Log.FastLogger.Date (newTimeCache, simpleTimeFormat) import System.Posix.Temp (mkdtemp) import Text.FDF qualified as FDF (mapWithKey, parse, serialize) import Text.Read (readMaybe)@@ -48,9 +49,10 @@ import Tax.Canada.Federal qualified as Federal import Tax.Canada.FormKey (FormKey) import Tax.Canada.FormKey qualified as FormKey-import Tax.FDF qualified as FDF import Tax.PDFtk (fdf2pdf, pdfFile2fdf) +import Prelude hiding (log)+ main :: IO () main = do dataDir <- getDataDir@@ -62,7 +64,11 @@ let t4fdf = case FDF.parse t4fdfBytes of Left err -> error ("Can't load built-in T4 FDF: " <> err) Right parsed -> parsed- scotty 3000 $ do+ logDestination = LogFile (FileLogSpec "taxell.log" (2^(20::Int)) 16) 1024+ timer <- newTimeCache simpleTimeFormat+ withTimedFastLogger timer logDestination+ $ \l-> let log msg = liftIO $ l (\timestamp-> toLogStr timestamp <> " - " <> msg <> "\n") in (log "Started" >>)+ $ scotty 3000 $ do middleware logStdoutDev get "/" $ do setHeader "Content-Type" "text/html; charset=utf-8"@@ -75,6 +81,8 @@ provinceCode <- pathParam "province" t4param <- formParamMaybe "T4" pdfFiles <- files+ log ("Save " <> toLogStr provinceCode <> ": "+ <> maybe "no" (const "with") t4param <> " T4s, " <> toLogStr (show (fst <$> pdfFiles))) now <- liftIO $ round . nominalDiffTimeToSeconds <$> getPOSIXTime let pdfArchive = foldr addPDF emptyArchive pdfFiles addPDF (_, FileInfo name _ c) = addEntryToArchive (toEntry (fromUTF8 name) now c)@@ -98,11 +106,13 @@ resolveForm (key, FileInfo name _ content) | Just formKey <- readMaybe (Text.unpack key) = Right (formKey, (fromUTF8 name, content)) | otherwise = Left key- pdfFiles <- files >>= \fs-> case traverse resolveForm fs of+ pdfFiles <- (traverse resolveForm <$> files) >>= \case Left name -> status notFound404 >> text ("No such form key as " <> Text.Lazy.fromStrict name) >> finish Right fs -> pure fs+ log ("Complete " <> toLogStr provinceCode <> ": "+ <> toLogStr (length t4m) <> " T4s, " <> toLogStr (show (fst <$> pdfFiles))) let allPdfFiles = Map.toList (Map.fromList pdfFiles <> emptyForms) dir <- liftIO $ mkdtemp "tax" fdfBytes <- liftIO $ fmap sequenceA $ forM allPdfFiles $ \(key, (name, content))-> do@@ -112,7 +122,7 @@ pure ((,) key <$> fdf) case do fdfs <- first ((,) unsupportedMediaType415) $ fdfBytes >>= traverse (traverse $ Lazy.toStrict >>> FDF.parse)- let (inputFDFs, ioFDFs) = List.partition ((FormKey.T4 ==) . fst) fdfs+ let (inputFDFs, _ioFDFs) = List.partition ((FormKey.T4 ==) . fst) fdfs inputT4s = foldMap injectT4 t4m injectT4 values = [(FormKey.T4, FDF.mapWithKey injectT4box t4fdf)] where injectT4box keyPath ""@@ -121,8 +131,11 @@ injectT4box _ v = v inputForms <- first ((,) unprocessableEntity422) $ Federal.loadInputForms $ inputFDFs <> inputT4s first ((,) unprocessableEntity422) $ completeRelevantForms province inputForms (Map.fromList fdfs)- of Left (code, err) -> status code >> text (fromString err)+ of Left (code, err) ->+ log ("Error " <> toLogStr code.statusCode <> ": " <> toLogStr err)+ >> status code >> text (fromString err) Right fdfs' -> do+ log ("Completed " <> toLogStr provinceCode <> ": " <> toLogStr (show (Map.keys fdfs'))) let fdfBytes' = Lazy.fromStrict . FDF.serialize <$> fdfs' replaceContent :: FormKey -> Lazy.ByteString -> IO (Either String (FilePath, Lazy.ByteString))@@ -132,6 +145,7 @@ pdfFiles' <- liftIO $ Map.traverseWithKey replaceContent fdfBytes' case toList <$> sequenceA pdfFiles' of Left err -> do+ log ("Error 500: " <> toLogStr err) status internalServerError500 >> text (fromString err) Right [(name, pdf)] -> do status ok200