# customer voiding rights..
###
'Customer payment void rights' => [
- { rightname=>'Credit card void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. cc-void
+ { rightname=>'Credit card void', desc=>'Enable local-only voiding of credit card payments in addition to refunds against the payment gateway.' }, #aka. cc-void
{ rightname=>'Echeck void', desc=>'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway.' }, #aka. echeck-void
'Void payments',
{ rightname=>'Unvoid payments', desc=>'Enable unvoiding of voided payments' }, #aka. unvoid
} )
or return { 'error' => "Service not found" };
- if ( exists($p->{'old_password'}) ) {
- return { 'error' => "Incorrect password." }
- unless $svc_acct->check_password($p->{'old_password'});
- }
+ my $error = '';
+
+ my $conf = new FS::Conf;
+ $error = 'Password too short.'
+ if length($p->{'new_password'}) < ($conf->config('passwordmin') || 6);
+ $error = 'Password too long.'
+ if length($p->{'new_password'}) > ($conf->config('passwordmax') || 8);
$svc_acct->set_password($p->{'new_password'});
- my $error = $svc_acct->replace();
+ $error ||= $svc_acct->replace();
my($label, $value) = $svc_acct->cust_svc->label;
},
{
+ 'key' => 'sip_passwordmin',
+ 'section' => 'telephony',
+ 'description' => 'Minimum SIP password length (default 6)',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'sip_passwordmax',
+ 'section' => 'telephony',
+ 'description' => 'Maximum SIP password length (default 8) (don\'t set this over 12 if you need to import or export crypt() passwords)',
+ 'type' => 'text',
+ },
+
+
+ {
'key' => 'password-noampersand',
'section' => 'password',
'description' => 'Disallow ampersands in passwords',
},
{
+ 'key' => 'show_ship_company',
+ 'section' => 'UI',
+ 'description' => 'Turns on display/collection of a "service company name" field for customers.',
+ 'type' => 'checkbox',
+ },
+
+ {
'key' => 'show_ss',
'section' => 'UI',
'description' => 'Turns on display/collection of social security numbers in the web interface. Sometimes required by electronic check (ACH) processors.',
},
{
+ 'key' => 'svc_phone-radius-password',
+ 'section' => 'telephony',
+ 'description' => 'Password when exporting svc_phone records to RADIUS',
+ 'type' => 'select',
+ 'select_hash' => [
+ '' => 'Use default from svc_phone-radius-default_password config',
+ 'countrycode_phonenum' => 'Phone number (with country code)',
+ ],
+ },
+
+ {
'key' => 'svc_phone-radius-default_password',
'section' => 'telephony',
'description' => 'Default password when exporting svc_phone records to RADIUS',
our $nowarn_identical = 0;
our $nowarn_classload = 0;
our $no_update_diff = 0;
+our $no_history = 0;
our $no_check_foreign = 1; #well, not inefficiently in perl by default anymore
}
my $h_sth;
- if ( defined dbdef->table('h_'. $table) ) {
+ if ( defined( dbdef->table('h_'. $table) ) && ! $no_history ) {
my $h_statement = $self->_h_statement('insert');
warn "[debug]$me $h_statement\n" if $DEBUG > 2;
$h_sth = dbh->prepare($h_statement) or do {
sub encrypt {
my ($self, $value) = @_;
- my $encrypted;
+ my $encrypted = $value;
if ($conf->exists('encryption')) {
if ($self->is_encrypted($value)) {
return 'cust_main.classnum in('. join(',',@$classnums) .')'
if @$classnums;
}
- '';
+ ();
}
use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache);
use subs qw(reload_dbdef);
use Exporter;
-use DBIx::DBSchema 0.43; #0.43 for foreign keys
+use DBIx::DBSchema 0.44; #for foreign keys with MATCH / ON DELETE/UPDATE
use DBIx::DBSchema::Table;
use DBIx::DBSchema::Column;
use DBIx::DBSchema::Index;
{ columns => [ 'invnum' ],
table => 'cust_bill_void',
},
- { columns => [ 'pkgnum' ],
- table => 'cust_pkg',
- },
+ #pkgnum 0 and -1 are used for special things
+ #{ columns => [ 'pkgnum' ],
+ # table => 'cust_pkg',
+ #},
{ columns => [ 'pkgpart_override' ],
table => 'part_pkg',
references => [ 'pkgpart' ],
'index' => [ [ 'billpaynum' ], [ 'billpkgnum' ], ],
'foreign_keys' => [
{ columns => [ 'billpaynum' ],
- table => 'cust_bill_pay_batch',
+ table => 'cust_bill_pay',
},
{ columns => [ 'billpkgnum' ],
table => 'cust_bill_pkg',
'foreign_keys' => [
{ columns => [ 'jobnum' ],
table => 'queue',
+ on_delete => 'CASCADE',
},
],
},
{ columns => [ 'depend_jobnum' ],
table => 'queue',
references => [ 'jobnum' ],
+ on_delete => 'CASCADE',
},
],
},
#currently only u4:
# terminating number (as opposed to dialed destination)
- 'dst_term', 'varchar', '', $char_d, \"''", '',
+ 'dst_term', 'varchar', 'NULL', $char_d, '', '',
#these don't seem to be logged by most of the SQL cdr_* modules
#except tds under sql-illegal names, so;
'statustext', 'varchar', 'NULL', $char_d, '', '',
],
'primary_key' => 'upgradenum',
- 'unique' => [ [ 'upgradenum' ] ],
+ 'unique' => [],
'index' => [ [ 'upgrade' ] ],
},
my $countrydefault = $conf->config('countrydefault') || 'US';
foreach ( qw( address1 address2 city state zip country fax) ){
my $method = 'ship_'.$_;
- $invoice_data{"ship_$_"} = _latex_escape($cust_main->$method);
+ $invoice_data{"ship_$_"} = $escape_function->($cust_main->$method);
}
- foreach ( qw( contact company ) ) { #compatibility
- $invoice_data{"ship_$_"} = _latex_escape($cust_main->$_);
+ if ( length($cust_main->ship_company) ) {
+ $invoice_data{'ship_company'} = $escape_function->($cust_main->ship_company);
+ } else {
+ $invoice_data{'ship_company'} = $escape_function->($cust_main->company);
}
+ $invoice_data{'ship_contact'} = $escape_function->($cust_main->contact);
$invoice_data{'ship_country'} = ''
if ( $invoice_data{'ship_country'} eq $countrydefault );
@cust_header;
}
+sub cust_sort_fields {
+ cust_header(@_);
+ #inefficientish, but tiny lists and only run once per page
+
+ map { $_ eq 'custnum' ? 'custnum' : '' } @cust_fields;
+
+}
+
=item cust_sql_fields [ CUST_FIELDS_VALUE ]
Returns a list of fields for the SELECT portion of an SQL query.
#${$opt{region_group_included_min}} -= $minutes
# if $region_group && $rate_detail->region_group;
- if ( $included_min->{$regionnum}{$ratetimenum} > $minutes ) {
+ if ( $included_min->{$regionnum}{$ratetimenum} >= $minutes ) {
$charge_sec = 0;
$included_min->{$regionnum}{$ratetimenum} -= $minutes;
} else {
use strict;
use FS::Record qw( qsearch qsearchs );
use FS::contact;
+use FS::phone_type;
=head1 NAME
qsearchs( 'contact', { 'contactnum' => $self->contactnum } );
}
+sub phone_type {
+ my $self = shift;
+ qsearchs('phone_type', { 'phonetypenum' => $self->phonetypenum } );
+}
+
+sub typename {
+ my $self = shift;
+ $self->phone_type->typename;
+}
+
=back
=head1 BUGS
my $time = $opt{'time'} || time;
+ my $tracctnum = ''; #leaking out from billco-specific sections :/
if ( $format eq 'billco' ) {
my $account_num =
$self->conf->config('billco-account_num', $cust_main->agentnum);
- my $tracctnum = $account_num eq 'display_custnum'
- ? $cust_main->display_custnum
- : $opt{'tracctnum'};
+ $tracctnum = $account_num eq 'display_custnum'
+ ? $cust_main->display_custnum
+ : $opt{'tracctnum'};
my $taxtotal = 0;
$taxtotal += $_->{'amount'} foreach $self->_items_tax;
$csv->combine(
'', # 1 | N/A-Leave Empty CHAR 2
'', # 2 | N/A-Leave Empty CHAR 15
- $opt{'tracctnum'}, # 3 | Account Number CHAR 15
+ $tracctnum, # 3 | Account Number CHAR 15
$self->invnum, # 4 | Invoice Number CHAR 15
$lineseq++, # 5 | Line Sequence (sort order) NUM 6
$item->{'description'}, # 6 | Transaction Detail CHAR 100
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
- foreach my $field ( 'first', 'last', 'company' ) {
+ foreach my $field ( 'first', 'last', 'company', 'ship_company' ) {
my $queue = new FS::queue {
'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield'
};
|| $self->ut_snumbern('spouse_birthdate')
|| $self->ut_snumbern('anniversary_date')
|| $self->ut_textn('company')
+ || $self->ut_textn('ship_company')
|| $self->ut_anything('comments')
|| $self->ut_numbern('referral_custnum')
|| $self->ut_textn('stateid')
|| $self->ut_currencyn('currency')
;
- my $company = $self->company;
- $company =~ s/^\s+//;
- $company =~ s/\s+$//;
- $company =~ s/\s+/ /g;
- $self->company($company);
+ foreach (qw(company ship_company)) {
+ my $company = $self->get($_);
+ $company =~ s/^\s+//;
+ $company =~ s/\s+$//;
+ $company =~ s/\s+/ /g;
+ $self->set($_, $company);
+ }
#barf. need message catalogs. i18n. etc.
$error .= "Please select an advertising source."
@fuzzyfields = (
'cust_main.first', 'cust_main.last', 'cust_main.company',
+ 'cust_main.ship_company', # if you're using it
'cust_location.address1',
'contact.first', 'contact.last',
);
$sql .= " ( LOWER(cust_main.first) = $q_value
OR LOWER(cust_main.last) = $q_value
OR LOWER(cust_main.company) = $q_value
+ OR LOWER(cust_main.ship_company) = $q_value
";
#address1 (yes, it's a kludge)
#substring
- my @hashrefs = (
+ my @company_hashrefs = (
{ 'company' => { op=>'ILIKE', value=>"%$value%" }, },
+ { 'ship_company' => { op=>'ILIKE', value=>"%$value%" }, },
);
+ my @hashrefs = ();
+
if ( $first && $last ) {
- push @hashrefs,
+ @hashrefs = (
{ 'first' => { op=>'ILIKE', value=>"%$first%" },
'last' => { op=>'ILIKE', value=>"%$last%" },
},
- ;
+ );
} else {
- push @hashrefs,
+ @hashrefs = (
{ 'first' => { op=>'ILIKE', value=>"%$value%" }, },
{ 'last' => { op=>'ILIKE', value=>"%$value%" }, },
- ;
+ );
}
- foreach my $hashref ( @hashrefs ) {
+ foreach my $hashref ( @company_hashrefs, @hashrefs ) {
push @cust_main, qsearch( {
'table' => 'cust_main',
#contact substring
- shift @hashrefs; #no company column in contact table
-
foreach my $hashref ( @hashrefs ) {
push @cust_main,
%fuzopts
);
}
- foreach my $field ( 'first', 'last', 'company' ) {
+ foreach my $field ( 'first', 'last', 'company', 'ship_company' ) {
push @cust_main, FS::cust_main::Search->fuzzy_search(
{ $field => $value },
%fuzopts
#foreach my $fuzzy (@fuzzyfields) {
foreach my $fuzzy ( 'cust_main.first', 'cust_main.last', 'cust_main.company',
'cust_location.address1',
+ 'cust_main.ship_company',
) {
append_fuzzyfiles_fuzzyfield($fuzzy, shift);
} );
@encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
=head1 NAME
# 3 is even more information including possibly sensitive data
$DEBUG = 0;
+#@encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
+
=head1 NAME
FS::cust_pay_batch - Object methods for batch cards
@ISA = qw( FS::payinfo_transaction_Mixin FS::cust_main_Mixin FS::Record );
@encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
=head1 NAME
use FS::cust_pkg;
@encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
+
$otaker_upgrade_kludge = 0;
=head1 NAME
if ( $opt->{cust_main} ) {
my $cust_main = $opt->{cust_main};
unless ( $cust_main->custnum ) {
- my $error = $cust_main->insert;
+ my $error = $cust_main->insert( @{ $opt->{cust_main_insert_args}||[] } );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return "inserting cust_main (transaction rolled back): $error";
foreach my $cust_svc (
grep {
my $part_svc = $_->part_svc;
- $part_svc->svcdb eq 'svc_acct'
- && scalar($part_svc->part_export_usage);
+ scalar($part_svc->part_export_usage);
} $self->cust_svc
) {
$sum += $cust_svc->attribute_since_sqlradacct($start, $end, $attrib);
use FS::cust_main;
@encrypted_fields = ('payinfo');
+sub nohistory_fields { ('payinfo'); }
=head1 NAME
'type' => 'select-table',
'table' => 'msg_template',
'name_col' => 'msgname',
+ 'hashref' => { disabled => '' },
'disable_empty' => 1,
},
);
'msgnum' => { 'label' => 'Template',
'type' => 'select-table',
'table' => 'msg_template',
+ 'hashref' => { disabled => '' },
'name_col' => 'msgname',
'disable_empty' => 1,
},
'type' => 'select-table',
'table' => 'msg_template',
'name_col' => 'msgname',
+ 'hashref' => { disabled => '' },
'disable_empty' => 1,
},
);
'type' => 'select-table',
'table' => 'msg_template',
'name_col' => 'msgname',
+ 'hashref' => { disabled => '' },
'disable_empty' => 1,
},
);
%templates = (0 => '',
map { $_->msgnum, $_->msgname }
qsearch({ table => 'msg_template',
- hashref => {},
+ hashref => { disabled => '' },
order_by => 'ORDER BY msgnum ASC'
})
);
%templates = (0 => '',
map { $_->msgnum, $_->msgname }
qsearch({ table => 'msg_template',
- hashref => {},
+ hashref => { disabled => 1 },
order_by => 'ORDER BY msgnum ASC'
})
);
my @fields = qw( pkgnum pkgpart agent_pkgid ); #others?
my @date_fields = qw( order_date start_date setup bill last_bill susp adjourn
- resume cancel uncancel expore contract_end );
+ resume cancel uncancel expire contract_end );
no strict 'vars';
{
no strict 'refs';
foreach (@fields) {
- ${"old_$_"} = $old_cust_pkg->getfield($_);
+ ${"old_$_"} = $old_cust_pkg ? $old_cust_pkg->getfield($_) : '';
${"new_$_"} = $new_cust_pkg->getfield($_);
}
foreach (@date_fields) {
- ${"old_$_"} = time2str('%Y-%m-%d', $old_cust_pkg->getfield($_));
+ ${"old_$_"} = $old_cust_pkg
+ ? time2str('%Y-%m-%d', $old_cust_pkg->getfield($_))
+ : '';
${"new_$_"} = time2str('%Y-%m-%d', $new_cust_pkg->getfield($_));
}
}
#some false laziness-ish w/agent.pm... not a lot
sub calc_recur {
- my($self, $cust_pkg, $sdate, $details ) = @_;
+ my($self, $cust_pkg, $sdate, $details, $param ) = @_;
my $conf = new FS::Conf;
my $money_char = $conf->config('money_char') || '$';
}
}
- sprintf('%.2f', $self->base_recur($cust_pkg, $sdate) + $total_svc_charge );
-}
+ my $charge = $self->base_recur($cust_pkg, $sdate) + $total_svc_charge;
+
+ $param->{'override_charges'} = $total_svc_charge / $self->freq;
+ my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param);
-sub can_discount { 0; }
+ sprintf('%.2f', $charge - $discount );
+}
sub hide_svc_detail { 1; }
sub delete {
my $self = shift;
- local $SIG{HUP} = 'IGNORE';
- local $SIG{INT} = 'IGNORE';
- local $SIG{QUIT} = 'IGNORE';
- local $SIG{TERM} = 'IGNORE';
- local $SIG{TSTP} = 'IGNORE';
- local $SIG{PIPE} = 'IGNORE';
-
- my $oldAutoCommit = $FS::UID::AutoCommit;
- local $FS::UID::AutoCommit = 0;
- my $dbh = dbh;
-
- my @del = qsearch( 'queue_arg', { 'jobnum' => $self->jobnum } );
- push @del, qsearch( 'queue_depend', { 'depend_jobnum' => $self->jobnum } );
-
my $reportname = '';
if ( $self->status =~/^done/ ) {
my $dropstring = rooturl(). '/misc/queued_report\?report=';
}
my $error = $self->SUPER::delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
-
- foreach my $del ( @del ) {
- $error = $del->delete;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return $error;
- }
- }
-
- $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ return $error if $error;
unlink $reportname if $reportname;
--- /dev/null
+package FS::svc_MAC_Mixin;
+
+use strict;
+#use FS::Record qw(qsearch);
+#use FS::Conf;
+# careful about importing anything here--it will end up in a LOT of
+# namespaces
+
+#use vars qw(@subclasses $DEBUG $conf);
+
+#$DEBUG = 0;
+
+# any subclass that can have MAC addresses needs to be added here
+#@subclasses = (qw(FS::svc_broadband FS::svc_cable));
+
+#sub conf {
+# $conf ||= FS::Conf->new;
+#}
+
+=head1 NAME
+
+FS::MAC_Mixin - Mixin class for objects that have MAC addresses assigned.
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head1 METHODS
+
+=over 4
+
+=item mac_addr_pretty
+
+=cut
+
+sub mac_addr_pretty {
+ my $self = shift;
+ $self->mac_addr_formatted('U',':');
+}
+
+=item mac_addr_formatted CASE DELIMITER
+
+Format the MAC address (for use by exports). If CASE starts with "l"
+(for "lowercase"), it's returned in lowercase. DELIMITER is inserted
+between octets.
+
+=cut
+
+sub mac_addr_formatted {
+ my $self = shift;
+ my ($case, $delim) = @_;
+ my $addr = $self->mac_addr;
+ $addr = lc($addr) if $case =~ /^l/i;
+ join( $delim || '', $addr =~ /../g );
+}
+
+=back
+
+=head1 BUGS
+
+=cut
+
+1;
FS::svc_Radius_Mixin
FS::svc_Tower_Mixin
FS::svc_IP_Mixin
+ FS::svc_MAC_Mixin
FS::svc_Common
);
=item label
-Returns the IP address.
+Returns the IP address, MAC address and description.
=cut
'';
}
-=item mac_addr_formatted CASE DELIMITER
-
-Format the MAC address (for use by exports). If CASE starts with "l"
-(for "lowercase"), it's returned in lowercase. DELIMITER is inserted
-between octets.
-
-=cut
-
-sub mac_addr_formatted {
- my $self = shift;
- my ($case, $delim) = @_;
- my $addr = $self->mac_addr;
- $addr = lc($addr) if $case =~ /^l/i;
- join( $delim || '', $addr =~ /../g );
-}
-
#class method
sub _upgrade_data {
my $class = shift;
package FS::svc_cable;
-use base qw( FS::svc_Common ); #qw( FS::device_Common FS::svc_Common );
+use base qw( FS::svc_MAC_Mixin
+ FS::svc_Common
+ ); #FS::device_Common
use strict;
use Tie::IxHash;
};
}
+=item label
+
+Returns the MAC address and serial number.
+
+=cut
+
+sub label {
+ my $self = shift;
+ my @label = ();
+ push @label, 'MAC:'. $self->mac_addr_pretty
+ if $self->mac_addr;
+ push @label, 'Serial#'. $self->serialnum
+ if $self->serialnum;
+ return join(', ', @label);
+}
+
=item insert
Adds this record to the database. If there is an error, returns the error,
use strict;
use base qw( FS::svc_Domain_Mixin FS::location_Mixin FS::svc_Common );
-use vars qw( $DEBUG $me @pw_set $conf $phone_name_max );
+use vars qw( $DEBUG $me @pw_set $conf $phone_name_max
+ $passwordmin $passwordmax
+ );
use Data::Dumper;
use Scalar::Util qw( blessed );
+use List::Util qw( min );
use FS::Conf;
use FS::Record qw( qsearch qsearchs dbh );
use FS::PagedSearch qw( psearch );
FS::UID->install_callback( sub {
$conf = new FS::Conf;
$phone_name_max = $conf->config('svc_phone-phone_name-max_length');
+ $passwordmin = $conf->config('sip_passwordmin') || 0;
+ $passwordmax = $conf->config('sip_passwordmax') || 80;
}
);
}
}
- unless ( length($self->sip_password) ) { # option for this?
+ if ( length($self->sip_password) ) {
+
+ return "SIP password must be longer than $passwordmin characters"
+ if length($self->sip_password) < $passwordmin;
+ return "SIP password must be shorter than $passwordmax characters"
+ if length($self->sip_password) > $passwordmax;
+
+ } else { # option for this?
$self->sip_password(
- join('', map $pw_set[ int(rand $#pw_set) ], (0..16) )
+ join('', map $pw_set[ int(rand $#pw_set) ], (1..min($passwordmax,16)) )
);
}
my $conf = new FS::Conf;
- $check{'User-Password'} = $conf->config('svc_phone-radius-default_password');
+ my $password;
+ if ( $conf->config('svc_phone-radius-password') eq 'countrycode_phonenum' ) {
+ $password = $self->countrycode. $self->phonenum;
+ } else {
+ $password = $conf->config('svc_phone-radius-default_password');
+ }
+ $check{'User-Password'} = $password;
%check;
}
use FS::queue;
use FS::queue_depend;
use FS::Log;
+use FS::Cron::expire_user_pref qw( expire_user_pref );
# no autoloading for non-FS classes...
use Net::SSH 0.07;
if ( $kids >= $max_kids ) {
warn "WARNING: maximum $kids children reached\n" unless $warnkids++;
&reap_kids;
+ expire_user_pref() unless $warnkids % 10;
sleep 1; #waiting for signals is cheap
next;
}
undef $FS::UID::dbh;
next;
};
+ expire_user_pref();
sleep $sleep_time;
next;
}
}
}
+if ( dbdef->table('upgrade_journal') ) {
+ push @bugfix, "SELECT SETVAL( 'upgrade_journal_upgradenum_seq',
+ ( SELECT MAX(upgradenum) FROM upgrade_journal )
+ )
+ ";
+}
+
if ( $DRY_RUN ) {
print
join(";\n", @bugfix ). ";\n";
--- /dev/null
+#!/usr/bin/perl
+
+use FS::UID 'adminsuidsetup';
+use FS::Record qw(qsearch qsearchs dbh);
+use FS::cust_main;
+my $user = shift or die "Usage:
+ restore-ship_company username [ max-age ]
+";
+adminsuidsetup($user);
+
+$FS::UID::AutoCommit = 1;
+local $FS::cust_main::import = 1;
+
+my $days = shift || 30;
+my $time = time - (86400*$days); # by default, only restore within the last
+ # 30 days
+foreach my $cust_main (qsearch('cust_main', { ship_company => '' })) {
+ my $custnum = $cust_main->custnum;
+ my $last_h = qsearchs({
+ table => 'h_cust_main',
+ extra_sql => " WHERE custnum = $custnum".
+ " AND ship_company IS NOT NULL".
+ " AND history_date >= $time",
+ order_by => " ORDER BY history_date DESC LIMIT 1",
+ });
+ next if !$last_h;
+ print "$custnum\t".$last_h->ship_company."\n";
+ $cust_main->set('ship_company' => $last_h->ship_company);
+ my $error = $cust_main->replace;
+ warn "Error setting service company for customer #$custnum:\n $error\n"
+ if $error;
+}
#This drops anything from the database that could cause live things to happen.
#You'd want to do this on a test copy of your live database but NEVER on the
#live database itself.
+die "remove this line to run -- NEVER ON A LIVE DATABASE";
#-all exports (all records in part_export, part_export_option export_svc)
#-all non-POST invoice destinations (cust_main_invoice)
or die dbh->errstr;
$dsth->execute or die $dsth->errstr;
+foreach my $table (qw( cust_main
+ cust_pay_pending cust_pay cust_pay_void cust_pay_batch
+ cust_refund
+)) {
+ my $ccsth = dbh->prepare("
+ UPDATE $table SET payinfo = '4111111111111111'
+ WHERE payby = 'CARD' OR payby = 'DCRD'
+ ") or die dbh->errstr;
+ $ccsth->execute or die $ccsth->errstr;
+}
+
my $sth = dbh->prepare("UPDATE part_event SET disabled = 'Y'");
$sth->execute or die $sth->errstr;
#Add this to the modules section of radiusd.conf
# perl {
# #path to this module
-# module=/usr/local/share/perl/5.8.8/FS/SelfService/FreeRadiusVoip.pm
+# # deb 6 example
+# #module=/usr/local/share/perl/5.10.1/FS/SelfService/FreeRadiusVoip.pm
+# # deb 7 example
+# module=/usr/local/share/perl/5.14.2/FS/SelfService/FreeRadiusVoip.pm
+#
# func_authorize = authorize;
+#
# }
#
#In the Authorize section
#
# #N/A# Add a line containing 'perl' to the Accounting section.
#
-# and on debian systems, add this to /etc/init.d/freeradius, with the
+# and on debian 6 systems, add this to /etc/init.d/freeradius, with the
# correct path (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=416266)
-# LD_PRELOAD=/usr/lib/libperl.so.5.8.8
+# LD_PRELOAD=/usr/lib/libperl.so.5.10
# export LD_PRELOAD
BEGIN { $FS::SelfService::skip_uid_check = 1; }
if ( $response->{'error'} ) {
$RAD_REPLY{'Reply-Message'} = $response->{'error'};
return RLM_MODULE_REJECT;
- } else {
+ } elsif ( $response->{'seconds'} ) {
$RAD_REPLY{'Session-Timeout'} = $response->{'seconds'};
return RLM_MODULE_OK;
+ } else {
+ # if the called number is free, put 1 in the Termination-Action attribute
+ $RAD_REPLY{'Termination-Action'} = 1;
+ return RLM_MODULE_OK;
}
}
'query' => { 'table' => 'msg_template', },
'count_query' => 'SELECT COUNT(*) FROM msg_template',
'disableable' => 1,
- 'disabled_statuspos' => 2,
+ 'disabled_statuspos' => (scalar(@locales) + 3),
'agent_virt' => 1,
'agent_null_right' => ['View global templates','Edit global templates'],
'agent_pos' => 1,
- 'header' => [ 'Name', '', map '', @locales ],
- 'fields' => [ 'msgname', @locales ],
- 'links' => [ $link, @locale_links ],
- 'cell_style' => [ '', '', map $locale_style, @locales ],
+ 'header' => [ 'Name', '', map ('', @locales), '' ],
+ 'fields' => [ 'msgname', @locales, $disable_link_label ],
+ 'links' => [ $link, @locale_links, '' ],
+ 'link_onclicks' => [ '', map('', @locale_links), $disable_link ],
+ 'cell_style' => [ '', '', map ($locale_style, @locales), $locale_style ],
)
%>
<%init>
my $link = [ "${p}edit/msg_template.html?msgnum=", 'msgnum' ];
-my $locale_style = 'font-size:0.8em; padding:3px; background-color:';
+my $locale_style = 'font-size:0.8em; padding:3px';
my (@locales, @locale_links);
foreach my $l ( FS::Locales->locales ) {
[ "${p}edit/msg_template.html?locale=$l;msgnum=", 'msgnum' ];
};
}
-
+
+my $disable_link = sub {
+ my $template = shift;
+ include('/elements/popup_link_onclick.html',
+ action => $p.'misc/disable-msg_template.cgi?msgnum=' .
+ $template->msgnum .
+ ($template->disabled ? ';enable=1' : ''),
+ actionlabel => 'Disable lemplate',
+ );
+};
+
+my $disable_link_label = sub {
+ my $template = shift;
+ $template->disabled ? '(enable)' : '(disable)' ;
+};
</%init>
><% mt('same as billing address') |h %>
<DIV CLASS="fsinnerbox">
<TABLE ID="table_ship_location" WIDTH="100%">
+ <& cust_main/before_ship_location.html, $cust_main &>
<& /elements/location.html,
object => $cust_main->ship_location,
prefix => 'ship_',
my $locationnum = '';
my $same = '';
+$m->comp('/elements/handle_uri_query', 'secure'=>1);
if ( $cgi->param('error') ) {
--- /dev/null
+% if ( length($cust_main->ship_company) or
+% $conf->exists('show_ship_company') ) {
+ <& /elements/tr-input-text.html,
+ label => mt('Company'),
+ field => 'ship_company',
+ curr_value => $cust_main->ship_company,
+ colspan => 6,
+ &>
+% }
+<%init>
+my $cust_main = shift;
+my $conf = FS::Conf->new;
+</%init>
</%def>
<%def phones>
- <TR>
- <TD ALIGN="right" VALIGN="top"><% mt('Phones') %></TD>
- <TD COLSPAN=6>
-
- <TABLE CELLSPACING=0 CELLPADDING=0>
- <TR>
- <TD>
- <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
- <BR><FONT SIZE=-1><% $daytime_label %></FONT>
- </TD>
- <TD> </TD>
- <TD>
- <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
- <BR><FONT SIZE=-1><% $night_label %></FONT>
- </TD>
- <TD> </TD>
- <TD>
- <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
- <BR><FONT SIZE=-1><% $mobile_label %></FONT>
- </TD>
- </TR>
- </TABLE>
- </TD>
- </TR>
+ <& /elements/tr-cust_main-phones.html,
+ 'prefix' => $pre,
+ 'cust_main' => $cust_main,
+ 'onchange' => $onchange,
+ 'disabled' => $disabled,
+ 'style' => $style,
+ &>
</%def>
<%def fax>
</%once>
<%shared>
-my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style,
- $daytime_label, $night_label, $mobile_label,
- );
+my( %opt, $cust_main, $pre, $onchange, $disabled, @style, $style );
</%shared>
<%init>
$opt{censustract} ||= $cust_main->censustract;
-$daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
- ? 'Day'
- : FS::Msgcat::_gettext('daytime');
-$night_label = FS::Msgcat::_gettext('night') =~/^(night)?$/
- ? 'Night'
- : FS::Msgcat::_gettext('night') || 'Night';
-$mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/
- ? 'Mobile'
- : FS::Msgcat::_gettext('mobile') || 'Mobile';
-
my $stateid_label = FS::Msgcat::_gettext('stateid') =~ /^(stateid)?$/
? 'Driver’s License'
: FS::Msgcat::_gettext('stateid') || 'Driver’s License';
% if ( $f->{curr_value_callback} ) {
% $curr_value = &{ $f->{curr_value_callback} }( $cgi, $object, $field ),
% } else {
-% $curr_value = $object->$field();
+% $curr_value = $object->$field() if $field;
% }
% $curr_value = &{ $opt{'value_callback'} }( $f->{'field'}, $curr_value )
% if $opt{'value_callback'} && $mode ne 'error';
% if ( $error ) {
% $cgi->param('error', $error);
-%
-<% $cgi->redirect(popurl(2). "cust_main.cgi?". $cgi->query_string ) %>
+% my $query = $m->scomp('/elements/create_uri_query', 'secure'=>1);
+<% $cgi->redirect(popurl(2). "cust_main.cgi?$query" ) %>
%
% } else {
%
map { $_ => scalar($cgi->param($_)) } FS::cust_main->location_fields
};
+#false laziness w/process/cust_main.cgi
+my @invoicing_list = split( /\s*\,\s*/, $cgi->param('invoicing_list') );
+push @invoicing_list, 'POST' if $cgi->param('invoicing_list_POST');
+push @invoicing_list, 'FAX' if $cgi->param('invoicing_list_FAX');
+$cgi->param('invoicing_list', join(',', @invoicing_list) );
+
my $cust_main = new FS::cust_main {
( map { ( $_, scalar($cgi->param($_)) ) } fields('cust_main') ),
( map { ( "ship_$_", '' ) } FS::cust_main->location_fields ),
- 'bill_location' => $cust_location,
- 'ship_location' => $cust_location,
+ 'bill_location' => $cust_location,
+ 'ship_location' => $cust_location,
};
my $pkg_or_error = $cust_pkg->change( {
- 'keep_dates' => 1,
- 'cust_main' => $cust_main,
+ 'keep_dates' => 1,
+ 'cust_main' => $cust_main,
+ 'cust_main_insert_args' => [ {}, \@invoicing_list ],
} );
my $error = ref($pkg_or_error) ? '' : $pkg_or_error;
<% $query %>\
<%init>
+my %opt = @_;
+
+if ( $opt{secure} ) {
+
+ foreach my $param (grep /pay(info\d?|cvv)$/, $cgi->param) {
+ my $value = $cgi->param($param);
+ next unless length($value);
+ my $encrypted = FS::Record->encrypt( $value );
+ $cgi->param($param, $encrypted);
+ }
+
+}
+
my $query = $cgi->query_string;
-if ( length($query) > 1920 ) { #stupid IE 2083 URL limit
+if ( length($query) > 1920 || $opt{secure} ) { #stupid IE 2083 URL limit
my $session = int(rand(4294967296)); #XXX
my $pref = new FS::access_user_pref({
'usernum' => $FS::CurrentUser::CurrentUser->usernum,
'prefname' => "redirect$session",
'prefvalue' => $query,
- 'expiration' => time + 3600, #1h? 1m?
+ 'expiration' => time + ( $opt{secure} ? 120 #2m?
+ : 3600 #1h?
+ ),
});
+ local($FS::Record::no_history) = 1;
+
my $pref_error = $pref->insert;
if ( $pref_error ) {
die "FATAL: couldn't even set redirect cookie: $pref_error".
<%init>
+
+my %opt = @_;
+
if ( $cgi->param('redirect') ) {
my $session = $cgi->param('redirect');
+
my $pref = $FS::CurrentUser::CurrentUser->option("redirect$session");
die "unknown redirect session $session\n" unless length($pref);
$cgi = new CGI($pref);
+
+ foreach my $param (grep /pay(info\d?|cvv)$/, $cgi->param) {
+ my $value = $cgi->param($param);
+ next unless length($value);
+ my $decrypted = FS::Record->decrypt( $value );
+ $cgi->param($param, $decrypted);
+ }
+
}
</%init>
-% my %opt = @_;
-% my $pager = '';
-%
% if ( $opt{'total'} != $opt{'num_rows'} && $opt{'maxrecords'} ) {
%
% unless ( $opt{'offset'} == 0 ) {
% $cgi->param('offset', $opt{'offset'} - $opt{'maxrecords'});
-
<A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Previous</FONT></B></A>
-
% }
%
% my $page = 0;
%
% unless ( $opt{'offset'} + $opt{'maxrecords'} > $opt{'total'} ) {
% $cgi->param('offset', $opt{'offset'} + $opt{'maxrecords'});
-
<A HREF="<% $cgi->self_url %>"><B><FONT SIZE="+1">Next</FONT></B></A>
-%
% }
%
+% $cgi->param('offset', $orig_offset); #so future $self_url invocations don't advance a page
+%
% }
+<%init>
+
+my %opt = @_;
+
+my $orig_offset = $opt{'offset'};
+
+</%init>
+
% if ( $curuser->access_right('List customers') ) {
- <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="GET" STYLE="margin:0">
+ <FORM ACTION="<%$fsurl%>search/cust_main.cgi" METHOD="POST" STYLE="margin:0">
<INPUT NAME="search_cust" TYPE="text" VALUE="<% $cust_label |n %>" STYLE="width:<%$width%>" onFocus="clearhint_search_cust(this);" onClick="clearhint_search_cust(this);" CLASS="fstext"><BR>
<A HREF="<%$fsurl%>search/report_cust_main.html" CLASS="fslink" STYLE="font-size: 11px"><% mt('Advanced') |h %></A>
<INPUT TYPE="submit" VALUE="<% mt('Search customers') |h %>" CLASS="fsblackbutton" onMouseOver="this.className='fsblackbuttonselected'; return true;" onMouseOut="this.className='fsblackbutton'; return true;" STYLE="font-size:11px">
--- /dev/null
+% if ($censustract) {
+<TR>
+ <TD ALIGN="right"><% mt('Census tract') |h %></TD>
+ <TD COLSPAN=5>
+ <SPAN STYLE="background-color: #ffffff; border: 1px solid #ffffff"><% $censustract |h %></SPAN>
+ <% $censusyear |h %>
+ </TD>
+</TR>
+% }
+<%init>
+
+my $location = shift;
+my $conf = FS::Conf->new;
+my ($censustract, $censusyear);
+if ($location->censustract) {
+ $censustract = $location->censustract;
+ $censusyear = '('. ($location->censusyear || mt('unknown year')) . ')';
+} elsif ($conf->exists('cust_main-require_censustract')) {
+ $censustract = mt('unknown');
+ $censusyear = '';
+}
+
+</%init>
--- /dev/null
+ <TR>
+ <TD ALIGN="right" VALIGN="top"><% mt('Phones') %></TD>
+ <TD COLSPAN=6>
+
+ <TABLE CELLSPACING=0 CELLPADDING=0>
+ <TR>
+ <TD>
+ <INPUT TYPE="text" NAME="<%$pre%>daytime" VALUE="<% $cust_main->get($pre.'daytime') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ <BR><FONT SIZE=-1><% $daytime_label %></FONT>
+ </TD>
+ <TD> </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="<%$pre%>night" VALUE="<% $cust_main->get($pre.'night') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ <BR><FONT SIZE=-1><% $night_label %></FONT>
+ </TD>
+ <TD> </TD>
+ <TD>
+ <INPUT TYPE="text" NAME="<%$pre%>mobile" VALUE="<% $cust_main->get($pre.'mobile') %>" SIZE=18 onChange="<% $onchange %>" <%$disabled%> <%$style%>>
+ <BR><FONT SIZE=-1><% $mobile_label %></FONT>
+ </TD>
+ </TR>
+ </TABLE>
+ </TD>
+ </TR>
+<%init>
+
+my $daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
+ ? 'Day'
+ : FS::Msgcat::_gettext('daytime');
+my $night_label = FS::Msgcat::_gettext('night') =~/^(night)?$/
+ ? 'Night'
+ : FS::Msgcat::_gettext('night') || 'Night';
+my $mobile_label = FS::Msgcat::_gettext('mobile') =~/^(mobile)?$/
+ ? 'Mobile'
+ : FS::Msgcat::_gettext('mobile') || 'Mobile';
+
+my %opt = @_;
+
+my $pre = $opt{'prefix'};
+my $cust_main = $opt{'cust_main'};
+my $onchange = $opt{'onchange'};
+my $disabled = $opt{'disabled'};
+my $style = $opt{'style'};
+
+</%init>
else what.form.<%$_%>.value = '';
if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#dddddd';
% }
+ if(what.form.enter_censustract) {
+ what.form.enter_censustract.disabled = true;
+ }
}
function location_clear(what) {
var ftype = what.form.<%$_%>.tagName;
if( ftype == 'INPUT' ) what.form.<%$_%>.value = '';
% }
+ if(what.form.enter_censustract) {
+ what.form.enter_censustract.value = '';
+ }
% if ( $opt{'alt_format'} ) {
changeSelect(what.form.location_kind, '');
changeSelect(what.form.location_type, '');
var ftype = what.form.<%$_%>.tagName;
if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff';
% }
+ if(what.form.enter_censustract) {
+ what.form.enter_censustract.disabled = false;
+ }
% if ( $opt{'alt_format'} ) {
if ( what.form.location_type &&
what.form.location_type.options[what.form.location_type.selectedIndex].value ) {
</TD>
</TR>
+% if ( $conf->config_bool('cust_main-require_phone') ) {
+% #XXX should be sticky on errors
+% my $ph_cust_main = FS::cust_main->new({});
+% foreach my $contact_phone ( $cust_contact->contact_phone ) {
+% my $t = $contact_phone->typename;
+% #countrycodes? interface doesn't parse/take em yet
+% $ph_cust_main->daytime( $contact_phone->phonenum ) if $t eq 'Work';
+% $ph_cust_main->night( $contact_phone->phonenum ) if $t eq 'Home';
+% $ph_cust_main->mobile( $contact_phone->phonenum ) if $t eq 'Mobile';
+% $ph_cust_main->fax( $contact_phone->phonenum ) if $t eq 'Fax';
+% }
+
+ <& /elements/tr-cust_main-phones.html,
+ 'cust_main' => $ph_cust_main,
+ &>
+% }
+
</TABLE>
-%#XXX payment info
+%#payment info
%#XXX should be sticky on errors...
<& /edit/cust_main/billing.html, FS::cust_main->new({}),
invoicing_list => [],
--- /dev/null
+% if ( @error ) {
+<& /elements/errorpage-popup.html, @error &>
+% } else {
+<& /elements/header-popup.html, "Template ${actioned}" &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+</BODY>
+</HTML>
+% }
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+my $conf = FS::Conf->new;
+my @error;
+my $actioned;
+
+die "access denied"
+ unless $curuser->access_right([ 'Edit templates', 'Edit global templates' ]);
+
+my $msgnum = $cgi->param('msgnum');
+$msgnum =~ /^\d+$/ or die "bad msgnum '$msgnum'";
+my $msg_template = qsearchs({
+ table => 'msg_template',
+ hashref => { msgnum => $msgnum },
+ extra_sql => ' AND '.
+ $curuser->agentnums_sql(null_right => 'Edit global templates'),
+});
+die "unknown msgnum $msgnum" unless $msg_template;
+
+if ( $cgi->param('enable') ) {
+ $actioned = 'enabled';
+ $msg_template->set('disabled' => '');
+} else {
+ $actioned = 'disabled';
+ # make sure it's not in use anywhere
+ my @inuse;
+
+ # notice, letter, notice_to events (if they're enabled)
+ my @events = qsearch({
+ table => 'part_event_option',
+ addl_from => ' JOIN part_event USING (eventpart)',
+ hashref => {
+ optionname => 'msgnum',
+ optionvalue => $msgnum,
+ },
+ extra_sql => ' AND disabled IS NULL',
+ });
+ push @inuse, map {"Billing event #".$_->eventpart} @events;
+
+ # send_email and rt_ticket exports
+ my @exports = qsearch( 'part_export_option', {
+ optionname => { op => 'LIKE', value => '%_template' },
+ optionvalue => $msgnum,
+ });
+ push @inuse, map {"Export #".$_->exportnum} @exports;
+
+ # payment_receipt_msgnum, decline_msgnum, etc.
+ my @confs = qsearch( 'conf', {
+ name => { op => 'LIKE', value => '%_msgnum' },
+ value => $msgnum,
+ });
+ push @inuse, map {"Configuration setting ".$_->name} @confs;
+ # XXX pending queue jobs?
+ if (@inuse) {
+ @error = ("This template is in use. Check the following settings:",
+ @inuse);
+ }
+
+ # good to go
+ $msg_template->set(disabled => 'Y');
+}
+if (!@error) {
+ my $error = $msg_template->replace;
+ push @error, $error if $error;
+}
+</%init>
<% include('/elements/select-table.html',
'label' => 'Template:',
'table' => 'msg_template',
+ 'hashref' => { disabled => '' },
'name_col' => 'msgname',
'empty_label' => '(none)',
'onchange' => 'toggle(this)',
@currency,
'invnum',
'_date',
- #'pay_amount',
- #'credit_amount',
+ '', #'pay_amount',
+ '', #'credit_amount',
+ FS::UI::Web::cust_sort_fields(),
],
'links' => [
@pkgnum_null,
<TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->company |h %></TD>
</TR>
% }
-% } # if $this eq 'bill'
+% } elsif ( $this eq 'ship' ) {
+% if ( $cust_main->ship_company ) { # mostly obsolete these days...
+ <TR>
+ <TD ALIGN="right"><% mt('Company') |h %></TD>
+ <TD COLSPAN=7 BGCOLOR="#ffffff"><% $cust_main->ship_company |h %></TD>
+ </TR>
+% }
+% }
% # now the actual address
<TR>
<TD ALIGN="right"><% mt('Address') |h %></TD>
$cust_main->agentnum,
&>
% }
+<& /elements/tr-censustract.html, $location &>
% if ( $this eq 'bill' ) {
% # billing contact phone numbers
<STYLE>
-span.loclabel {
+div.loclabel {
+ display: inline-block;
padding-left: 4px;
padding-right: 4px;
background-color: #cccccc;
- border: 1px solid black
+ border: 1px solid black;
+ border-bottom: 0px;
+ border-radius: 4px 4px 0 0;
+}
+div.disabled {
+ font-style: italic;
+ color: #808080;
}
table.location {
width: 100%;
padding: 1px;
border-spacing: 0px;
}
+.location-head th {
+ padding-bottom: 0px;
+ padding-left: 0px;
+ border-bottom: 1px solid black;
+ vertical-align: bottom;
+ text-align: left;
+ width: 100%;
+}
</STYLE>
% foreach my $locationnum (@sorted) {
% my $packages = $packages_in{$locationnum};
% my $loc = $locations{$locationnum};
% next if $loc->disabled and scalar(@$packages) == 0;
<TABLE CLASS="grid location">
-<TR><TH COLSPAN=3 ALIGN="left" VALIGN="bottom"
-STYLE="padding-bottom: 0px;
- padding-left: 0px;
- border-bottom-style: solid;
- border-bottom-color: black;
- border-bottom-width: 1px;">
-<SPAN CLASS="loclabel">
-% if ( $loc->disabled ) {
-<FONT COLOR="#808080"><I>
+<TR CLASS="location-head">
+<TH COLSPAN=5>
+<DIV CLASS="<% $loc->disabled ? 'loclabel disabled' : 'loclabel' %>">
+<% $loc->location_label %>
+% if ( $loc->censustract ) {
+ <BR>
+ <FONT SIZE=-1>
+ <% $loc->censustract %> (<% $loc->censusyear %> census)
+ </FONT>
+% } elsif ( $conf->exists('cust_main-require_censustract') ) {
+ <BR>
+ <FONT SIZE=-1 COLOR="#ee3300">
+ <% emt('Census tract unknown') %>
+ </FONT>
% }
-<% $loc->location_label %></SPAN>
-<SPAN STYLE="float:right;">
+</DIV>
+<DIV STYLE="display: inline; float:right;">
% if ( $locationnum && !$loc->disabled && ! $opt{no_links} ) {
<% edit_location_link($locationnum) %>
% }
% if ( $locationnum && !$loc->disabled && !$active{$locationnum} && ! $opt{no_links} ) {
<% disable_location_link($locationnum) %>
% }
-</SPAN></TH></TR>
+</DIV></TH></TR>
% if (@$packages) {
<& packages/section.html,
'packages' => $packages,
my %opt = @_;
my $cust_main = $opt{'cust_main'};
my $all_packages = $opt{'packages'};
+my $conf = FS::Conf->new;
my %locations = map { $_->locationnum => $_ } qsearch({
'table' => 'cust_location',
% }
-% if ( $conf->exists('cust_main-require_censustract') ) {
-
- <TR>
- <TD ALIGN="right">
- <% mt('Census tract ([_1])', $cust_main->ship_location->censusyear) |h %>
- </TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->ship_location->censustract %></TD>
- </TR>
-
-% }
-
% if ( $cust_main->district ) {
<TR>
% if ( $cust_pkg->change_from_pkg
-% and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum )
+% and $cust_pkg->change_from_pkg->locationnum == $cust_pkg->locationnum)
% {
% # don't show the location
% } else {
-% if ( $default ) {
- <DIV STYLE="font-style: italic; font-size: small">
-% }
+% if ( !$conf->exists('cust_pkg-group_by_location') ) {
+% if ( $default ) {
+ <DIV STYLE="font-style: italic; font-size: small">
+% }
- <% $loc->location_label( 'join_string' => '<BR>',
- 'double_space' => ' ',
- 'escape_function' => \&encode_entities,
- 'countrydefault' => $countrydefault,
- )
- %>
+ <% $loc->location_label( 'join_string' => '<BR>',
+ 'double_space' => ' ',
+ 'escape_function' => \&encode_entities,
+ 'countrydefault' => $countrydefault,
+ )
+ %>
-% if ( $loc->latitude && $loc->longitude ) {
- <BR>
- <FONT SIZE=-1>
- <% $loc->latitude %>, <% $loc->longitude %>
- <& /elements/coord-links.html,
- $loc->latitude,
- $loc->longitude,
- $opt{'cust_main'}->name_short. ': '. $opt{'part_pkg'}->pkg,
- $opt{'cust_main'}->agentnum,
- &>
- </FONT>
-% }
+% if ( $loc->latitude && $loc->longitude ) {
+ <BR>
+ <FONT SIZE=-1>
+ <% $loc->latitude %>, <% $loc->longitude %>
+ <& /elements/coord-links.html,
+ $loc->latitude,
+ $loc->longitude,
+ $opt{'cust_main'}->name_short. ': '. $opt{'part_pkg'}->pkg,
+ $opt{'cust_main'}->agentnum,
+ &>
+ </FONT>
+% }
+% if ( $loc->censustract ) {
+ <BR>
+ <FONT SIZE=-1>
+ <% $loc->censustract %> (<% $loc->censusyear %> census)
+ </FONT>
+% } elsif ( $conf->exists('cust_main-require_censustract') ) {
+ <BR>
+ <FONT SIZE=-1 COLOR="#ee3300">
+ <% emt('Census tract unknown') %>
+ </FONT>
+% }
-% if ( $default ) {
- </DIV>
-% }
+% if ( $default ) {
+ </DIV>
+% }
+% } # all of this is hidden if packages are grouped by location, because
+% # it's in the top banner
% if ( ! $cust_pkg->get('cancel')
% && $FS::CurrentUser::CurrentUser->access_right('Change customer package')
( <%pkg_change_location_link($cust_pkg)%> )
% }
% if ( $cust_pkg->locationnum && ! $opt{no_links} ) {
- ( <%edit_location_link($cust_pkg->locationnum)%> )
+ ( <%pkg_edit_location_link($cust_pkg->locationnum)%> )
% }
</FONT>
% }
-% }
+% } # if the package is a scheduled future package change without location
+% # change, then don't show any of this at all. It's all implied by the
+% # preceding package.
<%init>
my $conf = new FS::Conf;
);
}
-sub edit_location_link {
+sub pkg_edit_location_link {
my $locationnum = shift;
include( '/elements/popup_link.html',
'action' => $p. "edit/cust_location.cgi?locationnum=$locationnum",