71513: Card tokenization [v3 test tweak]
[freeside.git] / FS / FS / state.pm
1 package FS::state;
2
3 use strict;
4 use base qw( FS::Record );
5 use FS::Record qw( qsearch qsearchs );
6 use Locale::SubCountry;
7
8 =head1 NAME
9
10 FS::state - Object methods for state/province records
11
12 =head1 SYNOPSIS
13
14   use FS::state;
15
16   $record = new FS::state \%hash;
17   $record = new FS::state { 'column' => 'value' };
18
19   $error = $record->insert;
20
21   $error = $new_record->replace($old_record);
22
23   $error = $record->delete;
24
25   $error = $record->check;
26
27 =head1 DESCRIPTION
28
29 An FS::state object represents a state, province, or other top-level 
30 subdivision of a sovereign nation.  FS::state inherits from FS::Record.  
31 The following fields are currently supported:
32
33 =over 4
34
35 =item statenum
36
37 primary key
38
39 =item country
40
41 two-letter country code
42
43 =item state
44
45 state code/abbreviation/name (as used in cust_location.state)
46
47 =item fips
48
49 FIPS 10-4 code (not including country code)
50
51 =back
52
53 =head1 METHODS
54
55 =cut
56
57 sub table { 'state'; }
58
59 # no external API; this table maintains itself
60
61 sub check {
62   my $self = shift;
63
64   my $error = 
65     $self->ut_numbern('statenum')
66     || $self->ut_alpha('country')
67     || $self->ut_alpha('state')
68     || $self->ut_alpha('fips')
69   ;
70   return $error if $error;
71
72   $self->SUPER::check;
73 }
74
75 =back
76
77 =cut
78
79 sub _upgrade_data {
80   warn "Updating state and country codes...\n";
81   my %existing;
82   foreach my $state (qsearch('state')) {
83     $existing{$state->country} ||= {};
84     $existing{$state->country}{$state->state} = $state;
85   }
86   my $world = Locale::SubCountry::World->new;
87   foreach my $country_code ($world->all_codes) {
88     my $country = Locale::SubCountry->new($country_code);
89     next unless $country->has_sub_countries;
90     $existing{$country} ||= {};
91     foreach my $state_code ($country->all_codes) {
92       my $fips = $country->FIPS10_4_code($state_code);
93       # we really only need U.S. state codes at this point, so if there's
94       # no FIPS code, ignore it.
95       next if !$fips or $fips eq 'unknown' or $fips =~ /\W/;
96       my $this_state = $existing{$country_code}{$state_code};
97       if ($this_state) {
98         if ($this_state->fips ne $fips) { # this should never happen...
99           $this_state->set(fips => $fips);
100           my $error = $this_state->replace;
101           die "error updating $country_code/$state_code:\n$error\n" if $error;
102         }
103         delete $existing{$country_code}{$state_code};
104       } else {
105         $this_state = FS::state->new({
106           country => $country_code,
107           state   => $state_code,
108           fips    => $fips,
109         });
110         my $error = $this_state->insert;
111         die "error inserting $country_code/$state_code:\n$error\n" if $error;
112       }
113     }
114     # clean up states that no longer exist (does this ever happen?)
115     foreach my $state (values %{ $existing{$country_code} }) {
116       my $error = $state->delete;
117       die "error removing expired state ".$state->country.'/'.$state->state.
118           "\n$error\n" if $error;
119     }
120   } # foreach $country_code
121   '';
122 }
123
124 =head1 BUGS
125
126 =head1 SEE ALSO
127
128 L<FS::Record>
129
130 =cut
131
132 1;
133