summaryrefslogtreecommitdiff
path: root/FS/FS
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2015-06-13 15:18:37 -0700
committerIvan Kohler <ivan@freeside.biz>2015-06-13 15:18:37 -0700
commit7beec7068e00be5ae1b2599fdf2b494bc19e31d0 (patch)
tree055e1d25694ccfdac3a2d5aca79441cf7a3d89b5 /FS/FS
parent9e8d2a5bafb21c42f54cdd9b3703e2f88a36e0a8 (diff)
parenteb6536b41456955fc425dba2e156a996e8fe2837 (diff)
Merge branch 'FREESIDE_3_BRANCH' of git.freeside.biz:/home/git/freeside into FREESIDE_3_BRANCH
Diffstat (limited to 'FS/FS')
-rw-r--r--FS/FS/ClientAPI.pm2
-rw-r--r--FS/FS/ClientAPI/MasonComponent.pm1
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm60
-rw-r--r--FS/FS/ClientAPI/Signup.pm129
-rw-r--r--FS/FS/Conf.pm8
-rw-r--r--FS/FS/L10N/es_cu.pm4
-rw-r--r--FS/FS/L10N/es_es.pm6
-rw-r--r--FS/FS/L10N/es_mx.pm4
-rw-r--r--FS/FS/L10N/es_us.pm4
-rw-r--r--FS/FS/Locales.pm4
-rw-r--r--FS/FS/Record.pm38
-rw-r--r--FS/FS/Schema.pm1
-rw-r--r--FS/FS/cdr/FS/FS/cdr/vvs.pm33
-rw-r--r--FS/FS/cdr/earthlink.pm2
-rw-r--r--FS/FS/cdr/enswitch.pm27
-rw-r--r--FS/FS/cdr/ispphone.pm12
-rw-r--r--FS/FS/cdr/vss.pm1
-rw-r--r--FS/FS/cust_bill_pay_pkg.pm4
-rw-r--r--FS/FS/cust_bill_pkg.pm2
-rw-r--r--FS/FS/cust_main/Billing_Realtime.pm5
-rw-r--r--FS/FS/cust_pkg.pm75
-rw-r--r--FS/FS/part_event/Action/cust_bill_spool_csv.pm7
-rw-r--r--FS/FS/part_event/Condition/cust_bill_age_before_payby.pm4
-rw-r--r--FS/FS/part_pkg_link.pm3
-rw-r--r--FS/FS/part_svc.pm196
-rw-r--r--FS/FS/part_svc_column.pm3
-rw-r--r--FS/FS/pay_batch/RBC.pm7
-rw-r--r--FS/FS/svc_Common.pm39
-rw-r--r--FS/FS/svc_acct.pm7
-rw-r--r--FS/FS/svc_alarm.pm4
-rwxr-xr-xFS/FS/svc_broadband.pm1
-rw-r--r--FS/FS/svc_dish.pm3
-rw-r--r--FS/FS/svc_hardware.pm2
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 },