File Coverage

blib/lib/Business/UPC.pm
Criterion Covered Total %
statement 85 99 85.8
branch 17 48 35.4
condition 10 14 71.4
subroutine 24 26 92.3
pod 0 19 0.0
total 136 206 66.0


line stmt bran cond sub pod time code
1             package Business::UPC;
2              
3 1     1   1843 use strict;
  1         2  
  1         52  
4 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         1  
  1         89  
5 1     1   1632 use subs qw(_check_digit _zeropad _expand_upc_e);
  1         1257  
  1         7  
6              
7             require Exporter;
8              
9             @ISA = qw(Exporter);
10             # Items to export into callers namespace by default. Note: do not export
11             # names by default without a very good reason. Use EXPORT_OK instead.
12             # Do not simply export all your public functions/methods/constants.
13             @EXPORT = qw(
14             );
15             $VERSION = '0.05';
16              
17             # Preloaded methods go here.
18              
19             sub new
20             {
21 8     8 0 8606 my $class = shift;
22 8         13 my $value = shift;
23              
24 8 50       28 return undef if length($value) > 12;
25              
26 8         18 my ($number_system, $mfr_id, $prod_id, $check_digit) = unpack("AA5A5A", _zeropad $value);
27              
28 8 50       40 return undef unless $number_system =~ m/^\d$/;
29 8 50       28 return undef unless $mfr_id =~ m/^\d{5}$/;
30 8 50       26 return undef unless $prod_id =~ m/^\d{5}$/;
31 8 50       23 return undef unless $check_digit =~ m/^[\dx]$/i;
32              
33 8 50 66     144 return undef if ($number_system == 0 && $mfr_id == 0 && $prod_id == 0);
      33        
34              
35 8         48 my $upc = bless {
36             number_system => $number_system,
37             mfr_id => $mfr_id,
38             prod_id => $prod_id,
39             check_digit => $check_digit,
40             }, $class;
41              
42 8         26 return $upc;
43             }
44              
45             # alternate constructor: for creating from a zero-supressed (type E) value
46             sub type_e
47             {
48 2     2 0 687 my $class = shift;
49 2         3 my $value = shift;
50              
51 2 50       9 return undef if length($value) > 8;
52              
53 2         9 my $expanded = _expand_upc_e $value;
54              
55 2 50       11 return new Business::UPC($expanded) if $expanded;
56 0         0 return undef;
57             }
58              
59             sub number_system
60             {
61 22     22 0 932 my $attrname = 'number_system';
62 22         28 my $self = shift;
63 22 50       49 warn "UPC atribute '$attrname' is not settable." if (@_);
64 22         92 return $self->{$attrname};
65             }
66              
67             sub mfr_id
68             {
69 15     15 0 807 my $attrname = 'mfr_id';
70 15         19 my $self = shift;
71 15 50       33 warn "UPC atribute '$attrname' is not settable." if (@_);
72 15         57 return $self->{$attrname};
73             }
74              
75             sub prod_id
76             {
77 18     18 0 933 my $attrname = 'prod_id';
78 18         22 my $self = shift;
79 18 50       37 warn "UPC atribute '$attrname' is not settable." if (@_);
80 18         73 return $self->{$attrname};
81             }
82              
83             sub check_digit
84             {
85 16     16 0 1640 my $attrname = 'check_digit';
86 16         23 my $self = shift;
87 16 50       33 warn "UPC atribute '$attrname' is not settable." if (@_);
88 16         60 return $self->{$attrname};
89             }
90              
91             sub as_upc_a
92             {
93 2     2 0 5 my $self = shift;
94 2         5 return $self->number_system . $self->mfr_id
95             . $self->prod_id . $self->check_digit;
96             }
97              
98             sub as_upc_a_nocheck
99             {
100 0     0 0 0 my $self = shift;
101 0         0 return $self->number_system . $self->mfr_id . $self->prod_id;
102             }
103              
104             sub as_upc
105             {
106 2     2 0 937 my $self = shift;
107 2         7 return $self->as_upc_a
108             }
109              
110             sub as_upca_nocheckdigit
111             {
112 10     10 0 15 my $self = shift;
113 10         21 return $self->number_system . $self->mfr_id . $self->prod_id;
114             }
115              
116             sub number_system_description
117             {
118 1     1 0 249 my $self = shift;
119 1         5 return $Business::UPC::NumberSystems{$self->number_system};
120             }
121              
122             sub coupon_value_code
123             {
124 1     1 0 2 my $self = shift;
125 1 50       3 return undef unless $self->is_coupon;
126 1         3 return substr($self->prod_id, -2);
127             }
128              
129             sub coupon_value
130             {
131 1     1 0 247 my $self = shift;
132 1 50       4 return undef unless $self->is_coupon;
133 1         6 return $Business::UPC::CouponValues{$self->coupon_value_code};
134             }
135              
136             sub coupon_family_code
137             {
138 2     2 0 248 my $self = shift;
139 2 50       5 return undef unless $self->is_coupon;
140 2         7 return substr($self->prod_id, 0, 3);
141             }
142              
143             sub coupon_family_description
144             {
145 1     1 0 394 my $self = shift;
146 1         69 my $cfc = $self->coupon_family_code;
147 1   50     11 return $Business::UPC::CouponFamilies{$cfc} || 'Unknown';
148             }
149              
150             sub is_valid
151             {
152 7     7 0 55 my $self = shift;
153 7         19 return (_check_digit($self->as_upca_nocheckdigit) eq $self->check_digit);
154             }
155              
156             sub is_coupon
157             {
158 6     6 0 839 my $self = shift;
159 6         15 return ($self->number_system eq '5');
160             }
161              
162             sub fix_check_digit
163             {
164 3     3 0 549 my $self = shift;
165 3         9 $self->{check_digit} = _check_digit($self->as_upca_nocheckdigit);
166 3         8 $self;
167             }
168              
169             sub as_upc_e
170             {
171 0     0 0 0 my $self = shift;
172              
173 0         0 my $upca = $self->as_upc_a;
174              
175 0 0       0 return $upca if ($upca =~ s/^0(\d{2})([012])0000(\d{3})(\d)$/0${1}${3}${2}${4}/);
176 0 0       0 return $upca if ($upca =~ s/^0(\d{2}[3-9])00000(\d{2})(\d)$/0${1}${2}3${3}/);
177 0 0       0 return $upca if ($upca =~ s/^0(\d{3}[1-9])00000(\d)(\d)$/0${1}${2}4${3}/);
178 0 0       0 return $upca if ($upca =~ s/^0(\d{4}[1-9])0000([5-9])(\d)$/0${1}${2}${3}/);
179 0         0 return undef;
180             }
181              
182             # private functions: don't use these!
183              
184             sub _check_digit
185             {
186 10     10   14 my $num = shift;
187              
188 10         62 my @digits = split(//, $num);
189              
190             # To avoid warning when summing below.
191 10         21 push @digits, 0;
192              
193 10         14 my $sum = 0;
194              
195 10         18 foreach my $i (0, 2, 4, 6, 8, 10)
196             {
197 60   100     174 $sum += 3 * ($digits[$i] || 0);
198 60   100     166 $sum += $digits[$i+1] || 0;
199             }
200              
201 10         50 return (10 - ($sum % 10)) % 10;
202             }
203              
204             sub _zeropad
205             {
206 10     10   15 my $num = shift;
207 10   100     167 my $length = shift || 12;
208 10         80 return sprintf("%0${length}s", $num);
209             }
210              
211             sub _expand_upc_e
212             {
213 2     2   4 my $upc_e = _zeropad shift, 8;
214              
215 2 50       6 return undef if (length($upc_e) > 8);
216              
217 2 50       50 return $upc_e if ($upc_e =~ s/^0(\d{2})(\d{3})([012])([\dx])$/0${1}${3}0000${2}${4}/i);
218 0 0         return $upc_e if ($upc_e =~ s/^0(\d{3})(\d{2})3([\dx])$/0${1}00000${2}${3}/i);
219 0 0         return $upc_e if ($upc_e =~ s/^0(\d{4})(\d)4([\dx])$/0${1}00000${2}${3}/i);
220 0 0         return $upc_e if ($upc_e =~ s/^0(.....)([5-9])([\dx])$/0${1}0000${2}${3}/i);
221 0           return undef;
222             }
223              
224             BEGIN
225             {
226 1     1   7252 %Business::UPC::NumberSystems = (
227             '0' => 'Regular Item',
228             '1' => 'Reserved',
229             '2' => 'Random-Weight Item',
230             '3' => 'National Drug/Health-Related Item',
231             '4' => 'For Private Use',
232             '5' => 'Coupon',
233             '6' => 'Regular Item',
234             '7' => 'Regular Item',
235             '8' => 'Reserved',
236             '9' => 'Reserved',
237             );
238 1         27 %Business::UPC::CouponFamilies = (
239             '000' => 'Anything from Same Manufacturer',
240             '001' => 'Reserved',
241             '002' => 'Reserved',
242             '003' => 'Reserved',
243             '004' => 'Reserved',
244             '005' => 'Reserved',
245             '006' => 'Reserved',
246             '007' => 'Reserved',
247             '008' => 'Reserved',
248             '009' => 'Reserved',
249             '990' => 'Reserved',
250             '991' => 'Reserved',
251             '992' => 'Reserved',
252             '993' => 'Reserved',
253             '994' => 'Reserved',
254             '995' => 'Reserved',
255             '996' => 'Reserved',
256             '997' => 'Reserved',
257             '998' => 'Reserved',
258             '999' => 'Reserved',
259             );
260 1         120 %Business::UPC::CouponValues = (
261             '00' => 'Checker Intervention',
262             '01' => 'Free Merchandise',
263             '02' => 'Buy 4 or more, get 1 free (same product)',
264             '03' => '$1.10',
265             '04' => '$1.35',
266             '05' => 'Reserved for Future Use',
267             '06' => '$1.60',
268             '07' => 'Reserved for Future Use',
269             '08' => 'Reserved for Future Use',
270             '09' => 'Reserved for Future Use',
271             '10' => '$0.10',
272             '11' => '$1.85',
273             '12' => '$0.12',
274             '13' => 'Reserved for Future Use',
275             '14' => 'Buy 1, get 1 free (same product)',
276             '15' => '$0.15',
277             '16' => 'Buy 2, get 1 free (same product)',
278             '17' => '$2.10',
279             '18' => '$2.60',
280             '19' => 'Buy 3, get 1 free (same product)',
281             '20' => '$0.20',
282             '21' => 'Buy 2 or more, get $0.35 off',
283             '22' => 'Buy 2 or more, get $0.40 off',
284             '23' => 'Buy 2 or more, get $0.45 off',
285             '24' => 'Buy 2, get $0.50 off',
286             '25' => '$0.25',
287             '26' => '$2.85',
288             '27' => 'Reserved for Future Use',
289             '28' => 'Buy 2, get $0.55 off',
290             '29' => '$0.29',
291             '30' => '$0.30',
292             '31' => 'Buy 2 or more, get $0.60 off',
293             '32' => 'Buy 2 or more, get $0.75 off',
294             '33' => 'Buy 2, get $1.00 off',
295             '34' => 'Buy 2 or more, get $1.25 off',
296             '35' => '$0.35',
297             '36' => 'Buy 2 or more, get $1.50 off',
298             '37' => 'Buy 3 or more, get $0.25 off',
299             '38' => 'Buy 3 or more, get $0.30 off',
300             '39' => '$0.39',
301             '40' => '$0.40',
302             '41' => 'Buy 3 or more, get $0.50 off',
303             '42' => 'Buy 3 or more, get $1.00 off',
304             '43' => 'Buy 2 or more, get $1.10 off',
305             '44' => 'Buy 2 or more, get $1.35 off',
306             '45' => '$0.45',
307             '46' => 'Buy 2 or more, get $1.60 off',
308             '47' => 'Buy 2 or more, get $1.75 off',
309             '48' => 'Buy 2 or more, get $1.85 off',
310             '49' => '$0.49',
311             '50' => '$0.50',
312             '51' => 'Buy 2 or more, get $2.00 off',
313             '52' => 'Buy 3 or more, get $0.55 off',
314             '53' => 'Buy 2 or more, get $0.10 off',
315             '54' => 'Buy 2 or more, get $0.15 off',
316             '55' => '$0.55',
317             '56' => 'Buy 2 or more, get $0.20 off',
318             '57' => 'Buy 2, get $0.25 off',
319             '58' => 'Buy 2, get $0.30 off',
320             '59' => '$0.59',
321             '60' => '$0.60',
322             '61' => '$10.00',
323             '62' => '$9.50',
324             '63' => '$9.00',
325             '64' => '$8.50',
326             '65' => '$0.65',
327             '66' => '$8.00',
328             '67' => '$7.50',
329             '68' => '$7.00',
330             '69' => '$0.69',
331             '70' => '$0.70',
332             '71' => '$6.50',
333             '72' => '$6.00',
334             '73' => '$5.50',
335             '74' => '$5.00',
336             '75' => '$0.75',
337             '76' => '$1.00',
338             '77' => '$1.25',
339             '78' => '$1.50',
340             '79' => '$0.79',
341             '80' => '$0.80',
342             '81' => '$1.75',
343             '82' => '$2.00',
344             '83' => '$2.25',
345             '84' => '$2.50',
346             '85' => '$0.85',
347             '86' => '$2.75',
348             '87' => '$3.00',
349             '88' => '$3.25',
350             '89' => '$0.89',
351             '90' => '$0.90',
352             '91' => '$3.50',
353             '92' => '$3.75',
354             '93' => '$4.00',
355             '94' => '$4.25',
356             '95' => '$0.95',
357             '96' => '$4.50',
358             '97' => '$4.75',
359             '98' => 'Buy 2 or more, get $0.65 off',
360             '99' => '$0.99',
361             );
362             }
363              
364             # Autoload methods go after =cut, and are processed by the autosplit program.
365              
366             1;
367             __END__