X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2FClientAPI%2FMyAccount.pm;h=ac3ebdfc596a765862940dea3c37d68198e14164;hp=9a1cc50f3f9b5519e160ccee5650f04c7cf8a5f9;hb=687d99781b3a8c555534e0a4f7fd826e59e4cf2f;hpb=dcf8dfc4c58fb3e518e09f4f988c4e17c9fd9591 diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 9a1cc50f3..ac3ebdfc5 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -4,12 +4,16 @@ use 5.008; #require 5.8+ for Time::Local 1.05+ use strict; use vars qw( $cache $DEBUG $me ); use subs qw( _cache _provision ); +use IO::Scalar; use Data::Dumper; use Digest::MD5 qw(md5_hex); use Date::Format; -use Business::CreditCard; use Time::Duration; use Time::Local qw(timelocal_nocheck); +use Business::CreditCard; +use HTML::Entities; +use Text::CSV_XS; +use Spreadsheet::WriteExcel; use FS::UI::Web::small_custview qw(small_custview); #less doh use FS::UI::Web; use FS::UI::bytecount qw( display_bytecount ); @@ -19,24 +23,25 @@ use FS::Record qw(qsearch qsearchs dbh); use FS::Msgcat qw(gettext); use FS::Misc qw(card_types); use FS::Misc::DateTime qw(parse_datetime); +use FS::TicketSystem; use FS::ClientAPI_SessionCache; use FS::cust_svc; use FS::svc_acct; +use FS::svc_forward; use FS::svc_domain; use FS::svc_phone; use FS::svc_external; +use FS::svc_dsl; +use FS::dsl_device; use FS::part_svc; use FS::cust_main; use FS::cust_bill; +use FS::legacy_cust_bill; use FS::cust_main_county; use FS::cust_pkg; use FS::payby; use FS::acct_rt_transaction; -use HTML::Entities; -use FS::TicketSystem; -use Text::CSV_XS; -use IO::Scalar; -use Spreadsheet::WriteExcel; +use FS::msg_template; $DEBUG = 0; $me = '[FS::ClientAPI::MyAccount]'; @@ -249,6 +254,25 @@ sub logout { } } +sub switch_acct { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $svc_acct = _customer_svc_x( $custnum, $p->{'svcnum'}, 'svc_acct' ) + or return { 'error' => "Service not found" }; + + $session->{'svcnum'} = $svc_acct->svcnum; + + my $conf = new FS::Conf; + my $timeout = $conf->config('selfservice-session_timeout') || '1 hour'; + _cache->set( $p->{'session_id'}, $session, $timeout ); + + return { 'error' => '' }; + +} + sub payment_gateway { # internal use only # takes a cust_main and a cust_payby entry, returns the payment_gateway @@ -433,6 +457,7 @@ sub customer_info { if ( $session->{'svcnum'} ) { my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $session->{'svcnum'} }); $return{'svc_label'} = ($cust_svc->label)[1] if $cust_svc; + $return{'svcnum'} = $session->{'svcnum'}; } } elsif ( $session->{'svcnum'} ) { #no customer record @@ -500,6 +525,7 @@ sub customer_info_short { if ( $session->{'svcnum'} ) { my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $session->{'svcnum'} }); $return{'svc_label'} = ($cust_svc->label)[1] if $cust_svc; + $return{'svcnum'} = $session->{'svcnum'}; } } elsif ( $session->{'svcnum'} ) { #no customer record @@ -1115,7 +1141,63 @@ sub invoice_pdf { return { 'error' => '', 'invnum' => $invnum, - 'invoice_pdf' => $cust_bill->print_pdf( { unsquelch_cdr => 1 } ), + 'invoice_pdf' => $cust_bill->print_pdf({ + 'unsquelch_cdr' => 1, + 'locale' => $p->{'locale'}, + }), + }; + +} + +sub legacy_invoice { + my $p = shift; + my $session = _cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $custnum = $session->{'custnum'}; + + my $legacyinvnum = $p->{'legacyinvnum'}; + + my %hash = ( + 'legacyinvnum' => $legacyinvnum, + 'custnum' => $custnum, + ); + + my $legacy_cust_bill = + qsearchs('legacy_cust_bill', { %hash, 'locale' => $p->{'locale'} } ) + || qsearchs('legacy_cust_bill', \%hash ) + or return { 'error' => "Can't find legacyinvnum" }; + + #my %return; + + return { 'error' => '', + 'legacyinvnum' => $legacyinvnum, + 'legacyid' => $legacy_cust_bill->legacyid, + 'invoice_html' => $legacy_cust_bill->content_html, + }; + +} + +sub legacy_invoice_pdf { + my $p = shift; + my $session = _cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $custnum = $session->{'custnum'}; + + my $legacyinvnum = $p->{'legacyinvnum'}; + + my $legacy_cust_bill = qsearchs('legacy_cust_bill', { + 'legacyinvnum' => $legacyinvnum, + 'custnum' => $custnum, + }) or return { 'error' => "Can't find legacyinvnum" }; + + #my %return; + + return { 'error' => '', + 'legacyinvnum' => $legacyinvnum, + 'legacyid' => $legacy_cust_bill->legacyid, + 'invoice_pdf' => $legacy_cust_bill->content_pdf, }; } @@ -1163,7 +1245,11 @@ sub list_invoices { my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) or return { 'error' => "unknown custnum $custnum" }; - my @cust_bill = $cust_main->cust_bill; + my $conf = new FS::Conf; + + my @legacy_cust_bill = $cust_main->legacy_cust_bill; + + my @cust_bill = grep ! $_->hide, $cust_main->cust_bill; my $balance = 0; @@ -1173,18 +1259,32 @@ sub list_invoices { map { my $owed = $_->owed; $balance += $owed; - +{ 'invnum' => $_->invnum, - '_date' => $_->_date, - 'date' => time2str("%b %o, %Y", $_->_date), - 'date_short' => time2str("%m-%d-%Y", $_->_date), - 'previous' => sprintf('%.2f', ($_->previous)[0]), - 'charged' => sprintf('%.2f', $_->charged), - 'owed' => sprintf('%.2f', $owed), - 'balance' => sprintf('%.2f', $balance), + +{ 'invnum' => $_->invnum, + '_date' => $_->_date, + 'date' => time2str("%b %o, %Y", $_->_date), + 'date_short' => time2str("%m-%d-%Y", $_->_date), + 'previous' => sprintf('%.2f', ($_->previous)[0]), + 'charged' => sprintf('%.2f', $_->charged), + 'owed' => sprintf('%.2f', $owed), + 'balance' => sprintf('%.2f', $balance), } } @cust_bill - ] + ], + 'legacy_invoices' => [ + map { + +{ 'legacyinvnum' => $_->legacyinvnum, + 'legacyid' => $_->legacyid, + '_date' => $_->_date, + 'date' => time2str("%b %o, %Y", $_->_date), + 'date_short' => time2str("%m-%d-%Y", $_->_date), + 'charged' => sprintf('%.2f', $_->charged), + 'has_content' => ( length($_->content_pdf) + || length($_->content_html) ), + } + } + @legacy_cust_bill + ], }; } @@ -1308,12 +1408,18 @@ sub list_svcs { my $cust_main = qsearchs('cust_main', $search ) or return { 'error' => "unknown custnum $custnum" }; + my $pkgnum = $session->{'pkgnum'} || $p->{'pkgnum'} || ''; + if ( ! $pkgnum && $p->{'svcnum'} ) { + my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $p->{'svcnum'} } ); + $pkgnum = $cust_svc->pkgnum if $cust_svc; + } + my @cust_svc = (); #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) { foreach my $cust_pkg ( $p->{'ncancelled'} ? $cust_main->ncancelled_pkgs : $cust_main->unsuspended_pkgs ) { - next if $session->{'pkgnum'} && $cust_pkg->pkgnum != $session->{'pkgnum'}; + next if $pkgnum && $cust_pkg->pkgnum != $pkgnum; push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context } if ( $p->{'svcdb'} ) { @@ -1340,13 +1446,15 @@ sub list_svcs { my $svc_x = $_->svc_x; my($label, $value) = $_->label; my $svcdb = $_->part_svc->svcdb; - my $part_pkg = $_->cust_pkg->part_pkg; + my $cust_pkg = $_->cust_pkg; + my $part_pkg = $cust_pkg->part_pkg; my %hash = ( - 'svcnum' => $_->svcnum, - 'svcdb' => $svcdb, - 'label' => $label, - 'value' => $value, + 'svcnum' => $_->svcnum, + 'svcdb' => $svcdb, + 'label' => $label, + 'value' => $value, + 'pkg_status' => $cust_pkg->status, ); if ( $svcdb eq 'svc_acct' ) { @@ -1354,6 +1462,7 @@ sub list_svcs { %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), @@ -1370,11 +1479,21 @@ sub list_svcs { # more... ); - } elsif ( $svcdb eq 'svc_phone' || $svcdb eq 'svc_port' ) { - %hash = ( - %hash, - ); + } 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' || $svcdb eq 'svc_port' ) { + # %hash = ( + # %hash, + # ); + #} \%hash; } @@ -1384,6 +1503,163 @@ sub list_svcs { } +sub _customer_svc_x { + my($custnum, $svcnum, $table) = (shift, shift, shift); + my $hashref = ref($svcnum) ? $svcnum : { 'svcnum' => $svcnum }; + + $custnum =~ /^(\d+)$/ or die "illegal custnum"; + my $search = " AND custnum = $1"; + #$search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent'; + + qsearchs( { + 'table' => ($table || 'svc_acct'), + 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '. + 'LEFT JOIN cust_pkg USING ( pkgnum ) ',#. + #'LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => $hashref, + 'extra_sql' => $search, #important + } ); + +} + +sub svc_status_html { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + #XXX only svc_dsl for now + my $svc_x = _customer_svc_x( $custnum, $p->{'svcnum'}, 'svc_dsl') + or return { 'error' => "Service not found" }; + + my $html = $svc_x->getstatus_html; + + return { 'html' => $html }; + +} + +sub acct_forward_info { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $svc_forward = _customer_svc_x( $custnum, + { 'srcsvc' => $p->{'svcnum'} }, + 'svc_forward', + ) + or return { 'error' => '', + 'dst' => '', + }; + + return { 'error' => '', + 'dst' => $svc_forward->dst || $svc_forward->dstsvc_acct->email, + }; + +} + +sub process_acct_forward { + my $p = shift; + warn Dumper($p); + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $old = _customer_svc_x( $custnum, + { 'srcsvc' => $p->{'svcnum'} }, + 'svc_forward', + ); + + if ( $p->{'dst'} eq '' ) { + if ( $old ) { + my $error = $old->delete; + return { 'error' => $error }; + } + return { 'error' => '' }; + } + + my $new = new FS::svc_forward { 'srcsvc' => $p->{'svcnum'}, + 'dst' => $p->{'dst'}, + }; + + my $error; + if ( $old ) { + $new->svcnum($old->svcnum); + my $cust_svc = $old->cust_svc; + $new->svcpart($old->svcpart); + $new->pkgnuym($old->pkgnum); + $error = $new->replace($old); + } else { + my $conf = new FS::Conf; + $new->svcpart($conf->config('selfservice-svc_forward_svcpart')); + $new->pkgnum($old->cust_svc->pkgnum); + warn Dumper($new); + $error = $new->insert; + warn $error; + } + + return { 'error' => $error }; + +} + +sub list_dsl_devices { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $svc_dsl = _customer_svc_x( $custnum, $p->{'svcnum'}, 'svc_dsl' ) + or return { 'error' => "Service not found" }; + + return { + 'devices' => [ map { + +{ 'mac_addr' => $_->mac_addr }; + } $svc_dsl->dsl_device + ], + }; + +} + +sub add_dsl_device { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $svc_dsl = _customer_svc_x( $custnum, $p->{'svcnum'}, 'svc_dsl' ) + or return { 'error' => "Service not found" }; + + return { 'error' => 'No MAC address supplied' } + unless length($p->{'mac_addr'}); + + my $dsl_device = new FS::dsl_device { 'svcnum' => $svc_dsl->svcnum, + 'mac_addr' => scalar($p->{'mac_addr'}), + }; + my $error = $dsl_device->insert; + return { 'error' => $error }; + +} + +sub delete_dsl_device { + my $p = shift; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $svc_dsl = _customer_svc_x( $custnum, $p->{'svcnum'}, 'svc_dsl' ) + or return { 'error' => "Service not found" }; + + my $dsl_device = qsearchs('dsl_device', { 'svcnum' => $svc_dsl->svcnum, + 'mac_addr' => scalar($p->{'mac_addr'}), + } + ) + or return { 'error' => 'Unknown MAC address: '. $p->{'mac_addr'} }; + + my $error = $dsl_device->delete; + return { 'error' => $error }; + +} + sub port_graph { my $p = shift; _usage_details( \&_port_graph, $p, @@ -2146,6 +2422,11 @@ sub myaccount_passwd { } ) or return { 'error' => "Service not found" }; + if ( exists($p->{'old_password'}) ) { + return { 'error' => "Incorrect password." } + unless $svc_acct->check_password($p->{'old_password'}); + } + $svc_acct->_password($p->{'new_password'}); my $error = $svc_acct->replace(); @@ -2158,6 +2439,147 @@ sub myaccount_passwd { } +sub reset_passwd { + my $p = shift; + + my $conf = new FS::Conf; + my $verification = $conf->config('selfservice-password_reset_verification') + or return { 'error' => 'Password resets disabled' }; + + my $username = $p->{'username'}; + + my $svc_domain = qsearchs('svc_domain', { 'domain' => $p->{'domain'} } ) + or return { 'error' => 'Account not found' }; + + my $svc_acct = qsearchs('svc_acct', { 'username' => $p->{'username'}, + 'domsvc' => $svc_domain->svcnum } + ) + or return { 'error' => 'Account not found' }; + + my $cust_pkg = $svc_acct->cust_svc->cust_pkg + or return { 'error' => 'Account not found' }; + + my $cust_main = $cust_pkg->cust_main; + + my %verify = ( + 'paymask' => sub { + my( $p, $cust_main ) = @_; + $cust_main->payby =~ /^(CARD|DCRD|CHEK|DCHK)$/ + && $p->{'paymask'} eq substr($cust_main->paymask, -4) + }, + 'amount' => sub { + my( $p, $cust_main ) = @_; + my $cust_pay = qsearchs({ + 'table' => 'cust_pay', + 'hashref' => { 'custnum' => $cust_main->custnum }, + 'order_by' => 'ORDER BY _date DESC LIMIT 1', + }) + or return 0; + + $p->{'amount'} == $cust_pay->paid; + }, + 'zip' => sub { + my( $p, $cust_main ) = @_; + $p->{'zip'} eq $cust_main->zip + || ( $cust_main->ship_zip && $p->{'zip'} eq $cust_main->ship_zip ); + }, + ); + + foreach my $verify ( split(',', $verification) ) { + + &{ $verify{$verify} }( $p, $cust_main ) + or return { 'error' => 'Account not found' }; + + } + + #okay, we're verified, now create a unique session + + my $reset_session = { + 'svcnum' => $svc_acct->svcnum, + }; + + my $timeout = '1 hour'; #? + + my $reset_session_id; + do { + $reset_session_id = md5_hex(md5_hex(time(). {}. rand(). $$)) + } until ( ! defined _cache->get("reset_passwd_$reset_session_id") ); #just in case + + _cache->set( "reset_passwd_$reset_session_id", $reset_session, $timeout ); + + #email it + + my $msgnum = $conf->config('selfservice-password_reset_msgnum', $cust_main->agentnum); + #die "selfservice-password_reset_msgnum unset" unless $msgnum; + return { 'error' => "selfservice-password_reset_msgnum unset" } unless $msgnum; + my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ); + my $error = $msg_template->send( 'cust_main' => $cust_main, + 'object' => $svc_acct, + 'substitutions' => { + 'session_id' => $reset_session_id, + } + ); + if ( $error ) { + return { 'error' => $error }; #???? + } + + return { 'error' => '' }; +} + +sub check_reset_passwd { + my $p = shift; + + my $conf = new FS::Conf; + my $verification = $conf->config('selfservice-password_reset_verification') + or return { 'error' => 'Password resets disabled' }; + + my $reset_session = _cache->get('reset_passwd_'. $p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $svcnum = $reset_session->{'svcnum'}; + + my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) + or return { 'error' => "Service not found" }; + + return { 'error' => '', + 'username' => $svc_acct->username, + }; + +} + +sub process_reset_passwd { + my $p = shift; + + my $conf = new FS::Conf; + my $verification = $conf->config('selfservice-password_reset_verification') + or return { 'error' => 'Password resets disabled' }; + + return { 'error' => "New passwords don't match." } + if $p->{'new_password'} ne $p->{'new_password2'}; + + return { 'error' => 'Enter new password' } + unless length($p->{'new_password'}); + + my $reset_session = _cache->get('reset_passwd_'. $p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $svcnum = $reset_session->{'svcnum'}; + + my $svc_acct = qsearchs('svc_acct', { 'svcnum' => $svcnum } ) + or return { 'error' => "Service not found" }; + + $svc_acct->_password($p->{'new_password'}); + my $error = $svc_acct->replace(); + + my($label, $value) = $svc_acct->cust_svc->label; + + return { 'error' => $error, + #'label' => $label, + #'value' => $value, + }; + +} + sub create_ticket { my $p = shift; my($context, $session, $custnum) = _custoragent_session_custnum($p);