diff options
author | Ivan Kohler <ivan@freeside.biz> | 2015-06-13 15:18:37 -0700 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2015-06-13 15:18:37 -0700 |
commit | 7beec7068e00be5ae1b2599fdf2b494bc19e31d0 (patch) | |
tree | 055e1d25694ccfdac3a2d5aca79441cf7a3d89b5 /FS | |
parent | 9e8d2a5bafb21c42f54cdd9b3703e2f88a36e0a8 (diff) | |
parent | eb6536b41456955fc425dba2e156a996e8fe2837 (diff) |
Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH
Diffstat (limited to 'FS')
33 files changed, 484 insertions, 214 deletions
diff --git a/FS/FS/ClientAPI.pm b/FS/FS/ClientAPI.pm index e4031b26e..1fea28c67 100644 --- a/FS/FS/ClientAPI.pm +++ b/FS/FS/ClientAPI.pm @@ -6,7 +6,7 @@ use vars qw( @EXPORT_OK %handler $domain $DEBUG $me ); @EXPORT_OK = qw( load_clientapi_modules ); -$DEBUG = 1; +$DEBUG = 0; $me = '[FS::ClientAPI]'; %handler = (); diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm index b6f8aa4c6..50597e2cb 100644 --- a/FS/FS/ClientAPI/MasonComponent.pm +++ b/FS/FS/ClientAPI/MasonComponent.pm @@ -22,6 +22,7 @@ my %allowed_comps = map { $_=>1 } qw( /misc/counties.cgi /misc/svc_acct-domains.cgi /misc/part_svc-columns.cgi + /edit/elements/svc_forward.html ); my %session_comps = map { $_=>1 } qw( diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index d9378d8a8..11523013c 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -139,6 +139,7 @@ sub skin_info { 'logo' => scalar($conf->config_binary('logo.png', $agentnum )), ( map { $_ => join("\n", $conf->config("selfservice-$_", $agentnum ) ) } qw( head body_header body_footer company_address ) ), + 'money_char' => $conf->config("money_char") || '$', 'menu' => join("\n", $conf->config("ng_selfservice-menu", $agentnum ) ) || 'main.php Home @@ -443,11 +444,13 @@ sub customer_info { if ( $session->{'pkgnum'} ) { #XXX open invoices in the pkg-balances case } else { + $return{'money_char'} = $conf->config("money_char") || '$'; my @open = map { { - invnum => $_->invnum, - date => time2str("%b %o, %Y", $_->_date), - owed => $_->owed, + invnum => $_->invnum, + date => time2str("%b %o, %Y", $_->_date), + owed => $_->owed, + charged => $_->charged, }; } $cust_main->open_cust_bill; $return{open_invoices} = \@open; @@ -1554,25 +1557,31 @@ sub list_invoices { my @cust_bill = grep ! $_->hide, $cust_main->cust_bill; my $balance = 0; + my $invoices = [ + map { + #not super efficient, we also run cust_bill_pay/cust_credited inside owed + my @payments_and_credits = sort {$b->_date <=> $a->_date} ($_->cust_bill_pay,$_->cust_credited); + 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), + 'lastpay' => @payments_and_credits + ? time2str("%b %o, %Y", $payments_and_credits[0]->_date) + : '', + } + } @cust_bill + ]; return { 'error' => '', 'balance' => $cust_main->balance, - '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), - } - } - @cust_bill - ], + 'money_char' => $conf->config("money_char") || '$', + 'invoices' => $invoices, 'legacy_invoices' => [ map { +{ 'legacyinvnum' => $_->legacyinvnum, @@ -2751,6 +2760,15 @@ sub provision_external { ); } +sub provision_forward { + my $p = shift; + _provision( 'FS::svc_forward', + ['srcsvc','src','dstsvc','dst'], + [], + $p, + ); +} + sub _provision { my( $class, $fields, $return_fields, $p ) = splice(@_, 0, 4); warn "_provision called for $class\n" @@ -2876,6 +2894,10 @@ sub part_svc_info { } } + if ($ret->{'svcdb'} eq 'svc_forward') { + $ret->{'forward_emails'} = {$cust_pkg->forward_emails()}; + } + $ret; } diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index 6db33ac86..547f230c8 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -667,7 +667,6 @@ sub new_customer { my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } ) or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" }; - my $svcpart = $part_pkg->svcpart($svc_x); my $reg_code = ''; if ( $packet->{'reg_code'} ) { @@ -685,50 +684,55 @@ sub new_customer { #my $error = $cust_pkg->check; #return { 'error' => $error } if $error; - #should be all auto-magic and shit my @svc = (); - if ( $svc_x eq 'svc_acct' ) { + unless ( $svc_x eq 'none' ) { - my $svc = new FS::svc_acct { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( username _password sec_phrase popnum domsvc ), - }; - - my @acct_snarf; - my $snarfnum = 1; - while ( exists($packet->{"snarf_machine$snarfnum"}) - && length($packet->{"snarf_machine$snarfnum"}) ) { - my $acct_snarf = new FS::acct_snarf ( { - 'machine' => $packet->{"snarf_machine$snarfnum"}, - 'protocol' => $packet->{"snarf_protocol$snarfnum"}, - 'username' => $packet->{"snarf_username$snarfnum"}, - '_password' => $packet->{"snarf_password$snarfnum"}, - } ); - $snarfnum++; - push @acct_snarf, $acct_snarf; - } - $svc->child_objects( \@acct_snarf ); - push @svc, $svc; + my $svcpart = $part_pkg->svcpart($svc_x); + #should be all auto-magic and shit + if ( $svc_x eq 'svc_acct' ) { - } elsif ( $svc_x eq 'svc_phone' ) { + my $svc = new FS::svc_acct { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( username _password sec_phrase popnum domsvc ), + }; - push @svc, new FS::svc_phone ( { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( countrycode phonenum sip_password pin ), - } ); + my @acct_snarf; + my $snarfnum = 1; + while ( exists($packet->{"snarf_machine$snarfnum"}) + && length($packet->{"snarf_machine$snarfnum"}) ) { + my $acct_snarf = new FS::acct_snarf ( { + 'machine' => $packet->{"snarf_machine$snarfnum"}, + 'protocol' => $packet->{"snarf_protocol$snarfnum"}, + 'username' => $packet->{"snarf_username$snarfnum"}, + '_password' => $packet->{"snarf_password$snarfnum"}, + } ); + $snarfnum++; + push @acct_snarf, $acct_snarf; + } + $svc->child_objects( \@acct_snarf ); + push @svc, $svc; - } elsif ( $svc_x eq 'svc_pbx' ) { + } elsif ( $svc_x eq 'svc_phone' ) { - push @svc, new FS::svc_pbx ( { + push @svc, new FS::svc_phone ( { 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( id title ), - } ); + map { $_ => $packet->{$_} } + qw( countrycode phonenum sip_password pin ), + } ); + + } elsif ( $svc_x eq 'svc_pbx' ) { + + push @svc, new FS::svc_pbx ( { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( id title ), + } ); - } else { - die "unknown signup service $svc_x"; + } else { + die "unknown signup service $svc_x"; + } + } if ($packet->{'mac_addr'} && $conf->exists('signup_server-mac_addr_svcparts')) @@ -983,7 +987,6 @@ sub new_customer_minimal { my $part_pkg = qsearchs( 'part_pkg', { 'pkgpart' => $pkgpart } ) or return { 'error' => "WARNING: unknown pkgpart: $pkgpart" }; - my $svcpart = $part_pkg->svcpart($svc_x); my $cust_pkg = new FS::cust_pkg ( { #later#'custnum' => $custnum, @@ -992,35 +995,40 @@ sub new_customer_minimal { #my $error = $cust_pkg->check; #return { 'error' => $error } if $error; - #should be all auto-magic and shit - if ( $svc_x eq 'svc_acct' ) { + unless ( $svc_x eq 'none' ) { - my $svc = new FS::svc_acct { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( username _password sec_phrase popnum domsvc ), - }; + my $svcpart = $part_pkg->svcpart($svc_x); + #should be all auto-magic and shit + if ( $svc_x eq 'svc_acct' ) { - push @svc, $svc; + my $svc = new FS::svc_acct { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( username _password sec_phrase popnum domsvc ), + }; - } elsif ( $svc_x eq 'svc_phone' ) { + push @svc, $svc; - push @svc, new FS::svc_phone ( { - 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( countrycode phonenum sip_password pin ), - } ); - - } elsif ( $svc_x eq 'svc_pbx' ) { + } elsif ( $svc_x eq 'svc_phone' ) { - push @svc, new FS::svc_pbx ( { + push @svc, new FS::svc_phone ( { 'svcpart' => $svcpart, - map { $_ => $packet->{$_} } - qw( id title ), - } ); + map { $_ => $packet->{$_} } + qw( countrycode phonenum sip_password pin ), + } ); + + } elsif ( $svc_x eq 'svc_pbx' ) { + + push @svc, new FS::svc_pbx ( { + 'svcpart' => $svcpart, + map { $_ => $packet->{$_} } + qw( id title ), + } ); - } else { - die "unknown signup service $svc_x"; + } else { + die "unknown signup service $svc_x"; + } + } foreach my $svc ( @svc ) { @@ -1068,6 +1076,7 @@ sub new_customer_minimal { 'signup_service' => $svc_x, 'custnum' => $cust_main->custnum, 'session_id' => $session_id, + map { $_ => $cust_main->$_ } qw( first last company ), ); if ( $svc[0] ) { diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 38ccb6eaa..d2113616c 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -2377,6 +2377,7 @@ and customer address. Include units.', 'svc_acct' => 'Account (svc_acct)', 'svc_phone' => 'Phone number (svc_phone)', 'svc_pbx' => 'PBX (svc_pbx)', + 'none' => 'None - package only', ], }, @@ -5964,6 +5965,13 @@ and customer address. Include units.', 'type' => 'checkbox', }, + { + 'key' => 'default_appointment_length', + 'section' => 'UI', + 'description' => 'Default appointment length, in minutes (30 minute granularity).', + 'type' => 'text', + }, + { key => "apacheroot", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" }, { key => "apachemachine", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" }, { key => "apachemachines", section => "deprecated", description => "<b>DEPRECATED</b>", type => "text" }, diff --git a/FS/FS/L10N/es_cu.pm b/FS/FS/L10N/es_cu.pm new file mode 100644 index 000000000..f556c1f4b --- /dev/null +++ b/FS/FS/L10N/es_cu.pm @@ -0,0 +1,4 @@ +package FS::L10N::es_cu; +use base qw(FS::L10N::es_es); + +1; diff --git a/FS/FS/L10N/es_es.pm b/FS/FS/L10N/es_es.pm new file mode 100644 index 000000000..4071731b3 --- /dev/null +++ b/FS/FS/L10N/es_es.pm @@ -0,0 +1,6 @@ +package FS::L10N::es_es; +use base qw(FS::L10N::DBI); + +our %Lexicon = (); + +1; diff --git a/FS/FS/L10N/es_mx.pm b/FS/FS/L10N/es_mx.pm new file mode 100644 index 000000000..1901b9db4 --- /dev/null +++ b/FS/FS/L10N/es_mx.pm @@ -0,0 +1,4 @@ +package FS::L10N::es_mx; +use base qw(FS::L10N::es_es); + +1; diff --git a/FS/FS/L10N/es_us.pm b/FS/FS/L10N/es_us.pm new file mode 100644 index 000000000..838b0475c --- /dev/null +++ b/FS/FS/L10N/es_us.pm @@ -0,0 +1,4 @@ +package FS::L10N::es_us; +use base qw(FS::L10N::es_es); + +1; diff --git a/FS/FS/Locales.pm b/FS/FS/Locales.pm index 032c3b59a..849929cfa 100644 --- a/FS/FS/Locales.pm +++ b/FS/FS/Locales.pm @@ -31,6 +31,10 @@ tie our %locales, 'Tie::IxHash', 'en_US', { name => 'English', country => 'United States', }, 'en_AU', { name => 'English', country => 'Australia', }, 'en_CA', { name => 'English', country => 'Canada', }, + 'es_ES', { name => 'Spanish', country => 'Spain', }, + 'es_CU', { name => 'Spanish', country => 'Cuba', }, + 'es_MX', { name => 'Spanish', country => 'Mexico', }, + 'es_US', { name => 'Spanish', country => 'United States', }, 'fr_CA', { name => 'French', country => 'Canada', }, 'fr_FR', { name => 'French', country => 'France', }, 'fr_HT', { name => 'French', country => 'Haiti', }, diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 88d452ae9..72745fe13 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -3,7 +3,7 @@ package FS::Record; use strict; use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG %virtual_fields_cache - $conf $conf_encryption $money_char $lat_lower $lon_upper + $money_char $lat_lower $lon_upper $me $nowarn_identical $nowarn_classload $no_update_diff $no_history $qsearch_qualify_columns @@ -61,14 +61,20 @@ my $rsa_loaded; my $rsa_encrypt; my $rsa_decrypt; -$conf = ''; -$conf_encryption = ''; +our $conf = ''; +our $conf_encryption = ''; +our $conf_encryptionmodule = ''; +our $conf_encryptionpublickey = ''; +our $conf_encryptionprivatekey = ''; FS::UID->install_callback( sub { eval "use FS::Conf;"; die $@ if $@; $conf = FS::Conf->new; - $conf_encryption = $conf->exists('encryption'); + $conf_encryption = $conf->exists('encryption'); + $conf_encryptionmodule = $conf->config('encryptionmodule'); + $conf_encryptionpublickey = join("\n",$conf->config('encryptionpublickey')); + $conf_encryptionprivatekey = join("\n",$conf->config('encryptionprivatekey')); $money_char = $conf->config('money_char') || '$'; my $nw_coords = $conf->exists('geocode-require_nw_coordinates'); $lat_lower = $nw_coords ? 1 : -90; @@ -1158,7 +1164,7 @@ sub insert { # Encrypt before the database if ( defined(eval '@FS::'. $table . '::encrypted_fields') && scalar( eval '@FS::'. $table . '::encrypted_fields') - && $conf->exists('encryption') + && $conf_encryption ) { foreach my $field (eval '@FS::'. $table . '::encrypted_fields') { next if $field eq 'payinfo' @@ -1399,7 +1405,7 @@ sub replace { # Encrypt for replace my $saved = {}; - if ( $conf->exists('encryption') + if ( $conf_encryption && defined(eval '@FS::'. $new->table . '::encrypted_fields') && scalar( eval '@FS::'. $new->table . '::encrypted_fields') ) { @@ -2160,7 +2166,7 @@ sub _h_statement { ; # If we're encrypting then don't store the payinfo in the history - if ( $conf && $conf->exists('encryption') && $self->table ne 'banned_pay' ) { + if ( $conf_encryption && $self->table ne 'banned_pay' ) { @fields = grep { $_ ne 'payinfo' } @fields; } @@ -3057,7 +3063,7 @@ sub encrypt { my ($self, $value) = @_; my $encrypted = $value; - if ($conf->exists('encryption')) { + if ($conf_encryption) { if ($self->is_encrypted($value)) { # Return the original value if it isn't plaintext. $encrypted = $value; @@ -3105,7 +3111,7 @@ You should generally not have to worry about calling this, as the system handles sub decrypt { my ($self,$value) = @_; my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted. - if ($conf->exists('encryption') && $self->is_encrypted($value)) { + if ($conf_encryption && $self->is_encrypted($value)) { $self->loadRSA; if (ref($rsa_decrypt) =~ /::RSA/) { my $encrypted = unpack ("u*", $value); @@ -3121,8 +3127,8 @@ sub loadRSA { #Initialize the Module $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default - if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') { - $rsa_module = $conf->config('encryptionmodule'); + if ($conf_encryptionmodule && $conf_encryptionmodule ne '') { + $rsa_module = $conf_encryptionmodule; } if (!$rsa_loaded) { @@ -3130,15 +3136,13 @@ sub loadRSA { $rsa_loaded++; } # Initialize Encryption - if ($conf->exists('encryptionpublickey') && $conf->config('encryptionpublickey') ne '') { - my $public_key = join("\n",$conf->config('encryptionpublickey')); - $rsa_encrypt = $rsa_module->new_public_key($public_key); + if ($conf_encryptionpublickey && $conf_encryptionpublickey ne '') { + $rsa_encrypt = $rsa_module->new_public_key($conf_encryptionpublickey); } # Intitalize Decryption - if ($conf->exists('encryptionprivatekey') && $conf->config('encryptionprivatekey') ne '') { - my $private_key = join("\n",$conf->config('encryptionprivatekey')); - $rsa_decrypt = $rsa_module->new_private_key($private_key); + if ($conf_encryptionprivatekey && $conf_encryptionprivatekey ne '') { + $rsa_decrypt = $rsa_module->new_private_key($conf_encryptionprivatekey); } } diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 5dcf3c3c2..3a27b741b 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2447,6 +2447,7 @@ sub tables_hashref { 'columnlabel', 'varchar', 'NULL', $char_d, '', '', 'columnvalue', 'varchar', 'NULL', 512, '', '', 'columnflag', 'char', 'NULL', 1, '', '', + 'required', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'columnnum', 'unique' => [ [ 'svcpart', 'columnname' ] ], diff --git a/FS/FS/cdr/FS/FS/cdr/vvs.pm b/FS/FS/cdr/FS/FS/cdr/vvs.pm new file mode 100644 index 000000000..63a647ee8 --- /dev/null +++ b/FS/FS/cdr/FS/FS/cdr/vvs.pm @@ -0,0 +1,33 @@ +package FS::cdr::vvs; + +use strict; +use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year ); +use Time::Local; +use FS::cdr qw(_cdr_date_parser_maker); + +@ISA = qw(FS::cdr); + +%info = ( + 'name' => 'VVS', + 'weight' => 120, + 'header' => 1, + 'import_fields' => [ + + skip(1), # i_customer + 'accountcode', # account_id + 'src', # caller + 'dst', # called + skip(2), # reason + # call id + _cdr_date_parser_maker('startdate'), # time + 'billsec', # duration + skip(3), # ringtime + # status + # resller_charge + 'upstream_price',# customer_charge + ], +); + +sub skip { map {''} (1..$_[0]) } + +1; diff --git a/FS/FS/cdr/earthlink.pm b/FS/FS/cdr/earthlink.pm index 60cba654f..213a025c2 100644 --- a/FS/FS/cdr/earthlink.pm +++ b/FS/FS/cdr/earthlink.pm @@ -36,6 +36,8 @@ use Date::Parse; }, #TERM NUMBER skip(2), #TERM CITY / TERM STATE _cdr_min_parser_maker, #MINUTES + skip(1), #AMOUNT + 'disposition', #Call Type ], ); diff --git a/FS/FS/cdr/enswitch.pm b/FS/FS/cdr/enswitch.pm index 1cdd802fc..8026293e2 100644 --- a/FS/FS/cdr/enswitch.pm +++ b/FS/FS/cdr/enswitch.pm @@ -34,7 +34,13 @@ use FS::cdr_type; #Inbound called type, #Inbound called number, #Inbound destination type, Inbound destination number, - 'disposition', #Outbound calling type, + sub { my ($cdr, $data) = @_; + $data ||= 'none'; + + my $cdr_type = qsearchs('cdr_type', { 'cdrtypename' => $data } ); + $cdr->set('cdrtypenum', $cdr_type->cdrtypenum) if $cdr_type; + } , #Outbound calling type, + skip(11), #Outbound calling number, #Outbound called type, Outbound called number, #Outbound destination type, Outbound destination number, @@ -53,23 +59,4 @@ use FS::cdr_type; sub skip { map {''} (1..$_[0]) } -#create CDR types with names matching in_calling_type valuesj - 'none' -# (without the quotes) for blank -our %cdr_type = (); -sub in_calling_type { - my ($record, $data) = @_; - - $data ||= 'none'; - - my $cdr_type = exists($cdr_type{$data}) - ? $cdr_type{$data} - : qsearchs('cdr_type', { 'cdrtypename' => $data } ); - - $cdr_type{$data} = $cdr_type; - - $record->set('in_calling_type', $data); #for below - $record->set('cdrtypenum', $cdr_type->cdrtypenum) if $cdr_type; - -} - 1; diff --git a/FS/FS/cdr/ispphone.pm b/FS/FS/cdr/ispphone.pm index 2817d53c1..c2ba1862d 100644 --- a/FS/FS/cdr/ispphone.pm +++ b/FS/FS/cdr/ispphone.pm @@ -15,8 +15,16 @@ use Date::Parse; 'import_fields' => [ 'accountcode', # Accountcode - 'src', # Form - 'dst', # To + sub { my ($cdr, $src) = @_; + $src =~ s/^\s+//; + $cdr->set('src', $src); + + }, # Form + sub { my ($cdr, $dst) = @_; + $dst =~ s/^\s+//; + $cdr->set('dst', $dst); + + }, # To skip(1), # Country 'upstream_dst_regionname', # Description _cdr_date_parser_maker('startdate'), #DateTime diff --git a/FS/FS/cdr/vss.pm b/FS/FS/cdr/vss.pm index 6fc647aad..a550303df 100644 --- a/FS/FS/cdr/vss.pm +++ b/FS/FS/cdr/vss.pm @@ -13,7 +13,6 @@ use FS::cdr qw(_cdr_date_parser_maker); 'header' => 1, 'import_fields' => [ - skip(1), # Customer skip(1), # i_customer 'accountcode', # account_id 'src', # caller diff --git a/FS/FS/cust_bill_pay_pkg.pm b/FS/FS/cust_bill_pay_pkg.pm index 5d154d0b7..24ca7973a 100644 --- a/FS/FS/cust_bill_pay_pkg.pm +++ b/FS/FS/cust_bill_pay_pkg.pm @@ -1,14 +1,12 @@ package FS::cust_bill_pay_pkg; +use base qw( FS::cust_main_Mixin FS::Record ); use strict; -use vars qw( @ISA ); use FS::Conf; use FS::Record qw( qsearch qsearchs ); use FS::cust_bill_pay; use FS::cust_bill_pkg; -@ISA = qw(FS::Record); - =head1 NAME FS::cust_bill_pay_pkg - Object methods for cust_bill_pay_pkg records diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index 70c7adc7a..4448da62e 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -837,6 +837,8 @@ sub _item_discount { description => $self->mt('Discount'), amount => 0, ext_description => \@ext, + pkgpart => $self->pkgpart, + feepart => $self->feepart, # maybe should show quantity/unit discount? }; foreach my $pkg_discount (@pkg_discounts) { diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index d4bd1f304..5980bf5de 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -343,8 +343,9 @@ sub realtime_bop { my $cc_surcharge = 0; my $cc_surcharge_pct = 0; $cc_surcharge_pct = $conf->config('credit-card-surcharge-percentage') - if $conf->config('credit-card-surcharge-percentage'); - + if $conf->config('credit-card-surcharge-percentage') + && $options{method} eq 'CC'; + # always add cc surcharge if called from event if($options{'cc_surcharge_from_event'} && $cc_surcharge_pct > 0) { $cc_surcharge = $options{'amount'} * $cc_surcharge_pct / 100; diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index a03a44653..14555dd67 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -2043,7 +2043,7 @@ sub change { return "modifying package: $error"; } else { $dbh->commit if $oldAutoCommit; - return ''; + return $self; } } @@ -3723,6 +3723,7 @@ Returns the parent customer object (see L<FS::cust_main>). sub cust_main { my $self = shift; + cluck 'cust_pkg->cust_main called' if $DEBUG; qsearchs( 'cust_main', { 'custnum' => $self->custnum } ); } @@ -5666,6 +5667,78 @@ sub bulk_change { ''; } +=item forward_emails + +Returns a hash of svcnums and corresponding email addresses +for svc_acct services that can be used as source or dest +for svc_forward services provisioned in this package. + +Accepts options I<svc_forward> OR I<svcnum> for a svc_forward +service; if included, will ensure the current values of the +specified service are included in the list, even if for some +other reason they wouldn't be. If called as a class method +with a specified service, returns only these current values. + +Caution: does not actually check if svc_forward services are +available to be provisioned on this package. + +=cut + +sub forward_emails { + my $self = shift; + my %opt = @_; + + #load optional service, thoroughly validated + die "Use svcnum or svc_forward, not both" + if $opt{'svcnum'} && $opt{'svc_forward'}; + my $svc_forward = $opt{'svc_forward'}; + $svc_forward ||= qsearchs('svc_forward',{ 'svcnum' => $opt{'svcnum'} }) + if $opt{'svcnum'}; + die "Specified service is not a forward service" + if $svc_forward && (ref($svc_forward) ne 'FS::svc_forward'); + die "Specified service not found" + if ($opt{'svcnum'} || $opt{'svc_forward'}) && !$svc_forward; + + my %email; + + ## everything below was basically copied from httemplate/edit/svc_forward.cgi + ## with minimal refactoring, not sure why we can't just load all svc_accts for this custnum + + #add current values from specified service, if there was one + if ($svc_forward) { + foreach my $method (qw( srcsvc_acct dstsvc_acct )) { + my $svc_acct = $svc_forward->$method(); + $email{$svc_acct->svcnum} = $svc_acct->email if $svc_acct; + } + } + + if (ref($self) eq 'FS::cust_pkg') { + + #and including the rest for this customer + my($u_part_svc,@u_acct_svcparts); + foreach $u_part_svc ( qsearch('part_svc',{'svcdb'=>'svc_acct'}) ) { + push @u_acct_svcparts,$u_part_svc->getfield('svcpart'); + } + + my $custnum = $self->getfield('custnum'); + foreach my $i_cust_pkg ( qsearch('cust_pkg',{'custnum'=>$custnum}) ) { + my $cust_pkgnum = $i_cust_pkg->getfield('pkgnum'); + #now find the corresponding record(s) in cust_svc (for this pkgnum!) + foreach my $acct_svcpart (@u_acct_svcparts) { + foreach my $i_cust_svc ( + qsearch( 'cust_svc', { 'pkgnum' => $cust_pkgnum, + 'svcpart' => $acct_svcpart } ) + ) { + my $svc_acct = qsearchs( 'svc_acct', { 'svcnum' => $i_cust_svc->svcnum } ); + $email{$svc_acct->svcnum} = $svc_acct->email; + } + } + } + } + + return %email; +} + # Used by FS::Upgrade to migrate to a new database. sub _upgrade_data { # class method my ($class, %opts) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_spool_csv.pm b/FS/FS/part_event/Action/cust_bill_spool_csv.pm index 250c83042..0d84e77c4 100644 --- a/FS/FS/part_event/Action/cust_bill_spool_csv.pm +++ b/FS/FS/part_event/Action/cust_bill_spool_csv.pm @@ -33,6 +33,10 @@ sub option_fields { empty_label => '(do not upload)', order_by => 'targetnum', }, + 'skip_nopost' => { label => 'Skip customers without postal billing enabled', + type => 'checkbox', + value => 'Y', + }, ); } @@ -44,6 +48,9 @@ sub do_action { #my $cust_main = $self->cust_main($cust_bill); my $cust_main = $cust_bill->cust_main; + return if $self->option('skip_nopost') + && ! grep { $_ eq 'POST' } $cust_main->invoicing_list; + $cust_bill->spool_csv( 'time' => $cust_event->_date, 'format' => $self->option('spoolformat'), diff --git a/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm b/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm index 96d9da8d8..5a81fbbd0 100644 --- a/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm +++ b/FS/FS/part_event/Condition/cust_bill_age_before_payby.pm @@ -34,12 +34,12 @@ sub condition { 'order_by' => 'ORDER BY history_date DESC LIMIT 1', })) { - my $newest = $replace_new->history_date; + $newest = $replace_new->history_date; my $replace_old = qsearchs({ 'table' => 'h_cust_main', 'hashref' => { 'custnum' => $replace_new->custnum, 'history_action' => 'replace_old', - 'history_date' => $replace_new->history_date, + 'history_date' => $replace_new->history_date, #fuzz? } }) or next; #no replace_old? ignore and continue on i guess diff --git a/FS/FS/part_pkg_link.pm b/FS/FS/part_pkg_link.pm index 8e43d155e..ce071ef17 100644 --- a/FS/FS/part_pkg_link.pm +++ b/FS/FS/part_pkg_link.pm @@ -130,6 +130,7 @@ sub insert { return $error if $error; } + $dbh->commit if $oldAutoCommit; return; } @@ -166,7 +167,7 @@ sub delete { $dbh->rollback if $oldAutoCommit; return $error; } - $dbh->commit; + $dbh->commit if $oldAutoCommit; return; } diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index c2003c4aa..8a5fc35c9 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -96,8 +96,12 @@ the part_svc_column table appropriately (see L<FS::part_svc_column>). =item I<svcdb>__I<field> - Default or fixed value for I<field> in I<svcdb>. +=item I<svcdb>__I<field>_label + =item I<svcdb>__I<field>_flag - defines I<svcdb>__I<field> action: null or empty (no default), `D' for default, `F' for fixed (unchangeable), , `S' for selectable choice, `M' for manual selection from inventory, or `A' for automatic selection from inventory. For virtual fields, can also be 'X' for excluded. +=item I<svcdb>__I<field>_required - I<field> should always have a true value + =back If you want to add part_svc_column records for fields that do not exist as @@ -139,52 +143,53 @@ sub insert { # add part_svc_column records my $svcdb = $self->svcdb; -# my @rows = map { /^${svcdb}__(.*)$/; $1 } -# grep ! /_flag$/, -# grep /^${svcdb}__/, -# fields('part_svc'); - foreach my $field ( - grep { $_ ne 'svcnum' - && ( defined( $self->getfield($svcdb.'__'.$_.'_flag') ) - || $self->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ ) - } (fields($svcdb), @fields) - ) { - my $part_svc_column = $self->part_svc_column($field); - my $previous = qsearchs('part_svc_column', { - 'svcpart' => $self->svcpart, - 'columnname' => $field, - } ); + foreach my $field (fields($svcdb), @fields) { + next if $field eq 'svcnum'; + my $prefix = $svcdb.'__'; + if ( defined( $self->getfield($prefix.$field.'_flag')) + or defined($self->getfield($prefix.$field.'_required')) + or length($self->getfield($prefix.$field.'_label')) + ) { + my $part_svc_column = $self->part_svc_column($field); + my $previous = qsearchs('part_svc_column', { + 'svcpart' => $self->svcpart, + 'columnname' => $field, + } ); - my $flag = $self->getfield($svcdb.'__'.$field.'_flag'); - my $label = $self->getfield($svcdb.'__'.$field.'_label'); - if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { + my $flag = $self->getfield($prefix.$field.'_flag'); + my $label = $self->getfield($prefix.$field.'_label'); + my $required = $self->getfield($prefix.$field.'_required') ? 'Y' : ''; + if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { - if ( uc($flag) =~ /^([A-Z])$/ ) { - my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} - || sub { shift }; - $part_svc_column->setfield('columnflag', $1); - $part_svc_column->setfield('columnvalue', - &$parser($self->getfield($svcdb.'__'.$field)) - ); - } + if ( uc($flag) =~ /^([A-Z])$/ ) { + my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} + || sub { shift }; + $part_svc_column->setfield('columnflag', $1); + $part_svc_column->setfield('columnvalue', + &$parser($self->getfield($prefix.$field)) + ); + } - $part_svc_column->setfield('columnlabel', $label) - if $label !~ /^\s*$/; + $part_svc_column->setfield('columnlabel', $label) + if $label !~ /^\s*$/; + + $part_svc_column->setfield('required', $required); + + if ( $previous ) { + $error = $part_svc_column->replace($previous); + } else { + $error = $part_svc_column->insert; + } - if ( $previous ) { - $error = $part_svc_column->replace($previous); } else { - $error = $part_svc_column->insert; + $error = $previous ? $previous->delete : ''; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - } else { - $error = $previous ? $previous->delete : ''; - } - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; } - } # add export_svc records @@ -277,50 +282,54 @@ sub replace { # maintain part_svc_column records my $svcdb = $new->svcdb; - foreach my $field ( - grep { $_ ne 'svcnum' - && ( defined( $new->getfield($svcdb.'__'.$_.'_flag') ) - || $new->getfield($svcdb.'__'.$_.'_label') !~ /^\s*$/ ) - } (fields($svcdb),@fields) - ) { - - my $part_svc_column = $new->part_svc_column($field); - my $previous = qsearchs('part_svc_column', { - 'svcpart' => $new->svcpart, - 'columnname' => $field, - } ); - - my $flag = $new->getfield($svcdb.'__'.$field.'_flag'); - my $label = $new->getfield($svcdb.'__'.$field.'_label'); + foreach my $field (fields($svcdb),@fields) { + next if $field eq 'svcnum'; + my $prefix = $svcdb.'__'; + if ( defined( $new->getfield($prefix.$field.'_flag')) + or defined($new->getfield($prefix.$field.'_required')) + or length($new->getfield($prefix.$field.'_label')) + ) { + my $part_svc_column = $new->part_svc_column($field); + my $previous = qsearchs('part_svc_column', { + 'svcpart' => $new->svcpart, + 'columnname' => $field, + } ); + + my $flag = $new->getfield($svcdb.'__'.$field.'_flag'); + my $label = $new->getfield($svcdb.'__'.$field.'_label'); + my $required = $new->getfield($svcdb.'__'.$field.'_required') ? 'Y' : ''; - if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { + if ( uc($flag) =~ /^([A-Z])$/ || $label !~ /^\s*$/ ) { + + if ( uc($flag) =~ /^([A-Z])$/ ) { + $part_svc_column->setfield('columnflag', $1); + my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} + || sub { shift }; + $part_svc_column->setfield('columnvalue', + &$parser($new->getfield($svcdb.'__'.$field)) + ); + } else { + $part_svc_column->setfield('columnflag', ''); + $part_svc_column->setfield('columnvalue', ''); + } - if ( uc($flag) =~ /^([A-Z])$/ ) { - $part_svc_column->setfield('columnflag', $1); - my $parser = FS::part_svc->svc_table_fields($svcdb)->{$field}->{parse} - || sub { shift }; - $part_svc_column->setfield('columnvalue', - &$parser($new->getfield($svcdb.'__'.$field)) - ); - } else { - $part_svc_column->setfield('columnflag', ''); - $part_svc_column->setfield('columnvalue', ''); - } + $part_svc_column->setfield('columnlabel', $label) + if $label !~ /^\s*$/; - $part_svc_column->setfield('columnlabel', $label) - if $label !~ /^\s*$/; + $part_svc_column->setfield('required', $required); - if ( $previous ) { - $error = $part_svc_column->replace($previous); + if ( $previous ) { + $error = $part_svc_column->replace($previous); + } else { + $error = $part_svc_column->insert; + } } else { - $error = $part_svc_column->insert; + $error = $previous ? $previous->delete : ''; + } + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; } - } else { - $error = $previous ? $previous->delete : ''; - } - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; } } @@ -594,6 +603,7 @@ sub svc_x { =cut my $svc_defs; +my $svc_info; sub _svc_defs { return $svc_defs if $svc_defs; #cache @@ -648,7 +658,14 @@ sub _svc_defs { sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} } keys %info, ; - + + tie my %svc_info, 'Tie::IxHash', + map { $_ => $info{$_} } + sort { $info{$a}->{'display_weight'} <=> $info{$b}->{'display_weight'} } + keys %info, + ; + + $svc_info = \%svc_info; #access via svc_table_info $svc_defs = \%svc_defs; #cache } @@ -700,6 +717,8 @@ some components specified by "select-.*.html", and a bunch more... =item select_allow_empty - Used with select_table, adds an empty option +=item required - This field should always have a true value (do not use with type checkbox or disabled) + =back =cut @@ -722,6 +741,27 @@ sub svc_table_fields { $def; } +=item svc_table_info TABLE + +Returns table_info for TABLE from cache, or empty +hashref if none is found. + +Caution: caches table_info for ALL services when run; +access a service's table_info directly unless you know +you're loading them all. + +Caution: does not standardize fields into hashrefs; +use L</svc_table_fields> to access fields. + +=cut + +sub svc_table_info { + my $class = shift; + my $table = shift; + $class->_svc_defs; #creates cache if needed + return $svc_info->{$table} || {}; +} + =back =head1 SUBROUTINES @@ -777,7 +817,7 @@ sub process { and ref($param->{ $f }) ) { $param->{ $f } = join(',', @{ $param->{ $f } }); } - ( $f, $f.'_flag', $f.'_label' ); + ( $f, $f.'_flag', $f.'_label', $f.'_required' ); } @fields; diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm index 38ce1fa80..75a2dfb1a 100644 --- a/FS/FS/part_svc_column.pm +++ b/FS/FS/part_svc_column.pm @@ -45,6 +45,8 @@ fields are currently supported: =item columnflag - null or empty (no default), `D' for default, `F' for fixed (unchangeable), `S' for selectable choice, `M' for manual selection from inventory, `A' for automatic selection from inventory, or `H' for selection from a hardware class. For virtual fields, can also be 'X' for excluded. +=item required - column value expected to be true + =back =head1 METHODS @@ -91,6 +93,7 @@ sub check { || $self->ut_alpha('columnname') || $self->ut_textn('columnlabel') || $self->ut_anything('columnvalue') + || $self->ut_flag('required') ; return $error if $error; diff --git a/FS/FS/pay_batch/RBC.pm b/FS/FS/pay_batch/RBC.pm index 45e888dc3..47fc8d49d 100644 --- a/FS/FS/pay_batch/RBC.pm +++ b/FS/FS/pay_batch/RBC.pm @@ -4,6 +4,7 @@ use strict; use vars qw(@ISA %import_info %export_info $name); use Date::Format 'time2str'; use FS::Conf; +use Encode 'encode'; my $conf; my ($client_num, $shortname, $longname, $trans_code, $testmode, $i, $declined, $totaloffset); @@ -173,9 +174,9 @@ $name = 'RBC'; ' '. sprintf("%010.0f",$cust_pay_batch->amount*100). ' '. - time2str("%Y%j", $pay_batch->download). - sprintf("%-30s", $cust_pay_batch->cust_main->first . ' ' . - $cust_pay_batch->cust_main->last). + time2str("%Y%j", time + 86400). + sprintf("%-30.30s", encode('utf8', $cust_pay_batch->cust_main->first . ' ' . + $cust_pay_batch->cust_main->last)). 'E'. # English ' '. sprintf("%-15s", $shortname). diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 61a5f6aa8..d70209aef 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -155,13 +155,48 @@ sub cust_linked { Checks the validity of fields in this record. -At present, this does nothing but call FS::Record::check (which, in turn, -does nothing but run virtual field checks). +Only checks fields marked as required in table_info or +part_svc_column definition. Should be invoked by service-specific +check using SUPER. Invokes FS::Record::check using SUPER. =cut sub check { my $self = shift; + + ## Checking required fields + + # get fields marked as required in table_info + my $required = {}; + my $labels = {}; + my $tinfo = $self->can('table_info') ? $self->table_info : {}; + if ($tinfo->{'manual_require'}) { + my $fields = $tinfo->{'fields'} || {}; + foreach my $field (keys %$fields) { + if (ref($fields->{$field}) && $fields->{$field}->{'required'}) { + $required->{$field} = 1; + $labels->{$field} = $fields->{$field}->{'label'}; + } + } + # add fields marked as required in database + foreach my $column ( + qsearch('part_svc_column',{ + 'svcpart' => $self->svcpart, + 'required' => 'Y' + }) + ) { + $required->{$column->columnname} = 1; + $labels->{$column->columnname} = $column->columnlabel; + } + # do the actual checking + foreach my $field (keys %$required) { + unless (length($self->get($field)) > 0) { + my $name = $labels->{$field} || $field; + return "$name is required\n" + } + } + } + $self->SUPER::check; } diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 93e3f2c6a..9636b3ed1 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -261,6 +261,7 @@ sub table_info { 'display_weight' => 10, 'cancel_weight' => 50, 'ip_field' => 'slipip', + 'manual_require' => 1, 'fields' => { 'dir' => 'Home directory', 'uid' => { @@ -284,6 +285,7 @@ sub table_info { disable_default => 1, disable_fixed => 1, disable_select => 1, + required => 1, }, 'password_selfchange' => { label => 'Password modification', type => 'checkbox', @@ -311,7 +313,9 @@ sub table_info { type => 'text', disable_inventory => 1, }, - '_password' => 'Password', + '_password' => { label => 'Password', + required => 1 + }, 'gid' => { label => 'GID', def_info => 'when blank, defaults to UID', @@ -334,6 +338,7 @@ sub table_info { select_key => 'svcnum', select_label => 'domain', disable_inventory => 1, + required => 1, }, 'pbxsvc' => { label => 'PBX', type => 'select-svc_pbx.html', diff --git a/FS/FS/svc_alarm.pm b/FS/FS/svc_alarm.pm index 0624f18a8..2332f11b6 100644 --- a/FS/FS/svc_alarm.pm +++ b/FS/FS/svc_alarm.pm @@ -214,6 +214,10 @@ sub check { ; return $error if $error; + #really just an signed int, but to discourage storing other data (e.g. phone) + return 'CS Receiver must be 9 digits or less' + if $self->cs_receiver =~ /\d{10}/; + $self->SUPER::check; } diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 40f2c9b2d..2cdc284d7 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -103,6 +103,7 @@ sub table_info { 'display_weight' => 50, 'cancel_weight' => 70, 'ip_field' => 'ip_addr', + 'manual_require' => 1, 'fields' => { 'svcnum' => 'Service', 'description' => 'Descriptive label', diff --git a/FS/FS/svc_dish.pm b/FS/FS/svc_dish.pm index 5c9e21710..2d249d17f 100644 --- a/FS/FS/svc_dish.pm +++ b/FS/FS/svc_dish.pm @@ -63,9 +63,10 @@ sub table_info { 'name' => 'Dish service', 'display_weight' => 58, 'cancel_weight' => 85, + 'manual_require' => 1, 'fields' => { 'svcnum' => { label => 'Service' }, - 'acctnum' => { label => 'DISH account#', %opts }, + 'acctnum' => { label => 'DISH account#', required => 1, %opts }, 'installdate' => { label => 'Install date', %opts }, 'note' => { label => 'Installation notes', %opts }, } diff --git a/FS/FS/svc_hardware.pm b/FS/FS/svc_hardware.pm index d9c3c464e..2be8954c8 100644 --- a/FS/FS/svc_hardware.pm +++ b/FS/FS/svc_hardware.pm @@ -76,6 +76,7 @@ sub table_info { 'name_plural' => 'Hardware', 'display_weight' => 59, 'cancel_weight' => 86, + 'manual_require' => 1, 'fields' => { 'svcnum' => { label => 'Service' }, 'typenum' => { label => 'Device type', @@ -84,6 +85,7 @@ sub table_info { disable_fixed => 1, disable_default => 1, disable_inventory => 1, + required => 1, }, 'serial' => { label => 'Serial number', %opts }, 'hw_addr' => { label => 'Hardware address', %opts }, |