summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm2
-rw-r--r--FS/FS/Conf.pm25
-rw-r--r--FS/FS/Conf_compat17.pm7
-rw-r--r--FS/FS/Schema.pm2
-rw-r--r--FS/FS/cdr/cia.pm5
-rw-r--r--FS/FS/cust_location.pm36
-rw-r--r--FS/FS/cust_main_county.pm2
-rw-r--r--FS/FS/cust_pay.pm42
-rw-r--r--FS/FS/cust_pkg.pm50
-rw-r--r--FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm3
-rw-r--r--FS/FS/part_event/Action/cust_bill_spool_csv.pm3
-rw-r--r--FS/FS/part_event/Condition/pkg_dundate_age.pm43
-rwxr-xr-xFS/FS/svc_broadband.pm6
13 files changed, 198 insertions, 28 deletions
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index 7bc3011d2..e9394e4df 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -729,7 +729,7 @@ sub payment_info {
$return{payinfo2} = $payinfo2;
$return{paytype} = $cust_main->paytype;
$return{paystate} = $cust_main->paystate;
-
+ $return{payname} = $cust_main->payname; # override 'first/last name' default from above, if any. Is instution-name here. (#15819)
}
if ( $conf->config('prepayment_discounts-credit_type') ) {
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 81443632c..82e5ea89c 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -3052,6 +3052,24 @@ and customer address. Include units.',
},
{
+ 'key' => 'cust_location-label_prefix',
+ 'section' => 'UI',
+ 'description' => 'Optional "site ID" to show in the location label',
+ 'type' => 'select',
+ 'select_hash' => [ '' => '',
+ 'CoStAg' => 'CoStAgXXXXX (country, state, agent name, locationnum)',
+ ],
+ },
+
+ {
+ 'key' => 'cust_location-agent_code',
+ 'section' => 'UI',
+ 'description' => 'Optional agent string for cust_location-label_prefix',
+ 'type' => 'text',
+ 'per_agent' => 1,
+ },
+
+ {
'key' => 'cust_pkg-display_times',
'section' => 'UI',
'description' => 'Display full timestamps (not just dates) for customer packages. Useful if you are doing real-time things like hourly prepaid.',
@@ -3952,6 +3970,13 @@ and customer address. Include units.',
},
{
+ 'key' => 'unsuspend_email_admin',
+ 'section' => '',
+ 'description' => 'Destination admin email address to enable unsuspension notices',
+ 'type' => 'text',
+ },
+
+ {
'key' => 'email_report-subject',
'section' => '',
'description' => 'Subject for reports emailed by freeside-fetch. Defaults to "Freeside report".',
diff --git a/FS/FS/Conf_compat17.pm b/FS/FS/Conf_compat17.pm
index 6685935d3..2e4bb055f 100644
--- a/FS/FS/Conf_compat17.pm
+++ b/FS/FS/Conf_compat17.pm
@@ -2458,6 +2458,13 @@ httemplate/docs/config.html
},
{
+ 'key' => 'unsuspend_email_admin',
+ 'section' => '',
+ 'description' => 'Destination admin email address to enable unsuspension notices',
+ 'type' => 'text',
+ },
+
+ {
'key' => 'email_report-subject',
'section' => '',
'description' => 'Subject for reports emailed by freeside-fetch. Defaults to "Freeside report".',
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 3894f65f8..d42b946f2 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -2556,7 +2556,7 @@ sub tables_hashref {
'plan_id', 'varchar', 'NULL', $char_d, '', '',
],
'primary_key' => 'svcnum',
- 'unique' => [ [ 'mac_addr' ] ],
+ 'unique' => [ [ 'ip_addr' ], [ 'mac_addr' ] ],
'index' => [],
},
diff --git a/FS/FS/cdr/cia.pm b/FS/FS/cdr/cia.pm
index 070f3fb0d..ca44c0fdf 100644
--- a/FS/FS/cdr/cia.pm
+++ b/FS/FS/cdr/cia.pm
@@ -20,11 +20,12 @@ use FS::cdr qw(_cdr_date_parser_maker);
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);
- }, # Duration
- skip(2), # Roundup Duration, User Name
+ }, # Roundup Duration
+ skip(1), # User Name
'dst', # DNIS
'src', # ANI
skip(2), # Call Type, Toll Free,
diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm
index a5250ec05..a99fa17d8 100644
--- a/FS/FS/cust_location.pm
+++ b/FS/FS/cust_location.pm
@@ -408,6 +408,42 @@ sub dealternize {
'';
}
+=item location_label
+
+Returns the label of the location object, with an optional site ID
+string (based on the cust_location-label_prefix config option).
+
+=cut
+
+sub location_label {
+ my $self = shift;
+ my %opt = @_;
+ my $conf = new FS::Conf;
+ my $prefix = '';
+ my $format = $conf->config('cust_location-label_prefix') || '';
+ if ( $format eq 'CoStAg' ) {
+ my $cust_or_prospect;
+ if ( $self->custnum ) {
+ $cust_or_prospect = FS::cust_main->by_key($self->custnum);
+ }
+ elsif ( $self->prospectnum ) {
+ $cust_or_prospect = FS::prospect_main->by_key($self->prospectnum);
+ }
+ my $agent = $conf->config('cust_location-agent_code',
+ $cust_or_prospect->agentnum)
+ || $cust_or_prospect->agent->agent;
+ # else this location is invalid
+ $prefix = uc( join('',
+ $self->country,
+ ($self->state =~ /^(..)/),
+ ($agent =~ /^(..)/),
+ sprintf('%05d', $self->locationnum)
+ ) );
+ }
+ $prefix .= ($opt{join_string} || ': ') if $prefix;
+ $prefix . $self->SUPER::location_label(%opt);
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/cust_main_county.pm b/FS/FS/cust_main_county.pm
index e937b205c..6316f239a 100644
--- a/FS/FS/cust_main_county.pm
+++ b/FS/FS/cust_main_county.pm
@@ -176,7 +176,7 @@ with different tax classes.
sub sql_taxclass_sameregion {
my $self = shift;
- my $same_query = 'SELECT taxclass FROM cust_main_county '.
+ my $same_query = 'SELECT DISTINCT taxclass FROM cust_main_county '.
' WHERE taxnum != ? AND country = ?';
my @same_param = ( 'taxnum', 'country' );
foreach my $opt_field (qw( state county )) {
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index eca291a6e..f81a649db 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -760,6 +760,12 @@ objects. Returns a list, each element representing the status of inserting the
corresponding payment - empty. If there is an error inserting any payment, the
entire transaction is rolled back, i.e. all payments are inserted or none are.
+FS::cust_pay objects may have the pseudo-field 'apply_to', containing a
+reference to an array of (uninserted) FS::cust_bill_pay objects. If so,
+those objects will be inserted with the paynum of the payment, and for
+each one, an error message or an empty string will be inserted into the
+list of errors.
+
For example:
my @errors = FS::cust_pay->batch_insert(@cust_pay);
@@ -786,19 +792,35 @@ sub batch_insert {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- my $errors = 0;
+ my $num_errors = 0;
- my @errors = map {
- my $error = $_->insert( 'manual' => 1 );
- if ( $error ) {
- $errors++;
- } else {
- $_->cust_main->apply_payments;
+ my @errors;
+ foreach my $cust_pay (@_) {
+ my $error = $cust_pay->insert( 'manual' => 1 );
+ push @errors, $error;
+ $num_errors++ if $error;
+
+ if ( ref($cust_pay->get('apply_to')) eq 'ARRAY' ) {
+
+ foreach my $cust_bill_pay ( @{ $cust_pay->apply_to } ) {
+ if ( $error ) { # insert placeholders if cust_pay wasn't inserted
+ push @errors, '';
+ }
+ else {
+ $cust_bill_pay->set('paynum', $cust_pay->paynum);
+ my $apply_error = $cust_bill_pay->insert;
+ push @errors, $apply_error || '';
+ $num_errors++ if $apply_error;
+ }
+ }
+
+ } elsif ( !$error ) { #normal case: apply payments as usual
+ $cust_pay->cust_main->apply_payments;
}
- $error;
- } @_;
- if ( $errors ) {
+ }
+
+ if ( $num_errors ) {
$dbh->rollback if $oldAutoCommit;
} else {
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index bee1b82fb..4359de9a4 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -1239,6 +1239,8 @@ sub unsuspend {
} #if $date
+ my @labels = ();
+
foreach my $cust_svc (
qsearch('cust_svc',{'pkgnum'=> $self->pkgnum } )
) {
@@ -1258,6 +1260,8 @@ sub unsuspend {
$dbh->rollback if $oldAutoCommit;
return $error;
}
+ my( $label, $value ) = $cust_svc->label;
+ push @labels, "$label: $value";
}
}
@@ -1288,6 +1292,29 @@ sub unsuspend {
return $error;
}
+ if ( $conf->config('unsuspend_email_admin') ) {
+
+ my $error = send_email(
+ 'from' => $conf->config('invoice_from', $self->cust_main->agentnum),
+ #invoice_from ??? well as good as any
+ 'to' => $conf->config('unsuspend_email_admin'),
+ 'subject' => 'FREESIDE NOTIFICATION: Customer package unsuspended', 'body' => [
+ "This is an automatic message from your Freeside installation\n",
+ "informing you that the following customer package has been unsuspended:\n",
+ "\n",
+ 'Customer: #'. $self->custnum. ' '. $self->cust_main->name. "\n",
+ 'Package : #'. $self->pkgnum. " (". $self->part_pkg->pkg_comment. ")\n",
+ ( map { "Service : $_\n" } @labels ),
+ ],
+ );
+
+ if ( $error ) {
+ warn "WARNING: can't send unsuspension admin email (unsuspending anyway): ".
+ "$error\n";
+ }
+
+ }
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
''; #no errors
@@ -3445,7 +3472,13 @@ sub location_sql {
# '?' placeholders in _location_sql_where
my $x = $ornull ? 3 : 2;
- my @bill_param = ( ('city')x3, ('county')x$x, ('state')x$x, 'country' );
+ my @bill_param = (
+ ('district')x3,
+ ('city')x3,
+ ('county')x$x,
+ ('state')x$x,
+ 'country'
+ );
my $main_where;
my @main_param;
@@ -3504,16 +3537,17 @@ sub _location_sql_where {
$ornull = $ornull ? ' OR ? IS NULL ' : '';
- my $or_empty_city = " OR ( ? = '' AND $table.${prefix}city IS NULL ) ";
- my $or_empty_county = " OR ( ? = '' AND $table.${prefix}county IS NULL ) ";
- my $or_empty_state = " OR ( ? = '' AND $table.${prefix}state IS NULL ) ";
+ my $or_empty_city = " OR ( ? = '' AND $table.${prefix}city IS NULL )";
+ my $or_empty_county = " OR ( ? = '' AND $table.${prefix}county IS NULL )";
+ my $or_empty_state = " OR ( ? = '' AND $table.${prefix}state IS NULL )";
# ( $table.${prefix}city = ? $or_empty_city $ornull )
"
- ( $table.${prefix}city = ? OR ? = '' OR CAST(? AS text) IS NULL )
- AND ( $table.${prefix}county = ? $or_empty_county $ornull )
- AND ( $table.${prefix}state = ? $or_empty_state $ornull )
- AND $table.${prefix}country = ?
+ ( $table.${prefix}district = ? OR ? = '' OR CAST(? AS text) IS NULL )
+ AND ( $table.${prefix}city = ? OR ? = '' OR CAST(? AS text) IS NULL )
+ AND ( $table.${prefix}county = ? $or_empty_county $ornull )
+ AND ( $table.${prefix}state = ? $or_empty_state $ornull )
+ AND $table.${prefix}country = ?
";
}
diff --git a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm
index bf472683f..71bbaa89b 100644
--- a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm
+++ b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm
@@ -15,9 +15,10 @@ sub option_fields {
(
'ftpformat' => { label => 'Format',
type =>'select',
- options => ['default', 'billco'],
+ options => ['default', 'billco', 'oneline'],
option_labels => { 'default' => 'Default',
'billco' => 'Billco',
+ 'oneline' => 'One line',
},
},
'ftpserver' => 'FTP server',
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 11ecbc555..1504a4fa9 100644
--- a/FS/FS/part_event/Action/cust_bill_spool_csv.pm
+++ b/FS/FS/part_event/Action/cust_bill_spool_csv.pm
@@ -15,9 +15,10 @@ sub option_fields {
(
'spoolformat' => { label => 'Format',
type => 'select',
- options => ['default', 'billco'],
+ options => ['default', 'billco', 'oneline'],
option_labels => { 'default' => 'Default',
'billco' => 'Billco',
+ 'oneline' => 'One line',
},
},
'spoolbalanceover' => { label =>
diff --git a/FS/FS/part_event/Condition/pkg_dundate_age.pm b/FS/FS/part_event/Condition/pkg_dundate_age.pm
new file mode 100644
index 000000000..2ea2a2041
--- /dev/null
+++ b/FS/FS/part_event/Condition/pkg_dundate_age.pm
@@ -0,0 +1,43 @@
+package FS::part_event::Condition::pkg_dundate_age;
+use base qw( FS::part_event::Condition );
+
+use strict;
+
+sub description {
+ "Skip until specified #days before package suspension delay date";
+}
+
+
+sub option_fields {
+ (
+ 'age' => { 'label' => 'Time before suspension delay date',
+ 'type' => 'freq',
+ },
+ );
+}
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 0,
+ 'cust_pkg' => 1,
+ };
+}
+
+sub condition {
+ my($self, $cust_pkg, %opt) = @_;
+
+ my $age = $self->option_age_from('age', $opt{'time'} );
+
+ $cust_pkg->dundate <= $age;
+}
+
+sub condition_sql {
+ my( $class, $table, %opt ) = @_;
+ return 'true' unless $table eq 'cust_pkg';
+
+ my $age = $class->condition_sql_option_age_from('age', $opt{'time'});
+
+ "COALESCE($table.dundate,0) <= ". $age;
+}
+
+1;
diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm
index 64cc3770e..82102697d 100755
--- a/FS/FS/svc_broadband.pm
+++ b/FS/FS/svc_broadband.pm
@@ -543,9 +543,9 @@ sub _check_ip_addr {
sub _check_duplicate {
my $self = shift;
-
- $self->lock_table;
-
+ # Not a reliable check because the table isn't locked, but
+ # that's why we have a unique index. This is just to give a
+ # friendlier error message.
my @dup;
@dup = $self->find_duplicates('global', 'ip_addr');
if ( @dup ) {