Module: Jct

Extended by:
Jct
Included in:
Jct
Defined in:
lib/jct.rb,
lib/jct/version.rb

Constant Summary collapse

VERSION =
"0.3.1"

Instance Method Summary collapse

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.

Raises:

  • (ArgumentError)


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

Raises:

  • (ArgumentError)


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