{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE Strict #-}
{-# LANGUAGE StrictData #-}

{- |
Module      :  Business.OchintinDaicho

Copyright   :  Kadzuya Okamoto 2017
License     :  MIT

Stability   :  experimental
Portability :  unknown

This module exports core functions and types for payroll books.
-}
module Business.OchintinDaicho
  (
  -- * Usage examples
  -- $setup

  -- * Types
    Person(..)
  , Payment(..)
  , Payments
  , Hours

  -- * Pretty printers
  , ppr

  -- * Converters
  , toBookkeeping

  -- * Convenient functions for 年末調整
  , _年度内課税支給金額
  , _年度内社会保険控除額
  , _年度内所得税控除額
  ) where

import Business.Bookkeeping
       (Amount(..), Category(..), CategoryType(..), CreditCategory(..), Date(..),
        DateTransactions, DebitCategory(..), Month(..), Transaction,
        Transactions, Year(..), activity, dateTrans, month, runTransactions, tAmount,
        year)
import Control.Monad (forM_)
import Data.Monoid ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.String (IsString(..))


{- $setup
>>> import Business.Bookkeeping hiding (ppr)
>>> :{
let
  taroYamada :: Person
  taroYamada = Person
    { name = "山田 太郎"
    , sex = "男"
    , payments = \y -> case y of
      2017 ->
        [ ( (1, 10)
          , Payment
            { _賃金計算期間 = (2016, 12)
            , _労働日数 = 13
            , _労働時間数 = 13 * 5
            , _休日労働時間数 = 0
            , _早出残業時間数 = 0
            , _深夜労働時間数 = 0
            , _課税支給額 = 250000
            , _控除社会保険料 = \debit amount -> do
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "健康保険料(社員負担分)"
                $ if (200000 < amount && amount < 300000)
                  then 15000
                  else error "undefined 健康保険料"
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "厚生年金(社員負担分)"
                $ if (200000 < amount && amount < 300000)
                  then 20000
                  else error "undefined 厚生年金"
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "雇用保険料(社員負担分)"
                $ if (200000 < amount && amount < 300000)
                  then 1000
                  else error "undefined 雇用保険料"
            , _所得税額 = \debit amount ->
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "源泉所得税"
                $ if (200000 < amount && amount < 250000)
                  then 5000
                  else error $ "undefined 所得税額 for " <> show amount
            , _非課税支給額 = \credit -> do
              dateTrans
                (DebitCategory $ Category "旅費・交通費" Expenses)
                credit
                "立替交通費"
                3000
            , _その他控除 = \debit ->
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "親睦会費"
                5000
            }
          )
        , ( (2, 10)
          , Payment
            { _賃金計算期間 = (2017, 1)
            , _労働日数 = 12
            , _労働時間数 = 12 * 5
            , _休日労働時間数 = 0
            , _早出残業時間数 = 0
            , _深夜労働時間数 = 0
            , _課税支給額 = 250000
            , _控除社会保険料 = \debit amount -> do
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "健康保険料(社員負担分)"
                $ if (200000 < amount && amount < 300000)
                  then 15000
                  else error "undefined 健康保険料"
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "厚生年金(社員負担分)"
                $ if (200000 < amount && amount < 300000)
                  then 20000
                  else error "undefined 厚生年金"
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "雇用保険料(社員負担分)"
                $ if (200000 < amount && amount < 300000)
                  then 1000
                  else error "undefined 雇用保険料"
            , _所得税額 = \debit amount ->
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "源泉所得税"
                $ if (200000 < amount && amount < 250000)
                  then 5000
                  else error $ "undefined 所得税額2 for " <> show amount
            , _非課税支給額 = \credit -> do
              dateTrans
                (DebitCategory $ Category "旅費・交通費" Expenses)
                credit
                "立替交通費"
                4000
            , _その他控除 = \debit ->
              dateTrans debit
                (CreditCategory $ Category "預り金" Liabilities)
                "親睦会費"
                5000
            }
          )
        ]
      _ ->
        []
    }
:}
-}

{-| 対象年度内の課税支給額総計

>>> _年度内課税支給金額 taroYamada 2017
Amount {unAmount = 500000}
-}
_年度内課税支給金額 :: Person -> Year -> Amount
_年度内課税支給金額 Person {..} =
  sum . map (_課税支給額 . snd) . payments

{-| 対象年度内の社会保険料等控除額総計

>>> _年度内社会保険控除額 taroYamada 2017
Amount {unAmount = 72000}
-}
_年度内社会保険控除額 :: Person -> Year -> Amount
_年度内社会保険控除額 Person {..} =
  sum . map (_算出保険料 . snd) . payments

{-| 対象年度内の所得税控除額総計

>>> _年度内所得税控除額 taroYamada 2017
Amount {unAmount = 10000}
-}
_年度内所得税控除額 :: Person -> Year -> Amount
_年度内所得税控除額 Person {..} =
  sum . map (_算出所得税 . snd) . payments

{-| A pretty printer for payroll books.

>>> ppr taroYamada 2017
氏名: 山田 太郎
性別: 男
<BLANKLINE>
== 2017年支給分 ==
<BLANKLINE>
支給日: 1月10日
賃金計算期間: 2016年12月
労働日数: 13日
労働時間数: 65時間
休日労働時間数: 0時間
早出残業時間数: 0時間
深夜労働時間数: 0時間
課税支給額: 250000円
控除社会保険料: 36000円
社会保険料等控除の金額: 214000円
所得税額: 5000円
非課税支給額: 3000円
その他控除: 5000円
--> 実支払額: 207000円
<BLANKLINE>
支給日: 2月10日
賃金計算期間: 2017年1月
労働日数: 12日
労働時間数: 60時間
休日労働時間数: 0時間
早出残業時間数: 0時間
深夜労働時間数: 0時間
課税支給額: 250000円
控除社会保険料: 36000円
社会保険料等控除の金額: 214000円
所得税額: 5000円
非課税支給額: 4000円
その他控除: 5000円
--> 実支払額: 208000円
<BLANKLINE>
-}
ppr :: Person -> Year -> IO ()
ppr Person {..} y = do
  T.putStrLn $ "氏名: " <> name
  T.putStrLn $ "性別: " <> sex
  T.putStrLn ""
  T.putStrLn . T.concat $
    [ "== "
    , tshow . unYear $ y
    , "年支給分 =="
    ]
  T.putStrLn ""
  forM_ (payments y) $ \((m, d), payment@(Payment {..})) -> do
    let
      (targetYear, targetMonth) = _賃金計算期間
      _算出保険料' = _算出保険料 payment
      _算出所得税' = _算出所得税 payment
      _非課税支給額総計' = _非課税支給額総計 payment
      _その他控除総計' = _その他控除総計 payment
      _実支払額' = _実支払額 payment
    T.putStrLn $ "支給日: " <> (tshow . unMonth) m <> "月" <> (tshow . unDate) d <> "日"
    T.putStrLn $ "賃金計算期間: " <> (tshow . unYear) targetYear <> "年" <> (tshow . unMonth) targetMonth <> "月"
    T.putStrLn $ "労働日数: " <> tshow _労働日数 <> "日"
    T.putStrLn $ "労働時間数: " <> (tshowIntegral) _労働時間数 <> "時間"
    T.putStrLn $ "休日労働時間数: " <> (tshowIntegral) _休日労働時間数 <> "時間"
    T.putStrLn $ "早出残業時間数: " <> (tshowIntegral) _早出残業時間数 <> "時間"
    T.putStrLn $ "深夜労働時間数: " <> (tshowIntegral) _深夜労働時間数 <> "時間"
    T.putStrLn $ "課税支給額: " <> (tshow . unAmount) _課税支給額 <> "円"
    T.putStrLn $ "控除社会保険料: " <> (tshow . unAmount) _算出保険料' <> "円"
    T.putStrLn $ "社会保険料等控除の金額: " <> (tshow . unAmount) (_課税支給額 - _算出保険料') <> "円"
    T.putStrLn $ "所得税額: " <> (tshow . unAmount) _算出所得税' <> "円"
    T.putStrLn $ "非課税支給額: " <> (tshow . unAmount) _非課税支給額総計' <> "円"
    T.putStrLn $ "その他控除: " <> (tshow . unAmount) _その他控除総計' <> "円"
    T.putStrLn $ "--> 実支払額: " <> (tshow . unAmount) _実支払額' <> "円"
    T.putStrLn ""

{-|

>>> :{
Business.Bookkeeping.ppr $ toBookkeeping taroYamada 2017
  (DebitCategory $ Category "給与手当" Expenses)
  (CreditCategory $ Category "普通預金" Liabilities)
:}
tDay: 2017-01-10
tDescription: 12月度
tSubDescription: 立替交通費
tDebit: 旅費・交通費 (Expenses)
tCredit: 普通預金 (Liabilities)
tAmount: 3000
<BLANKLINE>
tDay: 2017-01-10
tDescription: 12月度
tSubDescription: 健康保険料(社員負担分)
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 15000
<BLANKLINE>
tDay: 2017-01-10
tDescription: 12月度
tSubDescription: 厚生年金(社員負担分)
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 20000
<BLANKLINE>
tDay: 2017-01-10
tDescription: 12月度
tSubDescription: 雇用保険料(社員負担分)
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 1000
<BLANKLINE>
tDay: 2017-01-10
tDescription: 12月度
tSubDescription: 源泉所得税
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 5000
<BLANKLINE>
tDay: 2017-01-10
tDescription: 12月度
tSubDescription: 親睦会費
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 5000
<BLANKLINE>
tDay: 2017-01-10
tDescription: 12月度
tSubDescription: 給与支払い
tDebit: 給与手当 (Expenses)
tCredit: 普通預金 (Liabilities)
tAmount: 207000
<BLANKLINE>
tDay: 2017-02-10
tDescription: 1月度
tSubDescription: 立替交通費
tDebit: 旅費・交通費 (Expenses)
tCredit: 普通預金 (Liabilities)
tAmount: 4000
<BLANKLINE>
tDay: 2017-02-10
tDescription: 1月度
tSubDescription: 健康保険料(社員負担分)
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 15000
<BLANKLINE>
tDay: 2017-02-10
tDescription: 1月度
tSubDescription: 厚生年金(社員負担分)
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 20000
<BLANKLINE>
tDay: 2017-02-10
tDescription: 1月度
tSubDescription: 雇用保険料(社員負担分)
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 1000
<BLANKLINE>
tDay: 2017-02-10
tDescription: 1月度
tSubDescription: 源泉所得税
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 5000
<BLANKLINE>
tDay: 2017-02-10
tDescription: 1月度
tSubDescription: 親睦会費
tDebit: 給与手当 (Expenses)
tCredit: 預り金 (Liabilities)
tAmount: 5000
<BLANKLINE>
tDay: 2017-02-10
tDescription: 1月度
tSubDescription: 給与支払い
tDebit: 給与手当 (Expenses)
tCredit: 普通預金 (Liabilities)
tAmount: 208000
<BLANKLINE>

-}
toBookkeeping :: Person -> Year -> DebitCategory -> CreditCategory -> Transactions
toBookkeeping Person {..} y debit credit =
  year y $
    forM_ (payments y) $ \((m, d), payment@(Payment {..})) -> do
      let
        (_, targetMonth) = _賃金計算期間
        _算出保険料' = _算出保険料 payment
        _算出所得税' = _算出所得税 payment
        _非課税支給額総計' = _非課税支給額総計 payment
        _その他控除総計' = _その他控除総計 payment
        _実支払額' = _実支払額 payment
      month m $
        activity d (fromString $ (show . unMonth) targetMonth <> "月度") $ do
          _非課税支給額 credit
          _控除社会保険料 debit _課税支給額
          _所得税額 debit (_課税支給額 - _算出保険料')
          _その他控除 debit
          dateTrans debit credit "給与支払い" _実支払額'


{- ==============
 -     Types
 - ============== -}

{-| Main data type to represent payroll books for a person.
 -}
data Person = Person
  { name :: Text
  , sex :: Text
  , payments :: Year -> Payments
  }

{-| A type for handling `Payment` values.
 -}
type Payments = [((Month, Date), Payment)]

data Payment = Payment
  { _賃金計算期間 :: (Year, Month)
  , _労働日数 :: Int
  , _労働時間数 :: Hours
  , _休日労働時間数 :: Hours
  , _早出残業時間数 :: Hours
  , _深夜労働時間数 :: Hours
  , _課税支給額 :: Amount
  , _非課税支給額 :: CreditCategory -> DateTransactions
  , _控除社会保険料 :: DebitCategory -> Amount -> DateTransactions
  , _所得税額 :: DebitCategory -> Amount -> DateTransactions
  , _その他控除 :: DebitCategory -> DateTransactions
  }

newtype Hours = Hours { _unHours :: Int }
  deriving (Show, Read, Ord, Eq, Num, Enum, Integral, Real)


-- Helper functions
tshow :: (Show a) => a -> T.Text
tshow = T.pack . show


tshowIntegral :: (Integral a) => a -> T.Text
tshowIntegral a = T.pack . show $ (fromIntegral a :: Int)


dummyDebit :: DebitCategory
dummyDebit = DebitCategory $ Category "DUMMY!" Expenses

dummyCredit :: CreditCategory
dummyCredit = CreditCategory $ Category "DUMMY!" Liabilities

_算出保険料 :: Payment -> Amount
_算出保険料 Payment {..} = sum . map tAmount . runDummyActivity $ _控除社会保険料 dummyDebit _課税支給額

_算出所得税 :: Payment -> Amount
_算出所得税 payment@(Payment {..}) = sum . map tAmount . runDummyActivity $ _所得税額 dummyDebit (_課税支給額 - _算出保険料 payment)

_非課税支給額総計 :: Payment -> Amount
_非課税支給額総計 Payment {..} = sum . map tAmount . runDummyActivity $ _非課税支給額 dummyCredit

_その他控除総計 :: Payment -> Amount
_その他控除総計 Payment {..} = sum . map tAmount . runDummyActivity $ _その他控除 dummyDebit

_実支払額 :: Payment -> Amount
_実支払額 payment@(Payment {..}) = _課税支給額 - _算出保険料 payment - _算出所得税 payment - _その他控除総計 payment + _非課税支給額総計 payment

runDummyActivity :: DateTransactions -> [Transaction]
runDummyActivity = runTransactions . year 1 . month 1 . activity 1 ""