X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2FClientAPI%2FMyAccount.pm;h=86c7ac324e1068b5e4c92af85530f403add47ff7;hp=9330049ebaa5197020096f54f44236be3479beb1;hb=167dbdad01e2c1b62fd9be43cc05212e8c874a02;hpb=4089c2c6d951b1d4355dbe63a692703e36a49ba9 diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 9330049eb..86c7ac324 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -46,6 +46,7 @@ use FS::payby; use FS::acct_rt_transaction; use FS::msg_template; use FS::contact; +use FS::cust_contact; $DEBUG = 1; $me = '[FS::ClientAPI::MyAccount]'; @@ -82,7 +83,7 @@ sub skin_info { #return { 'error' => $session } if $context eq 'error'; my $agentnum = ''; - if ( $context eq 'customer' ) { + if ( $context eq 'customer' && $custnum ) { my $sth = dbh->prepare('SELECT agentnum FROM cust_main WHERE custnum = ?') or die dbh->errstr; @@ -237,7 +238,16 @@ sub login { return { error => 'Incorrect contact password.' } unless $contact->authenticate_password($p->{'password'}); - $session->{'custnum'} = $contact->custnum; + my @cust_contact = grep $_->selfservice_access, $contact->cust_contact; + if ( scalar(@cust_contact) == 1 ) { + $session->{'custnum'} = $cust_contact[0]->custnum; + } elsif ( scalar(@cust_contact) ) { + $session->{'customers'} = { map { $_->custnum => $_->cust_main->name } + @cust_contact + }; + } else { + return { error => 'No customer self-service access for contact' }; #?? + } } else { @@ -303,16 +313,18 @@ sub login { return { 'error' => '', 'session_id' => $session_id, + %$session, }; } sub logout { my $p = shift; + my $skin_info = skin_info($p); if ( $p->{'session_id'} ) { _cache->remove($p->{'session_id'}); - return { %{ skin_info($p) }, 'error' => '' }; + return { %$skin_info, 'error' => '' }; } else { - return { %{ skin_info($p) }, 'error' => "Can't resume session" }; #better error message + return { %$skin_info, 'error' => "Can't resume session" }; #better error message } } @@ -335,6 +347,23 @@ sub switch_acct { } +sub switch_cust { + my $p = shift; + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + $session->{'custnum'} = $p->{'custnum'} + if exists $session->{'customers'}{ $p->{'custnum'} }; + + my $conf = new FS::Conf; + my $timeout = $conf->config('selfservice-session_timeout') || '1 hour'; + _cache->set( $p->{'session_id'}, $session, $timeout ); + + return { 'error' => '', + %{ customer_info( { session_id=>$p->{'session_id'} } ) }, + }; +} + sub payment_gateway { # internal use only # takes a cust_main and a cust_payby entry, returns the payment_gateway @@ -379,25 +408,28 @@ sub access_info { my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) - or return { 'error' => "unknown custnum $custnum" }; + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); $info->{'hide_payment_fields'} = [ map { - my $pg = payment_gateway($cust_main, $_); + my $pg = $cust_main && payment_gateway($cust_main, $_); $pg && $pg->gateway_namespace eq 'Business::OnlineThirdPartyPayment'; } @{ $info->{cust_paybys} } ]; $info->{'self_suspend_reason'} = - $conf->config('selfservice-self_suspend_reason', $cust_main->agentnum); + $conf->config('selfservice-self_suspend_reason', + $cust_main ? $cust_main->agentnum : '' + ); $info->{'edit_ticket_subject'} = $conf->exists('ticket_system-selfservice_edit_subject') && - $cust_main->edit_subject; + $cust_main && $cust_main->edit_subject; $info->{'timeout'} = $conf->config('selfservice-timeout') || 3600; + $info->{'hide_usage'} = $conf->exists('selfservice_hide-usage'); + return { %$info, 'custnum' => $custnum, 'access_pkgnum' => $session->{'pkgnum'}, @@ -429,7 +461,7 @@ sub customer_info { my $search = { 'custnum' => $custnum }; $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; my $cust_main = qsearchs('cust_main', $search ) - or return { 'error' => "unknown custnum $custnum" }; + or return { 'error' => "customer_info: unknown custnum $custnum" }; my $list_tickets = list_tickets($p); $return{'tickets'} = $list_tickets->{'tickets'}; @@ -462,8 +494,9 @@ sub customer_info { ); $return{has_ship_address} = $cust_main->has_ship_address; - $return{status} = $cust_main->status; + $return{status} = $cust_main->status_label; #$cust_main->status; #better to break anyone obscurely testing for strings in self-service than to have to upgrade every front-end to get the new status to display $return{statuscolor} = $cust_main->statuscolor; + $return{status_label} = $cust_main->status_label; # compatibility: some places in selfservice use this to determine # if there's a ship address @@ -532,7 +565,7 @@ sub customer_info_short { my $search = { 'custnum' => $custnum }; $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; my $cust_main = qsearchs('cust_main', $search ) - or return { 'error' => "unknown custnum $custnum" }; + or return { 'error' => "customer_info_short: unknown custnum $custnum" }; $return{display_custnum} = $cust_main->display_custnum; @@ -1132,6 +1165,7 @@ sub do_process_payment { my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount, 'quiet' => 1, + 'manual' => 1, 'selfservice' => 1, 'paynum_ref' => \$paynum, %$validate, @@ -1617,6 +1651,7 @@ sub list_pkgs { or return { 'error' => "unknown custnum $custnum" }; my $conf = new FS::Conf; + my $immutable = $conf->exists('selfservice_immutable-package'); # the duplication below is necessary: # 1. to maintain the current buggy behaviour wrt the cust_pkg and part_pkg @@ -1629,6 +1664,7 @@ sub list_pkgs { 'custnum' => $custnum, 'cust_pkg' => [ map { { $_->hash, + immutable => $immutable, part_pkg => [ map $_->hashref, $_->part_pkg ], part_svc => [ map $_->hashref, $_->available_part_svc ], @@ -1661,6 +1697,7 @@ sub list_pkgs { my $primary_cust_svc = $_->primary_cust_svc; +{ $_->hash, $_->part_pkg->hash, + immutable => $immutable, pkg_label => $_->pkg_locale, status => $_->status, statuscolor => $_->statuscolor, @@ -1710,6 +1747,9 @@ sub list_svcs { my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; + my $conf = new FS::Conf; + + my $hide_usage = $conf->exists('selfservice_hide-usage') ? 1 : 0; my $search = { 'custnum' => $custnum }; $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; my $cust_main = qsearchs('cust_main', $search ) @@ -1723,7 +1763,6 @@ sub list_svcs { my @cust_svc = (); my @cust_pkg_usage = (); - #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) { foreach my $cust_pkg ( $p->{'ncancelled'} ? $cust_main->ncancelled_pkgs : $cust_main->unsuspended_pkgs ) { @@ -1734,14 +1773,16 @@ sub list_svcs { @cust_svc = grep { $_->part_svc->selfservice_access ne 'hidden' } @cust_svc; my %usage_pools; - foreach (@cust_pkg_usage) { - my $part = $_->part_pkg_usage; - my $tag = $part->description . ($part->shared ? 1 : 0); - my $row = $usage_pools{$tag} - ||= [ $part->description, 0, 0, $part->shared ? 1 : 0 ]; - $row->[1] += sprintf('%.1f', $_->minutes); # minutes remaining - $row->[2] += $part->minutes; # minutes total - } + if (!$hide_usage) { + foreach (@cust_pkg_usage) { + my $part = $_->part_pkg_usage; + my $tag = $part->description . ($part->shared ? 1 : 0); + my $row = $usage_pools{$tag} + ||= [ $part->description, 0, 0, $part->shared ? 1 : 0 ]; + $row->[1] += sprintf('%.1f', $_->minutes); # minutes remaining + $row->[2] += $part->minutes; # minutes total + } + } # otherwise just leave them empty if ( $p->{'svcdb'} ) { my $svcdb = ref($p->{'svcdb'}) eq 'HASH' @@ -1755,108 +1796,110 @@ sub list_svcs { #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username } # @svc_x; - my $conf = new FS::Conf; + my @svcs; # stuff to return to the client + foreach my $cust_svc (@cust_svc) { + my $svc_x = $cust_svc->svc_x; + my($label, $value) = $cust_svc->label; + my $part_svc = $cust_svc->part_svc; + my $svcdb = $part_svc->svcdb; + my $cust_pkg = $cust_svc->cust_pkg; + my $part_pkg = $cust_pkg->part_pkg; - { + my %hash = ( + 'svcnum' => $cust_svc->svcnum, + 'display_svcnum' => $cust_svc->display_svcnum, + 'svcdb' => $svcdb, + 'label' => $label, + 'value' => $value, + 'pkg_label' => $cust_pkg->pkg_locale, + 'pkg_status' => $cust_pkg->status, + 'readonly' => ($part_svc->selfservice_access eq 'readonly'), + ); + + # would it make sense to put this in a svc_* method? + + if ( $svcdb eq 'svc_acct' ) { + foreach (qw(username email finger seconds)) { + $hash{$_} = $svc_x->$_; + } + + if (!$hide_usage) { + %hash = ( + %hash, + 'upbytes' => display_bytecount($svc_x->upbytes), + 'downbytes' => display_bytecount($svc_x->downbytes), + 'totalbytes' => display_bytecount($svc_x->totalbytes), + + 'recharge_amount' => $part_pkg->option('recharge_amount',1), + 'recharge_seconds' => $part_pkg->option('recharge_seconds',1), + 'recharge_upbytes' => + display_bytecount($part_pkg->option('recharge_upbytes',1)), + 'recharge_downbytes' => + display_bytecount($part_pkg->option('recharge_downbytes',1)), + 'recharge_totalbytes' => + display_bytecount($part_pkg->option('recharge_totalbytes',1)), + # more... + ); + } + + } elsif ( $svcdb eq 'svc_dsl' ) { + + $hash{'phonenum'} = $svc_x->phonenum; + if ( $svc_x->first || $svc_x->get('last') || $svc_x->company ) { + $hash{'name'} = $svc_x->first. ' '. $svc_x->get('last'); + $hash{'name'} = $svc_x->company. ' ('. $hash{'name'}. ')' + if $svc_x->company; + } else { + $hash{'name'} = $cust_main->name; + } + # no usage to hide here + + } elsif ( $svcdb eq 'svc_phone' ) { + if (!$hide_usage) { + # could potentially show lots of things... + $hash{'outbound'} = 1; + $hash{'inbound'} = 0; + if ( $part_pkg->plan eq 'voip_inbound' ) { + $hash{'outbound'} = 0; + $hash{'inbound'} = 1; + } elsif ( $part_pkg->option('selfservice_inbound_format') + or $conf->config('selfservice-default_inbound_cdr_format') + ) { + $hash{'inbound'} = 1; + } + foreach (qw(inbound outbound)) { + # hmm...we can't filter by status here, because there might + # not be cdr_terminations at all. have to go by date. + # find all since the last bill date. + # XXX cdr types? we are going to need them. + if ( $hash{$_} ) { + my $sum_cdr = $svc_x->sum_cdrs( + 'inbound' => ( $_ eq 'inbound' ? 1 : 0 ), + 'begin' => ($cust_pkg->last_bill || 0), + 'nonzero' => 1, + 'disable_charged_party' => 1, + ); + $hash{$_} = $sum_cdr->hashref; + } + } + } # not hiding usage + } # svcdb + + push @svcs, \%hash; + } # foreach $cust_svc + + return { 'svcnum' => $session->{'svcnum'}, 'custnum' => $custnum, 'date_format' => $conf->config('date_format') || '%m/%d/%Y', 'view_usage_nodomain' => $conf->exists('selfservice-view_usage_nodomain'), - 'svcs' => [ - map { - my $svc_x = $_->svc_x; - my($label, $value) = $_->label; - my $part_svc = $_->part_svc; - my $svcdb = $part_svc->svcdb; - my $cust_pkg = $_->cust_pkg; - my $part_pkg = $cust_pkg->part_pkg; - - my %hash = ( - 'svcnum' => $_->svcnum, - 'display_svcnum' => $_->display_svcnum, - 'svcdb' => $svcdb, - 'label' => $label, - 'value' => $value, - 'pkg_label' => $cust_pkg->pkg_locale, - 'pkg_status' => $cust_pkg->status, - 'readonly' => ($part_svc->selfservice_access eq 'readonly'), - ); - - if ( $svcdb eq 'svc_acct' ) { - %hash = ( - %hash, - 'username' => $svc_x->username, - 'email' => $svc_x->email, - 'finger' => $svc_x->finger, - 'seconds' => $svc_x->seconds, - 'upbytes' => display_bytecount($svc_x->upbytes), - 'downbytes' => display_bytecount($svc_x->downbytes), - 'totalbytes' => display_bytecount($svc_x->totalbytes), - - 'recharge_amount' => $part_pkg->option('recharge_amount',1), - 'recharge_seconds' => $part_pkg->option('recharge_seconds',1), - 'recharge_upbytes' => - display_bytecount($part_pkg->option('recharge_upbytes',1)), - 'recharge_downbytes' => - display_bytecount($part_pkg->option('recharge_downbytes',1)), - 'recharge_totalbytes' => - display_bytecount($part_pkg->option('recharge_totalbytes',1)), - # more... - ); - - } elsif ( $svcdb eq 'svc_dsl' ) { - $hash{'phonenum'} = $svc_x->phonenum; - if ( $svc_x->first || $svc_x->get('last') || $svc_x->company ) { - $hash{'name'} = $svc_x->first. ' '. $svc_x->get('last'); - $hash{'name'} = $svc_x->company. ' ('. $hash{'name'}. ')' - if $svc_x->company; - } else { - $hash{'name'} = $cust_main->name; - } - } elsif ( $svcdb eq 'svc_phone' ) { - # could potentially show lots of things... - $hash{'outbound'} = 1; - $hash{'inbound'} = 0; - if ( $part_pkg->plan eq 'voip_inbound' ) { - $hash{'outbound'} = 0; - $hash{'inbound'} = 1; - } elsif ( $part_pkg->option('selfservice_inbound_format') - or $conf->config('selfservice-default_inbound_cdr_format') - ) { - $hash{'inbound'} = 1; - } - foreach (qw(inbound outbound)) { - # hmm...we can't filter by status here, because there might - # not be cdr_terminations at all. have to go by date. - # find all since the last bill date. - # XXX cdr types? we are going to need them. - if ( $hash{$_} ) { - my $sum_cdr = $svc_x->sum_cdrs( - 'inbound' => ( $_ eq 'inbound' ? 1 : 0 ), - 'begin' => ($cust_pkg->last_bill || 0), - 'nonzero' => 1, - 'disable_charged_party' => 1, - ); - $hash{$_} = $sum_cdr->hashref; - } - } - } - - # elsif ( $svcdb eq 'svc_phone' || $svcdb eq 'svc_port' ) { - # %hash = ( - # %hash, - # ); - #} - - \%hash; - } - @cust_svc - ], + 'svcs' => \@svcs, 'usage_pools' => [ map { $usage_pools{$_} } sort { $a cmp $b } keys %usage_pools ], + 'hide_usage' => $hide_usage, }; } @@ -2138,6 +2181,10 @@ sub _usage_details { my($callback, $p, %opt) = @_; my $conf = FS::Conf->new; + if ( $conf->exists('selfservice_hide-usage') ) { + return { 'error' => 'Viewing usage is not allowed.' }; + } + my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; @@ -2353,6 +2400,10 @@ sub change_pkg { my($context, $session, $custnum) = _custoragent_session_custnum($p); return { 'error' => $session } if $context eq 'error'; + my $conf = new FS::Conf; + my $immutable = $conf->exists('selfservice_immutable-package'); + return { 'error' => "Package modification disabled" } if $immutable; + my $search = { 'custnum' => $custnum }; $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; my $cust_main = qsearchs('cust_main', $search ) @@ -2374,7 +2425,6 @@ sub change_pkg { \@newpkg, ); - my $conf = new FS::Conf; if ( $conf->exists('signup_server-realtime') ) { my $bill_error = _do_bop_realtime( $cust_main, $status, 'no_credit'=>1 ); @@ -2895,7 +2945,12 @@ sub myaccount_passwd { #need to support the "ISP provides email that's used as a contact email" case #as well as we can. my $contact = FS::contact->by_selfservice_email($svc_acct->email); - if ( $contact && $contact->custnum == $custnum ) { + if ( $contact && qsearchs('cust_contact', { contactnum=> $contact->contactnum, + custnum => $custnum, + selfservice_access => 'Y', + } + ) + ) { #svc_acct was successful but this one returns an error? "shouldn't happen" $error ||= $contact->change_password($p->{'new_password'}); } @@ -2972,7 +3027,10 @@ sub reset_passwd { $contact = FS::contact->by_selfservice_email($p->{'email'}); - $cust_main = $contact->cust_main if $contact; + if ( $contact ) { + my @cust_contact = grep $_->selfservice_access, $contact->cust_contact; + $cust_main = $cust_contact[0]->cust_main if scalar(@cust_contact) == 1; + } #also look for an svc_acct, otherwise it would be super confusing @@ -3014,6 +3072,9 @@ sub reset_passwd { } + return { %$info, 'error' => 'Multi-customer contacts incompatible with customer-based verification' } + if ! $cust_main && $verification ne 'email'; + my %verify = ( 'email' => sub { 1; }, 'paymask' => sub { @@ -3136,7 +3197,9 @@ sub check_reset_passwd { my @contact_email = $contact->contact_email; return { 'error' => 'No contact email' } unless @contact_email; - $p->{'agentnum'} = $contact->cust_main->agentnum; + my @cust_contact = grep $_->selfservice_access, $contact->cust_contact; + $p->{'agentnum'} = $cust_contact[0]->cust_main->agentnum + if scalar(@cust_contact) == 1; my $info = skin_info($p); return { %$info, @@ -3186,7 +3249,9 @@ sub process_reset_passwd { $contact = qsearchs('contact', { 'contactnum' => $contactnum } ) or return { 'error' => "Contact not found" }; - $p->{'agentnum'} ||= $contact->cust_main->agentnum; + my @cust_contact = grep $_->selfservice_access, $contact->cust_contact; + $p->{'agentnum'} = $cust_contact[0]->cust_main->agentnum + if scalar(@cust_contact) == 1; $info ||= skin_info($p); }