From: Ivan Kohler Date: Wed, 11 Feb 2015 19:56:39 +0000 (-0800) Subject: Merge branch 'master' of git.freeside.biz:/home/git/freeside X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=81bad22dc70a9277331d2d15ff25810f615c4a92;hp=b68c4a4a92d9b03527f109235093fcc98b98dd2a Merge branch 'master' of git.freeside.biz:/home/git/freeside --- diff --git a/FS/FS/API.pm b/FS/FS/API.pm index dd172c143..c49fb205a 100644 --- a/FS/FS/API.pm +++ b/FS/FS/API.pm @@ -603,8 +603,21 @@ sub location_info { Bills a single customer now, in the same fashion as the "Bill now" link in the UI. -Returns a hash reference with a single key, 'error'. If there is an error, -the value contains the error, otherwise it is empty. +Returns a hash reference with a single key, 'error'. If there is an error, +the value contains the error, otherwise it is empty. Takes a list of keys and +values as parameters with the following keys: + +=over 4 + +=item secret + +API Secret (required) + +=item custnum + +Customer number (required) + +=back =cut diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index a4e26f7c8..091070ec5 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -4627,6 +4627,16 @@ and customer address. Include units.', }, { + 'key' => 'part_pkg-delay_cancel-days', + 'section' => '', + 'description' => 'Expire packages in this many days when using delay_cancel (default is 1)', + 'type' => 'text', + 'validate' => sub { (($_[0] =~ /^\d*$/) && (($_[0] eq '') || $_[0])) + ? 'Must specify an integer number of days' + : '' } + }, + + { 'key' => 'mcp_svcpart', 'section' => '', 'description' => 'Master Control Program svcpart. Leave this blank.', diff --git a/FS/FS/cdr/cia.pm b/FS/FS/cdr/cia.pm index 0471e9bb4..ca44c0fdf 100644 --- a/FS/FS/cdr/cia.pm +++ b/FS/FS/cdr/cia.pm @@ -1,9 +1,8 @@ package FS::cdr::cia; use strict; -use vars qw( @ISA %info $date $tmp_mday $tmp_mon $tmp_year); +use vars qw( @ISA %info ); use FS::cdr qw(_cdr_date_parser_maker); -use Time::Local; @ISA = qw(FS::cdr); @@ -12,39 +11,26 @@ use Time::Local; 'weight' => 510, 'header' => 1, 'type' => 'csv', - 'sep_char' => "|", + 'sep_char' => "\t", 'import_fields' => [ - 'accountcode', - skip(2), # First and last name - - sub { my($cdr, $date) = @_; - $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/ - or die "unparsable date: $date"; #maybe we shouldn't die... - ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 ); - }, #Date - - sub { my($cdr, $time) = @_; - #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); - $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/ - or die "unparsable time: $time"; #maybe we shouldn't die... - $cdr->startdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)); - $cdr->answerdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)); - - }, # Start time - - sub { my($cdr, $time) = @_; - #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); - $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/ - or die "unparsable time: $time"; #maybe we shouldn't die... - #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) ); - $cdr->enddate( - timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year) ); - }, # End time - - 'disposition', # Disposition - 'dst', # PhoneNumber - skip(3), # Extension, Service Type, Filler - 'src', # ClientContactID + skip(2), # Reseller Account Number, Confirmation Number + 'description', # Conference Name + skip(3), # Organization Name, Bill Code, Q&A Active + 'userfield', # Chairperson Name + skip(2), # Conference Start Time, Conference End Time + _cdr_date_parser_maker('startdate'), # Connect Time + _cdr_date_parser_maker('enddate'), # Disconnect Time + skip(1), # Duration + sub { my($cdr, $data, $conf, $param) = @_; + $cdr->duration($data); + $cdr->billsec( $data); + }, # Roundup Duration + skip(1), # User Name + 'dst', # DNIS + 'src', # ANI + skip(2), # Call Type, Toll Free, + 'accountcode', # Chair Conference Entry Code + skip(1), # Participant Conference Entry Code, ], ); diff --git a/FS/FS/cdr/cia_callblast.pm b/FS/FS/cdr/cia_callblast.pm new file mode 100644 index 000000000..d59cd7823 --- /dev/null +++ b/FS/FS/cdr/cia_callblast.pm @@ -0,0 +1,54 @@ +package FS::cdr::cia_callblast; + +use strict; +use vars qw( @ISA %info $date $tmp_mday $tmp_mon $tmp_year); +use FS::cdr qw(_cdr_date_parser_maker); +use Time::Local; + +@ISA = qw(FS::cdr); + +%info = ( + 'name' => 'Client Instant Access Callblast', + 'weight' => 510, + 'header' => 1, + 'type' => 'csv', + 'sep_char' => "|", + 'import_fields' => [ + 'accountcode', + skip(2), # First and last name + + sub { my($cdr, $date) = @_; + $date =~ /^(\d{1,2})\/(\d{1,2})\/(\d\d(\d\d)?)$/ + or die "unparsable date: $date"; #maybe we shouldn't die... + ($tmp_mday, $tmp_mon, $tmp_year) = ( $2, $1-1, $3 ); + }, #Date + + sub { my($cdr, $time) = @_; + #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); + $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/ + or die "unparsable time: $time"; #maybe we shouldn't die... + $cdr->startdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)); + $cdr->answerdate( timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year)); + + }, # Start time + + sub { my($cdr, $time) = @_; + #my($sec, $min, $hour, $mday, $mon, $year)= localtime($cdr->startdate); + $time =~ /^(\d{1,2}):(\d{1,2}):(\d{1,2})$/ + or die "unparsable time: $time"; #maybe we shouldn't die... + #$cdr->startdate( timelocal($3, $2, $1 ,$mday, $mon, $year) ); + $cdr->enddate( + timelocal($3, $2, $1 ,$tmp_mday, $tmp_mon, $tmp_year) ); + }, # End time + + 'disposition', # Disposition + 'dst', # PhoneNumber + skip(3), # Extension, Service Type, Filler + 'src', # ClientContactID + ], + +); + +sub skip { map {''} (1..$_[0]) } + +1; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index d38f3d01b..c3b141e1b 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -3900,6 +3900,27 @@ sub cust_status { } } +=item is_status_delay_cancel + +Returns true if customer status is 'suspended' +and all suspended cust_pkg return true for +cust_pkg->is_status_delay_cancel. + +This is not a real status, this only meant for hacking display +values, because otherwise treating the customer as suspended is +really the whole point of the delay_cancel option. + +=cut + +sub is_status_delay_cancel { + my ($self) = @_; + return 0 unless $self->status eq 'suspended'; + foreach my $cust_pkg ($self->ncancelled_pkgs) { + return 0 unless $cust_pkg->is_status_delay_cancel; + } + return 1; +} + =item ucfirst_cust_status =item ucfirst_status diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 3bd210706..1cc83b6a0 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -823,6 +823,20 @@ sub cancel { my $date = $options{'date'} if $options{'date'}; # expire/cancel later $date = '' if ($date && $date <= $cancel_time); # complain instead? + my $delay_cancel = undef; + if ( !$date && $self->part_pkg->option('delay_cancel',1) + && (($self->status eq 'active') || ($self->status eq 'suspended')) + ) { + my $expdays = $conf->config('part_pkg-delay_cancel-days') || 1; + my $expsecs = 60*60*24*$expdays; + my $suspfor = $self->susp ? $cancel_time - $self->susp : 0; + $expsecs = $expsecs - $suspfor if $suspfor; + unless ($expsecs <= 0) { #if it's already been suspended long enough, don't re-suspend + $delay_cancel = 1; + $date = $cancel_time + $expsecs; + } + } + #race condition: usage could be ongoing until unprovisioned #resolved by performing a change package instead (which unprovisions) and #later cancelling @@ -893,6 +907,11 @@ sub cancel { my %hash = $self->hash; if ( $date ) { $hash{'expire'} = $date; + if ($delay_cancel) { + $hash{'susp'} = $cancel_time unless $self->susp; + $hash{'adjourn'} = undef; + $hash{'resume'} = undef; + } } else { $hash{'cancel'} = $cancel_time; } @@ -1643,7 +1662,7 @@ sub unsuspend { ) or $hash{'order_date'} == $hash{'susp'} or $self->part_pkg->option('unused_credit_suspend') - or ( defined($reason) and $reason->unused_credit ) + or ( ref($reason) and $reason->unused_credit ) ) { $adjust_bill = 0; } @@ -3343,6 +3362,31 @@ sub statuscolor { $statuscolor{$self->status}; } +=item is_status_delay_cancel + +Returns true if part_pkg has option delay_cancel, +cust_pkg status is 'suspended' and expire is set +to cancel package within the next day (or however +many days are set in global config part_pkg-delay_cancel-days. + +This is not a real status, this only meant for hacking display +values, because otherwise treating the package as suspended is +really the whole point of the delay_cancel option. + +=cut + +sub is_status_delay_cancel { + my ($self) = @_; + return 0 unless $self->part_pkg->option('delay_cancel',1); + return 0 unless $self->status eq 'suspended'; + return 0 unless $self->expire; + my $conf = new FS::Conf; + my $expdays = $conf->config('part_pkg-delay_cancel-days') || 1; + my $expsecs = 60*60*24*$expdays; + return 0 unless $self->expire < time + $expsecs; + return 1; +} + =item pkg_label Returns a label for this package. (Currently "pkgnum: pkg - comment" or diff --git a/FS/FS/part_pkg/global_Mixin.pm b/FS/FS/part_pkg/global_Mixin.pm index 263772955..2318c3e61 100644 --- a/FS/FS/part_pkg/global_Mixin.pm +++ b/FS/FS/part_pkg/global_Mixin.pm @@ -40,6 +40,10 @@ tie my %a2billing_simultaccess, 'Tie::IxHash', ( 'changing packages', 'type' => 'checkbox', }, + 'delay_cancel' => { + 'name' => 'Automatically suspend for one day before cancelling', + 'type' => 'checkbox', + }, # miscellany--maybe put this in a separate module? @@ -109,6 +113,7 @@ tie my %a2billing_simultaccess, 'Tie::IxHash', ( unused_credit_cancel unused_credit_suspend unused_credit_change + delay_cancel a2billing_tariff a2billing_type diff --git a/FS/FS/reason.pm b/FS/FS/reason.pm index 864804d26..9c34dd98a 100644 --- a/FS/FS/reason.pm +++ b/FS/FS/reason.pm @@ -174,7 +174,7 @@ sub new_or_existing { } } else { my %hash = ('class' => $opt{'class'}, 'type' => $opt{'type'}); - my $reason_type = qsearchs('reason_type', \%hash) + $reason_type = qsearchs('reason_type', \%hash) || FS::reason_type->new(\%hash); $error = $reason_type->insert unless $reason_type->typenum; diff --git a/bin/xmlrpc-customer_bill_now b/bin/xmlrpc-customer_bill_now new file mode 100755 index 000000000..244b66f06 --- /dev/null +++ b/bin/xmlrpc-customer_bill_now @@ -0,0 +1,21 @@ +#!/usr/bin/perl + +use strict; +use Frontier::Client; +use Data::Dumper; + +my $uri = new URI 'http://localhost:8008/'; + +my $server = new Frontier::Client ( 'url' => $uri ); + +my $result = $server->call( + 'FS.API.bill_now', + 'secret' => 'sharingiscaring', + 'custnum' => 3, +); + +#die $result->{'error'} if $result->{'error'}; + +print Dumper($result); + +1; diff --git a/httemplate/elements/change_history_common.html b/httemplate/elements/change_history_common.html index 9e32bef33..9fc85aa53 100644 --- a/httemplate/elements/change_history_common.html +++ b/httemplate/elements/change_history_common.html @@ -106,6 +106,9 @@ $item->fields ) %> +% if ( $single_cust && $h_table_descripsub{$item->table} ) { + <% &{ $h_table_descripsub{$item->table} }( $item ) %> +% } @@ -172,18 +175,6 @@ my $svc_labelsub = sub { $label. ': '. encode_entities($item->label($item->history_date)). ''; }; -my $discounts = {}; -my $discount_labelsub = sub { - my($item, $label) = @_; - my $dnum = $item->discountnum; - $discounts->{$dnum} ||= qsearchs({ - 'table'=>'discount', - 'hashref'=>{'discountnum'=>$dnum} - }); - my $d = $discounts->{$dnum}; - $label . ': ' . encode_entities($d->description_short) . ''; -}; - my %h_table_labelsub = ( 'h_cust_pkg' => $pkg_labelsub, 'h_svc_acct' => $svc_labelsub, @@ -195,7 +186,24 @@ my %h_table_labelsub = ( 'h_svc_external' => $svc_labelsub, 'h_svc_phone' => $svc_labelsub, #'h_phone_device' - 'h_cust_pkg_discount' => $discount_labelsub, +); + +my $discounts = {}; +my $discount_descripsub = sub { + my($item) = @_; + $pkgpart{$item->pkgpart} ||= $item->part_pkg->pkg; + my $dnum = $item->discountnum; + $discounts->{$dnum} ||= qsearchs({ + 'table'=>'discount', + 'hashref'=>{'discountnum'=>$dnum} + }); + my $d = $discounts->{$dnum}; + '(' . encode_entities($d->description_short) . '' + . ' on ' . encode_entities($pkgpart{$item->pkgpart}) . ')'; +}; + +my %h_table_descripsub = ( + 'h_cust_pkg_discount' => $discount_descripsub, ); my $cust_pkg_date_format = '%b %o, %Y'; diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi index a78a8b3dc..f6fd1e915 100755 --- a/httemplate/misc/cust_main-cancel.cgi +++ b/httemplate/misc/cust_main-cancel.cgi @@ -54,10 +54,11 @@ if ( $error ) { } else { warn "cancelling $cust_main"; - $error = $cust_main->cancel( + my @error = $cust_main->cancel( #returns list of errors 'ban' => $ban, 'reason' => $reasonnum, ); + $error = join(', ',@error); } if ( $error ) { diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html index 15def3283..fe0e329c3 100644 --- a/httemplate/view/cust_main/misc.html +++ b/httemplate/view/cust_main/misc.html @@ -7,7 +7,7 @@ <% mt('Status') |h %> - <% $cust_main->status_label %> + <% $status_label %> % my @part_tag = $cust_main->part_tag; @@ -204,4 +204,9 @@ my $curuser = $FS::CurrentUser::CurrentUser; my @agentnums = $curuser->agentnums; +my $status_label = $cust_main->status_label; +if ($cust_main->is_status_delay_cancel) { + $status_label .= ' (Cancelled)'; +} +