1 {-# LANGUAGE NoOverloadedLists #-}
2 module Haccounting.Unit where
4 import Control.DeepSeq (NFData)
5 --import Data.Word (Word8)
6 import Data.Bool (Bool(..))
7 import Data.Char (Char)
11 import GHC.Stack (HasCallStack)
12 import GHC.Generics (Generic)
13 import Data.String (String)
14 import Data.Functor ((<$>))
15 import Data.Monoid (Monoid(..))
16 import Data.Function (flip)
17 import Data.Semigroup (Semigroup(..))
18 import Text.Show (Show)
19 import qualified Data.Map.Strict as Map
20 import qualified Data.Char as Char
22 import Symantic.Compta.Utils.Error
24 errorWithStack :: HasCallStack => String -> a
25 errorWithStack = error -- (msg <> "\n" <> prettyCallStack callStack)
30 class Unitable repr where
31 unit :: HasCallStack => Ty repr TyUnit -> repr a -> repr a
32 --withUnit :: repr U -> repr a -> repr a
34 --withEuro :: repr a -> repr a
35 --withEuro = withUnit euro
38 class EURable repr where
39 eur :: repr TyQuantity -> repr TyAmount
40 (€) :: EURable repr => repr TyQuantity -> repr TyAmount
43 class USDable repr where
44 usd :: repr TyQuantity -> repr TyAmount
48 newtype Unit = Unit String
49 deriving newtype (Eq, Ord, Show, NFData)
50 deriving anyclass (Generic)
51 instance IsString Unit where
53 s@[c] | Char.CurrencySymbol <- Char.generalCategory c -> Unit s
55 _ -> errorWithStack "Unit"
58 newtype Quantity qty = Quantity qty
59 deriving newtype (Eq, Show, NFData, Zeroable, Nullable, Addable, Negable, FromInteger, FromRational)
60 instance Addable a => Semigroup (Quantity a) where
61 Quantity x <> Quantity y = Quantity (x + y)
62 instance (Zeroable a, Addable a) => Monoid (Quantity a) where
63 mempty = Quantity zero
66 newtype Amounts unit qty = Amounts (Map.Map unit qty)
67 deriving newtype (Eq, Show, NFData)
69 instance Zeroable (Amounts a) where
70 zero = Amounts Map.empty
71 instance Nullable (Amounts a) where
72 null (Amounts x) = Foldable.all null x
74 instance (Ord unit, Semigroup qty) => Semigroup (Amounts unit qty) where
75 Amounts x <> Amounts y = Amounts (Map.unionWith (flip (<>)) x y)
76 instance (Ord unit, Semigroup qty) => Monoid (Amounts unit qty) where
77 mempty = Amounts Map.empty
78 instance Negable qty => Negable (Amounts unit qty) where
79 negate (Amounts x) = Amounts (negate <$> x)
81 -- * Type 'AmountStyle'
82 data AmountStyle = AmountStyle
83 { amountStyleFractioning :: Char
84 , amountStyleGroupingIntegral :: (Char, [Int])
85 , amountStyleGroupingFractional :: (Char, [Int])
86 , amountStyleUnitSideLeft :: Bool
87 , amountStyleUnitSpaced :: Bool
88 -- TODO: , amountStyleSignPlus :: Bool
89 } deriving (Eq, Show, Generic, NFData)
91 -- ** Type 'AmountStylePrecision'
92 --type AmountStylePrecision = Natural
94 amountStyles :: Map.Map Unit AmountStyle
95 amountStyles = Map.fromList
96 [ (scalarUnit, AmountStyle
97 { amountStyleFractioning = '.'
98 , amountStyleGroupingFractional = (',', [3])
99 , amountStyleGroupingIntegral = (',', [3])
100 , amountStyleUnitSideLeft = False
101 , amountStyleUnitSpaced = False
103 , (chfUnit, AmountStyle
104 { amountStyleFractioning = ','
105 , amountStyleGroupingFractional = ('.', [3])
106 , amountStyleGroupingIntegral = ('.', [3])
107 , amountStyleUnitSideLeft = False
108 , amountStyleUnitSpaced = False
110 , (cnyUnit, AmountStyle
111 { amountStyleFractioning = ','
112 , amountStyleGroupingFractional = ('.', [3])
113 , amountStyleGroupingIntegral = ('.', [3])
114 , amountStyleUnitSideLeft = False
115 , amountStyleUnitSpaced = False
117 , (eurUnit, AmountStyle
118 { amountStyleFractioning = ','
119 , amountStyleGroupingFractional = ('.', [3])
120 , amountStyleGroupingIntegral = ('.', [3])
121 , amountStyleUnitSideLeft = False
122 , amountStyleUnitSpaced = False
124 , (gbpUnit, AmountStyle
125 { amountStyleFractioning = '.'
126 , amountStyleGroupingFractional = (',', [3])
127 , amountStyleGroupingIntegral = (',', [3])
128 , amountStyleUnitSideLeft = True
129 , amountStyleUnitSpaced = False
131 , (inrUnit, AmountStyle
132 { amountStyleFractioning = ','
133 , amountStyleGroupingFractional = ('.', [3])
134 , amountStyleGroupingIntegral = ('.', [3])
135 , amountStyleUnitSideLeft = False
136 , amountStyleUnitSpaced = False
138 , (jpyUnit, AmountStyle
139 { amountStyleFractioning = '.'
140 , amountStyleGroupingFractional = (',', [3])
141 , amountStyleGroupingIntegral = (',', [3])
142 , amountStyleUnitSideLeft = True
143 , amountStyleUnitSpaced = False
145 , (rubUnit, AmountStyle
146 { amountStyleFractioning = '.'
147 , amountStyleGroupingFractional = (',', [3])
148 , amountStyleGroupingIntegral = (',', [3])
149 , amountStyleUnitSideLeft = True
150 , amountStyleUnitSpaced = False
152 , (usdUnit, AmountStyle
153 { amountStyleFractioning = '.'
154 , amountStyleGroupingFractional = (',', [3])
155 , amountStyleGroupingIntegral = (',', [3])
156 , amountStyleUnitSideLeft = True
157 , amountStyleUnitSpaced = False
165 -- | <https://en.wikipedia.org/wiki/Swiss_franc Swiss franc> unit of currency.
169 -- | <https://en.wikipedia.org/wiki/Yuan Yuan> unit of currency.
173 -- | <https://en.wikipedia.org/wiki/Euro Euro> unit of currency.
177 -- | <https://en.wikipedia.org/wiki/Pound_sterling Pound sterling> unit of currency.
181 -- | <https://en.wikipedia.org/wiki/Indian_rupee Indian rupee> unit of currency.
185 -- | <https://en.wikipedia.org/wiki/Japanese_yen Japanese yen> unit of currency.
189 -- | <https://en.wikipedia.org/wiki/Russian_ruble Russian ruble> unit of currency.
193 -- | <https://en.wikipedia.org/wiki/United_States_dollar United States dollar> unit of currency.
200 import Control.DeepSeq
202 import Data.Char (Char)
204 import Data.Decimal (Decimal, roundTo)
205 import Data.Eq (Eq(..))
206 import Data.Function (($), (.), const, flip)
207 import Data.Map.Strict (Map)
208 import Data.Monoid (Monoid(..))
209 import Data.Ord (Ord(..))
210 import Data.Semigroup (Semigroup(..))
211 import Data.Strict.Maybe
212 import Data.String (IsString)
213 import Data.Text (Text)
214 import Data.Typeable ()
215 import Data.Word (Word8)
216 import Prelude (Int, seq)
217 import Text.Show (Show(..))
218 import qualified Data.Map.Strict as Map
219 import qualified Data.MonoTraversable as MT
220 import qualified Data.Strict as S
221 import qualified Data.Text as T
223 import qualified Hcompta as H
228 { amount_unit :: !Unit
229 , amount_quantity :: !Quantity
230 } deriving (Data, Eq, Ord, Show, Typeable)
232 -- type instance H.UnitFor Amount = Unit
233 -- type instance H.QuantityFor Amount = H.Polarized Quantity
235 instance NFData Amount where
236 rnf (Amount q u) = rnf q `seq` rnf u
237 -- instance H.Amount Amount
240 type instance H.Unit H.:@ Amount = Unit
241 instance H.GetI H.Unit Amount where
243 instance H.SetI H.Unit Amount where
244 setI amount_unit a = a{amount_unit}
246 type instance H.Quantity H.:@ Amount = Quantity
247 instance H.GetI H.Quantity Amount where
248 getI = amount_quantity
249 instance H.SetI H.Quantity Amount where
250 setI amount_quantity a = a{amount_quantity}
253 instance H.Zeroable Amount where
254 zero = Amount "" H.zero
255 instance H.Nullable Amount where
256 null = H.null . amount_quantity
257 -- NOTE: the 'Amount'’s 'amount_quantity' MUST already be rounded
258 -- at 'Amount'’s 'amount_style'’s 'style_amount_precision'.
259 instance H.Signable Amount where
260 sign = H.sign . amount_quantity
266 , amount_quantity = H.zero
269 amount_style :: Style_Amounts -> Amount -> Style_Amount
270 amount_style styles = style_amount_find styles . amount_unit
273 type Quantity = Decimal
275 quantity_round :: Word8 -> Quantity -> Quantity
276 quantity_round = Data.Decimal.roundTo
281 deriving (Data, Eq, IsString, Ord, Show, Typeable)
282 instance H.Zeroable Unit where
284 instance H.Nullable Unit where
285 null (Unit x) = T.null x
287 instance H.Unit Unit where
289 textUnit (Unit t) = t
291 instance NFData Unit where
294 -- ** Example 'Unit's
297 -- * Type 'Style_Amount'
300 { amountStyleFractioning :: !(S.Maybe Style_Amount_Fractioning)
301 , amountStyleGroupingIntegral :: !(S.Maybe AmountStyleGrouping)
302 , amountStyleGroupingFractional :: !(S.Maybe AmountStyleGrouping)
303 , amountStyleUnitSideLeft :: !(S.Maybe LR)
304 , amountStyleUnitSpaced :: !(S.Maybe Style_Amount_Spacing)
305 -- TODO: , style_amount_sign_plus :: S.Maybe Bool
306 } deriving (Data, Eq, Ord, Show, Typeable)
307 instance NFData Style_Amount where
308 rnf (Style_Amount f gi gf ui up) =
314 instance Semigroup Style_Amount where
315 (<>) = style_amount_union
316 instance Monoid Style_Amount where
317 mempty = style_amount
320 style_amount :: Style_Amount
323 { amountStyleFractioning = Nothing
324 , amountStyleGroupingIntegral = Nothing
325 , amountStyleGroupingFractional = Nothing
326 , amountStyleUnitSideLeft = Nothing
327 , amountStyleUnitSpaced = Nothing
336 { amountStyleFractioning=f
337 , amountStyleGroupingIntegral=gi
338 , amountStyleGroupingFractional=gf
339 , amountStyleUnitSideLeft=side
340 , amountStyleUnitSpaced=spaced
343 { amountStyleFractioning=f'
344 , amountStyleGroupingIntegral=gi'
345 , amountStyleGroupingFractional=gf'
346 , amountStyleUnitSideLeft=side'
347 , amountStyleUnitSpaced=spaced'
353 { amountStyleFractioning = S.maybe f' (const f) f
354 , amountStyleGroupingIntegral = S.maybe gi' (const gi) gi
355 , amountStyleGroupingFractional = S.maybe gf' (const gf) gf
356 , amountStyleUnitSideLeft = S.maybe side' (const side) side
357 , amountStyleUnitSpaced = S.maybe spaced' (const spaced) spaced
360 -- ** Type 'Style_Amount_Fractioning'
361 type Style_Amount_Fractioning
364 -- ** Type 'AmountStyleGrouping'
365 data AmountStyleGrouping
366 = AmountStyleGrouping Char [Int]
367 deriving (Data, Eq, Ord, Show, Typeable)
368 instance NFData AmountStyleGrouping where
369 rnf (AmountStyleGrouping s d) = rnf s `seq` rnf d
371 -- ** Type 'Style_Amount_Precision'
372 type Style_Amount_Precision
375 -- ** Type 'Style_Amount_Spacing'
376 type Style_Amount_Spacing
381 deriving (Data, Eq, Ord, Show, Typeable)
382 instance NFData LR where
386 -- ** Type 'Style_Amounts'
387 newtype Style_Amounts
388 = Style_Amounts (Map Unit Style_Amount)
389 deriving (Data, Eq, NFData, Ord, Show, Typeable)
391 -- type instance H.UnitFor Amounts = Unit
392 -- type instance H.QuantityFor Amounts = Quantity
394 instance Semigroup Style_Amounts where
395 Style_Amounts x <> Style_Amounts y =
396 Style_Amounts (Map.unionWith (flip (<>)) x y)
397 instance Monoid Style_Amounts where
398 mempty = Style_Amounts mempty
400 instance H.Zeroable Style_Amounts where
401 zero = Style_Amounts mempty
402 instance H.Sumable Style_Amounts (Unit, Style_Amount) where
403 Style_Amounts ss += (u, s) = Style_Amounts $ Map.insertWith (flip (<>)) u s ss
405 unStyle_Amounts :: Style_Amounts -> Map Unit Style_Amount
406 unStyle_Amounts (Style_Amounts fp) = fp
408 style_amount_find :: Style_Amounts -> Unit -> Style_Amount
409 style_amount_find (Style_Amounts s) u = Map.findWithDefault mempty u s
411 -- *** Example 'Style_Amounts'
412 amountStyles :: Style_Amounts
414 -- ** Type 'Styled_Amount'
415 type Styled_Amount t = (Style_Amount, t)
417 styled_amount :: Style_Amounts -> Amount -> Styled_Amount Amount
418 styled_amount styles amt = (amount_style styles amt, amt)
421 newtype Amounts = Amounts (Map Unit Quantity)
422 deriving (Data, Eq, NFData, Ord, Show, Typeable
423 , H.Addable, H.Negable, H.Subable)
424 unAmounts :: Amounts -> Map Unit Quantity
425 unAmounts (Amounts a) = a
427 type instance MT.Element Amounts = Amount
428 -- instance H.Amounts Amounts
430 -- instance H.Get (H.Balance_Amounts Unit Quantity) Amounts where
431 -- get (Amounts a) = a