Module: Jct
Constant Summary collapse
- VERSION =
"0.3.1"
Instance Method Summary collapse
-
#amount_separated_by_rate(amount:, start_on:, end_on:) ⇒ Object
Takes the amount and period and returns a HASH with the amount divided by the sales tax period.
- #amount_with_tax(amount, date: Date.today, fraction: :truncate) ⇒ Object
- #rate(date = Date.today) ⇒ Object
- #yearly_amount_with_tax(amount:, start_on:, end_on:, fraction: :truncate) ⇒ Object
Instance Method Details
#amount_separated_by_rate(amount:, start_on:, end_on:) ⇒ Object
Takes the amount and period and returns a HASH with the amount divided by the sales tax period. e.g. 1000, Date.new(1997, 3, 31), Date.new(1997, 4, 9)
> { Jct::RATE103 => 100, Jct::RATE105 => 900 }
MEMO: This method does not perform sales tax calculations For example, if there is an amount to which the 8% tax rate applies and an amount to which the 10% tax rate applies, and there are other charges that should be combined (e.g., the annual basic fee and the optional fee), if this method returns the amount including tax, it cannot be combined with the other charges.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/jct.rb', line 76 def amount_separated_by_rate(amount:, start_on:, end_on:) # You can convert Integer/BigDecimal/Float/String/Rational classes to Rational, # but the `amount` keyword argument does not accept BigDeciaml, Float and String in for the following reasons. # - Rational objects may be implicitly converted to BigDecimal or Float type # when performing arithmetic operations using BigDecimal and Rational, or Float and Rational. # - String is not accepted because an exception is raised by data that cannot be converted, such as 1.1.1, for example. raise ArgumentError.new('amount data-type must be Integer or Rational') unless amount.is_a?(Integer) || amount.is_a?(Rational) raise ArgumentError.new('start_on data-type must be Date') unless start_on.is_a?(Date) raise ArgumentError.new('end_on data-type must be Date') unless end_on.is_a?(Date) # By using the modified Julian date, we can handle all Date as Integer. This speeds up the process. start_on_mjd = start_on.mjd end_on_mjd = end_on.mjd raise ArgumentError.new('start_on must not be after than end_on') if start_on_mjd > end_on_mjd raise ArgumentError.new('start_on must bigger than 1873/1/1') if start_on_mjd < EXCISE_HASHES.first[:start_on].mjd raise ArgumentError.new('amount must be greater than or equal to zero') if amount < 0 # Use the number of days until end_on_mjd. daily_amount = Rational(amount, (start_on_mjd..end_on_mjd).count) {}.tap do |return_hash| EXCISE_HASHES.inject(0) do |sum, hash| # It determines whether there are overlapping periods by comparing the start and end dates of a certain consumption tax with # the start and end dates of the period for which the tax-inclusive price is to be calculated this time. # If there is an overlap, the price for the subject period is calculated by multiplying the number of days of the overlapping period # by the pro rata amount. larger_start_on_mjd = [start_on_mjd, hash[:start_on].mjd].max smaller_end_on_mjd = [end_on_mjd, hash[:end_on].mjd].min # Check if there is an overlapping period if larger_start_on_mjd <= smaller_end_on_mjd # Number of days of overlapping period number_of_days_in_this_excise_rate_term = (larger_start_on_mjd..smaller_end_on_mjd).count return_hash[hash[:rate]] = (daily_amount * number_of_days_in_this_excise_rate_term).truncate end end # If the divided amount is not divisible by the number of target tax rates, # the sum of the amount in the argument and the divided amount may be less than the actual value. # This is because the undivided value is truncated at the time of division. # e.g. # amount: 100000, start_on: 1997/3/31, end_on 2014/4/1の場合 # 3%:16 # 5%:99_967 # 8%:16 # => 16+99967+16=99999 # Add the amount that is out of alignment to the amount that belongs to the lowest sales tax amount # to equal the sum of the argument amount and the divided amount. # The reason for adding the shortfall to the amount that belongs to the least amount of consumption tax # is so that the user will have an advantage when the consumption tax is calculated based on this amount. # Example 1 # amount: 100000, start_on: 1997/3/31, end_on 2014/4/1の場合 # 3%:17 <- Actually 16, but add 1 yen. # 5%:99_967 # 8%:16 # => 17+99967+16=100000 # # Example 2: # amount: 100000, start_on: 2014/3/31, end_on 2019/10/1の場合 # 5%:51 <- Actually 49, but add 2 yen. # 8%:99_900 # 10%:49 # => 51+99900+49=100000 # # FIXME: `Enumerable#sum` has been supported since ruby 2.4, but this gem uses `reduce` because it still needs to support ruby 2.3 series. summarize_separated_amount = return_hash.each_value.reduce(&:+) if amount != summarize_separated_amount return_hash[return_hash.each_key.min] += (amount - summarize_separated_amount) end end end |
#amount_with_tax(amount, date: Date.today, fraction: :truncate) ⇒ Object
27 28 29 30 31 |
# File 'lib/jct.rb', line 27 def amount_with_tax(amount, date: Date.today, fraction: :truncate) return amount if amount < 0 (BigDecimal("#{amount}") * rate(date)).__send__(fraction) end |
#rate(date = Date.today) ⇒ Object
149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/jct.rb', line 149 def rate(date = Date.today) case date when Date.new(1989, 4, 1)..Date.new(1997, 3, 31) RATE103 when Date.new(1997, 4, 1)..Date.new(2014, 3, 31) RATE105 when Date.new(2014, 4, 1)..Date.new(2019, 9, 30) RATE108 when Date.new(2019, 10, 1)..Date::Infinity.new RATE110 else RATE100 end end |
#yearly_amount_with_tax(amount:, start_on:, end_on:, fraction: :truncate) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/jct.rb', line 33 def yearly_amount_with_tax(amount:, start_on:, end_on:, fraction: :truncate) # You can convert Integer/BigDecimal/Float/String/Rational classes to Rational, # but the `amount` keyword argument does not accept BigDeciaml, Float and String for the following reasons. # - Rational objects may be implicitly converted to BigDecimal type when performing arithmetic operations using BigDecimal and Rational. # - Also, when you try to convert BigDecimal to Rational, the resulting value may not be Rational, but BigDecimal. # - Float is not accepted because it is not suitable for calculating sales tax rates. # - String is not accepted because an exception is raised by data that cannot be converted, such as 1.1.1, for example. raise ArgumentError.new('amount data-type must be Integer or Rational') unless amount.is_a?(Integer) || amount.is_a?(Rational) raise ArgumentError.new('start_on data-type must be Date') unless start_on.is_a?(Date) raise ArgumentError.new('end_on data-type must be Date') unless end_on.is_a?(Date) raise ArgumentError.new('start_on must not be after than end_on') if start_on > end_on return amount if amount < 0 daily_amount = Rational(amount, (start_on..end_on).count) EXCISE_HASHES.inject(0) do |sum, hash| # It determines whether there are overlapping periods by comparing the start and end dates of a certain consumption tax with # the start and end dates of the period for which the tax-inclusive price is to be calculated this time. # If there is an overlap, the tax-inclusive price is calculated by multiplying the consumption tax rate for the applicable period # by the number of days and pro rata amount for the overlapping period. larger_start_on = [start_on, hash[:start_on]].max smaller_end_on = [end_on, hash[:end_on]].min # Check if there is an overlapping period if larger_start_on <= smaller_end_on # Number of days of overlapping period number_of_days_in_this_excise_rate_term = (larger_start_on..smaller_end_on).count sum += (daily_amount * number_of_days_in_this_excise_rate_term * hash[:rate]).__send__(fraction) end sum end end |