summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm20
-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_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/once_percust_every.pm58
-rw-r--r--FS/FS/part_event/Condition/pkg_dundate_age.pm43
-rwxr-xr-xFS/FS/svc_broadband.pm6
13 files changed, 240 insertions, 20 deletions
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index 7bc3011d2..08e3f7e4b 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -570,6 +570,12 @@ sub edit_info {
or return { 'error' => "unknown custnum $custnum" };
my $new = new FS::cust_main { $cust_main->hash };
+ # Avoid accidentally changing the service address.
+ if ( !$new->has_ship_address ) {
+ $new->set( $_ => $new->get($_) )
+ foreach $new->addr_fields;
+ }
+
$new->set( $_ => $p->{$_} )
foreach grep { exists $p->{$_} } @cust_main_editable_fields;
@@ -729,7 +735,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') ) {
@@ -927,9 +933,17 @@ sub do_process_payment {
my $new = new FS::cust_main { $cust_main->hash };
if ($payby eq 'CARD' || $payby eq 'DCRD') {
$new->set( $_ => $validate->{$_} )
- foreach qw( payname paystart_month paystart_year payissue payip
- address1 address2 city state zip country );
+ foreach qw( payname paystart_month paystart_year payissue payip );
$new->set( 'payby' => $validate->{'auto'} ? 'CARD' : 'DCRD' );
+
+ # Avoid accidentally changing the service address.
+ if ( !$new->has_ship_address ) {
+ $new->set( "ship_$_" => $new->get($_) )
+ foreach $new->addr_fields;
+ }
+ $new->set( $_ => $validate->{$_} )
+ foreach qw(address1 address2 city state country zip);
+
} elsif ($payby eq 'CHEK' || $payby eq 'DCHK') {
$new->set( $_ => $validate->{$_} )
foreach qw( payname payip paytype paystate
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 047386478..535504cbf 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -3030,6 +3030,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.',
@@ -3957,6 +3975,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 fdf29e0a0..a403af309 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -2544,7 +2544,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 e01e1d2af..ae3b66cd9 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_pkg.pm b/FS/FS/cust_pkg.pm
index a0e21fec0..27cdc9ec8 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -1176,6 +1176,8 @@ sub unsuspend {
} #if $date
+ my @labels = ();
+
foreach my $cust_svc (
qsearch('cust_svc',{'pkgnum'=> $self->pkgnum } )
) {
@@ -1195,6 +1197,8 @@ sub unsuspend {
$dbh->rollback if $oldAutoCommit;
return $error;
}
+ my( $label, $value ) = $cust_svc->label;
+ push @labels, "$label: $value";
}
}
@@ -1225,6 +1229,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
@@ -3375,7 +3402,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;
@@ -3434,16 +3467,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/once_percust_every.pm b/FS/FS/part_event/Condition/once_percust_every.pm
new file mode 100644
index 000000000..9e2ec1f00
--- /dev/null
+++ b/FS/FS/part_event/Condition/once_percust_every.pm
@@ -0,0 +1,58 @@
+package FS::part_event::Condition::once_percust_every;
+
+use strict;
+use FS::Record qw( qsearch );
+use FS::part_event;
+use FS::cust_event;
+
+use base qw( FS::part_event::Condition );
+
+sub description { "Don't run this event more than once per customer in the specified interval"; }
+
+sub eventtable_hashref {
+ { 'cust_main' => 0,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ };
+}
+
+# Runs the event at most "once every X", per customer.
+
+sub option_fields {
+ (
+ 'run_delay' => { label=>'Interval', type=>'freq', value=>'1m', },
+ );
+}
+
+sub condition {
+ my($self, $object, %opt) = @_;
+
+ my $obj_pkey = $object->primary_key;
+ my $obj_table = $object->table;
+ my $custnum = $object->custnum;
+
+ my @where = (
+ "tablenum IN ( SELECT $obj_pkey FROM $obj_table WHERE custnum = $custnum )"
+ );
+ if ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/ ) {
+ push @where, " eventnum != $1 ";
+ }
+ my $extra_sql = ' AND '. join(' AND ', @where);
+
+ my $max_date = $self->option_age_from('run_delay', $opt{'time'});
+
+ my @existing = qsearch( {
+ 'table' => 'cust_event',
+ 'hashref' => {
+ 'eventpart' => $self->eventpart,
+ 'status' => { op=>'!=', value=>'failed' },
+ '_date' => { op=>'>', value=>$max_date },
+ },
+ 'extra_sql' => $extra_sql,
+ } );
+
+ ! scalar(@existing);
+
+}
+
+1;
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 636ba1fd9..307493264 100755
--- a/FS/FS/svc_broadband.pm
+++ b/FS/FS/svc_broadband.pm
@@ -542,9 +542,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 ) {