upc_tools 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +42 -0
- data/.rspec +2 -0
- data/.travis.yml +12 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +37 -0
- data/Guardfile +8 -0
- data/LICENSE +21 -0
- data/README.md +42 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/upc_tools/version.rb +3 -0
- data/lib/upc_tools.rb +289 -0
- data/upc_tools.gemspec +25 -0
- metadata +117 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: ed3fa91c8d56d01afc5b7c44b768a2fcec6011de
|
|
4
|
+
data.tar.gz: 331515539dd02cab7be75e08befb01c62b0cb607
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a6e9c7e542fdfd7aa4f12e16fa1c1e98e958e3fc7c7643706eb3c73f8868223aae0668cc1d95d3323afad1613b935e148e7b622cd6fa8fbc1a4bafbf9a29df6b
|
|
7
|
+
data.tar.gz: 2d46596725e21caf305d2d9fc91f661329ddf3a373810594d8af4ddce0290006d8c9c104169a230d06a214080a1de635c1c4846485b1c898241b11c76ea414a3
|
data/.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
*.gem
|
|
2
|
+
*.rbc
|
|
3
|
+
/.config
|
|
4
|
+
/coverage/
|
|
5
|
+
/spec/reports/
|
|
6
|
+
/test/tmp/
|
|
7
|
+
/test/version_tmp/
|
|
8
|
+
/tmp/
|
|
9
|
+
|
|
10
|
+
## Documentation cache and generated files:
|
|
11
|
+
/.yardoc/
|
|
12
|
+
/_yardoc/
|
|
13
|
+
/doc/
|
|
14
|
+
/rdoc/
|
|
15
|
+
|
|
16
|
+
## Environment normalisation:
|
|
17
|
+
/.bundle/
|
|
18
|
+
/lib/bundler/man/
|
|
19
|
+
|
|
20
|
+
# for a library or gem, you might want to ignore these files since the code is
|
|
21
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
22
|
+
Gemfile.lock
|
|
23
|
+
.ruby-version
|
|
24
|
+
.ruby-gemset
|
|
25
|
+
|
|
26
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
27
|
+
.rvmrc
|
|
28
|
+
|
|
29
|
+
# Mac OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
.AppleDouble
|
|
32
|
+
.LSOverride
|
|
33
|
+
Icon
|
|
34
|
+
._*
|
|
35
|
+
.Spotlight-V100
|
|
36
|
+
.Trashes
|
|
37
|
+
|
|
38
|
+
#sublime text
|
|
39
|
+
*.sublime-workspace
|
|
40
|
+
|
|
41
|
+
#RubyMine
|
|
42
|
+
.idea
|
data/.rspec
ADDED
data/.travis.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
language: ruby
|
|
2
|
+
cache: bundler
|
|
3
|
+
rvm:
|
|
4
|
+
- "1.9.3"
|
|
5
|
+
- "2.1.9"
|
|
6
|
+
- "2.2.4"
|
|
7
|
+
- "2.3.1"
|
|
8
|
+
- "jruby-9.0.5.0"
|
|
9
|
+
before_install:
|
|
10
|
+
- gem install bundler
|
|
11
|
+
# uncomment this line if your project needs to run something other than `rake`:
|
|
12
|
+
# script: bundle exec rspec spec
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Contributor Code of Conduct
|
|
2
|
+
|
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
|
7
|
+
|
|
8
|
+
We are committed to making participation in this project a harassment-free
|
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
|
12
|
+
|
|
13
|
+
Examples of unacceptable behavior by participants include:
|
|
14
|
+
|
|
15
|
+
* The use of sexualized language or imagery
|
|
16
|
+
* Personal attacks
|
|
17
|
+
* Trolling or insulting/derogatory comments
|
|
18
|
+
* Public or private harassment
|
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
|
20
|
+
addresses, without explicit permission
|
|
21
|
+
* Other unethical or unprofessional conduct
|
|
22
|
+
|
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
|
27
|
+
threatening, offensive, or harmful.
|
|
28
|
+
|
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
|
32
|
+
Conduct may be permanently removed from the project team.
|
|
33
|
+
|
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
|
35
|
+
when an individual is representing the project or its community.
|
|
36
|
+
|
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
38
|
+
reported by contacting a project maintainer at jhart@onyxraven.com. All
|
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
|
42
|
+
incident.
|
|
43
|
+
|
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
45
|
+
version 1.3.0, available at
|
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
|
47
|
+
|
|
48
|
+
[homepage]: http://contributor-covenant.org
|
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
upc_tools (0.2.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
diff-lcs (1.2.5)
|
|
10
|
+
rake (10.5.0)
|
|
11
|
+
rspec (3.4.0)
|
|
12
|
+
rspec-core (~> 3.4.0)
|
|
13
|
+
rspec-expectations (~> 3.4.0)
|
|
14
|
+
rspec-mocks (~> 3.4.0)
|
|
15
|
+
rspec-core (3.4.4)
|
|
16
|
+
rspec-support (~> 3.4.0)
|
|
17
|
+
rspec-expectations (3.4.0)
|
|
18
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
19
|
+
rspec-support (~> 3.4.0)
|
|
20
|
+
rspec-mocks (3.4.1)
|
|
21
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
22
|
+
rspec-support (~> 3.4.0)
|
|
23
|
+
rspec-support (3.4.1)
|
|
24
|
+
yard (0.8.7.6)
|
|
25
|
+
|
|
26
|
+
PLATFORMS
|
|
27
|
+
ruby
|
|
28
|
+
|
|
29
|
+
DEPENDENCIES
|
|
30
|
+
bundler (~> 1.12)
|
|
31
|
+
rake (~> 10.0)
|
|
32
|
+
rspec (~> 3.0)
|
|
33
|
+
upc_tools!
|
|
34
|
+
yard
|
|
35
|
+
|
|
36
|
+
BUNDLED WITH
|
|
37
|
+
1.12.4
|
data/Guardfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Ibotta, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# UpcTools
|
|
2
|
+
|
|
3
|
+
[](https://travis-ci.org/Ibotta/ruby_upc_tools)
|
|
4
|
+
|
|
5
|
+
Utilities to generate, convert and validate UPCs.
|
|
6
|
+
|
|
7
|
+
Includes handling of type-2 (random weight) UPCs, and conversion between UPC-A and UPC-E formats.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add this line to your application's Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'upc_tools'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
And then execute:
|
|
18
|
+
|
|
19
|
+
$ bundle
|
|
20
|
+
|
|
21
|
+
Or install it yourself as:
|
|
22
|
+
|
|
23
|
+
$ gem install upc_tools
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
See the YARD docs
|
|
28
|
+
|
|
29
|
+
## Development
|
|
30
|
+
|
|
31
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
32
|
+
|
|
33
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
34
|
+
|
|
35
|
+
## Contributing
|
|
36
|
+
|
|
37
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Ibotta/ruby_upc_tools. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "upc_tools"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/upc_tools.rb
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
require "upc_tools/version"
|
|
2
|
+
|
|
3
|
+
#UPC Tools
|
|
4
|
+
module UpcTools
|
|
5
|
+
|
|
6
|
+
#Generate one UPC check digit
|
|
7
|
+
# @see http://www.gs1.org/barcodes/support/check_digit_calculator/
|
|
8
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.1
|
|
9
|
+
# @param num [String] base number to generate check digit for
|
|
10
|
+
# @return [Integer] check digit (always between 0-9)
|
|
11
|
+
def self.generate_upc_check_digit(num)
|
|
12
|
+
even = odd = 0
|
|
13
|
+
#pad everything to max (13)
|
|
14
|
+
num.to_s.rjust(13, '0').split('').each_with_index do |item, index|
|
|
15
|
+
item = item.to_i
|
|
16
|
+
even += item if index.odd? #opposite because of 0 indexing
|
|
17
|
+
odd += item if index.even?
|
|
18
|
+
end
|
|
19
|
+
chk_total = (odd * 3) + even
|
|
20
|
+
(10 - (chk_total % 10)) % 10
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#Validate UPC check digit
|
|
24
|
+
# @see http://www.gs1.org/barcodes/support/check_digit_calculator/
|
|
25
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.1
|
|
26
|
+
# @param upc [String] UPC with check digit to check
|
|
27
|
+
# @return [Boolean] truth of valid check digit
|
|
28
|
+
def self.valid_upc_check_digit?(upc)
|
|
29
|
+
full_upc = upc.to_s.rjust(14, '0') #extend to full 14 digits first
|
|
30
|
+
gen_check = generate_upc_check_digit(full_upc[0, full_upc.size - 1])
|
|
31
|
+
full_upc[-1] == gen_check.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#Add check digit and properly pad
|
|
35
|
+
# @param num [String] base number to extend
|
|
36
|
+
# @param extended_length [Integer] resulting target to pad number to
|
|
37
|
+
# @return [String] resulting UPC with check digit
|
|
38
|
+
def self.extend_upc_with_check_digit(num, extended_length=12)
|
|
39
|
+
upc = num.to_s << generate_upc_check_digit(num).to_s
|
|
40
|
+
upc.rjust(extended_length, '0') #extend to at least the given length
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#Type 2 format 2 | ID3456 | X | 000P | C (Check X is optional, price can overflow in some cases)
|
|
44
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.2
|
|
45
|
+
|
|
46
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 1 Weight Factor 2-
|
|
47
|
+
WEIGHT_FACTOR_2 = [0,2,4,6,8,9,1,3,5,7]
|
|
48
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 2 Weight Factor 3
|
|
49
|
+
WEIGHT_FACTOR_3 = [0,3,6,9,2,5,8,1,4,7]
|
|
50
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 2 Weight Factor 5+
|
|
51
|
+
WEIGHT_FACTOR_5plus = [0,5,1,6,2,7,3,8,4,9]
|
|
52
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 4 Weight Factor 5-
|
|
53
|
+
WEIGHT_FACTOR_5mins = [0,5,9,4,8,3,7,2,6,1]
|
|
54
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf Figure 3.A.1.2 – 4 inverse Weight Factor 5-
|
|
55
|
+
WEIGHT_FACTOR_5mins_opposite = [0,9,7,5,3,1,8,6,4,2]
|
|
56
|
+
|
|
57
|
+
#Trim UPC to proper length for type2 checking
|
|
58
|
+
# @param upc [String] UPC
|
|
59
|
+
# @return [String] trimmed string
|
|
60
|
+
def self.trim_type2_upc(upc)
|
|
61
|
+
#if length is > 12, strip leading 0
|
|
62
|
+
upc = upc.to_s
|
|
63
|
+
upc = upc.gsub(/^0+/, '') if upc.size > 12
|
|
64
|
+
upc
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
#Is this a type2 UPC?
|
|
68
|
+
# @param upc [String] upc to check
|
|
69
|
+
# @return [Boolean] is UPC a type-2?
|
|
70
|
+
def self.type2_upc?(upc)
|
|
71
|
+
upc = trim_type2_upc(upc)
|
|
72
|
+
return false if upc.size > 13 || upc.size < 12 #length is wrong
|
|
73
|
+
upc.start_with?('2')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#Validate UPC and Price check digit for a type 2 upc. Does NOT also check the UPC itself
|
|
77
|
+
# @param upc [String] Type 2 UPC to check with check digit(s)
|
|
78
|
+
# @return [Boolean] matching check digit(s)?
|
|
79
|
+
def self.valid_type2_upc_check_digit?(upc)
|
|
80
|
+
upc = trim_type2_upc(upc)
|
|
81
|
+
return false unless type2_upc?(upc)
|
|
82
|
+
plu, price, chk, price_chk = split_type2_upc(upc)
|
|
83
|
+
price_chk_calc = if price.size == 4
|
|
84
|
+
generate_type2_upc_price_check_digit_4(price)
|
|
85
|
+
elsif price.size == 5
|
|
86
|
+
generate_type2_upc_price_check_digit_5(price)
|
|
87
|
+
else
|
|
88
|
+
raise ArgumentError, "Price is an unknown size"
|
|
89
|
+
end
|
|
90
|
+
price_chk == price_chk_calc.to_s
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
#Convenience method validates that upc is type2 with valid check digit
|
|
94
|
+
# @param upc [String] Type 2 UPC to check
|
|
95
|
+
# @return [Boolean] is UPC a type-2 with valid check digit(s)?
|
|
96
|
+
def self.valid_type2_upc?(upc)
|
|
97
|
+
type2_upc?(upc) && valid_type2_upc_check_digit?(upc)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
#Convert item ID (PLU) and price to type2 UPC string
|
|
101
|
+
# @param plu [String] item identifier (not including leading 2)
|
|
102
|
+
# @param price [String] price as integer (in cents). Will be 0 padded if necessary
|
|
103
|
+
# @param opts [Hash] options hash
|
|
104
|
+
# @option opts [Integer] :price_length (4) price length (4 or 5). Will override given price length.
|
|
105
|
+
# @option opts [Integer] :upc_length (12) price length (12 or 13)
|
|
106
|
+
def self.item_price_to_type2(plu, price, opts={})
|
|
107
|
+
upc_length = opts[:upc_length] || 12
|
|
108
|
+
price_length = opts[:price_length] || 4
|
|
109
|
+
raise ArgumentError, "opts[:upc_length] must be 12 or 13" if upc_length != 12 && upc_length != 13
|
|
110
|
+
|
|
111
|
+
if upc_length == 13
|
|
112
|
+
raise ArgumentError, "Price length cannot be 4 if UPC length is 13" if opts[:price_length] == 4
|
|
113
|
+
price_length = 5
|
|
114
|
+
raise ArgumentError, "opts[:price_length] must be 4 or 5" if price_length != 4 && price_length != 5
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
plu = plu.to_s
|
|
118
|
+
raise ArgumentError, "plu must be 5 digits long" if plu.size != 5
|
|
119
|
+
|
|
120
|
+
price = price.to_s.rjust(price_length, '0')
|
|
121
|
+
raise ArgumentError, "price must be less than or equal to 5 digits long" if price.size > 5
|
|
122
|
+
|
|
123
|
+
price_chk_calc = if price.size == 4
|
|
124
|
+
generate_type2_upc_price_check_digit_4(price)
|
|
125
|
+
elsif price.size == 5 && upc_length == 13
|
|
126
|
+
generate_type2_upc_price_check_digit_5(price)
|
|
127
|
+
else
|
|
128
|
+
''
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
upc = "2#{plu}#{price_chk_calc}#{price}"
|
|
132
|
+
upc << generate_upc_check_digit(upc).to_s
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
#Split a Type2 UPC into its component parts
|
|
136
|
+
# @see http://www.meattrack.com/Background/UPC.php
|
|
137
|
+
# @see http://www.iddba.org/upccharacter2.aspx
|
|
138
|
+
# @param upc [String] UPC to split up
|
|
139
|
+
# @param skip_price_check [Boolean] Ignore price check digit (include digit in price field)
|
|
140
|
+
# @return [Array<String>] elements of array: ItemID/PLU (not including leading 2), Price, UPC Check Digit, Price Check Digit
|
|
141
|
+
def self.split_type2_upc(upc, skip_price_check=false)
|
|
142
|
+
upc = trim_type2_upc(upc)
|
|
143
|
+
plu = upc[1,5]
|
|
144
|
+
chk = upc[-1]
|
|
145
|
+
if upc.size == 13 || skip_price_check
|
|
146
|
+
price = upc[-6, 5]
|
|
147
|
+
price_chk = upc[-7] unless skip_price_check
|
|
148
|
+
else
|
|
149
|
+
price = upc[-5,4]
|
|
150
|
+
price_chk = upc[-6] unless skip_price_check
|
|
151
|
+
end
|
|
152
|
+
[plu, price, chk, price_chk]
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
#Get the float price from a Type2 UPC
|
|
156
|
+
# @param upc [String] UPC to get price from
|
|
157
|
+
# @param skip_price_check [Boolean] Ignore price check digit (include digit in price field)
|
|
158
|
+
# @return [Float] calculated price (rounded to nearest cent)
|
|
159
|
+
def self.get_price_from_type2_upc(upc, skip_price_check=false)
|
|
160
|
+
_, price = UpcTools.split_type2_upc(upc, skip_price_check)
|
|
161
|
+
(price.to_f / 100.0).round(2)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Split a type2 UPC into the UPC itself and the price contained therein.
|
|
165
|
+
# @param number [String] upc to check
|
|
166
|
+
# @return [Array<String,Float>] elements of array: type2 UPC string, Price. The UPC ends up with a 0 price if it is type2. The Price will be nil if the number passed in is not type2.
|
|
167
|
+
def self.type2_number_price(number)
|
|
168
|
+
if type2_upc?(number) && valid_type2_upc_check_digit?(number)
|
|
169
|
+
#looks like a type-2 and the price chk is valid
|
|
170
|
+
item_code, price = split_type2_upc(number)
|
|
171
|
+
price = (price.to_f / 100.0).round(2)
|
|
172
|
+
|
|
173
|
+
upc = item_price_to_type2(item_code, 0).rjust(14, '0')
|
|
174
|
+
[upc, price]
|
|
175
|
+
else
|
|
176
|
+
[number, nil]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
#Generate price check digit for type 2 upc price of 4 digits
|
|
181
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.3
|
|
182
|
+
# @see http://barcodes.gs1us.org/GS1%20US%20BarCodes%20and%20eCom%20-%20The%20Global%20Language%20of%20Business.htm
|
|
183
|
+
# @param price [String] price as integer (in cents)
|
|
184
|
+
# @return [Integer] calculated price check digit
|
|
185
|
+
def self.generate_type2_upc_price_check_digit_4(price)
|
|
186
|
+
#digit weighting factors 2-, 2-, 3, 5-
|
|
187
|
+
digits = price.to_s.rjust(4, '0').split('').map(&:to_i)
|
|
188
|
+
sum = 0
|
|
189
|
+
sum += WEIGHT_FACTOR_2[digits[0]]
|
|
190
|
+
sum += WEIGHT_FACTOR_2[digits[1]]
|
|
191
|
+
sum += WEIGHT_FACTOR_3[digits[2]]
|
|
192
|
+
sum += WEIGHT_FACTOR_5mins[digits[3]]
|
|
193
|
+
(sum * 3) % 10
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
#Generate price check digit for type 2 upc price of 5 digits
|
|
197
|
+
# @see http://www.gs1tw.org/twct/web/BarCode/GS1_Section3V6-0.pdf section 3.A.1.4
|
|
198
|
+
# @param price [String] price as integer (in cents)
|
|
199
|
+
# @return [Integer] calculated price check digit
|
|
200
|
+
def self.generate_type2_upc_price_check_digit_5(price)
|
|
201
|
+
#digit weighting factors 5+, 2-, 5-, 5+, 2- => opposite of 5-
|
|
202
|
+
digits = price.to_s.rjust(5, '0').split('').map(&:to_i)
|
|
203
|
+
sum = 0
|
|
204
|
+
sum += WEIGHT_FACTOR_5plus[digits[0]]
|
|
205
|
+
sum += WEIGHT_FACTOR_2[digits[1]]
|
|
206
|
+
sum += WEIGHT_FACTOR_5mins[digits[2]]
|
|
207
|
+
sum += WEIGHT_FACTOR_5plus[digits[3]]
|
|
208
|
+
sum += WEIGHT_FACTOR_2[digits[4]]
|
|
209
|
+
sum = (10 - (sum % 10)) % 10
|
|
210
|
+
WEIGHT_FACTOR_5mins_opposite[sum]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# UPC-E = ABCDEFGH
|
|
214
|
+
# seen as 0 123456 7
|
|
215
|
+
# maps as A BCDEFG H
|
|
216
|
+
# ABC and H never change
|
|
217
|
+
|
|
218
|
+
#position conversions
|
|
219
|
+
# G = 0 XXNNN0 A + BC000-00DEF + H
|
|
220
|
+
# G = 1 XXNNN1 A + BC100-00DEF + H
|
|
221
|
+
# G = 2 XXNNN2 A + BC200-00DEF + H
|
|
222
|
+
# G = 3 XXXNN3 A + BCD00-000EF + H
|
|
223
|
+
# G = 4 XXXXN4 A + BCDE0-0000F + H
|
|
224
|
+
# G = 5 XXXXX5 A + BCDEF-00005 + H
|
|
225
|
+
# G = 6 XXXXX6 A + BCDEF-00006 + H
|
|
226
|
+
# G = 7 XXXXX7 A + BCDEF-00007 + H
|
|
227
|
+
# G = 8 XXXXX8 A + BCDEF-00008 + H
|
|
228
|
+
# G = 9 XXXXX9 A + BCDEF-00009 + H
|
|
229
|
+
|
|
230
|
+
#Convert short (8 digit) UPC-E to 12 digit UPC-A
|
|
231
|
+
# @see http://www.taltech.com/barcodesoftware/symbologies/upc
|
|
232
|
+
# @see http://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
|
|
233
|
+
# @param upc_e [String] 8 digit UPC-E to convert
|
|
234
|
+
# @return [String] 12 digit UPC-A
|
|
235
|
+
def self.convert_upce_to_upca(upc_e)
|
|
236
|
+
#todo should i zero pad upc_e?
|
|
237
|
+
#todo allow without check digit?
|
|
238
|
+
upc_e = upc_e.to_s
|
|
239
|
+
raise ArgumentError, "UPC-E must be 8 digits" unless upc_e.size == 8
|
|
240
|
+
|
|
241
|
+
map_id = upc_e[-2].to_i #G
|
|
242
|
+
chk = upc_e[-1] #H
|
|
243
|
+
prefix = upc_e[0,3] #ABC
|
|
244
|
+
prefix_next = upc_e[3,3] #DEF
|
|
245
|
+
|
|
246
|
+
if map_id >= 5
|
|
247
|
+
"#{prefix}#{prefix_next}0000#{map_id}#{chk}"
|
|
248
|
+
elsif map_id <= 2
|
|
249
|
+
"#{prefix}#{map_id}0000#{prefix_next}#{chk}"
|
|
250
|
+
elsif map_id == 3
|
|
251
|
+
"#{prefix}#{upc_e[3]}00000#{upc_e[4,2]}#{chk}"
|
|
252
|
+
elsif map_id == 4
|
|
253
|
+
"#{prefix}#{upc_e[3,2]}00000#{upc_e[5]}#{chk}"
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
#Convert (zero-suppress) 12 digit UPC-A to 8 digit UPC-E
|
|
258
|
+
# @see http://www.taltech.com/barcodesoftware/symbologies/upc
|
|
259
|
+
# @see http://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
|
|
260
|
+
# @see http://www.barcodeisland.com/upce.phtml#Conversion
|
|
261
|
+
# @param upc_a [String] 12 digit UPC-A to convert
|
|
262
|
+
# @return [String] 8 digit UPC-E
|
|
263
|
+
def self.convert_upca_to_upce(upc_a)
|
|
264
|
+
#todo should i zero pad upc_a?
|
|
265
|
+
#todo allow without check digit?
|
|
266
|
+
upc_a = upc_a.to_s
|
|
267
|
+
raise ArgumentError, "Must be 12 characters long" unless upc_a.size == 12
|
|
268
|
+
start = upc_a[0] #first char
|
|
269
|
+
raise ArgumentError, "Must be type 0 or 1" unless ["0", "1"].include?(start)
|
|
270
|
+
|
|
271
|
+
chk = upc_a[-1] #last char
|
|
272
|
+
mfr = upc_a[1...6] #next 5 characters
|
|
273
|
+
prod = upc_a[6...11] #last 4 characters w/o chk
|
|
274
|
+
|
|
275
|
+
upc_e = if ["000", "100", "200"].include?(mfr[-3,3])
|
|
276
|
+
"#{mfr[0,2]}#{prod[-3,3]}#{mfr[2]}"
|
|
277
|
+
elsif mfr[-2,2] == '00' && prod.to_i <= 99
|
|
278
|
+
"#{mfr[0,3]}#{prod[-2,2]}3"
|
|
279
|
+
elsif mfr[-1] == '0' && prod.to_i <= 9
|
|
280
|
+
"#{mfr[0,4]}#{prod[-1]}4"
|
|
281
|
+
elsif mfr[-1] != '0' && [5,6,7,8,9].include?(prod.to_i)
|
|
282
|
+
"#{mfr}#{prod[-1]}"
|
|
283
|
+
end
|
|
284
|
+
raise ArgumentError, "Must meet formatting requirements" unless upc_e
|
|
285
|
+
|
|
286
|
+
"#{start}#{upc_e}#{chk}"
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
end
|
data/upc_tools.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'upc_tools/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "upc_tools"
|
|
8
|
+
spec.version = UpcTools::VERSION
|
|
9
|
+
spec.authors = ["Justin Hart", "Ibotta, Inc."]
|
|
10
|
+
spec.email = ["jhart@ibotta.com"]
|
|
11
|
+
spec.summary = %q{UPC validation and creation utilities}
|
|
12
|
+
spec.description = %q{create, validate, convert UPCs}
|
|
13
|
+
spec.homepage = "https://github.com/Ibotta/ruby_upc_tools"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
+
spec.bindir = "exe"
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
24
|
+
spec.add_development_dependency "yard"
|
|
25
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: upc_tools
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Justin Hart
|
|
8
|
+
- Ibotta, Inc.
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: exe
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2016-05-27 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: bundler
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - "~>"
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '1.12'
|
|
21
|
+
type: :development
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - "~>"
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '1.12'
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: rake
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: '10.0'
|
|
35
|
+
type: :development
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '10.0'
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: rspec
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - "~>"
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '3.0'
|
|
49
|
+
type: :development
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - "~>"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '3.0'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: yard
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
description: create, validate, convert UPCs
|
|
71
|
+
email:
|
|
72
|
+
- jhart@ibotta.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- ".gitignore"
|
|
78
|
+
- ".rspec"
|
|
79
|
+
- ".travis.yml"
|
|
80
|
+
- CODE_OF_CONDUCT.md
|
|
81
|
+
- Gemfile
|
|
82
|
+
- Gemfile.lock
|
|
83
|
+
- Guardfile
|
|
84
|
+
- LICENSE
|
|
85
|
+
- README.md
|
|
86
|
+
- Rakefile
|
|
87
|
+
- bin/console
|
|
88
|
+
- bin/setup
|
|
89
|
+
- lib/upc_tools.rb
|
|
90
|
+
- lib/upc_tools/version.rb
|
|
91
|
+
- upc_tools.gemspec
|
|
92
|
+
homepage: https://github.com/Ibotta/ruby_upc_tools
|
|
93
|
+
licenses:
|
|
94
|
+
- MIT
|
|
95
|
+
metadata: {}
|
|
96
|
+
post_install_message:
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
requirements: []
|
|
111
|
+
rubyforge_project:
|
|
112
|
+
rubygems_version: 2.2.5
|
|
113
|
+
signing_key:
|
|
114
|
+
specification_version: 4
|
|
115
|
+
summary: UPC validation and creation utilities
|
|
116
|
+
test_files: []
|
|
117
|
+
has_rdoc:
|