Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Faker::Company.indian_gst_number fixed #2823 #2825

Merged
merged 8 commits into from Nov 2, 2023
4 changes: 4 additions & 0 deletions doc/default/company.md
Expand Up @@ -84,4 +84,8 @@ Faker::Company.russian_tax_number #=> "0965855857"
Faker::Company.russian_tax_number(region: '77') #=> "7717152803"
Faker::Company.russian_tax_number(type: :individual) #=> "488935903348"
Faker::Company.russian_tax_number(region: '77', type: :individual) #=> "779124694601"

# Get a random formatted Indian tax number (GST)
Faker::Company.indian_gst_number #=> "15VQPNZ2126J2ZU"
Faker::Company.indian_gst_number(state_code: "22") #=> "22ZVWEY6632K0ZN"
```
66 changes: 66 additions & 0 deletions lib/faker/default/company.rb
Expand Up @@ -464,6 +464,59 @@ def sic_code
fetch('company.sic_code')
end

##
# Get a random Indian Goods and Services Tax (GST) number.
# For more on Indian tax number here:
# https://simple.wikipedia.org/wiki/GSTIN
# @params state code [String] Any state code.
#
# @return [String]
# @example
# Faker::Company.indian_gst_number #=> "15VQPNZ2126J2ZU"
# Faker::Company.indian_gst_number(state_code: "22") #=> "22ZVWEY6632K0ZN"
#
# @faker.version 3.2.1
def indian_gst_number(state_code: nil)
ankitkhadria marked this conversation as resolved.
Show resolved Hide resolved
# Check if state code is valid
state_code_ranges = ['02'..'38', ['98']]
raise ArgumentError, 'state code must be in a range of 02 to 38 or 98' if state_code && (!state_code_ranges[0].cover?(state_code) || state_code_ranges[1][0] != '98')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this locally and the generator was accepting any argument. For example:

Faker::Company.indian_gst_number(state_code: '99') = > "99BBHPN5079J6ZN"
Faker::Company.indian_gst_number(state_code: '100') => "100IXDHY2033Z1ZL"

I believe this is what we want:

        state_code_ranges = ['02'..'38', ['98']]

        if state_code && !(state_code_ranges[0].cover?(state_code) || state_code == '98')
          raise ArgumentError, 'state code must be in a range of 02 to 38 or 98'
        end

It was a bit hard to follow the condition, so this way is easier to understand what we want. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, the generator was accepting invalid state codes too, it seems like cover? method is related to the Comparable module, and checks whether an item would fit between the end points in a sorted list. It will return true even if the item is not in the set implied by the Range, so using include? to check whether the item is in complete set.

2.7.4 :002 > ("02".."38").cover?("100")
 => true 
2.7.4 :003 > ("02".."38").cover?("1000")
 => true
2.7.4 :004 >  ("02".."38").include?("100")
 => false

changed to

raise ArgumentError, 'state code must be in a range of 02 to 38 or 98' if state_code && !(state_code_ranges[0].include?(state_code) || state_code == '98')


PositionalGenerator.new(:string) do |gen|
# Generate a state code if not given
if state_code
gen.lit(state_code, name: :state_code_param)
else
gen.letter(name: :state_code_param, length: 1, ranges: state_code_ranges)
end

# Construct taxpayer number
gen.group(name: :taxpayer_number) do |g_|
g_.letter(length: 3, ranges: ['A'..'Z'])
g_.letter(length: 1, ranges: [%w[A B C F G H L J P T K]].to_a)
g_.letter(length: 1, ranges: ['A'..'Z'])
g_.int(length: 4, ranges: [0..9999])
g_.letter(length: 1, ranges: ['A'..'Z'])
end

gen.int(name: :registration_number, length: 1, ranges: [0..9])

gen.letter(name: :z_char, length: 1, ranges: [['Z']])

gen.computed(deps: %i[state_code_param taxpayer_number registration_number]) do |state_code_param, taxpayer_number, registration_number|
gst_base = "#{state_code_param}#{taxpayer_number}#{registration_number}"
chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.chars
values = gst_base.chars
sum = values.map.with_index do |char, index|
product = chars.index(char) * (index.odd? ? 2 : 1)
(product / chars.length).floor + (product % chars.length)
end.reduce(:+)

checksum = (chars.length - (sum % chars.length)) % chars.length
chars[checksum]
end
end.generate
end

private

# Mod11 functionality from https://github.com/badmanski/mod11/blob/master/lib/mod11.rb
Expand Down Expand Up @@ -605,6 +658,19 @@ def spanish_b_algorithm(value)

result.to_s[0].to_i + result.to_s[1].to_i
end

def calculate_gst_checksum(state_code, taxpayer_number, registration_number)
gst_base = "#{state_code}#{taxpayer_number}#{registration_number}"
chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.chars
values = gst_base.upcase.chars
sum = values.map.with_index do |char, index|
product = chars.index(char) * (index.odd? ? 2 : 1)
(product / chars.length).floor + (product % chars.length)
end.reduce(:+)

checksum = (chars.length - (sum % chars.length)) % chars.length
chars[checksum]
end
end
end
end
14 changes: 14 additions & 0 deletions test/faker/default/test_faker_company.rb
Expand Up @@ -250,6 +250,20 @@ def test_spanish_b_algorithm
assert_equal(3, @tester.send(:spanish_b_algorithm, 6))
end

def text_indian_gst_number
assert_match(/^([0-2][0-9]|[3][0-7])[A-Z]{3}[ABCFGHLJPTK][A-Z]\d{4}[A-Z][A-Z0-9][Z][A-Z0-9]$/i, @tester.indian_gst_number)
end

def test_state_code_in_indian_gst_number
assert_raise ArgumentError do
@tester.indian_gst_number(state_code: '01')
end
end

def test_indian_gst_number_with_state_code
ankitkhadria marked this conversation as resolved.
Show resolved Hide resolved
assert_match(/^(22)[A-Z]{3}[ABCFGHLJPTK][A-Z]\d{4}[A-Z][A-Z0-9][Z][A-Z0-9]$/i, @tester.indian_gst_number(state_code: '22'))
end

private

def czech_o_n_checksum(org_no)
Expand Down