epic-ts
[kivitendo-erp.git] / SL / Controller / PhoneNumber.pm
1 package SL::Controller::PhoneNumber;
2
3 use utf8;
4 use strict;
5 use parent qw(SL::Controller::Base);
6
7 use List::MoreUtils qw(any);
8 use List::Util qw(first);
9
10 use SL::JSON;
11 use SL::DBUtils;
12 use SL::Locale::String;
13 use SL::CTI;
14
15 use SL::DB::Contact;
16 use SL::DB::Customer;
17 use SL::DB::Vendor;
18
19 use Data::Dumper;
20
21 sub action_look_up {
22   my ($self) = @_;
23
24   my $number = $self->normalize_number($::form->{number} // '');
25
26   return $self->render(\to_json({}), { type => 'json', process => 0 }) if ($number eq '');
27
28   my $result = $self->find_contact_for_number($number)
29     //         $self->find_customer_vendor_for_number($number)
30     //         {};
31
32   $self->render(\to_json($result), { type => 'json', process => 0 });
33 }
34
35 sub find_contact_for_number {
36   my ($self, $number) = @_;
37
38   my @number_fields = qw(cp_phone1 cp_phone2 cp_mobile1 cp_mobile2 cp_fax);
39
40   my $contacts = SL::DB::Manager::Contact->get_all(
41     inject_results => 1,
42     where          => [ map({ (or => [ "!$_" => undef, "!$_"  => "" ],) } @number_fields) ],
43   );
44
45   my @hits;
46
47   foreach my $contact (@{ $contacts }) {
48     foreach my $field (@number_fields) {
49       next if $self->normalize_number($contact->$field) ne $number;
50
51       push @hits, $contact;
52       last;
53     }
54   }
55
56   return if !@hits;
57
58   my @cv_ids = grep { $_ } map { $_->cp_cv_id } @hits;
59
60   my %customers_vendors =
61     map { ($_->id => $_) } (
62       @{ SL::DB::Manager::Customer->get_all(where => [ id => \@cv_ids ], inject_results => 1) },
63       @{ SL::DB::Manager::Vendor  ->get_all(where => [ id => \@cv_ids ], inject_results => 1) },
64     );
65
66   my $chosen = first {
67        $_->cp_cv_id
68     &&  $customers_vendors{$_->cp_cv_id}
69     && !$customers_vendors{$_->cp_cv_id}->obsolete
70     && ($_->cp_name !~ m{ungültig}i)
71   } @hits;
72
73   $chosen //= $hits[0];
74
75   return {
76     full_name => join(' ', grep { $_ ne '' } map { $_ // '' } ($chosen->cp_title, $chosen->cp_givenname, $chosen->cp_name)),
77     id        => $chosen->cp_id,
78     type      => 'contact',
79     map({ my $method = "cp_$_"; ($_ => $chosen->$method // '') } qw(title givenname name phone1 phone2 mobile1 mobile2 fax)),
80   };
81 }
82
83 sub find_customer_vendor_for_number {
84   my ($self, $number) = @_;
85
86   my @number_fields = qw(phone fax);
87
88   my @customers_vendors = map {
89       my $class = "SL::DB::Manager::$_";
90       @{ $class->get_all(
91            inject_results => 1,
92            where          => [ map({ (or => [ "!$_" => undef, "!$_"  => "" ],) } @number_fields) ],
93          ) }
94     } qw(Customer Vendor);
95
96   my @hits;
97
98   foreach my $customer_vendor (@customers_vendors) {
99     foreach my $field (@number_fields) {
100       next if $self->normalize_number($customer_vendor->$field) ne $number;
101
102       push @hits, $customer_vendor;
103       last;
104     }
105   }
106
107   return if !@hits;
108
109   my $chosen = first { !$_->obsolete } @hits;
110   $chosen  //= $hits[0];
111
112   return {
113     full_name => $chosen->name  // '',
114     phone1    => $chosen->phone // '',
115     fax       => $chosen->fax   // '',
116     id        => $chosen->id,
117     type      => ref($chosen) eq 'SL::DB::Customer' ? 'customer' : 'vendor',
118     map({ ($_ => '') } qw(title givenname name phone2 mobile1 mobile2)),
119   };
120 }
121
122 sub normalize_number {
123   my ($self, $number) = @_;
124
125   return '' if ($number // '') eq '';
126
127   my $config       = $::lx_office_conf{cti} || {};
128   my $idp          = $config->{international_dialing_prefix} // '00';
129   my $country_code = $config->{our_country_code}             // '49';
130
131   $number          = SL::CTI->sanitize_number(number => $number);
132
133   return $number if $number =~ m{^$idp};
134
135   $number =~ s{^0+}{};
136
137   return $idp . $country_code . $number;
138 }
139
140 1;
141
142 __END__
143
144 =pod
145
146 =encoding utf8
147
148 =head1 NAME
149
150 SL::Controller::Contact - Looking up information on contacts/customers/vendors based on a phone number
151
152 =head1 FUNCTIONS
153
154 =over 4
155
156 =item C<action_look_up>
157
158 This action can be used by external systems such as PBXes in order to
159 match a calling number to a name. Requires one form parameter to be
160 passed, C<number>.
161
162 The number will then be normalized. This requires that the
163 international dialing prefix and the server's own country code be set
164 up in C<kivitendo.conf>, section C<[cti]>. They default to C<00> and
165 C<49> (Germany) respectively.
166
167 Next the function will look up a contact whose normalized numbers
168 equals the requested number. The fields C<phone1>, C<phone2>,
169 C<mobile1>, C<mobile2> and C<fax> are considered. Active contacts are
170 given preference over inactive ones (inactive meaning that they don't
171 belong to a customer/vendor anymore or that the customer/vendor itself
172 is flagged as obsolete).
173
174 If no contact is found, customers & vendors are searched. Their fields
175 C<phone> and C<fax> are considered. The first customer/vendor who
176 isn't flagged as being obsolete is chosen; if there's none, the first
177 obsolete one is.
178
179 The function always sends one JSON-encoded object. If there's no hit
180 for the number, an empty object is returned. Otherwise the following
181 fields are present:
182
183 =over 4
184
185 =item C<id> — the database ID of the corresponding record
186
187 =item C<type> — describes the type of record returned; can be either
188 C<contact>, C<customer> or C<vendor>
189
190 =item C<full_name> — for contacts this is the concatenation of the
191 title, given name and family name; for customers/vendors it's the
192 company name
193
194 =item C<title> — title (empty for customers/vendors)
195
196 =item C<givenname> — first/given name (empty for customers/vendors)
197
198 =item C<name> — last/family name (empty for customers/vendors)
199
200 =item C<phone1> — first phone number (for all object types)
201
202 =item C<phone2> — second phone number (empty for customers/vendors)
203
204 =item C<mobile1> — first mobile number (empty for customers/vendors)
205
206 =item C<mobile2> — second mobile number (empty for customers/vendors)
207
208 =item C<fax> — fax number (for all object types)
209
210 =back
211
212 Here's an example how querying the API via C<curl> might look:
213
214     curl --user user_name:password 'https://…/kivitendo/controller.pl?action=PhoneNumber/look_up&number=0049987655443321'
215
216 Note that the request must be authenticated via a valid Kivitendo
217 login. However, the user doesn't need any special permissions within
218 Kivitendo; any valid Kivitendo user will do.
219
220 =back
221
222 =head1 BUGS
223
224 Nothing here yet.
225
226 =head1 AUTHOR
227
228 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
229
230 =cut