fix UI: displaying "Add" on invoice event edits
[freeside.git] / FS / FS / ClientAPI / MyAccount.pm
1 package FS::ClientAPI::MyAccount;
2
3 use strict;
4 use vars qw($cache);
5 use Digest::MD5 qw(md5_hex);
6 use Date::Format;
7 use Cache::SharedMemoryCache; #store in db?
8 use FS::CGI qw(small_custview); #doh
9 use FS::Conf;
10 use FS::Record qw(qsearchs);
11 use FS::svc_acct;
12 use FS::svc_domain;
13 use FS::cust_main;
14 use FS::cust_bill;
15 use FS::cust_pkg;
16
17 use FS::ClientAPI; #hmm
18 FS::ClientAPI->register_handlers(
19   'MyAccount/login'            => \&login,
20   'MyAccount/customer_info'    => \&customer_info,
21   'MyAccount/edit_info'        => \&edit_info,
22   'MyAccount/invoice'          => \&invoice,
23   'MyAccount/cancel'           => \&cancel,
24   'MyAccount/list_pkgs'        => \&list_pkgs,
25   'MyAccount/order_pkg'        => \&order_pkg,
26   'MyAccount/cancel_pkg'       => \&cancel_pkg,
27 );
28
29 use vars qw( @cust_main_editable_fields );
30 @cust_main_editable_fields = qw(
31   first last company address1 address2 city
32     county state zip country daytime night fax
33   ship_first ship_last ship_company ship_address1 ship_address2 ship_city
34     ship_state ship_zip ship_country ship_daytime ship_night ship_fax
35 );
36
37 #store in db?
38 my $cache = new Cache::SharedMemoryCache();
39
40 #false laziness w/FS::ClientAPI::passwd::passwd (needs to handle encrypted pw)
41 sub login {
42   my $p = shift;
43
44   my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } )
45     or return { error => "Domain not found" };
46
47   my $svc_acct =
48     ( length($p->{'password'}) < 13
49       && qsearchs( 'svc_acct', { 'username'  => $p->{'username'},
50                                  'domsvc'    => $svc_domain->svcnum,
51                                  '_password' => $p->{'password'}     } )
52     )
53     || qsearchs( 'svc_acct', { 'username'  => $p->{'username'},
54                                'domsvc'    => $svc_domain->svcnum,
55                                '_password' => $p->{'password'}     } );
56
57   unless ( $svc_acct ) { return { error => 'Incorrect password.' } }
58
59   my $session = {
60     'svcnum' => $svc_acct->svcnum,
61   };
62
63   my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
64   if ( $cust_pkg ) {
65     my $cust_main = $cust_pkg->cust_main;
66     $session->{'custnum'} = $cust_main->custnum;
67   }
68
69   my $session_id;
70   do {
71     $session_id = md5_hex(md5_hex(time(). {}. rand(). $$))
72   } until ( ! defined $cache->get($session_id) ); #just in case
73
74   $cache->set( $session_id, $session, '1 hour' );
75
76   return { 'error'      => '',
77            'session_id' => $session_id,
78          };
79 }
80
81 sub customer_info {
82   my $p = shift;
83   my $session = $cache->get($p->{'session_id'})
84     or return { 'error' => "Can't resume session" }; #better error message
85
86   my %return;
87
88   my $custnum = $session->{'custnum'};
89
90   if ( $custnum ) { #customer record
91
92     my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
93       or return { 'error' => "unknown custnum $custnum" };
94
95     $return{balance} = $cust_main->balance;
96
97     my @open = map {
98                      {
99                        invnum => $_->invnum,
100                        date   => time2str("%b %o, %Y", $_->_date),
101                        owed   => $_->owed,
102                      };
103                    } $cust_main->open_cust_bill;
104     $return{open_invoices} = \@open;
105
106     my $conf = new FS::Conf;
107     $return{small_custview} =
108       small_custview( $cust_main, $conf->config('defaultcountry') );
109
110     $return{name} = $cust_main->first. ' '. $cust_main->get('last');
111
112     for (@cust_main_editable_fields) {
113       $return{$_} = $cust_main->get($_);
114     }
115
116   } else { #no customer record
117
118     my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $session->{'svcnum'} } )
119       or die "unknown svcnum";
120     $return{name} = $svc_acct->email;
121
122   }
123
124
125   return { 'error'          => '',
126            'custnum'        => $custnum,
127            %return,
128          };
129
130 }
131
132 sub edit_info {
133   my $p = shift;
134   my $session = $cache->get($p->{'session_id'})
135     or return { 'error' => "Can't resume session" }; #better error message
136
137   my $custnum = $session->{'custnum'}
138     or return { 'error' => "no customer record" };
139
140   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
141     or return { 'error' => "unknown custnum $custnum" };
142
143   my $new = new FS::cust_main { $cust_main->hash };
144   $new->set( $_ => $p->{$_} )
145     foreach grep { exists $p->{$_} } @cust_main_editable_fields;
146   my $error = $new->replace($cust_main);
147   return { 'error' => $error } if $error;
148   #$cust_main = $new;
149   
150   return { 'error' => '' };
151 }
152
153 sub invoice {
154   my $p = shift;
155   my $session = $cache->get($p->{'session_id'})
156     or return { 'error' => "Can't resume session" }; #better error message
157
158   my $custnum = $session->{'custnum'};
159
160   my $invnum = $p->{'invnum'};
161
162   my $cust_bill = qsearchs('cust_bill', { 'invnum'  => $invnum,
163                                           'custnum' => $custnum } )
164     or return { 'error' => "Can't find invnum" };
165
166   #my %return;
167
168   return { 'error'        => '',
169            'invnum'       => $invnum,
170            'invoice_text' => join('', $cust_bill->print_text ),
171          };
172
173 }
174
175 sub cancel {
176   my $p = shift;
177   my $session = $cache->get($p->{'session_id'})
178     or return { 'error' => "Can't resume session" }; #better error message
179
180   my $custnum = $session->{'custnum'};
181
182   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
183     or return { 'error' => "unknown custnum $custnum" };
184
185   my @errors = $cust_main->cancel( 'quiet'=>1 );
186
187   my $error = scalar(@errors) ? join(' / ', @errors) : '';
188
189   return { 'error' => $error };
190
191 }
192
193 sub list_pkgs {
194   my $p = shift;
195   my $session = $cache->get($p->{'session_id'})
196     or return { 'error' => "Can't resume session" }; #better error message
197
198   my $custnum = $session->{'custnum'};
199
200   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
201     or return { 'error' => "unknown custnum $custnum" };
202
203   return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] };
204
205 }
206
207 sub order_pkg {
208   my $p = shift;
209   my $session = $cache->get($p->{'session_id'})
210     or return { 'error' => "Can't resume session" }; #better error message
211
212   my $custnum = $session->{'custnum'};
213
214   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
215     or return { 'error' => "unknown custnum $custnum" };
216
217   #false laziness w/ClientAPI/Signup.pm
218
219   my $cust_pkg = new FS::cust_pkg ( {
220     'custnum' => $custnum,
221     'pkgpart' => $p->{'pkgpart'},
222   } );
223   my $error = $cust_pkg->check;
224   return { 'error' => $error } if $error;
225
226   my $svc_acct = new FS::svc_acct ( {
227     'svcpart'   => $p->{'svcpart'} || $cust_pkg->part_pkg->svcpart('svc_acct'),
228     map { $_ => $p->{$_} }
229       qw( username _password sec_phrase popnum ),
230   } );
231
232   my @acct_snarf;
233   my $snarfnum = 1;
234   while ( length($p->{"snarf_machine$snarfnum"}) ) {
235     my $acct_snarf = new FS::acct_snarf ( {
236       'machine'   => $p->{"snarf_machine$snarfnum"},
237       'protocol'  => $p->{"snarf_protocol$snarfnum"},
238       'username'  => $p->{"snarf_username$snarfnum"},
239       '_password' => $p->{"snarf_password$snarfnum"},
240     } );
241     $snarfnum++;
242     push @acct_snarf, $acct_snarf;
243   }
244   $svc_acct->child_objects( \@acct_snarf );
245
246   my $y = $svc_acct->setdefault; # arguably should be in new method
247   return { 'error' => $y } if $y && !ref($y);
248
249   $error = $svc_acct->check;
250   return { 'error' => $error } if $error;
251
252   use Tie::RefHash;
253   tie my %hash, 'Tie::RefHash';
254   %hash = ( $cust_pkg => [ $svc_acct ] );
255   #msgcat
256   $error = $cust_main->order_pkgs( \%hash, '', 'noexport' => 1 );
257   return { 'error' => $error } if $error;
258
259   my $conf = new FS::Conf;
260   if ( $conf->exists('signup_server-realtime') ) {
261
262     my $old_balance = $cust_main->balance;
263
264     my $bill_error = $cust_main->bill;
265     $cust_main->apply_payments;
266     $cust_main->apply_credits;
267     $bill_error = $cust_main->collect;
268
269     if ( $cust_main->balance > $old_balance ) {
270       $cust_pkg->cancel('quiet'=>1);
271       return { 'error' => '_decline' };
272     } else {
273       $cust_pkg->reexport;
274     }
275
276   } else {
277     $cust_pkg->reexport;
278   }
279
280   return { error => '' };
281
282 }
283
284 sub cancel_pkg {
285   my $p = shift;
286   my $session = $cache->get($p->{'session_id'})
287     or return { 'error' => "Can't resume session" }; #better error message
288
289   my $custnum = $session->{'custnum'};
290
291   my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } )
292     or return { 'error' => "unknown custnum $custnum" };
293
294   my $pkgnum = $session->{'pkgnum'};
295
296   my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum,
297                                         'pkgnum'  => $pkgnum,   } )
298     or return { 'error' => "unknown pkgnum $pkgnum" };
299
300   my $error = $cust_main->cancel( 'quiet'=>1 );
301   return { 'error' => $error };
302
303 }
304
305 1;
306