summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Van Dam <rvandam00@gmail.com>2013-11-07 15:27:24 -0700
committerRob Van Dam <rvandam00@gmail.com>2013-11-07 15:27:24 -0700
commitdf1ebf662a9fc3f89503036e0dbf6833c1b95f9e (patch)
treef62eafd9d77427277e6aaa4353fb3bf8567926fd
parent45cd27b19e8ad50f835915746fb89e486cbb69c0 (diff)
parenta180208786cccb72ab017e39fff0cb128aa6ba01 (diff)
Merge remote-tracking branch 'upstream/master'
Conflicts: FS/FS/cust_pkg.pm
-rw-r--r--FS/FS.pm4
-rw-r--r--FS/FS/ClientAPI/MyAccount.pm2
-rw-r--r--FS/FS/Conf.pm6
-rw-r--r--FS/FS/Mason.pm1
-rw-r--r--FS/FS/Misc/Geo.pm181
-rw-r--r--FS/FS/Record.pm35
-rw-r--r--FS/FS/Report/Table/Daily.pm4
-rw-r--r--FS/FS/Schema.pm2935
-rw-r--r--FS/FS/Upgrade.pm3
-rw-r--r--FS/FS/access_user.pm14
-rw-r--r--FS/FS/contact.pm65
-rw-r--r--FS/FS/contact_email.pm55
-rw-r--r--FS/FS/contact_phone.pm65
-rw-r--r--FS/FS/cust_bill.pm3
-rw-r--r--FS/FS/cust_credit.pm97
-rw-r--r--FS/FS/cust_main.pm16
-rw-r--r--FS/FS/cust_main/Billing.pm7
-rw-r--r--FS/FS/cust_main/Location.pm44
-rw-r--r--FS/FS/cust_main/Search.pm236
-rw-r--r--FS/FS/cust_pkg.pm46
-rw-r--r--FS/FS/discount.pm39
-rw-r--r--FS/FS/discount_class.pm109
-rw-r--r--FS/FS/part_pkg/prorate_calendar.pm223
-rw-r--r--FS/FS/payinfo_Mixin.pm21
-rw-r--r--FS/FS/sales.pm81
-rw-r--r--FS/FS/tower.pm7
-rw-r--r--FS/FS/tower_sector.pm5
-rw-r--r--FS/MANIFEST3
-rwxr-xr-xFS/bin/freeside-upgrade22
-rw-r--r--FS/t/discount_class.t5
-rw-r--r--eg/table_template.pm2
-rw-r--r--httemplate/browse/discount.html3
-rw-r--r--httemplate/browse/discount_class.html34
-rw-r--r--httemplate/browse/tower.html19
-rw-r--r--httemplate/edit/access_user.html22
-rw-r--r--httemplate/edit/discount.html2
-rw-r--r--httemplate/edit/discount_class.html10
-rw-r--r--httemplate/edit/elements/class_Common.html8
-rw-r--r--httemplate/edit/elements/svc_Common.html18
-rw-r--r--httemplate/edit/process/discount_class.html11
-rw-r--r--httemplate/edit/process/tower.html2
-rw-r--r--httemplate/edit/tower.html28
-rw-r--r--httemplate/elements/contact.html2
-rw-r--r--httemplate/elements/input-text.html1
-rw-r--r--httemplate/elements/menu.html1
-rw-r--r--httemplate/elements/select-discount_class.html18
-rw-r--r--httemplate/elements/standardize_locations.js39
-rw-r--r--httemplate/elements/tower_sector.html5
-rw-r--r--httemplate/elements/tr-input-beginning_ending.html4
-rw-r--r--httemplate/elements/tr-input-mask.html37
-rw-r--r--httemplate/elements/tr-password.html3
-rw-r--r--httemplate/elements/tr-select-discount_class.html27
-rw-r--r--httemplate/graph/report_cust_bill_pkg_discount.html31
-rw-r--r--httemplate/graph/report_money_time_daily.html9
-rw-r--r--httemplate/loginout/login.html2
-rw-r--r--httemplate/misc/confirm-address_standardize.html34
-rw-r--r--httemplate/pref/pref-process.html1
-rw-r--r--httemplate/pref/pref.html7
-rw-r--r--httemplate/search/cust_bill_pkg.cgi124
-rw-r--r--httemplate/search/cust_bill_pkg_discount.html64
-rwxr-xr-xhttemplate/search/cust_pkg.cgi4
-rw-r--r--httemplate/search/cust_pkg_discount.html48
-rw-r--r--httemplate/search/cust_tax_exempt_pkg.cgi39
-rw-r--r--httemplate/search/report_cust_bill_pkg_discount.html43
-rwxr-xr-xhttemplate/search/report_cust_main.html14
-rwxr-xr-xhttemplate/search/report_cust_pkg.html64
-rw-r--r--httemplate/search/report_cust_pkg_discount.html31
-rw-r--r--httemplate/search/report_sales_commission.html52
-rwxr-xr-xhttemplate/search/report_tax.cgi847
-rwxr-xr-xhttemplate/search/report_tax.html115
-rw-r--r--httemplate/search/sales_commission.html12
-rw-r--r--httemplate/search/sales_pkg_class.html27
-rw-r--r--httemplate/view/cust_main/billing.html5
-rw-r--r--httemplate/view/cust_main/contacts_new.html2
-rw-r--r--httemplate/view/svc_external.cgi1
75 files changed, 4380 insertions, 1826 deletions
diff --git a/FS/FS.pm b/FS/FS.pm
index b3ebf3050..abba99b19 100644
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -312,7 +312,9 @@ L<FS:;cust_pkg_discount> - Customer package discount class
L<FS:;cust_bill_pkg_discount> - Customer package discount line item application class
-L<FS:;discount> - Discount class
+L<FS::discount> - Discount class
+
+L<FS::discount_class> - Discount class class
L<FS::reason_type> - Reason type class
diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm
index 77a4683e5..db50d4250 100644
--- a/FS/FS/ClientAPI/MyAccount.pm
+++ b/FS/FS/ClientAPI/MyAccount.pm
@@ -1011,7 +1011,7 @@ sub validate_payment {
{
'cust_main' => $cust_main, #XXX or just custnum??
- 'amount' => $amount,
+ 'amount' => sprintf('%.2f', $amount),
'payby' => $payby,
'payinfo' => $payinfo,
'paymask' => $cust_main->mask_payinfo( $payby, $payinfo ),
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index dd4cd6869..eed84fc52 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -4259,9 +4259,9 @@ and customer address. Include units.',
{
'key' => 'census_year',
'section' => 'UI',
- 'description' => 'The year to use in census tract lookups. NOTE: you need to select 2012 for Year 2010 Census tract codes. A selection of 2011 or 2010 provides Year 2000 Census tract codes. Use the freeside-censustract-update tool if exisitng customers need to be changed.',
+ 'description' => 'The year to use in census tract lookups. NOTE: you need to select 2012 or 2013 for Year 2010 Census tract codes. A selection of 2011 provides Year 2000 Census tract codes. Use the freeside-censustract-update tool if exisitng customers need to be changed.',
'type' => 'select',
- 'select_enum' => [ qw( 2012 2011 2010 ) ],
+ 'select_enum' => [ qw( 2013 2012 2011 ) ],
},
{
@@ -5239,7 +5239,7 @@ and customer address. Include units.',
{
'key' => 'svc_phone-did-summary',
'section' => 'invoicing',
- 'description' => 'Enable DID activity summary on invoices, showing # DIDs activated/deactivated/ported-in/ported-out and total minutes usage, covering period since last invoice.',
+ 'description' => 'Experimental feature to enable DID activity summary on invoices, showing # DIDs activated/deactivated/ported-in/ported-out and total minutes usage, covering period since last invoice.',
'type' => 'checkbox',
},
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 398d78561..fc25a8638 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -357,6 +357,7 @@ if ( -e $addl_handler_use_file ) {
use FS::invoice_conf;
use FS::cable_provider;
use FS::cust_credit_void;
+ use FS::discount_class;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm
index c6d6f1f14..9f6b89bf2 100644
--- a/FS/FS/Misc/Geo.pm
+++ b/FS/FS/Misc/Geo.pm
@@ -84,7 +84,7 @@ sub get_censustract_ffiec {
my($zip5, $zip4) = split('-',$location->{zip});
- $year ||= '2012';
+ $year ||= '2013';
my @ffiec_args = (
__VIEWSTATE => $viewstate,
__EVENTVALIDATION => $eventvalidation,
@@ -414,13 +414,30 @@ sub standardize_ezlocate {
\%result;
}
+sub _tomtom_query { # helper method for the below
+ my %args = @_;
+ my $result = Geo::TomTom::Geocoding->query(%args);
+ die "TomTom geocoding error: ".$result->message."\n"
+ unless ( $result->is_success );
+ my ($match) = $result->locations;
+ my $type = $match->{type};
+ # match levels below "intersection" should not be considered clean
+ my $clean = ($type eq 'addresspoint' ||
+ $type eq 'poi' ||
+ $type eq 'house' ||
+ $type eq 'intersection'
+ ) ? 'Y' : '';
+ warn "tomtom returned $type match\n" if $DEBUG;
+ warn Dumper($match) if $DEBUG > 1;
+ ($match, $clean);
+}
+
sub standardize_tomtom {
# post-2013 TomTom API
# much better, but incompatible with ezlocate
my $self = shift;
my $location = shift;
- my $class = 'Geo::TomTom::Geocoding';
- eval "use $class";
+ eval "use Geo::TomTom::Geocoding; use Geo::StreetAddress::US";
die $@ if $@;
my $key = $conf->config('tomtom-userid')
@@ -428,12 +445,25 @@ sub standardize_tomtom {
my $country = code2country($location->{country});
my ($address1, $address2) = ($location->{address1}, $location->{address2});
+ my $subloc = '';
+
+ # trim whitespace
+ $address1 =~ s/^\s+//;
+ $address1 =~ s/\s+$//;
+ $address2 =~ s/^\s+//;
+ $address2 =~ s/\s+$//;
+
# try to fix some cases of the address fields being switched
if ( $address2 =~ /^\d/ and $address1 !~ /^\d/ ) {
$address2 = $address1;
$address1 = $location->{address2};
}
- my $result = $class->query(
+ # parse sublocation part (unit/suite/apartment...) and clean up
+ # non-sublocation address2
+ ($subloc, $address2) =
+ subloc_address2($address1, $address2, $location->{country});
+ # ask TomTom to standardize address1:
+ my %args = (
key => $key,
T => $address1,
L => $location->{city},
@@ -441,40 +471,48 @@ sub standardize_tomtom {
PC => $location->{zip},
CC => country2code($country, LOCALE_CODE_ALPHA_3),
);
- unless ( $result->is_success ) {
- die "TomTom geocoding error: ".$result->message."\n";
+
+ my ($match, $clean) = _tomtom_query(%args);
+
+ if (!$match or !$clean) {
+ # Then try cleaning up the input; TomTom is picky about junk in the
+ # address. Any of these can still be a clean match.
+ my $h = Geo::StreetAddress::US->parse_location($address1);
+ # First conservatively:
+ if ( $h->{sec_unit_type} ) {
+ my $strip = '\s+' . $h->{sec_unit_type};
+ $strip .= '\s*' . $h->{sec_unit_num} if $h->{sec_unit_num};
+ $strip .= '$';
+ $args{T} =~ s/$strip//;
+ ($match, $clean) = _tomtom_query(%args);
+ }
+ if ( !$match or !$clean ) {
+ # Then more aggressively:
+ $args{T} = uc( join(' ', @$h{'number', 'street', 'type'}) );
+ ($match, $clean) = _tomtom_query(%args);
+ }
}
- my ($match) = $result->locations;
- if (!$match) {
- die "Location not found.\n";
+
+ if ( !$match or !$clean ) { # partial matches are not useful
+ die "Address not found\n";
}
- my $type = $match->{type};
- warn "tomtom returned $type match\n" if $DEBUG;
- warn Dumper($match) if $DEBUG > 1;
my $tract = '';
if ( defined $match->{censusTract} ) {
$tract = $match->{censusStateCode}. $match->{censusFipsCountyCode}.
join('.', $match->{censusTract} =~ /(....)(..)/);
}
- # match levels below "intersection" should not be considered clean
- my $clean = ($type eq 'addresspoint' ||
- $type eq 'poi' ||
- $type eq 'house' ||
- $type eq 'intersection'
- ) ? 'Y' : '';
-
- $address2 = normalize_address2($address2, $location->{country});
-
$address1 = '';
$address1 = $match->{houseNumber} . ' ' if length($match->{houseNumber});
$address1 .= $match->{street} if $match->{street};
+ $address1 .= ' '.$subloc if $subloc;
+ $address1 = uc($address1); # USPS standards
return +{
address1 => $address1,
address2 => $address2,
- city => $match->{city},
- state => $location->{state}, # this will never change
- country => $location->{country}, # ditto
+ city => uc($match->{city}),
+ state => uc($location->{state}),
+ country => uc($location->{country}),
zip => ($match->{standardPostalCode} || $match->{postcode}),
latitude => $match->{latitude},
longitude => $match->{longitude},
@@ -483,15 +521,16 @@ sub standardize_tomtom {
};
}
-=iten normalize_address2 STRING, COUNTRY
+=iten subloc_address2 ADDRESS1, ADDRESS2, COUNTRY
-Given an 'address2' STRING, normalize it for COUNTRY postal standards.
-Currently only works for US and CA.
+Given 'address1' and 'address2' strings, extract the sublocation part
+(from either one) and return it. If the sublocation was found in ADDRESS1,
+also return ADDRESS2 (cleaned up for postal standards) as it's assumed to
+contain something relevant.
=cut
-# XXX really ought to be a separate module
-my %address2_forms = (
+my %subloc_forms = (
# Postal Addressing Standards, Appendix C
# (plus correction of "hanger" to "hangar")
US => {qw(
@@ -532,26 +571,76 @@ my %address2_forms = (
)},
);
-sub normalize_address2 {
+sub subloc_address2 {
# Some things seen in the address2 field:
# Whitespace
# The complete address (with address1 containing part of the company name,
# or an ATTN or DBA line, or P.O. Box, or department name, or building/suite
# number, etc.)
- my ($addr2, $country) = @_;
- $addr2 = uc($addr2);
- if ( exists($address2_forms{$country}) ) {
- my $dict = $address2_forms{$country};
- # protect this
- $addr2 =~ s/#\s*(\d)/NUMBER$1/; # /g?
- my @words;
- # remove all punctuation and spaces
- foreach my $w (split(/\W+/, $addr2)) {
- if ( exists($dict->{$w}) ) {
- push @words, $dict->{$w};
- } else {
- push @words, $w;
- }
+
+ # try to parse sublocation parts from address1; if they are present we'll
+ # append them back to address1 after standardizing
+ my $subloc = '';
+ my ($addr1, $addr2, $country) = map uc, @_;
+ my $dict = $subloc_forms{$country} or return('', $addr2);
+
+ my $found_in = 0; # which address is the sublocation
+ my $h;
+ foreach my $string (
+ # patterns to try to parse
+ $addr1,
+ "$addr1 Nullcity, CA"
+ ) {
+ $h = Geo::StreetAddress::US->parse_location($addr1);
+ last if exists($h->{sec_unit_type});
+ }
+ if (exists($h->{sec_unit_type})) {
+ $found_in = 1
+ } else {
+ foreach my $string (
+ # more patterns
+ $addr2,
+ "$addr1, $addr2",
+ "$addr1, $addr2 Nullcity, CA"
+ ) {
+ $h = Geo::StreetAddress::US->parse_location("$addr1, $addr2");
+ last if exists($h->{sec_unit_type});
+ }
+ if (exists($h->{sec_unit_type})) {
+ $found_in = 2;
+ }
+ }
+ if ( $found_in ) {
+ $subloc = $h->{sec_unit_type};
+ # special case: do not combine P.O. box sublocs with address1
+ if ( $h->{sec_unit_type} =~ /^P *O *BOX/i ) {
+ if ( $found_in == 2 ) {
+ $addr2 = "PO BOX ".$h->{sec_unit_num};
+ } # else it's in addr1, and leave it alone
+ return ('', $addr2);
+ } elsif ( exists($dict->{$subloc}) ) {
+ # substitute the official abbreviation
+ $subloc = $dict->{$subloc};
+ }
+ $subloc .= ' ' . $h->{sec_unit_num} if length($h->{sec_unit_num});
+ } # otherwise $subloc = ''
+
+ if ( $found_in == 2 ) {
+ # address2 should be fully combined into address1
+ return ($subloc, '');
+ }
+ # else address2 is not the canonical sublocation, but do our best to
+ # clean it up
+ #
+ # protect this
+ $addr2 =~ s/#\s*(\d)/NUMBER$1/; # /g?
+ my @words;
+ # remove all punctuation and spaces
+ foreach my $w (split(/\W+/, $addr2)) {
+ if ( exists($dict->{$w}) ) {
+ push @words, $dict->{$w};
+ } else {
+ push @words, $w;
}
my $result = join(' ', @words);
# correct spacing of pound sign + number
@@ -559,7 +648,8 @@ sub normalize_address2 {
warn "normalizing '$addr2' to '$result'\n" if $DEBUG > 1;
$addr2 = $result;
}
- $addr2;
+ $addr2 = '' if $addr2 eq $subloc; # if it was entered redundantly
+ ($subloc, $addr2);
}
@@ -567,5 +657,4 @@ sub normalize_address2 {
=cut
-
1;
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
index 71eddc1eb..1a88c3acd 100644
--- a/FS/FS/Record.pm
+++ b/FS/FS/Record.pm
@@ -1,22 +1,18 @@
package FS::Record;
+use base qw( Exporter );
use strict;
-use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG
+use vars qw( $AUTOLOAD
%virtual_fields_cache
- $conf $conf_encryption $money_char $lat_lower $lon_upper
- $me
- $nowarn_identical $nowarn_classload
- $no_update_diff $no_check_foreign
- @encrypt_payby
+ $money_char $lat_lower $lon_upper
);
-use Exporter;
use Carp qw(carp cluck croak confess);
use Scalar::Util qw( blessed );
use File::Slurp qw( slurp );
use File::CounterFile;
use Text::CSV_XS;
use DBI qw(:sql_types);
-use DBIx::DBSchema 0.38;
+use DBIx::DBSchema 0.43; #0.43 for foreign keys
use Locale::Country;
use Locale::Currency;
use NetAddr::IP; # for validation
@@ -31,32 +27,31 @@ use FS::part_virtual_field;
use Tie::IxHash;
-@ISA = qw(Exporter);
-
-@encrypt_payby = qw( CARD DCRD CHEK DCHK );
+our @encrypt_payby = qw( CARD DCRD CHEK DCHK );
#export dbdef for now... everything else expects to find it here
-@EXPORT_OK = qw(
+our @EXPORT_OK = qw(
dbh fields hfields qsearch qsearchs dbdef jsearch
str2time_sql str2time_sql_closing regexp_sql not_regexp_sql concat_sql
midnight_sql
);
-$DEBUG = 0;
-$me = '[FS::Record]';
+our $DEBUG = 0;
+our $me = '[FS::Record]';
+
+our $nowarn_identical = 0;
+our $nowarn_classload = 0;
+our $no_update_diff = 0;
-$nowarn_identical = 0;
-$nowarn_classload = 0;
-$no_update_diff = 0;
-$no_check_foreign = 0;
+our $no_check_foreign = 1; #well, not inefficiently in perl by default anymore
my $rsa_module;
my $rsa_loaded;
my $rsa_encrypt;
my $rsa_decrypt;
-$conf = '';
-$conf_encryption = '';
+our $conf = '';
+our $conf_encryption = '';
FS::UID->install_callback( sub {
eval "use FS::Conf;";
diff --git a/FS/FS/Report/Table/Daily.pm b/FS/FS/Report/Table/Daily.pm
index 6087b0dcc..570fefe4d 100644
--- a/FS/FS/Report/Table/Daily.pm
+++ b/FS/FS/Report/Table/Daily.pm
@@ -27,6 +27,7 @@ FS::Report::Table::Daily - Tables of report data, indexed daily
'end_day' => 27,
#opt
'agentnum' => 54
+ 'cust_classnum' => [ 1,2,4 ],
'params' => [ [ 'paramsfor', 'item_one' ], [ 'item', 'two' ] ], # ...
'remove_empty' => 1, #collapse empty rows, default 0
'item_labels' => [ ], #useful with remove_empty
@@ -54,6 +55,8 @@ sub data {
my $emonth = $self->{'end_month'};
my $eyear = $self->{'end_year'};
my $agentnum = $self->{'agentnum'};
+ my $cust_classnum = $self->{'cust_classnum'} || [];
+ $cust_classnum = [ $cust_classnum ] if !ref($cust_classnum);
my %data;
@@ -83,6 +86,7 @@ sub data {
for ( $i = 0; $i < scalar(@items); $i++ ) {
my $item = $items[$i];
my @param = $self->{'params'} ? @{ $self->{'params'}[$col] }: ();
+ push @param, 'cust_classnum' => $cust_classnum if @$cust_classnum;
my $value = $self->$item($speriod, $eperiod, $agentnum, @param);
push @{$data{data}->[$col++]}, $value;
}
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 3029ab579..8ba60200e 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -3,10 +3,11 @@ package FS::Schema;
use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache);
use subs qw(reload_dbdef);
use Exporter;
-use DBIx::DBSchema 0.40; #0.40 for mysql upgrade fixes
+use DBIx::DBSchema 0.43; #0.43 for foreign keys
use DBIx::DBSchema::Table;
use DBIx::DBSchema::Column;
use DBIx::DBSchema::Index;
+use DBIx::DBSchema::ForeignKey;
#can't use this yet, dependency bs #use FS::Conf;
@ISA = qw(Exporter);
@@ -149,12 +150,17 @@ sub dbdef_dist {
}
@index;
+ my @foreign_keys =
+ map DBIx::DBSchema::ForeignKey->new($_),
+ @{ $tables_hashref->{$tablename}{'foreign_keys'} || [] };
+
DBIx::DBSchema::Table->new({
- 'name' => $tablename,
- 'primary_key' => $tables_hashref->{$tablename}{'primary_key'},
- 'columns' => \@columns,
- 'indices' => \@indices,
- 'local_options' => $local_options,
+ name => $tablename,
+ primary_key => $tables_hashref->{$tablename}{'primary_key'},
+ columns => \@columns,
+ indices => \@indices,
+ foreign_keys => \@foreign_keys,
+ local_options => $local_options,
});
} keys %$tables_hashref;
@@ -204,7 +210,7 @@ sub dbdef_dist {
my %h_indices = ();
- unless ( $table eq 'cust_event' ) { #others?
+ unless ( $table eq 'cust_event' || $table eq 'cdr' ) { #others?
my %indices = $tableobj->indices;
@@ -487,12 +493,28 @@ sub tables_hashref {
'freq', 'int', 'NULL', '', '', '', #deprecated (never used)
'prog', @perl_type, '', '', #deprecated (never used)
],
- 'primary_key' => 'agentnum',
+ 'primary_key' => 'agentnum',
#'unique' => [ [ 'agent_custnum' ] ], #one agent per customer?
#insert is giving it a value, tho..
#'index' => [ ['typenum'], ['disabled'] ],
- 'unique' => [],
- 'index' => [ ['typenum'], ['disabled'], ['agent_custnum'] ],
+ 'unique' => [],
+ 'index' => [ ['typenum'], ['disabled'], ['agent_custnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'typenum' ],
+ table => 'agent_type',
+ },
+ # 1. RT tables aren't part of our data structure, so
+ # we can't make sure Queue is created already
+ # 2. Future ability to plug in other ticketing systems
+ #{ columns => [ 'ticketing_queueid' ],
+ # table => 'Queue',
+ # references => [ 'id' ],
+ #},
+ { columns => [ 'agent_custnum' ],
+ table => 'cust_main',
+ references => [ 'custnum' ],
+ },
+ ],
},
'agent_pkg_class' => {
@@ -502,9 +524,17 @@ sub tables_hashref {
'classnum', 'int', 'NULL', '', '', '',
'commission_percent', 'decimal', '', '7,4', '', '',
],
- 'primary_key' => 'agentpkgclassnum',
- 'unique' => [ [ 'agentnum', 'classnum' ], ],
- 'index' => [],
+ 'primary_key' => 'agentpkgclassnum',
+ 'unique' => [ [ 'agentnum', 'classnum' ], ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ { columns => [ 'classnum' ],
+ table => 'pkg_class',
+ },
+ ],
},
'agent_type' => {
@@ -523,9 +553,17 @@ sub tables_hashref {
'typenum', 'int', '', '', '', '',
'pkgpart', 'int', '', '', '', '',
],
- 'primary_key' => 'typepkgnum',
- 'unique' => [ ['typenum', 'pkgpart'] ],
- 'index' => [ ['typenum'] ],
+ 'primary_key' => 'typepkgnum',
+ 'unique' => [ ['typenum', 'pkgpart'] ],
+ 'index' => [ ['typenum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'typenum' ],
+ table => 'agent_type',
+ },
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ ],
},
'agent_currency' => {
@@ -534,9 +572,14 @@ sub tables_hashref {
'agentnum', 'int', '', '', '', '',
'currency', 'char', '', 3, '', '',
],
- 'primary_key' => 'agentcurrencynum',
- 'unique' => [],
- 'index' => [ ['agentnum'] ],
+ 'primary_key' => 'agentcurrencynum',
+ 'unique' => [],
+ 'index' => [ ['agentnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'sales' => {
@@ -547,9 +590,18 @@ sub tables_hashref {
'sales_custnum', 'int', 'NULL', '', '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'salesnum',
- 'unique' => [],
- 'index' => [ ['salesnum'], ['disabled'] ],
+ 'primary_key' => 'salesnum',
+ 'unique' => [],
+ 'index' => [ ['salesnum'], ['disabled'] ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ { columns => [ 'sales_custnum' ],
+ table => 'cust_main',
+ references => [ 'custnum' ],
+ },
+ ],
},
'sales_pkg_class' => {
@@ -560,9 +612,17 @@ sub tables_hashref {
'commission_percent', 'decimal', '', '7,4', '', '',
'commission_duration', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'salespkgclassnum',
- 'unique' => [ [ 'salesnum', 'classnum' ], ],
- 'index' => [],
+ 'primary_key' => 'salespkgclassnum',
+ 'unique' => [ [ 'salesnum', 'classnum' ], ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'salesnum' ],
+ table => 'sales',
+ },
+ { columns => [ 'classnum' ],
+ table => 'pkg_class',
+ },
+ ],
},
'cust_attachment' => {
@@ -578,10 +638,18 @@ sub tables_hashref {
'body', 'blob', 'NULL', '', '', '',
'disabled', @date_type, '', '',
],
- 'primary_key' => 'attachnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['usernum'], ],
- },
+ 'primary_key' => 'attachnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['usernum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
+ },
'cust_bill' => {
'columns' => [
@@ -606,9 +674,19 @@ sub tables_hashref {
'agent_invid', 'int', 'NULL', '', '', '', #(varchar?) importing legacy
'promised_date', @date_type, '', '',
],
- 'primary_key' => 'invnum',
- 'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh
- 'index' => [ ['custnum'], ['_date'], ['statementnum'], ['agent_invid'] ],
+ 'primary_key' => 'invnum',
+ 'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh
+ 'index' => [ ['custnum'], ['_date'], ['statementnum'],
+ ['agent_invid'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'statementnum' ],
+ table => 'cust_statement',
+ },
+ ],
},
'cust_bill_void' => {
@@ -636,9 +714,23 @@ sub tables_hashref {
'reason', 'varchar', 'NULL', $char_d, '', '',
'void_usernum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'invnum',
- 'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh
- 'index' => [ ['custnum'], ['_date'], ['statementnum'], ['agent_invid'], [ 'void_usernum' ] ],
+ 'primary_key' => 'invnum',
+ 'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh
+ 'index' => [ ['custnum'], ['_date'], ['statementnum'],
+ ['agent_invid'], [ 'void_usernum' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'statementnum' ],
+ table => 'cust_statement', #_void? both?
+ },
+ { columns => [ 'void_usernum' ],
+ table => 'access_user',
+ references => [ 'usernum' ],
+ },
+ ],
},
#for importing invoices from a legacy system for display purposes only
@@ -655,9 +747,14 @@ sub tables_hashref {
'content_html', 'text', 'NULL', '', '', '',
'locale', 'varchar', 'NULL', 16, '', '',
],
- 'primary_key' => 'legacyinvnum',
- 'unique' => [],
- 'index' => [ ['legacyid', 'custnum', 'locale' ], ],
+ 'primary_key' => 'legacyinvnum',
+ 'unique' => [],
+ 'index' => [ ['legacyid', 'custnum', 'locale' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'cust_statement' => {
@@ -666,11 +763,17 @@ sub tables_hashref {
'custnum', 'int', '', '', '', '',
'_date', @date_type, '', '',
],
- 'primary_key' => 'statementnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['_date'], ],
+ 'primary_key' => 'statementnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['_date'], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
+ #old "invoice" events, deprecated
'cust_bill_event' => {
'columns' => [
'eventnum', 'serial', '', '', '', '',
@@ -680,14 +783,23 @@ sub tables_hashref {
'status', 'varchar', '', $char_d, '', '',
'statustext', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'eventnum',
+ 'primary_key' => 'eventnum',
#no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
- 'unique' => [],
- 'index' => [ ['invnum'], ['status'], ['eventpart'],
- ['statustext'], ['_date'],
- ],
+ 'unique' => [],
+ 'index' => [ ['invnum'], ['status'], ['eventpart'],
+ ['statustext'], ['_date'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ { columns => [ 'eventpart' ],
+ table => 'part_bill_event',
+ },
+ ],
},
+ #old "invoice" events, deprecated
'part_bill_event' => {
'columns' => [
'eventpart', 'serial', '', '', '', '',
@@ -702,9 +814,15 @@ sub tables_hashref {
'reason', 'int', 'NULL', '', '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'eventpart',
- 'unique' => [],
- 'index' => [ ['payby'], ['disabled'], ],
+ 'primary_key' => 'eventpart',
+ 'unique' => [],
+ 'index' => [ ['payby'], ['disabled'], ],
+ 'foreign_keys' => [
+ { columns => [ 'reason' ],
+ table => 'reason',
+ references => [ 'reasonnum' ],
+ },
+ ],
},
'part_event' => {
@@ -718,9 +836,16 @@ sub tables_hashref {
'action', 'varchar', '', $char_d, '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'eventpart',
- 'unique' => [],
- 'index' => [ ['agentnum'], ['eventtable'], ['check_freq'], ['disabled'], ],
+ 'primary_key' => 'eventpart',
+ 'unique' => [],
+ 'index' => [ ['agentnum'], ['eventtable'], ['check_freq'],
+ ['disabled'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'part_event_option' => {
@@ -730,9 +855,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'eventpart' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'eventpart' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'eventpart' ],
+ table => 'part_event',
+ },
+ ],
},
'part_event_condition' => {
@@ -741,9 +871,14 @@ sub tables_hashref {
'eventpart', 'int', '', '', '', '',
'conditionname', 'varchar', '', $char_d, '', '',
],
- 'primary_key' => 'eventconditionnum',
- 'unique' => [],
- 'index' => [ [ 'eventpart' ], [ 'conditionname' ] ],
+ 'primary_key' => 'eventconditionnum',
+ 'unique' => [],
+ 'index' => [ [ 'eventpart' ], [ 'conditionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'eventpart' ],
+ table => 'part_event',
+ },
+ ],
},
'part_event_condition_option' => {
@@ -753,9 +888,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'eventconditionnum' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'eventconditionnum' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'eventconditionnum' ],
+ table => 'part_event_condition',
+ },
+ ],
},
'part_event_condition_option_option' => {
@@ -765,9 +905,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionoptionnum',
- 'unique' => [],
- 'index' => [ [ 'optionnum' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionoptionnum',
+ 'unique' => [],
+ 'index' => [ [ 'optionnum' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'optionnum' ],
+ table => 'part_event_condition_option',
+ },
+ ],
},
'cust_event' => {
@@ -779,12 +924,17 @@ sub tables_hashref {
'status', 'varchar', '', $char_d, '', '',
'statustext', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'eventnum',
+ 'primary_key' => 'eventnum',
#no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
- 'unique' => [],
- 'index' => [ ['eventpart'], ['tablenum'], ['status'],
- ['statustext'], ['_date'],
- ],
+ 'unique' => [],
+ 'index' => [ ['eventpart'], ['tablenum'], ['status'],
+ ['statustext'], ['_date'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'eventpart' ],
+ table => 'part_event',
+ },
+ ],
},
'cust_bill_pkg' => {
@@ -810,9 +960,22 @@ sub tables_hashref {
'quantity', 'int', 'NULL', '', '', '',
'hidden', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'billpkgnum',
- 'unique' => [],
- 'index' => [ ['invnum'], [ 'pkgnum' ], [ 'itemdesc' ], ],
+ 'primary_key' => 'billpkgnum',
+ 'unique' => [],
+ 'index' => [ ['invnum'], [ 'pkgnum' ], [ 'itemdesc' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ #pkgnum 0 and -1 are used for special things
+ #{ columns => [ 'pkgnum' ],
+ # table => 'cust_pkg',
+ #},
+ { columns => [ 'pkgpart_override' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ],
+ },
+ ],
},
'cust_bill_pkg_detail' => {
@@ -831,9 +994,25 @@ sub tables_hashref {
'regionname', 'varchar', 'NULL', $char_d, '', '',
'detail', 'varchar', '', 255, '', '',
],
- 'primary_key' => 'detailnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ], [ 'classnum' ], [ 'pkgnum', 'invnum' ] ],
+ 'primary_key' => 'detailnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'classnum' ],
+ [ 'pkgnum', 'invnum' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ { columns => [ 'classnum' ],
+ table => 'usage_class',
+ },
+ ],
},
'cust_bill_pkg_display' => {
@@ -847,9 +1026,14 @@ sub tables_hashref {
'type', 'char', 'NULL', 1, '', '',
'summary', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'billpkgdisplaynum',
- 'unique' => [],
- 'index' => [ ['billpkgnum'], ],
+ 'primary_key' => 'billpkgdisplaynum',
+ 'unique' => [],
+ 'index' => [ ['billpkgnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ ],
},
'cust_bill_pkg_tax_location' => {
@@ -864,14 +1048,29 @@ sub tables_hashref {
'currency', 'char', 'NULL', 3, '', '',
'taxable_billpkgnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'billpkgtaxlocationnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ],
- [ 'taxnum' ],
- [ 'pkgnum' ],
- [ 'locationnum' ],
- [ 'taxable_billpkgnum' ],
- ],
+ 'primary_key' => 'billpkgtaxlocationnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ],
+ [ 'taxnum' ],
+ [ 'pkgnum' ],
+ [ 'locationnum' ],
+ [ 'taxable_billpkgnum' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ { columns => [ 'taxable_billpkgnum' ],
+ table => 'cust_bill_pkg',
+ references => [ 'billpkgnum' ],
+ },
+ ],
},
'cust_bill_pkg_tax_rate_location' => {
@@ -886,10 +1085,23 @@ sub tables_hashref {
'currency', 'char', 'NULL', 3, '', '',
'taxable_billpkgnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'billpkgtaxratelocationnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ], [ 'taxnum' ], [ 'taxratelocationnum' ],
- [ 'taxable_billpkgnum' ], ],
+ 'primary_key' => 'billpkgtaxratelocationnum',
+ 'unique' => [],
+ 'index' => [ ['billpkgnum'], ['taxnum'], ['taxratelocationnum'],
+ ['taxable_billpkgnum'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ { columns => [ 'taxratelocationnum' ],
+ table => 'tax_rate_location',
+ },
+ { columns => [ 'taxable_billpkgnum' ],
+ table => 'cust_bill_pkg',
+ references => [ 'billpkgnum' ],
+ },
+ ],
},
'cust_bill_pkg_void' => {
@@ -917,9 +1129,27 @@ sub tables_hashref {
'reason', 'varchar', 'NULL', $char_d, '', '',
'void_usernum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'billpkgnum',
- 'unique' => [],
- 'index' => [ ['invnum'], [ 'pkgnum' ], [ 'itemdesc' ], [ 'void_usernum' ], ],
+ 'primary_key' => 'billpkgnum',
+ 'unique' => [],
+ 'index' => [ ['invnum'], ['pkgnum'], ['itemdesc'],
+ ['void_usernum'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'invnum' ],
+ table => 'cust_bill_void',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'pkgpart_override' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ],
+ },
+ { columns => [ 'void_usernum' ],
+ table => 'access_user',
+ references => [ 'usernum' ],
+ },
+ ],
},
'cust_bill_pkg_detail_void' => {
@@ -938,9 +1168,23 @@ sub tables_hashref {
'regionname', 'varchar', 'NULL', $char_d, '', '',
'detail', 'varchar', '', 255, '', '',
],
- 'primary_key' => 'detailnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ], [ 'classnum' ], [ 'pkgnum', 'invnum' ] ],
+ 'primary_key' => 'detailnum',
+ 'unique' => [],
+ 'index' => [ ['billpkgnum'], ['classnum'], ['pkgnum', 'invnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg_void',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ { columns => [ 'classnum' ],
+ table => 'usage_class',
+ },
+ ],
},
'cust_bill_pkg_display_void' => {
@@ -954,9 +1198,14 @@ sub tables_hashref {
'type', 'char', 'NULL', 1, '', '',
'summary', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'billpkgdisplaynum',
- 'unique' => [],
- 'index' => [ ['billpkgnum'], ],
+ 'primary_key' => 'billpkgdisplaynum',
+ 'unique' => [],
+ 'index' => [ ['billpkgnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg_void',
+ },
+ ],
},
'cust_bill_pkg_tax_location_void' => {
@@ -971,9 +1220,26 @@ sub tables_hashref {
'currency', 'char', 'NULL', 3, '', '',
'taxable_billpkgnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'billpkgtaxlocationnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ], [ 'taxnum' ], [ 'pkgnum' ], [ 'locationnum' ] ],
+ 'primary_key' => 'billpkgtaxlocationnum',
+ 'unique' => [],
+ 'index' => [ ['billpkgnum'], ['taxnum'], ['pkgnum'],
+ ['locationnum'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg_void',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ { columns => [ 'taxable_billpkgnum' ],
+ table => 'cust_bill_pkg_void',
+ references => [ 'billpkgnum' ],
+ },
+ ],
},
'cust_bill_pkg_tax_rate_location_void' => {
@@ -987,9 +1253,17 @@ sub tables_hashref {
'amount', @money_type, '', '',
'currency', 'char', 'NULL', 3, '', '',
],
- 'primary_key' => 'billpkgtaxratelocationnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ], [ 'taxnum' ], [ 'taxratelocationnum' ] ],
+ 'primary_key' => 'billpkgtaxratelocationnum',
+ 'unique' => [],
+ 'index' => [ ['billpkgnum'], ['taxnum'], ['taxratelocationnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg_void',
+ },
+ { columns => [ 'taxratelocationnum' ],
+ table => 'tax_rate_location',
+ },
+ ],
},
'cust_credit' => {
@@ -1011,11 +1285,40 @@ sub tables_hashref {
'commission_salesnum', 'int', 'NULL', '', '', '', #
'commission_pkgnum', 'int', 'NULL', '', '', '', #
],
- 'primary_key' => 'crednum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['_date'], ['usernum'], ['eventnum'],
- [ 'commission_salesnum' ],
- ],
+ 'primary_key' => 'crednum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['_date'], ['usernum'], ['eventnum'],
+ ['commission_salesnum'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'reasonnum' ],
+ table => 'reason',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'eventnum' ],
+ table => 'cust_event',
+ },
+ { columns => [ 'commission_agentnum' ],
+ table => 'agent',
+ references => [ 'agentnum' ],
+ },
+ { columns => [ 'commission_salesnum' ],
+ table => 'sales',
+ references => [ 'salesnum' ],
+ },
+ { columns => [ 'commission_pkgnum' ],
+ table => 'cust_pkg',
+ references => [ 'pkgnum' ],
+ },
+ ],
},
'cust_credit_void' => {
@@ -1041,11 +1344,44 @@ sub tables_hashref {
'void_reason', 'varchar', 'NULL', $char_d, '', '',
'void_usernum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'crednum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['_date'], ['usernum'], ['eventnum'],
- [ 'commission_salesnum' ],
- ],
+ 'primary_key' => 'crednum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['_date'], ['usernum'], ['eventnum'],
+ ['commission_salesnum'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'reasonnum' ],
+ table => 'reason',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'eventnum' ],
+ table => 'cust_event',
+ },
+ { columns => [ 'commission_agentnum' ],
+ table => 'agent',
+ references => [ 'agentnum' ],
+ },
+ { columns => [ 'commission_salesnum' ],
+ table => 'sales',
+ references => [ 'salesnum' ],
+ },
+ { columns => [ 'commission_pkgnum' ],
+ table => 'cust_pkg',
+ references => [ 'pkgnum' ],
+ },
+ { columns => [ 'void_usernum' ],
+ table => 'access_user',
+ references => [ 'usernum' ],
+ },
+ ],
},
@@ -1058,9 +1394,20 @@ sub tables_hashref {
'amount', @money_type, '', '',
'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
],
- 'primary_key' => 'creditbillnum',
- 'unique' => [],
- 'index' => [ ['crednum'], ['invnum'] ],
+ 'primary_key' => 'creditbillnum',
+ 'unique' => [],
+ 'index' => [ ['crednum'], ['invnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'crednum' ],
+ table => 'cust_credit',
+ },
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ ],
},
'cust_credit_bill_pkg' => {
@@ -1075,13 +1422,27 @@ sub tables_hashref {
'sdate', @date_type, '', '',
'edate', @date_type, '', '',
],
- 'primary_key' => 'creditbillpkgnum',
- 'unique' => [],
- 'index' => [ [ 'creditbillnum' ],
- [ 'billpkgnum' ],
- [ 'billpkgtaxlocationnum' ],
- [ 'billpkgtaxratelocationnum' ],
- ],
+ 'primary_key' => 'creditbillpkgnum',
+ 'unique' => [],
+ 'index' => [ [ 'creditbillnum' ],
+ [ 'billpkgnum' ],
+ [ 'billpkgtaxlocationnum' ],
+ [ 'billpkgtaxratelocationnum' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'creditbillnum' ],
+ table => 'cust_credit_bill',
+ },
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ { columns => [ 'billpkgtaxlocationnum' ],
+ table => 'cust_bill_pkg_tax_location',
+ },
+ { columns => [ 'billpkgtaxratelocationnum' ],
+ table => 'cust_bill_pkg_tax_rate_location',
+ },
+ ],
},
'cust_main' => {
@@ -1186,16 +1547,45 @@ sub tables_hashref {
'bill_locationnum', 'int', 'NULL', '', '', '',
'ship_locationnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'custnum',
- 'unique' => [ [ 'agentnum', 'agent_custid' ] ],
- #'index' => [ ['last'], ['company'] ],
- 'index' => [
- [ 'agentnum' ], [ 'refnum' ], [ 'classnum' ], [ 'usernum' ],
- [ 'custbatch' ],
- [ 'referral_custnum' ],
- [ 'payby' ], [ 'paydate' ],
- [ 'archived' ],
- ],
+ 'primary_key' => 'custnum',
+ 'unique' => [ [ 'agentnum', 'agent_custid' ] ],
+ #'index' => [ ['last'], ['company'] ],
+ 'index' => [
+ ['agentnum'], ['refnum'], ['classnum'], ['usernum'],
+ [ 'custbatch' ],
+ [ 'referral_custnum' ],
+ [ 'payby' ], [ 'paydate' ],
+ [ 'archived' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ { columns => [ 'salesnum' ],
+ table => 'sales',
+ },
+ { columns => [ 'refnum' ],
+ table => 'part_referral',
+ },
+ { columns => [ 'classnum' ],
+ table => 'cust_class',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'referral_custnum' ],
+ table => 'cust_main',
+ references => [ 'custnum' ],
+ },
+ { columns => [ 'bill_locationnum' ],
+ table => 'cust_location',
+ references => [ 'locationnum' ],
+ },
+ { columns => [ 'ship_locationnum' ],
+ table => 'cust_location',
+ references => [ 'locationnum' ],
+ },
+ ],
},
'cust_payby' => {
@@ -1218,9 +1608,17 @@ sub tables_hashref {
'payip', 'varchar', 'NULL', 15, '', '',
'locationnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'custpaybynum',
- 'unique' => [],
- 'index' => [ [ 'custnum' ] ],
+ 'primary_key' => 'custpaybynum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ ],
},
'cust_recon' => { # (some sort of not-well understood thing for OnPac)
@@ -1275,11 +1673,25 @@ sub tables_hashref {
'comment', 'varchar', 'NULL', 255, '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'contactnum',
- 'unique' => [],
- 'index' => [ [ 'prospectnum' ], [ 'custnum' ], [ 'locationnum' ],
- [ 'last' ], [ 'first' ],
- ],
+ 'primary_key' => 'contactnum',
+ 'unique' => [],
+ 'index' => [ [ 'prospectnum' ], [ 'custnum' ], [ 'locationnum' ],
+ [ 'last' ], [ 'first' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'prospectnum' ],
+ table => 'prospect_main',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ { columns => [ 'classnum' ],
+ table => 'contact_class',
+ },
+ ],
},
'contact_phone' => {
@@ -1292,9 +1704,17 @@ sub tables_hashref {
'extension', 'varchar', 'NULL', 7, '', '',
#?#'comment', 'varchar', '', $char_d, '', '',
],
- 'primary_key' => 'contactphonenum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'contactphonenum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'contactnum' ],
+ table => 'contact',
+ },
+ { columns => [ 'phonetypenum' ],
+ table => 'phone_type',
+ },
+ ],
},
'phone_type' => {
@@ -1314,9 +1734,14 @@ sub tables_hashref {
'contactnum', 'int', '', '', '', '',
'emailaddress', 'varchar', '', $char_d, '', '',
],
- 'primary_key' => 'contactemailnum',
- 'unique' => [ [ 'contactnum', 'emailaddress' ], ],
- 'index' => [],
+ 'primary_key' => 'contactemailnum',
+ 'unique' => [ [ 'contactnum', 'emailaddress' ], ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'contactnum' ],
+ table => 'contact',
+ },
+ ],
},
'prospect_main' => {
@@ -1328,9 +1753,17 @@ sub tables_hashref {
'disabled', 'char', 'NULL', 1, '', '',
'custnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'prospectnum',
- 'unique' => [],
- 'index' => [ [ 'company' ], [ 'agentnum' ], [ 'disabled' ] ],
+ 'primary_key' => 'prospectnum',
+ 'unique' => [],
+ 'index' => [ [ 'company' ], [ 'agentnum' ], [ 'disabled' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'quotation' => {
@@ -1345,9 +1778,20 @@ sub tables_hashref {
#'total', @money_type, '', '',
#'quotation_term', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'quotationnum',
- 'unique' => [],
- 'index' => [ [ 'prospectnum' ], ['custnum'], ],
+ 'primary_key' => 'quotationnum',
+ 'unique' => [],
+ 'index' => [ [ 'prospectnum' ], ['custnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'prospectnum' ],
+ table => 'prospect_main',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
},
'quotation_pkg' => {
@@ -1362,9 +1806,20 @@ sub tables_hashref {
'quantity', 'int', 'NULL', '', '', '',
'waive_setup', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'quotationpkgnum',
- 'unique' => [],
- 'index' => [ ['pkgpart'], ],
+ 'primary_key' => 'quotationpkgnum',
+ 'unique' => [],
+ 'index' => [ ['pkgpart'], ],
+ 'foreign_keys' => [
+ { columns => [ 'quotationnum' ],
+ table => 'quotation',
+ },
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ ],
},
'quotation_pkg_discount' => {
@@ -1374,9 +1829,17 @@ sub tables_hashref {
'discountnum', 'int', '', '', '', '',
#'end_date', @date_type, '', '',
],
- 'primary_key' => 'quotationpkgdiscountnum',
- 'unique' => [],
- 'index' => [ [ 'quotationpkgnum' ], ], #[ 'discountnum' ] ],
+ 'primary_key' => 'quotationpkgdiscountnum',
+ 'unique' => [],
+ 'index' => [ [ 'quotationpkgnum' ], ], #[ 'discountnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'quotationpkgnum' ],
+ table => 'quotation_pkg',
+ },
+ { columns => [ 'discountnum' ],
+ table => 'discount',
+ },
+ ],
},
'cust_location' => { #'location' now that its prospects too, but...
@@ -1404,12 +1867,20 @@ sub tables_hashref {
'location_kind', 'char', 'NULL', 1, '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'locationnum',
- 'unique' => [],
- 'index' => [ [ 'prospectnum' ], [ 'custnum' ],
- [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ],
- [ 'city' ], [ 'district' ]
- ],
+ 'primary_key' => 'locationnum',
+ 'unique' => [],
+ 'index' => [ [ 'prospectnum' ], [ 'custnum' ],
+ [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ],
+ [ 'city' ], [ 'district' ]
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'prospectnum' ],
+ table => 'prospect_main',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'cust_main_invoice' => {
@@ -1418,9 +1889,14 @@ sub tables_hashref {
'custnum', 'int', '', '', '', '',
'dest', 'varchar', '', $char_d, '', '',
],
- 'primary_key' => 'destnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ],
+ 'primary_key' => 'destnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'cust_main_note' => {
@@ -1433,11 +1909,22 @@ sub tables_hashref {
'usernum', 'int', 'NULL', '', '', '',
'comments', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'notenum',
- 'unique' => [],
- 'index' => [ [ 'custnum' ], [ '_date' ], [ 'usernum' ], ],
+ 'primary_key' => 'notenum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ], [ '_date' ], [ 'usernum' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'classnum' ],
+ table => 'cust_note_class',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
},
-
+
'cust_note_class' => {
'columns' => [
'classnum', 'serial', '', '', '', '',
@@ -1469,9 +1956,14 @@ sub tables_hashref {
'tax', 'char', 'NULL', 1, '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'classnum',
- 'unique' => [],
- 'index' => [ ['disabled'] ],
+ 'primary_key' => 'classnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ 'foreign_keys' => [
+ { columns => [ 'categorynum' ],
+ table => 'cust_category',
+ },
+ ],
},
'cust_tag' => {
@@ -1480,9 +1972,17 @@ sub tables_hashref {
'custnum', 'int', '', '', '', '',
'tagnum', 'int', '', '', '', '',
],
- 'primary_key' => 'custtagnum',
- 'unique' => [ [ 'custnum', 'tagnum' ] ],
- 'index' => [ [ 'custnum' ] ],
+ 'primary_key' => 'custtagnum',
+ 'unique' => [ [ 'custnum', 'tagnum' ] ],
+ 'index' => [ [ 'custnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'tagnum' ],
+ table => 'part_tag',
+ },
+ ],
},
'part_tag' => {
@@ -1507,9 +2007,14 @@ sub tables_hashref {
'exempt_number', 'varchar', 'NULL', $char_d, '', '',
#start/end dates? for reporting?
],
- 'primary_key' => 'exemptionnum',
- 'unique' => [],
- 'index' => [ [ 'custnum' ] ],
+ 'primary_key' => 'exemptionnum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'cust_tax_adjustment' => {
@@ -1523,9 +2028,17 @@ sub tables_hashref {
'billpkgnum', 'int', 'NULL', '', '', '',
#more? no cust_bill_pkg_tax_location?
],
- 'primary_key' => 'adjustmentnum',
- 'unique' => [],
- 'index' => [ [ 'custnum' ], [ 'billpkgnum' ] ],
+ 'primary_key' => 'adjustmentnum',
+ 'unique' => [],
+ 'index' => [ [ 'custnum' ], [ 'billpkgnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ ],
},
'cust_main_county' => { #district+city+county+state+country are checked
@@ -1587,9 +2100,14 @@ sub tables_hashref {
'manual', 'char', 'NULL', 1, '', '', # Y = manually edited
'disabled', 'char', 'NULL', 1, '', '', # Y = tax disabled
],
- 'primary_key' => 'taxnum',
- 'unique' => [],
- 'index' => [ ['taxclassnum'], ['data_vendor', 'geocode'] ],
+ 'primary_key' => 'taxnum',
+ 'unique' => [],
+ 'index' => [ ['taxclassnum'], ['data_vendor', 'geocode'] ],
+ 'foreign_keys' => [
+ { columns => [ 'taxclassnum' ],
+ table => 'tax_class',
+ },
+ ],
},
'tax_rate_location' => {
@@ -1666,9 +2184,29 @@ sub tables_hashref {
'discount_term','int', 'NULL', '', '', '',
'failure_status','varchar','NULL', 16, '', '',
],
- 'primary_key' => 'paypendingnum',
- 'unique' => [ [ 'payunique' ] ],
- 'index' => [ [ 'custnum' ], [ 'status' ], ],
+ 'primary_key' => 'paypendingnum',
+ 'unique' => [ [ 'payunique' ] ],
+ 'index' => [ [ 'custnum' ], [ 'status' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'gatewaynum' ],
+ table => 'payment_gateway',
+ },
+ { columns => [ 'paynum' ],
+ table => 'cust_pay',
+ },
+ { columns => [ 'jobnum' ],
+ table => 'queue',
+ },
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ ],
},
'cust_pay' => {
@@ -1703,9 +2241,28 @@ sub tables_hashref {
'auth', 'varchar', 'NULL', 16, '', '', # CC auth number
'order_number','varchar', 'NULL', $char_d, '', '', # transaction number
],
- 'primary_key' => 'paynum',
+ 'primary_key' => 'paynum',
#i guess not now, with cust_pay_pending, if we actually make it here, we _do_ want to record it# 'unique' => [ [ 'payunique' ] ],
- 'index' => [ [ 'custnum' ], [ 'paybatch' ], [ 'payby' ], [ '_date' ], [ 'usernum' ] ],
+ 'index' => [ ['custnum'], ['paybatch'], ['payby'], ['_date'],
+ ['usernum'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'batchnum' ],
+ table => 'pay_batch',
+ },
+ { columns => [ 'gatewaynum' ],
+ table => 'payment_gateway',
+ },
+ ],
},
'cust_pay_void' => {
@@ -1743,9 +2300,30 @@ sub tables_hashref {
'reason', 'varchar', 'NULL', $char_d, '', '',
'void_usernum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'paynum',
- 'unique' => [],
- 'index' => [ [ 'custnum' ], [ 'usernum' ], [ 'void_usernum' ] ],
+ 'primary_key' => 'paynum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['usernum'], ['void_usernum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'batchnum' ],
+ table => 'pay_batch',
+ },
+ { columns => [ 'gatewaynum' ],
+ table => 'payment_gateway',
+ },
+ { columns => [ 'void_usernum' ],
+ table => 'access_user',
+ references => [ 'usernum' ],
+ },
+ ],
},
'cust_bill_pay' => {
@@ -1757,9 +2335,20 @@ sub tables_hashref {
'_date', @date_type, '', '',
'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances
],
- 'primary_key' => 'billpaynum',
- 'unique' => [],
- 'index' => [ [ 'paynum' ], [ 'invnum' ] ],
+ 'primary_key' => 'billpaynum',
+ 'unique' => [],
+ 'index' => [ [ 'paynum' ], [ 'invnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ { columns => [ 'paynum' ],
+ table => 'cust_pay',
+ },
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ ],
},
'cust_bill_pay_batch' => {
@@ -1770,9 +2359,17 @@ sub tables_hashref {
'amount', @money_type, '', '',
'_date', @date_type, '', '',
],
- 'primary_key' => 'billpaynum',
- 'unique' => [],
- 'index' => [ [ 'paybatchnum' ], [ 'invnum' ] ],
+ 'primary_key' => 'billpaynum',
+ 'unique' => [],
+ 'index' => [ [ 'paybatchnum' ], [ 'invnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ { columns => [ 'paybatchnum' ],
+ table => 'cust_pay_batch',
+ },
+ ],
},
'cust_bill_pay_pkg' => {
@@ -1787,9 +2384,23 @@ sub tables_hashref {
'sdate', @date_type, '', '',
'edate', @date_type, '', '',
],
- 'primary_key' => 'billpaypkgnum',
- 'unique' => [],
- 'index' => [ [ 'billpaynum' ], [ 'billpkgnum' ], ],
+ 'primary_key' => 'billpaypkgnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpaynum' ], [ 'billpkgnum' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'billpaynum' ],
+ table => 'cust_bill_pay_batch',
+ },
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ { columns => [ 'billpkgtaxlocationnum' ],
+ table => 'cust_bill_pkg_tax_location',
+ },
+ { columns => [ 'billpkgtaxratelocationnum' ],
+ table => 'cust_bill_pkg_tax_rate_location',
+ },
+ ],
},
'pay_batch' => { #batches of payments to an external processor
@@ -1802,9 +2413,14 @@ sub tables_hashref {
'upload', @date_type, '', '',
'title', 'varchar', 'NULL',255, '', '',
],
- 'primary_key' => 'batchnum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'batchnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'cust_pay_batch' => { #list of customers in current CARD/CHEK batch
@@ -1832,9 +2448,20 @@ sub tables_hashref {
'failure_status','varchar', 'NULL', 16, '', '',
'error_message', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'paybatchnum',
- 'unique' => [],
- 'index' => [ ['batchnum'], ['invnum'], ['custnum'] ],
+ 'primary_key' => 'paybatchnum',
+ 'unique' => [],
+ 'index' => [ ['batchnum'], ['invnum'], ['custnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'batchnum' ],
+ table => 'pay_batch',
+ },
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'fcc477map' => {
@@ -1888,15 +2515,67 @@ sub tables_hashref {
'setup_show_zero', 'char', 'NULL', 1, '', '',
'change_to_pkgnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'pkgnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['pkgpart'], [ 'pkgbatch' ], [ 'locationnum' ],
- [ 'usernum' ], [ 'agent_pkgid' ],
- ['order_date'], [ 'start_date' ], ['setup'], ['bill'],
- ['last_bill'], ['susp'], ['adjourn'], ['cancel'],
- ['expire'], ['contract_end'], ['change_date'], ['no_auto'],
- ],
- },
+ 'primary_key' => 'pkgnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['pkgpart'], ['pkgbatch'],
+ ['locationnum'], ['usernum'], ['agent_pkgid'],
+ ['order_date'], [ 'start_date' ], ['setup'], ['bill'],
+ ['last_bill'], ['susp'], ['adjourn'], ['cancel'],
+ ['expire'], ['contract_end'], ['change_date'],
+ ['no_auto'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ { columns => [ 'contactnum' ],
+ table => 'contact',
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'salesnum' ],
+ table => 'sales',
+ },
+ { columns => [ 'uncancel_pkgnum' ],
+ table => 'cust_pkg',
+ references => [ 'pkgnum' ],
+ },
+ { columns => [ 'change_pkgnum' ],
+ table => 'cust_pkg',
+ references => [ 'pkgnum' ],
+ },
+ { columns => [ 'change_pkgpart' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ],
+ },
+ { columns => [ 'change_locationnum' ],
+ table => 'cust_location',
+ references => [ 'locationnum' ],
+ },
+ { columns => [ 'change_custnum' ],
+ table => 'cust_main',
+ references => [ 'custnum' ],
+ },
+ { columns => [ 'main_pkgnum' ],
+ table => 'cust_pkg',
+ references => [ 'pkgnum' ],
+ },
+ { columns => [ 'pkglinknum' ],
+ table => 'part_pkg_link',
+ },
+ { columns => [ 'change_to_pkgnum' ],
+ table => 'cust_pkg',
+ references => [ 'pkgnum' ],
+ },
+ ],
+ },
'cust_pkg_option' => {
'columns' => [
@@ -1905,9 +2584,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'pkgnum' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ ],
},
'cust_pkg_detail' => {
@@ -1918,9 +2602,14 @@ sub tables_hashref {
'detailtype', 'char', '', 1, '', '', # "I"nvoice or "C"omment
'weight', 'int', '', '', '', '',
],
- 'primary_key' => 'pkgdetailnum',
- 'unique' => [],
- 'index' => [ [ 'pkgnum', 'detailtype' ] ],
+ 'primary_key' => 'pkgdetailnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum', 'detailtype' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ ],
},
'cust_pkg_reason' => {
@@ -1933,9 +2622,20 @@ sub tables_hashref {
'usernum', 'int', 'NULL', '', '', '',
'date', @date_type, '', '',
],
- 'primary_key' => 'num',
- 'unique' => [],
- 'index' => [ [ 'pkgnum' ], [ 'reasonnum' ], ['action'], [ 'usernum' ], ],
+ 'primary_key' => 'num',
+ 'unique' => [],
+ 'index' => [ ['pkgnum'], ['reasonnum'], ['action'], ['usernum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'reasonnum' ],
+ table => 'reason',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
},
'cust_pkg_discount' => {
@@ -1949,9 +2649,20 @@ sub tables_hashref {
'usernum', 'int', 'NULL', '', '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'pkgdiscountnum',
- 'unique' => [],
- 'index' => [ [ 'pkgnum' ], [ 'discountnum' ], [ 'usernum' ], ],
+ 'primary_key' => 'pkgdiscountnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum' ], [ 'discountnum' ], [ 'usernum' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'discountnum' ],
+ table => 'discount',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
},
'cust_pkg_usage' => {
@@ -1961,9 +2672,17 @@ sub tables_hashref {
'minutes', 'int', '', '', '', '',
'pkgusagepart', 'int', '', '', '', '',
],
- 'primary_key' => 'pkgusagenum',
- 'unique' => [],
- 'index' => [ [ 'pkgnum' ], [ 'pkgusagepart' ] ],
+ 'primary_key' => 'pkgusagenum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgnum' ], [ 'pkgusagepart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'pkgusagepart' ],
+ table => 'part_pkg_usage',
+ },
+ ],
},
'cdr_cust_pkg_usage' => {
@@ -1973,9 +2692,17 @@ sub tables_hashref {
'pkgusagenum', 'int', '', '', '', '',
'minutes', 'int', '', '', '', '',
],
- 'primary_key' => 'cdrusagenum',
- 'unique' => [],
- 'index' => [ [ 'pkgusagenum' ], [ 'acctid' ] ],
+ 'primary_key' => 'cdrusagenum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgusagenum' ], [ 'acctid' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'acctid' ],
+ table => 'cdr',
+ },
+ { columns => [ 'pkgusagenum' ],
+ table => 'cust_pkg_usage',
+ },
+ ],
},
'cust_bill_pkg_discount' => {
@@ -1986,9 +2713,17 @@ sub tables_hashref {
'amount', @money_type, '', '',
'months', 'decimal', 'NULL', '7,4', '', '',
],
- 'primary_key' => 'billpkgdiscountnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ], [ 'pkgdiscountnum' ] ],
+ 'primary_key' => 'billpkgdiscountnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'pkgdiscountnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ { columns => [ 'pkgdiscountnum' ],
+ table => 'cust_pkg_discount',
+ },
+ ],
},
'cust_bill_pkg_discount_void' => {
@@ -1999,15 +2734,24 @@ sub tables_hashref {
'amount', @money_type, '', '',
'months', 'decimal', 'NULL', '7,4', '', '',
],
- 'primary_key' => 'billpkgdiscountnum',
- 'unique' => [],
- 'index' => [ [ 'billpkgnum' ], [ 'pkgdiscountnum' ] ],
+ 'primary_key' => 'billpkgdiscountnum',
+ 'unique' => [],
+ 'index' => [ [ 'billpkgnum' ], [ 'pkgdiscountnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg_void',
+ },
+ { columns => [ 'pkgdiscountnum' ],
+ table => 'cust_pkg_discount',
+ },
+ ],
},
'discount' => {
'columns' => [
'discountnum', 'serial', '', '', '', '',
#'agentnum', 'int', 'NULL', '', '', '',
+ 'classnum', 'int', 'NULL', '', '', '',
'name', 'varchar', 'NULL', $char_d, '', '',
'amount', @money_type, '', '',
'percent', 'decimal', '', '7,4', '', '',
@@ -2016,9 +2760,26 @@ sub tables_hashref {
'setup', 'char', 'NULL', 1, '', '',
#'linked', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'discountnum',
+ 'primary_key' => 'discountnum',
+ 'unique' => [],
+ 'index' => [], # [ 'agentnum' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'classnum' ],
+ table => 'discount_class',
+ },
+ ],
+ },
+
+ 'discount_class' => {
+ 'columns' => [
+ 'classnum', 'serial', '', '', '', '',
+ 'classname', 'varchar', '', $char_d, '', '',
+ #'categorynum', 'int', 'NULL', '', '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'classnum',
'unique' => [],
- 'index' => [], # [ 'agentnum' ], ],
+ 'index' => [ ['disabled'] ],
},
'cust_refund' => {
@@ -2044,9 +2805,20 @@ sub tables_hashref {
'auth', 'varchar','NULL',16, '', '', # CC auth number
'order_number', 'varchar','NULL',$char_d, '', '', # transaction number
],
- 'primary_key' => 'refundnum',
- 'unique' => [],
- 'index' => [ ['custnum'], ['_date'], [ 'usernum' ], ],
+ 'primary_key' => 'refundnum',
+ 'unique' => [],
+ 'index' => [ ['custnum'], ['_date'], [ 'usernum' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'gatewaynum' ],
+ table => 'payment_gateway',
+ },
+ ],
},
'cust_credit_refund' => {
@@ -2057,9 +2829,17 @@ sub tables_hashref {
'amount', @money_type, '', '',
'_date', @date_type, '', '',
],
- 'primary_key' => 'creditrefundnum',
- 'unique' => [],
- 'index' => [ ['crednum'], ['refundnum'] ],
+ 'primary_key' => 'creditrefundnum',
+ 'unique' => [],
+ 'index' => [ ['crednum'], ['refundnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'crednum' ],
+ table => 'cust_credit',
+ },
+ { columns => [ 'refundnum' ],
+ table => 'cust_refund',
+ },
+ ],
},
@@ -2071,9 +2851,19 @@ sub tables_hashref {
'agent_svcid', 'int', 'NULL', '', '', '',
'overlimit', @date_type, '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'], [ 'agent_svcid' ] ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['svcnum'], ['pkgnum'], ['svcpart'],
+ ['agent_svcid'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'svcpart' ],
+ table => 'part_svc',
+ },
+ ],
},
'cust_svc_option' => {
@@ -2083,9 +2873,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'svcnum' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'svcnum' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'svc_export_machine' => {
@@ -2095,9 +2890,20 @@ sub tables_hashref {
'exportnum', 'int', '', '', '', '',
'machinenum', 'int', '', '', '', '',
],
- 'primary_key' => 'svcexportmachinenum',
- 'unique' => [ ['svcnum', 'exportnum'] ],
- 'index' => [],
+ 'primary_key' => 'svcexportmachinenum',
+ 'unique' => [ ['svcnum', 'exportnum'] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ { columns => [ 'machinenum' ],
+ table => 'part_export_machine',
+ },
+ ],
},
'part_export_machine' => {
@@ -2107,9 +2913,14 @@ sub tables_hashref {
'machine', 'varchar', 'NULL', $char_d, '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'machinenum',
- 'unique' => [ [ 'exportnum', 'machine' ] ],
- 'index' => [ [ 'exportnum' ] ],
+ 'primary_key' => 'machinenum',
+ 'unique' => [ [ 'exportnum', 'machine' ] ],
+ 'index' => [ [ 'exportnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ ],
},
'part_pkg' => {
@@ -2142,11 +2953,34 @@ sub tables_hashref {
'family_pkgpart','int', 'NULL', '', '', '',
'delay_start', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'pkgpart',
- 'unique' => [],
- 'index' => [ [ 'promo_code' ], [ 'disabled' ], [ 'classnum' ],
- [ 'agentnum' ], ['no_auto'],
- ],
+ 'primary_key' => 'pkgpart',
+ 'unique' => [],
+ 'index' => [ [ 'promo_code' ], [ 'disabled' ], [ 'classnum' ],
+ [ 'agentnum' ], ['no_auto'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'classnum' ],
+ table => 'pkg_class',
+ },
+ { columns => [ 'addon_classnum' ],
+ table => 'pkg_class',
+ references => [ 'classnum' ],
+ },
+ { columns => [ 'taxproductnum' ],
+ table => 'part_pkg_taxproduct',
+ },
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ { columns => [ 'successor' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ],
+ },
+ { columns => [ 'family_pkgpart' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ],
+ },
+ ],
},
'part_pkg_msgcat' => {
@@ -2157,9 +2991,14 @@ sub tables_hashref {
'pkg', 'varchar', '', $char_d, '', '', #longer/no limit?
'comment', 'varchar', 'NULL', 2*$char_d, '', '', #longer/no limit?
],
- 'primary_key' => 'pkgpartmsgnum',
- 'unique' => [ [ 'pkgpart', 'locale' ] ],
- 'index' => [],
+ 'primary_key' => 'pkgpartmsgnum',
+ 'unique' => [ [ 'pkgpart', 'locale' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ ],
},
'part_pkg_currency' => {
@@ -2170,9 +3009,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', '', '', '', '',
],
- 'primary_key' => 'pkgcurrencynum',
- 'unique' => [ [ 'pkgpart', 'currency', 'optionname' ] ],
- 'index' => [ ['pkgpart'] ],
+ 'primary_key' => 'pkgcurrencynum',
+ 'unique' => [ [ 'pkgpart', 'currency', 'optionname' ] ],
+ 'index' => [ ['pkgpart'] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ ],
},
'currency_exchange' => {
@@ -2195,9 +3039,19 @@ sub tables_hashref {
'link_type', 'varchar', '', $char_d, '', '',
'hidden', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'pkglinknum',
- 'unique' => [ [ 'src_pkgpart', 'dst_pkgpart', 'link_type', 'hidden' ] ],
- 'index' => [ [ 'src_pkgpart' ] ],
+ 'primary_key' => 'pkglinknum',
+ 'unique' => [ ['src_pkgpart','dst_pkgpart','link_type','hidden'] ],
+ 'index' => [ [ 'src_pkgpart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'src_pkgpart' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ]
+ },
+ { columns => [ 'dst_pkgpart' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ]
+ },
+ ],
},
# XXX somewhat borked unique: we don't really want a hidden and unhidden
# it turns out we'd prefer to use svc, bill, and invisibill (or something)
@@ -2208,9 +3062,17 @@ sub tables_hashref {
'pkgpart', 'int', '', '', '', '',
'discountnum', 'int', '', '', '', '',
],
- 'primary_key' => 'pkgdiscountnum',
- 'unique' => [ [ 'pkgpart', 'discountnum' ] ],
- 'index' => [],
+ 'primary_key' => 'pkgdiscountnum',
+ 'unique' => [ [ 'pkgpart', 'discountnum' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ { columns => [ 'discountnum' ],
+ table => 'discount',
+ },
+ ],
},
'part_pkg_taxclass' => {
@@ -2253,9 +3115,21 @@ sub tables_hashref {
'effdate', @date_type, '', '',
'taxable', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'pkgtaxratenum',
- 'unique' => [],
- 'index' => [ [ 'data_vendor', 'geocode', 'taxproductnum' ] ],
+ 'primary_key' => 'pkgtaxratenum',
+ 'unique' => [],
+ 'index' => [ [ 'data_vendor', 'geocode', 'taxproductnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'taxproductnum' ],
+ table => 'part_pkg_taxproduct',
+ },
+ { columns => [ 'taxclassnumtaxed' ],
+ table => 'tax_class',
+ references => [ 'taxclassnum' ],
+ },
+ { columns => [ 'taxclassnum' ],
+ table => 'tax_class',
+ },
+ ],
},
'part_pkg_taxoverride' => {
@@ -2265,9 +3139,17 @@ sub tables_hashref {
'taxclassnum', 'int', '', '', '', '',
'usage_class', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'taxoverridenum',
- 'unique' => [],
- 'index' => [ [ 'pkgpart' ], [ 'taxclassnum' ] ],
+ 'primary_key' => 'taxoverridenum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgpart' ], [ 'taxclassnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ { columns => [ 'taxclassnum' ],
+ table => 'tax_class',
+ },
+ ],
},
# 'part_title' => {
@@ -2290,9 +3172,17 @@ sub tables_hashref {
'hidden', 'char', 'NULL', 1, '', '',
'bulk_skip', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'pkgsvcnum',
- 'unique' => [ ['pkgpart', 'svcpart'] ],
- 'index' => [ ['pkgpart'], ['quantity'] ],
+ 'primary_key' => 'pkgsvcnum',
+ 'unique' => [ ['pkgpart', 'svcpart'] ],
+ 'index' => [ ['pkgpart'], ['quantity'] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ { columns => [ 'svcpart' ],
+ table => 'part_svc',
+ },
+ ],
},
'part_referral' => {
@@ -2302,9 +3192,14 @@ sub tables_hashref {
'disabled', 'char', 'NULL', 1, '', '',
'agentnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'refnum',
- 'unique' => [],
- 'index' => [ ['disabled'], ['agentnum'], ],
+ 'primary_key' => 'refnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'], ['agentnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'part_svc' => {
@@ -2319,9 +3214,14 @@ sub tables_hashref {
'restrict_edit_password','char', 'NULL', 1, '', '',
'has_router', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'svcpart',
- 'unique' => [],
- 'index' => [ [ 'disabled' ] ],
+ 'primary_key' => 'svcpart',
+ 'unique' => [],
+ 'index' => [ [ 'disabled' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'classnum' ],
+ table => 'part_svc_class',
+ },
+ ],
},
'part_svc_column' => {
@@ -2333,9 +3233,14 @@ sub tables_hashref {
'columnvalue', 'varchar', 'NULL', 512, '', '',
'columnflag', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'columnnum',
- 'unique' => [ [ 'svcpart', 'columnname' ] ],
- 'index' => [ [ 'svcpart' ] ],
+ 'primary_key' => 'columnnum',
+ 'unique' => [ [ 'svcpart', 'columnname' ] ],
+ 'index' => [ [ 'svcpart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcpart' ],
+ table => 'part_svc',
+ },
+ ],
},
'part_svc_class' => {
@@ -2373,11 +3278,16 @@ sub tables_hashref {
'npa', 'char', '', 3, '', '',
'nxx', 'char', '', 3, '', '',
],
- 'primary_key' => 'localnum',
- 'unique' => [],
- 'index' => [ [ 'npa', 'nxx' ], [ 'popnum' ] ],
+ 'primary_key' => 'localnum',
+ 'unique' => [],
+ 'index' => [ [ 'npa', 'nxx' ], [ 'popnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'popnum' ],
+ table => 'svc_acct_pop',
+ },
+ ],
},
-
+
'qual' => {
'columns' => [
'qualnum', 'serial', '', '', '', '',
@@ -2389,12 +3299,27 @@ sub tables_hashref {
'vendor_qual_id', 'varchar', 'NULL', $char_d, '', '',
'status', 'char', '', 1, '', '',
],
- 'primary_key' => 'qualnum',
- 'unique' => [],
- 'index' => [ [ 'locationnum' ], ['custnum'], ['prospectnum'],
- ['phonenum'], ['vendor_qual_id'] ],
+ 'primary_key' => 'qualnum',
+ 'unique' => [],
+ 'index' => [ ['locationnum'], ['custnum'], ['prospectnum'],
+ ['phonenum'], ['vendor_qual_id'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'prospectnum' ],
+ table => 'prospect_main',
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ ],
},
-
+
'qual_option' => {
'columns' => [
'optionnum', 'serial', '', '', '', '',
@@ -2402,9 +3327,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'qualnum' ],
+ table => 'qual',
+ },
+ ],
},
'svc_acct' => {
@@ -2468,24 +3398,62 @@ sub tables_hashref {
#XXX RPOP settings
#
],
- 'primary_key' => 'svcnum',
- #'unique' => [ [ 'username', 'domsvc' ] ],
- 'unique' => [],
- 'index' => [ ['username'], ['domsvc'], ['pbxsvc'] ],
+ 'primary_key' => 'svcnum',
+ #'unique' => [ [ 'username', 'domsvc' ] ],
+ 'unique' => [],
+ 'index' => [ ['username'], ['domsvc'], ['pbxsvc'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'popnum' ],
+ table => 'svc_acct_pop',
+ },
+ { columns => [ 'sectornum' ],
+ table => 'tower_sector',
+ },
+ { columns => [ 'routernum' ],
+ table => 'router',
+ },
+ { columns => [ 'blocknum' ],
+ table => 'addr_block',
+ },
+ { columns => [ 'domsvc' ],
+ table => 'svc_domain', #'cust_svc',
+ references => [ 'svcnum' ],
+ },
+ { columns => [ 'pbxsvc' ],
+ table => 'svc_pbx', #'cust_svc',
+ references => [ 'svcnum' ],
+ },
+ ],
},
'acct_rt_transaction' => {
'columns' => [
- 'svcrtid', 'int', '', '', '', '',
+ 'svcrtid', 'int', '', '', '', '', #why am i not a serial
'svcnum', 'int', '', '', '', '',
'transaction_id', 'int', '', '', '', '',
'_date', @date_type, '', '',
'seconds', 'int', '', '', '', '', #uhhhh
'support', 'int', '', '', '', '',
],
- 'primary_key' => 'svcrtid',
- 'unique' => [],
- 'index' => [ ['svcnum', 'transaction_id'] ],
+ 'primary_key' => 'svcrtid',
+ 'unique' => [],
+ 'index' => [ ['svcnum', 'transaction_id'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'svc_acct', #'cust_svc',
+ },
+ # 1. RT tables aren't part of our data structure, so
+ # we can't make sure Queue is created already
+ # 2. This is our internal hack for time tracking, not
+ # a user-facing feature
+ #{ columns => [ 'transaction_id' ],
+ # table => 'Transaction',
+ # references => [ 'id' ],
+ #},
+ ],
},
#'svc_charge' => {
@@ -2540,11 +3508,27 @@ sub tables_hashref {
'acct_def_cgp_prontoskinname', 'varchar', 'NULL', $char_d, '', '',
'acct_def_cgp_sendmdnmode', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [ ],
- 'index' => [ ['domain'] ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['domain'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'catchall' ],
+ table => 'svc_acct',
+ references => [ 'svcnum' ],
+ },
+ { columns => [ 'parent_svcnum' ],
+ table => 'cust_svc',
+ references => [ 'svcnum' ],
+ },
+ { columns => [ 'registrarnum' ],
+ table => 'registrar',
+ },
+ ],
},
-
+
'svc_dsl' => {
'columns' => [
'svcnum', 'int', '', '', '', '',
@@ -2574,9 +3558,14 @@ sub tables_hashref {
'monitored', 'char', 'NULL', 1, '', '',
'last_pull', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [ ],
- 'index' => [ ['phonenum'], ['vendor_order_id'] ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [ ],
+ 'index' => [ ['phonenum'], ['vendor_order_id'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'dsl_device' => {
@@ -2587,11 +3576,16 @@ sub tables_hashref {
'svcnum', 'int', '', '', '', '',
'mac_addr', 'varchar', '', 12, '', '',
],
- 'primary_key' => 'devicenum',
- 'unique' => [ [ 'mac_addr' ], ],
- 'index' => [ [ 'svcnum' ], ], # [ 'devicepart' ] ],
+ 'primary_key' => 'devicenum',
+ 'unique' => [ [ 'mac_addr' ], ],
+ 'index' => [ [ 'svcnum' ], ], # [ 'devicepart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'svc_dsl',
+ },
+ ],
},
-
+
'dsl_note' => {
'columns' => [
'notenum', 'serial', '', '', '', '',
@@ -2601,9 +3595,14 @@ sub tables_hashref {
'_date', 'int', 'NULL', '', '', '',
'note', 'text', '', '', '', '',
],
- 'primary_key' => 'notenum',
- 'unique' => [ ],
- 'index' => [ ['svcnum'] ],
+ 'primary_key' => 'notenum',
+ 'unique' => [],
+ 'index' => [ ['svcnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'svc_dsl',
+ },
+ ],
},
'svc_dish' => {
@@ -2613,9 +3612,14 @@ sub tables_hashref {
'installdate', @date_type, '', '',
'note', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [ ],
- 'index' => [ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'svc_hardware' => {
@@ -2629,9 +3633,20 @@ sub tables_hashref {
'statusnum','int', 'NULL', '', '', '',
'note', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [ ],
- 'index' => [ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'typenum' ],
+ table => 'hardware_type',
+ },
+ { columns => [ 'statusnum' ],
+ table => 'hardware_status',
+ },
+ ],
},
'hardware_class' => {
@@ -2651,9 +3666,14 @@ sub tables_hashref {
'model', 'varchar', '', $char_d, '', '',
'revision','varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'typenum',
- 'unique' => [ [ 'classnum', 'model', 'revision' ] ],
- 'index' => [ ],
+ 'primary_key' => 'typenum',
+ 'unique' => [ [ 'classnum', 'model', 'revision' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'classnum' ],
+ table => 'hardware_class',
+ },
+ ],
},
'hardware_status' => {
@@ -2677,9 +3697,14 @@ sub tables_hashref {
'recdata', 'varchar', '', 255, '', '',
'ttl', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'recnum',
- 'unique' => [],
- 'index' => [ ['svcnum'] ],
+ 'primary_key' => 'recnum',
+ 'unique' => [],
+ 'index' => [ ['svcnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'svc_domain',
+ },
+ ],
},
'registrar' => {
@@ -2703,6 +3728,11 @@ sub tables_hashref {
'primary_key' => 'rulenum',
'unique' => [ [ 'svcnum', 'name' ] ],
'index' => [ [ 'svcnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc', #svc_acct / svc_domain
+ },
+ ],
},
'cgp_rule_condition' => {
@@ -2713,9 +3743,14 @@ sub tables_hashref {
'params', 'varchar', 'NULL', 255, '', '',
'rulenum', 'int', '', '', '', '',
],
- 'primary_key' => 'ruleconditionnum',
- 'unique' => [],
- 'index' => [ [ 'rulenum' ] ],
+ 'primary_key' => 'ruleconditionnum',
+ 'unique' => [],
+ 'index' => [ [ 'rulenum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'rulenum' ],
+ table => 'cgp_rule',
+ },
+ ],
},
'cgp_rule_action' => {
@@ -2725,9 +3760,14 @@ sub tables_hashref {
'params', 'varchar', 'NULL', 255, '', '',
'rulenum', 'int', '', '', '', '',
],
- 'primary_key' => 'ruleactionnum',
- 'unique' => [],
- 'index' => [ [ 'rulenum' ] ],
+ 'primary_key' => 'ruleactionnum',
+ 'unique' => [],
+ 'index' => [ [ 'rulenum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'rulenum' ],
+ table => 'cgp_rule',
+ },
+ ],
},
'svc_forward' => {
@@ -2738,9 +3778,22 @@ sub tables_hashref {
'dstsvc', 'int', 'NULL', '', '', '',
'dst', 'varchar', 'NULL', 255, '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [ ['srcsvc'], ['dstsvc'] ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['srcsvc'], ['dstsvc'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'srcsvc' ],
+ table => 'svc_acct',
+ references => [ 'svcnum' ]
+ },
+ { columns => [ 'dstsvc' ],
+ table => 'svc_acct',
+ references => [ 'svcnum' ]
+ },
+ ],
},
'svc_www' => {
@@ -2753,6 +3806,18 @@ sub tables_hashref {
'primary_key' => 'svcnum',
'unique' => [],
'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'recnum' ],
+ table => 'domain_record',
+ },
+ { columns => [ 'usersvc' ],
+ table => 'svc_acct',
+ references => [ 'svcnum' ]
+ },
+ ],
},
#'svc_wo' => {
@@ -2779,9 +3844,14 @@ sub tables_hashref {
'totalbytes', 'bigint', 'NULL', '', '', '',
'agentnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'prepaynum',
- 'unique' => [ ['identifier'] ],
- 'index' => [],
+ 'primary_key' => 'prepaynum',
+ 'unique' => [ ['identifier'] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'port' => {
@@ -2791,9 +3861,14 @@ sub tables_hashref {
'nasport', 'int', 'NULL', '', '', '',
'nasnum', 'int', '', '', '', '',
],
- 'primary_key' => 'portnum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'portnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'nasnum' ],
+ table => 'nas',
+ },
+ ],
},
'nas' => {
@@ -2809,9 +3884,14 @@ sub tables_hashref {
'description', 'varchar', '', 200, 'RADIUS Client', '',
'svcnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'nasnum',
- 'unique' => [ [ 'nasname' ], ],
- 'index' => [],
+ 'primary_key' => 'nasnum',
+ 'unique' => [ [ 'nasname' ], ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'svc_broadband',
+ },
+ ],
},
'export_nas' => {
@@ -2820,9 +3900,17 @@ sub tables_hashref {
'exportnum', 'int', '', '', '', '',
'nasnum', 'int', '', '', '', '',
],
- 'primary_key' => 'exportnasnum',
- 'unique' => [ [ 'exportnum', 'nasnum' ] ],
- 'index' => [ [ 'exportnum' ], [ 'nasnum' ] ],
+ 'primary_key' => 'exportnasnum',
+ 'unique' => [ [ 'exportnum', 'nasnum' ] ],
+ 'index' => [ [ 'exportnum' ], [ 'nasnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ { columns => [ 'nasnum' ],
+ table => 'nas',
+ },
+ ],
},
'queue' => {
@@ -2837,11 +3925,19 @@ sub tables_hashref {
'secure', 'char', 'NULL', 1, '', '',
'priority', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'jobnum',
- 'unique' => [],
- 'index' => [ [ 'secure' ], [ 'priority' ],
- [ 'job' ], [ 'svcnum' ], [ 'custnum' ], [ 'status' ],
- ],
+ 'primary_key' => 'jobnum',
+ 'unique' => [],
+ 'index' => [ [ 'secure' ], [ 'priority' ],
+ [ 'job' ], [ 'svcnum' ], [ 'custnum' ], [ 'status' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'queue_arg' => {
@@ -2851,9 +3947,14 @@ sub tables_hashref {
'frozen', 'char', 'NULL', 1, '', '',
'arg', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'argnum',
- 'unique' => [],
- 'index' => [ [ 'jobnum' ] ],
+ 'primary_key' => 'argnum',
+ 'unique' => [],
+ 'index' => [ [ 'jobnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'jobnum' ],
+ table => 'queue',
+ },
+ ],
},
'queue_depend' => {
@@ -2862,9 +3963,18 @@ sub tables_hashref {
'jobnum', 'bigint', '', '', '', '',
'depend_jobnum', 'bigint', '', '', '', '',
],
- 'primary_key' => 'dependnum',
- 'unique' => [],
- 'index' => [ [ 'jobnum' ], [ 'depend_jobnum' ] ],
+ 'primary_key' => 'dependnum',
+ 'unique' => [],
+ 'index' => [ [ 'jobnum' ], [ 'depend_jobnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'jobnum' ],
+ table => 'queue',
+ },
+ { columns => [ 'depend_jobnum' ],
+ table => 'queue',
+ references => [ 'jobnum' ],
+ },
+ ],
},
'export_svc' => {
@@ -2873,20 +3983,36 @@ sub tables_hashref {
'exportnum' => 'int', '', '', '', '',
'svcpart' => 'int', '', '', '', '',
],
- 'primary_key' => 'exportsvcnum',
- 'unique' => [ [ 'exportnum', 'svcpart' ] ],
- 'index' => [ [ 'exportnum' ], [ 'svcpart' ] ],
+ 'primary_key' => 'exportsvcnum',
+ 'unique' => [ [ 'exportnum', 'svcpart' ] ],
+ 'index' => [ [ 'exportnum' ], [ 'svcpart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ { columns => [ 'svcpart' ],
+ table => 'part_svc',
+ },
+ ],
},
'export_device' => {
'columns' => [
'exportdevicenum' => 'serial', '', '', '', '',
- 'exportnum' => 'int', '', '', '', '',
+ 'exportnum' => 'int', '', '', '', '',
'devicepart' => 'int', '', '', '', '',
],
- 'primary_key' => 'exportdevicenum',
- 'unique' => [ [ 'exportnum', 'devicepart' ] ],
- 'index' => [ [ 'exportnum' ], [ 'devicepart' ] ],
+ 'primary_key' => 'exportdevicenum',
+ 'unique' => [ [ 'exportnum', 'devicepart' ] ],
+ 'index' => [ [ 'exportnum' ], [ 'devicepart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ { columns => [ 'devicepart' ],
+ table => 'part_device',
+ },
+ ],
},
'part_export' => {
@@ -2898,9 +4024,15 @@ sub tables_hashref {
'nodomain', 'char', 'NULL', 1, '', '',
'default_machine','int', 'NULL', '', '', '',
],
- 'primary_key' => 'exportnum',
- 'unique' => [],
- 'index' => [ [ 'machine' ], [ 'exporttype' ] ],
+ 'primary_key' => 'exportnum',
+ 'unique' => [],
+ 'index' => [ [ 'machine' ], [ 'exporttype' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'default_machine' ],
+ table => 'part_export_machine',
+ references => [ 'machinenum' ]
+ },
+ ],
},
'part_export_option' => {
@@ -2910,23 +4042,36 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'exportnum' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'exportnum' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ ],
},
'radius_usergroup' => {
'columns' => [
'usergroupnum', 'serial', '', '', '', '',
'svcnum', 'int', '', '', '', '',
- 'groupname', 'varchar', 'NULL', $char_d, '', '',
+ 'groupname', 'varchar', 'NULL', $char_d, '', '', #deprecated
'groupnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'usergroupnum',
- 'unique' => [],
- 'index' => [ [ 'svcnum' ], [ 'groupname' ] ],
+ 'primary_key' => 'usergroupnum',
+ 'unique' => [],
+ 'index' => [ [ 'svcnum' ], [ 'groupname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc', #svc_acct / svc_broadband
+ },
+ { columns => [ 'groupnum' ],
+ table => 'radius_group',
+ },
+ ],
},
-
+
'radius_group' => {
'columns' => [
'groupnum', 'serial', '', '', '', '',
@@ -2950,9 +4095,14 @@ sub tables_hashref {
'attrtype', 'char', '', 1, '', '',
'op', 'char', '', 2, '', '',
],
- 'primary_key' => 'attrnum',
- 'unique' => [],
- 'index' => [ ['groupnum'], ],
+ 'primary_key' => 'attrnum',
+ 'unique' => [],
+ 'index' => [ ['groupnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'groupnum' ],
+ table => 'radius_group',
+ },
+ ],
},
'msgcat' => {
@@ -2976,9 +4126,17 @@ sub tables_hashref {
'month', 'int', '', '', '', '',
'amount', @money_type, '', '',
],
- 'primary_key' => 'exemptnum',
- 'unique' => [ [ 'custnum', 'taxnum', 'year', 'month' ] ],
- 'index' => [],
+ 'primary_key' => 'exemptnum',
+ 'unique' => [ [ 'custnum', 'taxnum', 'year', 'month' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'taxnum' ],
+ table => 'cust_main_county',
+ },
+ ],
},
'cust_tax_exempt_pkg' => {
@@ -2998,13 +4156,24 @@ sub tables_hashref {
'exempt_cust_taxname', 'char', 'NULL', 1, '', '',
'exempt_monthly', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'exemptpkgnum',
- 'unique' => [],
- 'index' => [ [ 'taxnum', 'year', 'month' ],
- [ 'billpkgnum' ],
- [ 'taxnum' ],
- [ 'creditbillpkgnum' ],
- ],
+ 'primary_key' => 'exemptpkgnum',
+ 'unique' => [],
+ 'index' => [ [ 'taxnum', 'year', 'month' ],
+ [ 'billpkgnum' ],
+ [ 'taxnum' ],
+ [ 'creditbillpkgnum' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg',
+ },
+ { columns => [ 'taxnum' ],
+ table => 'cust_main_county',
+ },
+ { columns => [ 'creditbillpkgnum' ],
+ table => 'cust_credit_bill_pkg',
+ },
+ ],
},
'cust_tax_exempt_pkg_void' => {
@@ -3024,13 +4193,24 @@ sub tables_hashref {
'exempt_cust_taxname', 'char', 'NULL', 1, '', '',
'exempt_monthly', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'exemptpkgnum',
- 'unique' => [],
- 'index' => [ [ 'taxnum', 'year', 'month' ],
- [ 'billpkgnum' ],
- [ 'taxnum' ],
- [ 'creditbillpkgnum' ],
- ],
+ 'primary_key' => 'exemptpkgnum',
+ 'unique' => [],
+ 'index' => [ [ 'taxnum', 'year', 'month' ],
+ [ 'billpkgnum' ],
+ [ 'taxnum' ],
+ [ 'creditbillpkgnum' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'billpkgnum' ],
+ table => 'cust_bill_pkg_void',
+ },
+ { columns => [ 'taxnum' ],
+ table => 'cust_main_county',
+ },
+ { columns => [ 'creditbillpkgnum' ],
+ table => 'cust_credit_bill_pkg',
+ },
+ ],
},
'router' => {
@@ -3041,9 +4221,17 @@ sub tables_hashref {
'agentnum', 'int', 'NULL', '', '', '',
'manual_addr', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'routernum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'routernum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc', #svc_acct / svc_broadband
+ },
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'part_svc_router' => {
@@ -3052,9 +4240,17 @@ sub tables_hashref {
'svcpart', 'int', '', '', '', '',
'routernum', 'int', '', '', '', '',
],
- 'primary_key' => 'svcrouternum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'svcrouternum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcpart' ],
+ table => 'part_svc',
+ },
+ { columns => [ 'routernum' ],
+ table => 'router',
+ },
+ ],
},
'addr_block' => {
@@ -3066,9 +4262,17 @@ sub tables_hashref {
'agentnum', 'int', 'NULL', '', '', '',
'manual_flag', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'blocknum',
- 'unique' => [ [ 'blocknum', 'routernum' ] ],
- 'index' => [],
+ 'primary_key' => 'blocknum',
+ 'unique' => [ [ 'blocknum', 'routernum' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'routernum' ],
+ table => 'router',
+ },
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'svc_broadband' => {
@@ -3096,21 +4300,42 @@ sub tables_hashref {
'suid', 'int', 'NULL', '', '', '',
'shared_svcnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [ [ 'ip_addr' ], [ 'mac_addr' ] ],
- 'index' => [],
+ 'primary_key' => 'svcnum',
+ 'unique' => [ [ 'ip_addr' ], [ 'mac_addr' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'routernum' ],
+ table => 'router',
+ },
+ { columns => [ 'blocknum' ],
+ table => 'addr_block',
+ },
+ { columns => [ 'sectornum' ],
+ table => 'tower_sector',
+ },
+ { columns => [ 'shared_svcnum' ],
+ table => 'svc_broadband',
+ references => [ 'svcnum' ],
+ },
+ ],
},
'tower' => {
'columns' => [
- 'towernum', 'serial', '', '', '', '',
- #'agentnum', 'int', 'NULL', '', '', '',
- 'towername', 'varchar', '', $char_d, '', '',
- 'disabled', 'char', 'NULL', 1, '', '',
- 'latitude', 'decimal', 'NULL', '10,7', '', '',
- 'longitude','decimal', 'NULL', '10,7', '', '',
- 'altitude', 'decimal', 'NULL', '', '', '',
- 'coord_auto', 'char', 'NULL', 1, '', '',
+ 'towernum', 'serial', '', '', '', '',
+ #'agentnum', 'int', 'NULL', '', '', '',
+ 'towername', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ 'latitude', 'decimal', 'NULL', '10,7', '', '',
+ 'longitude', 'decimal', 'NULL', '10,7', '', '',
+ 'coord_auto', 'char', 'NULL', 1, '', '',
+ 'altitude', 'decimal', 'NULL', '', '', '',
+ 'height', 'decimal', 'NULL', '', '', '',
+ 'veg_height', 'decimal', 'NULL', '', '', '',
+ 'color', 'varchar', 'NULL', 6, '', '',
],
'primary_key' => 'towernum',
'unique' => [ [ 'towername' ] ], # , 'agentnum' ] ],
@@ -3123,10 +4348,21 @@ sub tables_hashref {
'towernum', 'int', '', '', '', '',
'sectorname', 'varchar', '', $char_d, '', '',
'ip_addr', 'varchar', 'NULL', 15, '', '',
- ],
- 'primary_key' => 'sectornum',
- 'unique' => [ [ 'towernum', 'sectorname' ], [ 'ip_addr' ], ],
- 'index' => [ [ 'towernum' ] ],
+ 'height', 'decimal', 'NULL', '', '', '',
+ 'freq_mhz', 'int', 'NULL', '', '', '',
+ 'direction', 'int', 'NULL', '', '', '',
+ 'width', 'int', 'NULL', '', '', '',
+ #downtilt etc? rfpath has profile files for devices/antennas you upload?
+ 'range', 'decimal', 'NULL', '', '', '', #?
+ ],
+ 'primary_key' => 'sectornum',
+ 'unique' => [ [ 'towernum', 'sectorname' ], [ 'ip_addr' ], ],
+ 'index' => [ [ 'towernum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'towernum' ],
+ table => 'tower',
+ },
+ ],
},
'part_virtual_field' => {
@@ -3149,9 +4385,14 @@ sub tables_hashref {
'vfieldpart', 'int', '', '', '', '',
'value', 'varchar', '', 128, '', '',
],
- 'primary_key' => 'vfieldnum',
- 'unique' => [ [ 'vfieldpart', 'recnum' ] ],
- 'index' => [],
+ 'primary_key' => 'vfieldnum',
+ 'unique' => [ [ 'vfieldpart', 'recnum' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'vfieldpart' ],
+ table => 'part_virtual_field',
+ },
+ ],
},
'acct_snarf' => {
@@ -3169,9 +4410,14 @@ sub tables_hashref {
'tls', 'char', 'NULL', 1, '', '',
'mailbox', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'snarfnum',
- 'unique' => [],
- 'index' => [ [ 'svcnum' ] ],
+ 'primary_key' => 'snarfnum',
+ 'unique' => [],
+ 'index' => [ [ 'svcnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'svc_acct',
+ },
+ ],
},
'svc_external' => {
@@ -3180,9 +4426,14 @@ sub tables_hashref {
'id', 'bigint', 'NULL', '', '', '',
'title', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'cust_pay_refund' => {
@@ -3193,9 +4444,17 @@ sub tables_hashref {
'_date', @date_type, '', '',
'amount', @money_type, '', '',
],
- 'primary_key' => 'payrefundnum',
- 'unique' => [],
- 'index' => [ ['paynum'], ['refundnum'] ],
+ 'primary_key' => 'payrefundnum',
+ 'unique' => [],
+ 'index' => [ ['paynum'], ['refundnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'paynum' ],
+ table => 'cust_pay',
+ },
+ { columns => [ 'refundnum' ],
+ table => 'cust_refund',
+ },
+ ],
},
'part_pkg_option' => {
@@ -3205,9 +4464,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'pkgpart' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'pkgpart' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ ],
},
'part_pkg_vendor' => {
@@ -3217,11 +4481,19 @@ sub tables_hashref {
'exportnum', 'int', '', '', '', '',
'vendor_pkg_id', 'varchar', '', $char_d, '', '',
],
- 'primary_key' => 'num',
- 'unique' => [ [ 'pkgpart', 'exportnum' ] ],
- 'index' => [ [ 'pkgpart' ] ],
+ 'primary_key' => 'num',
+ 'unique' => [ [ 'pkgpart', 'exportnum' ] ],
+ 'index' => [ [ 'pkgpart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ ],
},
-
+
'part_pkg_report_option' => {
'columns' => [
'num', 'serial', '', '', '', '',
@@ -3243,9 +4515,14 @@ sub tables_hashref {
'rollover', 'char', 'NULL', 1, '', '',
'description', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'pkgusagepart',
- 'unique' => [],
- 'index' => [ [ 'pkgpart' ] ],
+ 'primary_key' => 'pkgusagepart',
+ 'unique' => [],
+ 'index' => [ [ 'pkgpart' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ ],
},
'part_pkg_usage_class' => {
@@ -3254,9 +4531,17 @@ sub tables_hashref {
'pkgusagepart', 'int', '', '', '', '',
'classnum', 'int','NULL', '', '', '',
],
- 'primary_key' => 'num',
- 'unique' => [ [ 'pkgusagepart', 'classnum' ] ],
- 'index' => [],
+ 'primary_key' => 'num',
+ 'unique' => [ [ 'pkgusagepart', 'classnum' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'pkgusagepart' ],
+ table => 'part_pkg_usage',
+ },
+ { columns => [ 'classnum' ],
+ table => 'usage_class',
+ },
+ ],
},
'rate' => {
@@ -3285,11 +4570,33 @@ sub tables_hashref {
'cdrtypenum', 'int', 'NULL', '', '', '',
'region_group', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'ratedetailnum',
- 'unique' => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
- 'index' => [ [ 'ratenum', 'dest_regionnum' ],
- [ 'ratenum', 'ratetimenum' ]
- ],
+ 'primary_key' => 'ratedetailnum',
+ 'unique' => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
+ 'index' => [ [ 'ratenum', 'dest_regionnum' ],
+ [ 'ratenum', 'ratetimenum' ]
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'ratenum' ],
+ table => 'rate',
+ },
+ { columns => [ 'orig_regionnum' ],
+ table => 'rate_region',
+ references => [ 'regionnum' ],
+ },
+ { columns => [ 'dest_regionnum' ],
+ table => 'rate_region',
+ references => [ 'regionnum' ],
+ },
+ { columns => [ 'ratetimenum' ],
+ table => 'rate_time',
+ },
+ { columns => [ 'classnum' ],
+ table => 'usage_class',
+ },
+ { columns => [ 'cdrtypenum' ],
+ table => 'cdr_type',
+ },
+ ],
},
'rate_region' => {
@@ -3314,9 +4621,17 @@ sub tables_hashref {
'state', 'char', 'NULL', 2, '', '',
'ocn', 'char', 'NULL', 4, '', '',
],
- 'primary_key' => 'prefixnum',
- 'unique' => [],
- 'index' => [ [ 'countrycode' ], [ 'npa' ], [ 'regionnum' ] ],
+ 'primary_key' => 'prefixnum',
+ 'unique' => [],
+ 'index' => [ [ 'countrycode' ], [ 'npa' ], [ 'regionnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'regionnum' ],
+ table => 'rate_region',
+ },
+ { columns => [ 'latanum' ],
+ table => 'lata',
+ },
+ ],
},
'rate_time' => {
@@ -3336,10 +4651,15 @@ sub tables_hashref {
'etime', 'int', '', '', '', '',
'ratetimenum', 'int', '', '', '', '',
],
- 'primary_key' => 'intervalnum',
- 'unique' => [],
- 'index' => [],
- },
+ 'primary_key' => 'intervalnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'ratetimenum' ],
+ table => 'rate_time',
+ },
+ ],
+ },
#not really part of the above rate_ stuff (used with flat rate rather than
# rated billing), but could be eventually, and its a rate
@@ -3360,9 +4680,14 @@ sub tables_hashref {
'min_quan', 'int', '', '', '', '',
'min_charge', 'decimal', '', '10,4', '', '',
],
- 'primary_key' => 'tierdetailnum',
- 'unique' => [],
- 'index' => [ ['tiernum'], ],
+ 'primary_key' => 'tierdetailnum',
+ 'unique' => [],
+ 'index' => [ ['tiernum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'tiernum' ],
+ table => 'rate_tier',
+ },
+ ],
},
'usage_class' => {
@@ -3384,10 +4709,15 @@ sub tables_hashref {
'code', 'varchar', '', $char_d, '', '',
'agentnum', 'int', '', '', '', '',
],
- 'primary_key' => 'codenum',
- 'unique' => [ [ 'agentnum', 'code' ] ],
- 'index' => [ [ 'agentnum' ] ],
- },
+ 'primary_key' => 'codenum',
+ 'unique' => [ [ 'agentnum', 'code' ] ],
+ 'index' => [ [ 'agentnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
+ },
'reg_code_pkg' => {
'columns' => [
@@ -3395,9 +4725,17 @@ sub tables_hashref {
'codenum', 'int', '', '', '', '',
'pkgpart', 'int', '', '', '', '',
],
- 'primary_key' => 'codepkgnum',
- 'unique' => [ [ 'codenum', 'pkgpart' ] ],
- 'index' => [ [ 'codenum' ] ],
+ 'primary_key' => 'codepkgnum',
+ 'unique' => [ [ 'codenum', 'pkgpart' ] ],
+ 'index' => [ [ 'codenum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'codenum' ],
+ table => 'reg_code',
+ },
+ { columns => [ 'pkgpart' ],
+ table => 'part_pkg',
+ },
+ ],
},
'clientapi_session' => {
@@ -3418,9 +4756,14 @@ sub tables_hashref {
'fieldname', 'varchar', '', $char_d, '', '',
'fieldvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'fieldnum',
- 'unique' => [ [ 'sessionnum', 'fieldname' ] ],
- 'index' => [],
+ 'primary_key' => 'fieldnum',
+ 'unique' => [ [ 'sessionnum', 'fieldname' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'sessionnum' ],
+ table => 'clientapi_session',
+ },
+ ],
},
'payment_gateway' => {
@@ -3447,9 +4790,14 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'gatewaynum' ], [ 'optionname' ] ],
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'gatewaynum' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'gatewaynum' ],
+ table => 'payment_gateway',
+ },
+ ],
},
'agent_payment_gateway' => {
@@ -3460,9 +4808,19 @@ sub tables_hashref {
'cardtype', 'varchar', 'NULL', $char_d, '', '',
'taxclass', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'agentgatewaynum',
- 'unique' => [],
- 'index' => [ [ 'agentnum', 'cardtype' ], ],
+ 'primary_key' => 'agentgatewaynum',
+ 'unique' => [],
+ 'index' => [ [ 'agentnum', 'cardtype' ], ],
+
+ 'foreign_keys' => [
+
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ { columns => [ 'gatewaynum' ],
+ table => 'payment_gateway',
+ },
+ ],
},
'banned_pay' => {
@@ -3478,9 +4836,14 @@ sub tables_hashref {
'bantype', 'varchar', 'NULL', $char_d, '', '',
'reason', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'bannum',
- 'unique' => [],
- 'index' => [ [ 'payby', 'payinfo' ], [ 'usernum' ], ],
+ 'primary_key' => 'bannum',
+ 'unique' => [],
+ 'index' => [ [ 'payby', 'payinfo' ], [ 'usernum' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
},
'pkg_category' => {
@@ -3504,9 +4867,14 @@ sub tables_hashref {
'disabled', 'char', 'NULL', 1, '', '',
'fcc_ds0s', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'classnum',
- 'unique' => [],
- 'index' => [ ['disabled'] ],
+ 'primary_key' => 'classnum',
+ 'unique' => [],
+ 'index' => [ ['disabled'] ],
+ 'foreign_keys' => [
+ { columns => [ 'categorynum' ],
+ table => 'pkg_category',
+ },
+ ],
},
'cdr' => {
@@ -3588,7 +4956,7 @@ sub tables_hashref {
###
'servicecode', 'int', 'NULL', '', '', '',
'quantity_able', 'int', 'NULL', '', '', '',
-
+
###
#and now for our own fields
###
@@ -3648,6 +5016,8 @@ sub tables_hashref {
[ 'cdrbatch' ], [ 'cdrbatchnum' ],
[ 'src_ip_addr' ], [ 'dst_ip_addr' ], [ 'dst_term' ],
],
+ #no FKs on cdr table... choosing not to throw errors no matter what's
+ # thrown in here. better to have the data.
},
'cdr_batch' => {
@@ -3672,9 +5042,17 @@ sub tables_hashref {
'status', 'varchar', 'NULL', 32, '', '',
'svcnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'cdrtermnum',
- 'unique' => [ [ 'acctid', 'termpart' ] ],
- 'index' => [ [ 'acctid' ], [ 'status' ], ],
+ 'primary_key' => 'cdrtermnum',
+ 'unique' => [ [ 'acctid', 'termpart' ] ],
+ 'index' => [ [ 'acctid' ], [ 'status' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'acctid' ],
+ table => 'cdr',
+ },
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
#to handle multiple termination/settlement passes...
@@ -3745,9 +5123,20 @@ sub tables_hashref {
'svcnum', 'int', 'NULL', '', '', '',
'svc_field', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'itemnum',
- 'unique' => [ [ 'classnum', 'item' ] ],
- 'index' => [ [ 'classnum' ], [ 'agentnum' ], [ 'svcnum' ] ],
+ 'primary_key' => 'itemnum',
+ 'unique' => [ [ 'classnum', 'item' ] ],
+ 'index' => [ [ 'classnum' ], [ 'agentnum' ], [ 'svcnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'classnum' ],
+ table => 'inventory_class',
+ },
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'inventory_class' => {
@@ -3768,9 +5157,14 @@ sub tables_hashref {
'start_date', @date_type, '', '',
'last_date', @date_type, '', '',
],
- 'primary_key' => 'sessionnum',
- 'unique' => [ [ 'sessionkey' ] ],
- 'index' => [],
+ 'primary_key' => 'sessionnum',
+ 'unique' => [ [ 'sessionkey' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
},
'access_user' => {
@@ -3782,11 +5176,22 @@ sub tables_hashref {
'last', 'varchar', 'NULL', $char_d, '', '',
'first', 'varchar', 'NULL', $char_d, '', '',
'user_custnum', 'int', 'NULL', '', '', '',
+ 'report_salesnum', 'int', 'NULL', '', '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'usernum',
- 'unique' => [ [ 'username' ] ],
- 'index' => [ [ 'user_custnum' ] ],
+ 'primary_key' => 'usernum',
+ 'unique' => [ [ 'username' ] ],
+ 'index' => [ [ 'user_custnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'user_custnum' ],
+ table => 'cust_main',
+ references => [ 'custnum' ],
+ },
+ { columns => [ 'report_salesnum' ],
+ table => 'sales',
+ references => [ 'salesnum' ],
+ },
+ ],
},
'access_user_pref' => {
@@ -3797,9 +5202,14 @@ sub tables_hashref {
'prefvalue', 'text', 'NULL', '', '', '',
'expiration', @date_type, '', '',
],
- 'primary_key' => 'prefnum',
- 'unique' => [],
- 'index' => [ [ 'usernum' ] ],
+ 'primary_key' => 'prefnum',
+ 'unique' => [],
+ 'index' => [ [ 'usernum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ ],
},
'access_group' => {
@@ -3818,10 +5228,18 @@ sub tables_hashref {
'usernum', 'int', '', '', '', '',
'groupnum', 'int', '', '', '', '',
],
- 'primary_key' => 'usergroupnum',
- 'unique' => [ [ 'usernum', 'groupnum' ] ],
- 'index' => [ [ 'usernum' ] ],
- },
+ 'primary_key' => 'usergroupnum',
+ 'unique' => [ [ 'usernum', 'groupnum' ] ],
+ 'index' => [ [ 'usernum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'usernum' ],
+ table => 'access_user',
+ },
+ { columns => [ 'groupnum' ],
+ table => 'access_group',
+ },
+ ],
+ },
'access_groupagent' => {
'columns' => [
@@ -3829,9 +5247,17 @@ sub tables_hashref {
'groupnum', 'int', '', '', '', '',
'agentnum', 'int', '', '', '', '',
],
- 'primary_key' => 'groupagentnum',
- 'unique' => [ [ 'groupnum', 'agentnum' ] ],
- 'index' => [ [ 'groupnum' ] ],
+ 'primary_key' => 'groupagentnum',
+ 'unique' => [ [ 'groupnum', 'agentnum' ] ],
+ 'index' => [ [ 'groupnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'groupnum' ],
+ table => 'access_group',
+ },
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'access_right' => {
@@ -3872,11 +5298,31 @@ sub tables_hashref {
'sms_account', 'varchar', 'NULL', $char_d, '', '',
'max_simultaneous', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [ [ 'sms_carrierid', 'sms_account'] ],
- 'index' => [ ['countrycode', 'phonenum'], ['pbxsvc'], ['domsvc'],
- ['locationnum'], ['sms_carrierid'],
- ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [ [ 'sms_carrierid', 'sms_account'] ],
+ 'index' => [ ['countrycode', 'phonenum'], ['pbxsvc'], ['domsvc'],
+ ['locationnum'], ['sms_carrierid'],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'pbxsvc' ],
+ table => 'svc_pbx', #'cust_svc',
+ references => [ 'svcnum' ],
+ },
+ { columns => [ 'domsvc' ],
+ table => 'svc_domain', #'cust_svc',
+ references => [ 'svcnum' ],
+ },
+ { columns => [ 'locationnum' ],
+ table => 'cust_location',
+ },
+ { columns => [ 'sms_carrierid' ],
+ table => 'cdr_carrier',
+ references => [ 'carrierid' ],
+ },
+ ],
},
'phone_device' => {
@@ -3886,9 +5332,17 @@ sub tables_hashref {
'svcnum', 'int', '', '', '', '',
'mac_addr', 'varchar', 'NULL', 12, '', '',
],
- 'primary_key' => 'devicenum',
- 'unique' => [ [ 'mac_addr' ], ],
- 'index' => [ [ 'devicepart' ], [ 'svcnum' ], ],
+ 'primary_key' => 'devicenum',
+ 'unique' => [ [ 'mac_addr' ], ],
+ 'index' => [ [ 'devicepart' ], [ 'svcnum' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'devicepart' ],
+ table => 'part_device',
+ },
+ { columns => [ 'svcnum' ],
+ table => 'svc_phone',
+ },
+ ],
},
'part_device' => {
@@ -3897,9 +5351,15 @@ sub tables_hashref {
'devicename', 'varchar', '', $char_d, '', '',
'inventory_classnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'devicepart',
- 'unique' => [ [ 'devicename' ] ], #?
- 'index' => [],
+ 'primary_key' => 'devicepart',
+ 'unique' => [ [ 'devicename' ] ], #?
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'inventory_classnum' ],
+ table => 'inventory_class',
+ references => [ 'classnum' ],
+ },
+ ],
},
'phone_avail' => {
@@ -3919,18 +5379,35 @@ sub tables_hashref {
'svcnum', 'int', 'NULL', '', '', '',
'availbatch', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'availnum',
- 'unique' => [],
- 'index' => [ [ 'exportnum', 'countrycode', 'state' ], #npa search
- [ 'exportnum', 'countrycode', 'npa' ], #nxx search
- [ 'exportnum', 'countrycode', 'npa', 'nxx' ],#station search
- [ 'exportnum', 'countrycode', 'npa', 'nxx', 'station' ], # #
- [ 'svcnum' ],
- [ 'availbatch' ],
- [ 'latanum' ],
- ],
+ 'primary_key' => 'availnum',
+ 'unique' => [],
+ 'index' => [ ['exportnum','countrycode','state'], #npa search
+ ['exportnum','countrycode','npa'], #nxx search
+ ['exportnum','countrycode','npa','nxx'],#station srch
+ [ 'exportnum','countrycode','npa','nxx','station'], #
+ [ 'svcnum' ],
+ [ 'availbatch' ],
+ [ 'latanum' ],
+ ],
+ 'foreign_keys' => [
+ { columns => [ 'exportnum' ],
+ table => 'part_export',
+ },
+ { columns => [ 'latanum' ],
+ table => 'lata',
+ },
+ { columns => [ 'msanum' ],
+ table => 'msa',
+ },
+ { columns => [ 'ordernum' ],
+ table => 'did_order',
+ },
+ { columns => [ 'svcnum' ],
+ table => 'svc_phone',
+ },
+ ],
},
-
+
'lata' => {
'columns' => [
'latanum', 'int', '', '', '', '',
@@ -3941,7 +5418,7 @@ sub tables_hashref {
'unique' => [],
'index' => [],
},
-
+
'msa' => {
'columns' => [
'msanum', 'int', '', '', '', '',
@@ -3951,7 +5428,7 @@ sub tables_hashref {
'unique' => [],
'index' => [],
},
-
+
'rate_center' => {
'columns' => [
'ratecenternum', 'serial', '', '', '', '',
@@ -3971,7 +5448,7 @@ sub tables_hashref {
'unique' => [],
'index' => [],
},
-
+
'did_order_item' => {
'columns' => [
'orderitemnum', 'serial', '', '', '', '',
@@ -3984,9 +5461,26 @@ sub tables_hashref {
'quantity', 'int', '', '', '', '',
'custnum', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'orderitemnum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'orderitemnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'ordernum' ],
+ table => 'did_order',
+ },
+ { columns => [ 'msanum' ],
+ table => 'msa',
+ },
+ { columns => [ 'latanum' ],
+ table => 'lata',
+ },
+ { columns => [ 'ratecenternum' ],
+ table => 'rate_center',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'did_order' => {
@@ -3999,9 +5493,17 @@ sub tables_hashref {
'confirmed', 'int', 'NULL', '', '', '',
'received', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'ordernum',
- 'unique' => [ [ 'vendornum', 'vendor_order_id' ] ],
- 'index' => [],
+ 'primary_key' => 'ordernum',
+ 'unique' => [ [ 'vendornum', 'vendor_order_id' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'vendornum' ],
+ table => 'did_vendor',
+ },
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ ],
},
'reason_type' => {
@@ -4024,9 +5526,19 @@ sub tables_hashref {
'unsuspend_pkgpart', 'int', 'NULL', '', '', '',
'unsuspend_hold','char', 'NULL', 1, '', '',
],
- 'primary_key' => 'reasonnum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'reasonnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'reason_type' ],
+ table => 'reason_type',
+ references => [ 'typenum' ],
+ },
+ { columns => [ 'unsuspend_pkgpart' ],
+ table => 'part_pkg',
+ references => [ 'pkgpart' ],
+ },
+ ],
},
'conf' => {
@@ -4037,9 +5549,14 @@ sub tables_hashref {
'name', 'varchar', '', $char_d, '', '',
'value', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'confnum',
- 'unique' => [ [ 'agentnum', 'locale', 'name' ] ],
- 'index' => [],
+ 'primary_key' => 'confnum',
+ 'unique' => [ [ 'agentnum', 'locale', 'name' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'pkg_referral' => {
@@ -4048,9 +5565,17 @@ sub tables_hashref {
'pkgnum', 'int', '', '', '', '',
'refnum', 'int', '', '', '', '',
],
- 'primary_key' => 'pkgrefnum',
- 'unique' => [ [ 'pkgnum', 'refnum' ] ],
- 'index' => [ [ 'pkgnum' ], [ 'refnum' ] ],
+ 'primary_key' => 'pkgrefnum',
+ 'unique' => [ [ 'pkgnum', 'refnum' ] ],
+ 'index' => [ [ 'pkgnum' ], [ 'refnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'pkgnum' ],
+ table => 'cust_pkg',
+ },
+ { columns => [ 'refnum' ],
+ table => 'part_referral',
+ },
+ ],
},
'svc_pbx' => {
@@ -4061,9 +5586,14 @@ sub tables_hashref {
'max_extensions', 'int', 'NULL', '', '', '',
'max_simultaneous', 'int', 'NULL', '', '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [ [ 'id' ] ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ [ 'id' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'svc_mailinglist' => { #svc_group?
@@ -4077,9 +5607,21 @@ sub tables_hashref {
'reject_auto', 'char', 'NULL', 1, '', '',#RejectAuto
'remove_to_and_cc', 'char', 'NULL', 1, '', '',#RemoveToAndCc
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [ ['username'], ['domsvc'], ['listnum'] ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ ['username'], ['domsvc'], ['listnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'domsvc' ],
+ table => 'svc_domain', #'cust_svc',
+ references => [ 'svcnum' ],
+ },
+ { columns => [ 'listnum' ],
+ table => 'mailinglist',
+ },
+ ],
},
'mailinglist' => {
@@ -4100,9 +5642,20 @@ sub tables_hashref {
'contactemailnum', 'int', 'NULL', '', '', '',
'email', 'varchar', 'NULL', 255, '', '',
],
- 'primary_key' => 'membernum',
- 'unique' => [],
- 'index' => [['listnum'],['svcnum'],['contactemailnum'],['email']],
+ 'primary_key' => 'membernum',
+ 'unique' => [],
+ 'index' => [['listnum'],['svcnum'],['contactemailnum'],['email']],
+ 'foreign_keys' => [
+ { columns => [ 'listnum' ],
+ table => 'mailinglist',
+ },
+ { columns => [ 'svcnum' ],
+ table => 'svc_acct',
+ },
+ { columns => [ 'contactemailnum' ],
+ table => 'contact_email',
+ },
+ ],
},
'bill_batch' => {
@@ -4112,9 +5665,14 @@ sub tables_hashref {
'status', 'char', 'NULL', '1', '', '',
'pdf', 'blob', 'NULL', '', '', '',
],
- 'primary_key' => 'batchnum',
- 'unique' => [],
- 'index' => [ ['agentnum'] ],
+ 'primary_key' => 'batchnum',
+ 'unique' => [],
+ 'index' => [ ['agentnum'] ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'cust_bill_batch' => {
@@ -4123,9 +5681,17 @@ sub tables_hashref {
'batchnum', 'int', '', '', '', '',
'invnum', 'int', '', '', '', '',
],
- 'primary_key' => 'billbatchnum',
- 'unique' => [],
- 'index' => [ [ 'batchnum' ], [ 'invnum' ] ],
+ 'primary_key' => 'billbatchnum',
+ 'unique' => [],
+ 'index' => [ [ 'batchnum' ], [ 'invnum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'batchnum' ],
+ table => 'bill_batch',
+ },
+ { columns => [ 'invnum' ],
+ table => 'cust_bill',
+ },
+ ],
},
'cust_bill_batch_option' => {
@@ -4135,10 +5701,15 @@ sub tables_hashref {
'optionname', 'varchar', '', $char_d, '', '',
'optionvalue', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'optionnum',
- 'unique' => [],
- 'index' => [ [ 'billbatchnum' ], [ 'optionname' ] ],
- },
+ 'primary_key' => 'optionnum',
+ 'unique' => [],
+ 'index' => [ [ 'billbatchnum' ], [ 'optionname' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'billbatchnum' ],
+ table => 'cust_bill_batch',
+ },
+ ],
+ },
'msg_template' => {
'columns' => [
@@ -4152,9 +5723,14 @@ sub tables_hashref {
'from_addr', 'varchar', 'NULL', 255, '', '',
'bcc_addr', 'varchar', 'NULL', 255, '', '',
],
- 'primary_key' => 'msgnum',
- 'unique' => [ ],
- 'index' => [ ['agentnum'], ],
+ 'primary_key' => 'msgnum',
+ 'unique' => [ ],
+ 'index' => [ ['agentnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'template_content' => {
@@ -4165,9 +5741,14 @@ sub tables_hashref {
'subject', 'varchar', 'NULL', 512, '', '',
'body', 'text', 'NULL', '', '', '',
],
- 'primary_key' => 'contentnum',
- 'unique' => [ ['msgnum', 'locale'] ],
- 'index' => [ ],
+ 'primary_key' => 'contentnum',
+ 'unique' => [ ['msgnum', 'locale'] ],
+ 'index' => [ ],
+ 'foreign_keys' => [
+ { columns => [ 'msgnum' ],
+ table => 'msg_template',
+ },
+ ],
},
'cust_msg' => {
@@ -4183,9 +5764,17 @@ sub tables_hashref {
'error', 'varchar', 'NULL', 255, '', '',
'status', 'varchar', '',$char_d, '', '',
],
- 'primary_key' => 'custmsgnum',
- 'unique' => [ ],
- 'index' => [ ['custnum'], ],
+ 'primary_key' => 'custmsgnum',
+ 'unique' => [ ],
+ 'index' => [ ['custnum'], ],
+ 'foreign_keys' => [
+ { columns => [ 'custnum' ],
+ table => 'cust_main',
+ },
+ { columns => [ 'msgnum' ],
+ table => 'msg_template',
+ },
+ ],
},
'svc_cert' => {
@@ -4204,9 +5793,17 @@ sub tables_hashref {
'country', 'char', 'NULL', 2, '', '',
'cert_contact', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [], #recnum
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [], #recnum
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'recnum' ],
+ table => 'domain_record',
+ },
+ ],
},
'svc_port' => {
@@ -4214,9 +5811,14 @@ sub tables_hashref {
'svcnum', 'int', '', '', '', '',
'serviceid', 'varchar', '', 64, '', '', #srvexport / reportfields
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [], #recnum
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [], #recnum
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'areacode' => {
@@ -4258,9 +5860,14 @@ sub tables_hashref {
'subject', 'varchar', 'NULL', '255', '', '',
'handling', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'targetnum',
- 'unique' => [ [ 'targetnum' ] ],
- 'index' => [],
+ 'primary_key' => 'targetnum',
+ 'unique' => [ [ 'targetnum' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'log' => {
@@ -4273,9 +5880,14 @@ sub tables_hashref {
'level', 'int', '', '', '', '',
'message', 'text', '', '', '', '',
],
- 'primary_key' => 'lognum',
- 'unique' => [],
- 'index' => [ ['_date'], ['level'] ],
+ 'primary_key' => 'lognum',
+ 'unique' => [],
+ 'index' => [ ['_date'], ['level'] ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'log_context' => {
@@ -4284,9 +5896,14 @@ sub tables_hashref {
'lognum', 'int', '', '', '', '',
'context', 'varchar', '', 32, '', '',
],
- 'primary_key' => 'logcontextnum',
- 'unique' => [ [ 'lognum', 'context' ] ],
- 'index' => [],
+ 'primary_key' => 'logcontextnum',
+ 'unique' => [ [ 'lognum', 'context' ] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'lognum' ],
+ table => 'log',
+ },
+ ],
},
'svc_alarm' => {
@@ -4300,9 +5917,14 @@ sub tables_hashref {
#cs
#rep
],
- 'primary_key' => 'svcnum',
- 'unique' => [], #system/type/acctnum??
- 'index' => [],
+ 'primary_key' => 'svcnum',
+ 'unique' => [], #system/type/acctnum??
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ ],
},
'svc_cable' => {
@@ -4314,9 +5936,20 @@ sub tables_hashref {
'serialnum', 'varchar', 'NULL', $char_d, '', '',
'mac_addr', 'varchar', 'NULL', 12, '', '',
],
- 'primary_key' => 'svcnum',
- 'unique' => [],
- 'index' => [],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'providernum' ],
+ table => 'cable_provider',
+ },
+ { columns => [ 'modelnum' ],
+ table => 'cable_model',
+ },
+ ],
},
'cable_model' => {
@@ -4348,9 +5981,14 @@ sub tables_hashref {
'classnum', 'int', '', '', '', '',
'disabled', 'char', 'NULL', 1, '', '',
],
- 'primary_key' => 'vendnum',
- 'unique' => [ ['vendname', 'disabled'] ],
- 'index' => [],
+ 'primary_key' => 'vendnum',
+ 'unique' => [ ['vendname', 'disabled'] ],
+ 'index' => [],
+ 'foreign_keys' => [
+ { columns => [ 'classnum' ],
+ table => 'vend_class',
+ },
+ ],
},
'vend_class' => {
@@ -4371,9 +6009,14 @@ sub tables_hashref {
'_date', @date_type, '', '',
'charged', @money_type, '', '',
],
- 'primary_key' => 'vendbillnum',
- 'unique' => [],
- 'index' => [ ['vendnum'], ['_date'], ],
+ 'primary_key' => 'vendbillnum',
+ 'unique' => [],
+ 'index' => [ ['vendnum'], ['_date'], ],
+ 'foreign_keys' => [
+ { columns => [ 'vendnum' ],
+ table => 'vend_main',
+ },
+ ],
},
'vend_pay' => {
@@ -4383,9 +6026,14 @@ sub tables_hashref {
'_date', @date_type, '', '',
'paid', @money_type, '', '',
],
- 'primary_key' => 'vendpaynum',
- 'unique' => [],
- 'index' => [ [ 'vendnum' ], [ '_date' ], ],
+ 'primary_key' => 'vendpaynum',
+ 'unique' => [],
+ 'index' => [ [ 'vendnum' ], [ '_date' ], ],
+ 'foreign_keys' => [
+ { columns => [ 'vendnum' ],
+ table => 'vend_main',
+ },
+ ],
},
'vend_bill_pay' => {
@@ -4396,9 +6044,17 @@ sub tables_hashref {
'amount', @money_type, '', '',
#? '_date', @date_type, '', '',
],
- 'primary_key' => 'vendbillpaynum',
- 'unique' => [],
- 'index' => [ [ 'vendbillnum' ], [ 'vendpaynum' ] ],
+ 'primary_key' => 'vendbillpaynum',
+ 'unique' => [],
+ 'index' => [ [ 'vendbillnum' ], [ 'vendpaynum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'vendbillnum' ],
+ table => 'vend_bill',
+ },
+ { columns => [ 'vendpaynum' ],
+ table => 'vend_pay',
+ },
+ ],
},
%{ tables_hashref_torrus() },
@@ -4421,9 +6077,14 @@ sub tables_hashref {
'derivenum', 'int', '', '', '', '',
'serviceid', 'varchar', '', 64, '', '', #srvexport / reportfields
],
- 'primary_key' => 'componentnum',
- 'unique' => [ [ 'derivenum', 'serviceid' ], ],
- 'index' => [ [ 'derivenum', ], ],
+ 'primary_key' => 'componentnum',
+ 'unique' => [ [ 'derivenum', 'serviceid' ], ],
+ 'index' => [ [ 'derivenum', ], ],
+ 'foreign_keys' => [
+ { columns => [ 'derivenum' ],
+ table => 'torrus_srvderive',
+ },
+ ],
},
'invoice_mode' => {
@@ -4435,6 +6096,11 @@ sub tables_hashref {
'primary_key' => 'modenum',
'unique' => [ ],
'index' => [ ],
+ 'foreign_keys' => [
+ { columns => [ 'agentnum' ],
+ table => 'agent',
+ },
+ ],
},
'invoice_conf' => {
@@ -4468,9 +6134,14 @@ sub tables_hashref {
'logo_eps', 'blob', 'NULL', '', '', '',
'lpr', 'varchar', 'NULL', $char_d, '', '',
],
- 'primary_key' => 'confnum',
- 'unique' => [ [ 'modenum', 'locale' ] ],
- 'index' => [ ],
+ 'primary_key' => 'confnum',
+ 'unique' => [ [ 'modenum', 'locale' ] ],
+ 'index' => [ ],
+ 'foreign_keys' => [
+ { columns => [ 'modenum' ],
+ table => 'invoice_mode',
+ },
+ ],
},
# name type nullability length default local
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index 037c4b3f2..a7fe99f90 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -132,6 +132,9 @@ sub upgrade {
local $FS::UID::AutoCommit = 0;
local $FS::UID::AutoCommit = 0;
+ local $FS::cust_pkg::upgrade = 1; #go away after setup+start dates cleaned up for old customers
+
+
foreach my $table ( keys %$data ) {
my $class = "FS::$table";
diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm
index 7c25acbe3..c938474a6 100644
--- a/FS/FS/access_user.pm
+++ b/FS/FS/access_user.pm
@@ -11,6 +11,7 @@ use FS::access_user_pref;
use FS::access_usergroup;
use FS::agent;
use FS::cust_main;
+use FS::sales;
$DEBUG = 0;
$me = '[FS::access_user]';
@@ -213,6 +214,7 @@ sub check {
|| $self->ut_textn('last')
|| $self->ut_textn('first')
|| $self->ut_foreign_keyn('user_custnum', 'cust_main', 'custnum')
+ || $self->ut_foreign_keyn('report_salesnum', 'sales', 'salesnum')
|| $self->ut_enum('disabled', [ '', 'Y' ] )
;
return $error if $error;
@@ -246,6 +248,18 @@ sub user_cust_main {
qsearchs( 'cust_main', { 'custnum' => $self->user_custnum } );
}
+=item report_sales
+
+Returns the FS::sales object (see L<FS::sales>), if any, for this
+user.
+
+=cut
+
+sub report_sales {
+ my $self = shift;
+ qsearchs( 'sales', { 'salesnum' => $self->report_salesnum } );
+}
+
=item access_usergroup
Returns links to the the groups this user is a part of, as FS::access_usergroup
diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm
index 8fcd724a0..da6f2eb99 100644
--- a/FS/FS/contact.pm
+++ b/FS/FS/contact.pm
@@ -1,7 +1,7 @@
package FS::contact;
+use base qw( FS::Record );
use strict;
-use base qw( FS::Record );
use FS::Record qw( qsearch qsearchs dbh );
use FS::prospect_main;
use FS::cust_main;
@@ -153,6 +153,16 @@ sub insert {
}
+ #unless ( $import || $skip_fuzzyfiles ) {
+ #warn " queueing fuzzyfiles update\n"
+ # if $DEBUG > 1;
+ $error = $self->queue_fuzzyfiles_update;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "updating fuzzy search cache: $error";
+ }
+ #}
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
@@ -277,6 +287,16 @@ sub replace {
}
+ #unless ( $import || $skip_fuzzyfiles ) {
+ #warn " queueing fuzzyfiles update\n"
+ # if $DEBUG > 1;
+ $error = $self->queue_fuzzyfiles_update;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "updating fuzzy search cache: $error";
+ }
+ #}
+
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
@@ -306,6 +326,44 @@ sub _parse_phonestring {
);
}
+=item queue_fuzzyfiles_update
+
+Used by insert & replace to update the fuzzy search cache
+
+=cut
+
+use FS::cust_main::Search;
+sub queue_fuzzyfiles_update {
+ 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;
+
+ foreach my $field ( 'first', 'last' ) {
+ my $queue = new FS::queue {
+ 'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield'
+ };
+ my @args = "contact.$field", $self->get($field);
+ my $error = $queue->insert( @args );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
=item check
Checks all fields to make sure this is a valid example. If there is
@@ -381,6 +439,11 @@ sub contact_email {
qsearch('contact_email', { 'contactnum' => $self->contactnum } );
}
+sub cust_main {
+ my $self = shift;
+ qsearchs('cust_main', { 'custnum' => $self->custnum } );
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/contact_email.pm b/FS/FS/contact_email.pm
index 1276d8d68..4f787358b 100644
--- a/FS/FS/contact_email.pm
+++ b/FS/FS/contact_email.pm
@@ -1,8 +1,9 @@
package FS::contact_email;
+use base qw( FS::Record );
use strict;
-use base qw( FS::Record );
use FS::Record qw( qsearch qsearchs );
+use FS::contact;
=head1 NAME
@@ -25,8 +26,9 @@ FS::contact_email - Object methods for contact_email records
=head1 DESCRIPTION
-An FS::contact_email object represents an example. FS::contact_email inherits from
-FS::Record. The following fields are currently supported:
+An FS::contact_email object represents a contact's email address.
+FS::contact_email inherits from FS::Record. The following fields are currently
+supported:
=over 4
@@ -51,15 +53,14 @@ emailaddress
=item new HASHREF
-Creates a new example. To add the example to the database, see L<"insert">.
+Creates a new contact email address. To add the email address to the database,
+see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
-# the new method can be inherited from FS::Record, if a table method is defined
-
sub table { 'contact_email'; }
=item insert
@@ -67,60 +68,62 @@ sub table { 'contact_email'; }
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
-=cut
-
-# the insert method can be inherited from FS::Record
-
=item delete
Delete this record from the database.
-=cut
-
-# the delete method can be inherited from FS::Record
-
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
-=cut
-
-# the replace method can be inherited from FS::Record
-
=item check
-Checks all fields to make sure this is a valid example. If there is
+Checks all fields to make sure this is a valid email address. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
=cut
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
sub check {
my $self = shift;
my $error =
$self->ut_numbern('contactemailnum')
|| $self->ut_number('contactnum')
- || $self->ut_text('emailaddress')
;
return $error if $error;
+ #technically \w and also ! # $ % & ' * + - / = ? ^ _ ` { | } ~
+ # and even more technically need to deal with i18n addreesses soon
+ # (maybe the UI can convert them for us ala punycode.js)
+ # but for now in practice have not encountered anything outside \w . - & + '
+ # and even & and ' are super rare and probably have scarier "pass to shell"
+ # implications than worth being pedantic about accepting
+ # (we always String::ShellQuote quote them, but once passed...)
+ # SO: \w . - +
+ if ( $self->emailaddress =~ /^\s*([\w\.\-\+]+)\@(([\w\.\-]+\.)+\w+)\s*$/ ) {
+ my($user, $domain) = ($1, $2);
+ $self->emailaddress("$1\@$2");
+ } else {
+ return gettext("illegal_email_invoice_address"). ': '. $self->emailaddress;
+ }
+
$self->SUPER::check;
}
+sub contact {
+ my $self = shift;
+ qsearchs( 'contact', { 'contactnum' => $self->contactnum } );
+}
+
=back
=head1 BUGS
-The author forgot to customize this manpage.
-
=head1 SEE ALSO
-L<FS::Record>, schema.html from the base documentation.
+L<FS::contact>, L<FS::Record>
=cut
diff --git a/FS/FS/contact_phone.pm b/FS/FS/contact_phone.pm
index ad8e8f737..0eb216668 100644
--- a/FS/FS/contact_phone.pm
+++ b/FS/FS/contact_phone.pm
@@ -1,8 +1,9 @@
package FS::contact_phone;
+use base qw( FS::Record );
use strict;
-use base qw( FS::Record );
use FS::Record qw( qsearch qsearchs );
+use FS::contact;
=head1 NAME
@@ -25,8 +26,8 @@ FS::contact_phone - Object methods for contact_phone records
=head1 DESCRIPTION
-An FS::contact_phone object represents an example. FS::contact_phone inherits from
-FS::Record. The following fields are currently supported:
+An FS::contact_phone object represents a contatct's phone number.
+FS::contact_phone inherits from FS::Record. The following fields are currently supported:
=over 4
@@ -63,15 +64,14 @@ extension
=item new HASHREF
-Creates a new example. To add the example to the database, see L<"insert">.
+Creates a new phone number. To add the phone number to the database, see
+L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
-# the new method can be inherited from FS::Record, if a table method is defined
-
sub table { 'contact_phone'; }
=item insert
@@ -79,38 +79,23 @@ sub table { 'contact_phone'; }
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
-=cut
-
-# the insert method can be inherited from FS::Record
-
=item delete
Delete this record from the database.
-=cut
-
-# the delete method can be inherited from FS::Record
-
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
returns the error, otherwise returns false.
-=cut
-
-# the replace method can be inherited from FS::Record
-
=item check
-Checks all fields to make sure this is a valid example. If there is
+Checks all fields to make sure this is a valid phone number. If there is
an error, returns the error, otherwise returns false. Called by the insert
and replace methods.
=cut
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
sub check {
my $self = shift;
@@ -124,18 +109,48 @@ sub check {
;
return $error if $error;
+ #strip non-digits, UI should format numbers per countrycode
+ (my $phonenum = $self->phonenum ) =~ s/\D//g;
+ $self->phonenum($phonenum);
+
$self->SUPER::check;
}
+sub phonenum_pretty {
+ my $self = shift;
+
+ #until/unless we have the upgrade strip all whitespace
+ (my $phonenum = $self->phonenum ) =~ s/\D//g;
+
+ if ( $self->countrycode == 1 ) {
+
+ $phonenum =~ /^(\d{3})(\d{3})(\d{4})(\d*)$/
+ or return $self->phonenum; #wtf?
+
+ $phonenum = "($1) $2-$3";
+ $phonenum .= " x$4" if $4;
+ return $phonenum;
+
+ } else {
+ warn "don't know how to format phone numbers for country +". $self->countrycode;
+ #also, the UI doesn't have a good way for you to enter them yet or parse a countrycode from the number
+ return $self->phonenum;
+ }
+
+}
+
+sub contact {
+ my $self = shift;
+ qsearchs( 'contact', { 'contactnum' => $self->contactnum } );
+}
+
=back
=head1 BUGS
-The author forgot to customize this manpage.
-
=head1 SEE ALSO
-L<FS::Record>, schema.html from the base documentation.
+L<FS::contact>, L<FS::Record>
=cut
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index 66d98c257..4e34ef47b 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -3070,7 +3070,8 @@ sub _items_payments {
my $cust_pay = $obj->isa('FS::cust_pay') ? $obj : $obj->cust_pay;
my $desc = $self->mt('Payment received').' '.
time2str($date_format, $cust_pay->_date );
- $desc .= $self->mt(' via ' . $cust_pay->payby_payinfo_pretty)
+ $desc .= $self->mt(' via ') .
+ $cust_pay->payby_payinfo_pretty( $self->cust_main->locale )
if $detailed;
push @b, {
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
index 96789343a..c459d827b 100644
--- a/FS/FS/cust_credit.pm
+++ b/FS/FS/cust_credit.pm
@@ -22,6 +22,7 @@ use FS::cust_event;
use FS::agent;
use FS::sales;
use FS::cust_credit_void;
+use FS::upgrade_journal;
$me = '[ FS::cust_credit ]';
$DEBUG = 0;
@@ -620,6 +621,100 @@ sub _upgrade_data { # class method
local($ignore_empty_reasonnum) = 1;
$class->_upgrade_otaker(%opts);
+ if ( !FS::upgrade_journal->is_done('cust_credit__tax_link')
+ and !$conf->exists('enable_taxproducts') ) {
+ # RT#25458: fix credit line item applications that should refer to a
+ # specific tax allocation
+ my @cust_credit_bill_pkg = qsearch({
+ table => 'cust_credit_bill_pkg',
+ select => 'cust_credit_bill_pkg.*',
+ addl_from => ' LEFT JOIN cust_bill_pkg USING (billpkgnum)',
+ extra_sql =>
+ 'WHERE cust_credit_bill_pkg.billpkgtaxlocationnum IS NULL '.
+ 'AND cust_bill_pkg.pkgnum = 0', # is a tax
+ });
+ my %tax_items;
+ my %credits;
+ foreach (@cust_credit_bill_pkg) {
+ my $billpkgnum = $_->billpkgnum;
+ $tax_items{$billpkgnum} ||= FS::cust_bill_pkg->by_key($billpkgnum);
+ $credits{$billpkgnum} ||= [];
+ push @{ $credits{$billpkgnum} }, $_;
+ }
+ TAX_ITEM: foreach my $tax_item (values %tax_items) {
+ my $billpkgnum = $tax_item->billpkgnum;
+ # get all pkg/location/taxrate allocations of this tax line item
+ my @allocations = sort {$b->amount <=> $a->amount}
+ qsearch('cust_bill_pkg_tax_location', {
+ billpkgnum => $billpkgnum
+ });
+ # and these are all credit applications to it
+ my @credits = sort {$b->amount <=> $a->amount}
+ @{ $credits{$billpkgnum} };
+ my $c = shift @credits;
+ my $a = shift @allocations; # we will NOT modify these
+ while ($c and $a) {
+ if ( abs($c->amount - $a->amount) < 0.005 ) {
+ # by far the most common case: the tax line item is for a single
+ # tax, so we just fill in the billpkgtaxlocationnum
+ $c->set('billpkgtaxlocationnum', $a->billpkgtaxlocationnum);
+ my $error = $c->replace;
+ if ($error) {
+ warn "error fixing credit application to tax item #$billpkgnum:\n$error\n";
+ next TAX_ITEM;
+ }
+ $c = shift @credits;
+ $a = shift @allocations;
+ } elsif ( $c->amount > $a->amount ) {
+ # fairly common: the tax line contains tax for multiple packages
+ # (or multiple taxes) but the credit isn't divided up
+ my $new_link = FS::cust_credit_bill_pkg->new({
+ creditbillnum => $c->creditbillnum,
+ billpkgnum => $c->billpkgnum,
+ billpkgtaxlocationnum => $a->billpkgtaxlocationnum,
+ amount => $a->amount,
+ setuprecur => 'setup',
+ });
+ my $error = $new_link->insert;
+ if ($error) {
+ warn "error fixing credit application to tax item #$billpkgnum:\n$error\n";
+ next TAX_ITEM;
+ }
+ $c->set(amount => sprintf('%.2f', $c->amount - $a->amount));
+ $a = shift @allocations;
+ } elsif ( $c->amount < 0.005 ) {
+ # also fairly common; we can delete these with no harm
+ my $error = $c->delete;
+ warn "error removing zero-amount credit application (probably harmless):\n$error\n" if $error;
+ $c = shift @credits;
+ } elsif ( $c->amount < $a->amount ) {
+ # should never happen, but if it does, handle it gracefully
+ $c->set('billpkgtaxlocationnum', $a->billpkgtaxlocationnum);
+ my $error = $c->replace;
+ if ($error) {
+ warn "error fixing credit application to tax item #$billpkgnum:\n$error\n";
+ next TAX_ITEM;
+ }
+ $a->set(amount => $a->amount - $c->amount);
+ $c = shift @credits;
+ }
+ } # while $c and $a
+ if ( $c ) {
+ if ( $c->amount < 0.005 ) {
+ my $error = $c->delete;
+ warn "error removing zero-amount credit application (probably harmless):\n$error\n" if $error;
+ } elsif ( $c->modified ) {
+ # then we've allocated part of it, so reduce the nonspecific
+ # application by that much
+ my $error = $c->replace;
+ warn "error fixing credit application to tax item #$billpkgnum:\n$error\n" if $error;
+ }
+ # else there are probably no allocations, i.e. this is a pre-3.x
+ # record that was never migrated over, so leave it alone
+ } # if $c
+ } # foreach $tax_item
+ FS::upgrade_journal->set_done('cust_credit__tax_link');
+ }
}
=back
@@ -902,7 +997,7 @@ sub credit_lineitems {
{
# the existing tax_Xlocation object
my $old_loc =
- $tax_links{$tax_item->billpkgnum}{$new_loc->taxable_billpkgnum};
+ $tax_links{$tax_item->billpkgnum}{$new_loc->taxable_cust_bill_pkg->billpkgnum};
next if !$old_loc; # apply the leftover amount nonspecifically
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 3e36c6049..d768f8406 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -1662,13 +1662,25 @@ sub queue_fuzzyfiles_update {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ foreach my $field ( 'first', 'last', 'company' ) {
+ my $queue = new FS::queue {
+ 'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield'
+ };
+ my @args = "cust_main.$field", $self->get($field);
+ my $error = $queue->insert( @args );
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "queueing job (transaction rolled back): $error";
+ }
+ }
+
my @locations = $self->bill_location;
push @locations, $self->ship_location if $self->has_ship_address;
foreach my $location (@locations) {
my $queue = new FS::queue {
- 'job' => 'FS::cust_main::Search::append_fuzzyfiles'
+ 'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield'
};
- my @args = map $location->get($_), @FS::cust_main::Search::fuzzyfields;
+ my @args = 'cust_location.address1', $location->address1;
my $error = $queue->insert( @args );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm
index 0a364f509..b8a71d4a9 100644
--- a/FS/FS/cust_main/Billing.pm
+++ b/FS/FS/cust_main/Billing.pm
@@ -373,6 +373,11 @@ sub bill {
my $time = $options{'time'} || time;
my $invoice_time = $options{'invoice_time'} || $time;
+ my $cmp_time = ( $conf->exists('next-bill-ignore-time')
+ ? day_end( $time )
+ : $time
+ );
+
$options{'not_pkgpart'} ||= {};
$options{'not_pkgpart'} = { map { $_ => 1 }
split(/\s*,\s*/, $options{'not_pkgpart'})
@@ -480,7 +485,7 @@ sub bill {
my $next_bill = $cust_pkg->getfield('bill') || 0;
my $error;
# let this run once if this is the last bill upon cancellation
- while ( $next_bill <= $time or $options{cancel} ) {
+ while ( $next_bill <= $cmp_time or $options{cancel} ) {
$error =
$self->_make_lines( 'part_pkg' => $part_pkg,
'cust_pkg' => $cust_pkg,
diff --git a/FS/FS/cust_main/Location.pm b/FS/FS/cust_main/Location.pm
index 5590f8869..52fe313c6 100644
--- a/FS/FS/cust_main/Location.pm
+++ b/FS/FS/cust_main/Location.pm
@@ -165,7 +165,10 @@ sub _upgrade_data {
map { $_ => $cust_main->get($_) } location_fields(),
}
);
- $bill_location->set('censustract', ''); # properly goes with ship_location
+ $bill_location->set('censustract', '');
+ $bill_location->set('censusyear', '');
+ # properly goes with ship_location; if they're the same, will be set
+ # on ship_location before inserting either one
my $ship_location = $bill_location; # until proven otherwise
if ( $cust_main->get('ship_address1') ) {
@@ -187,8 +190,6 @@ sub _upgrade_data {
);
} # else it stays equal to $bill_location
- $ship_location->set('censustract', $cust_main->get('censustract'));
-
# Step 2: Extract shipping address contact fields into contact
my %unlike = map { $_ => 1 }
grep { $cust_main->get($_) ne $cust_main->get("ship_$_") }
@@ -251,6 +252,11 @@ sub _upgrade_data {
}
}
+ # this always goes with the ship_location (whether it's the same as
+ # bill_location or not)
+ $ship_location->set('censustract', $cust_main->get('censustract'));
+ $ship_location->set('censusyear', $cust_main->get('censusyear'));
+
$error = $bill_location->insert;
die "error migrating billing address for customer $custnum: $error"
if $error;
@@ -286,6 +292,38 @@ sub _upgrade_data {
}
} #foreach $cust_main
+
+ # repair an error in earlier upgrades
+ if (!FS::upgrade_journal->is_done('cust_location_censustract_repair')
+ and FS::Conf->new->exists('cust_main-require_censustract') ) {
+
+ foreach my $cust_location (
+ qsearch('cust_location', { 'censustract' => '' })
+ ) {
+ my $custnum = $cust_location->custnum;
+ next if !$custnum; # avoid doing this for prospect locations
+ my $address1 = $cust_location->address1;
+ # find the last history record that had that address
+ my $last_h = qsearchs({
+ table => 'h_cust_main',
+ extra_sql => " WHERE custnum = $custnum AND address1 = ".
+ dbh->quote($address1) .
+ " AND censustract IS NOT NULL",
+ order_by => " ORDER BY history_date DESC LIMIT 1",
+ });
+ if (!$last_h) {
+ # this is normal; just means it never had a census tract before
+ next;
+ }
+ $cust_location->set('censustract' => $last_h->get('censustract'));
+ $cust_location->set('censusyear' => $last_h->get('censusyear'));
+ my $error = $cust_location->replace;
+ warn "Error setting census tract for customer #$custnum:\n $error\n"
+ if $error;
+ } # foreach $cust_location
+ FS::upgrade_journal->set_done('cust_location_censustract_repair');
+ }
+
}
=back
diff --git a/FS/FS/cust_main/Search.pm b/FS/FS/cust_main/Search.pm
index 182527ff9..16db71271 100644
--- a/FS/FS/cust_main/Search.pm
+++ b/FS/FS/cust_main/Search.pm
@@ -19,8 +19,11 @@ use FS::payinfo_Mixin;
$DEBUG = 0;
$me = '[FS::cust_main::Search]';
-@fuzzyfields = ( 'cust_main.first', 'cust_main.last', 'cust_main.company',
- 'cust_location.address1' );
+@fuzzyfields = (
+ 'cust_main.first', 'cust_main.last', 'cust_main.company',
+ 'cust_location.address1',
+ 'contact.first', 'contact.last',
+);
install_callback FS::UID sub {
$conf = new FS::Conf;
@@ -72,6 +75,7 @@ sub smart_search {
#here is the agent virtualization
my $agentnums_sql =
$FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main');
+ my $agentnums_href = $FS::CurrentUser::CurrentUser->agentnums_href;
my @cust_main = ();
@@ -85,6 +89,10 @@ sub smart_search {
my $phonen = "$1-$2-$3";
$phonen .= " x$4" if $4;
+ my $phonenum = "$1$2$3";
+ #my $extension = $4;
+
+ #cust_main phone numbers
push @cust_main, qsearch( {
'table' => 'cust_main',
'hashref' => { %options },
@@ -97,6 +105,16 @@ sub smart_search {
" AND $agentnums_sql", #agent virtualization
} );
+ #contact phone numbers
+ push @cust_main,
+ grep $agentnums_href->{$_->agentnum}, #agent virt
+ grep $_, #skip contacts that don't have cust_main records
+ map $_->contact->cust_main,
+ qsearch({
+ 'table' => 'contact_phone',
+ 'hashref' => { 'phonenum' => $phonenum },
+ });
+
unless ( @cust_main || $phonen =~ /x\d+$/ ) { #no exact match
#try looking for matches with extensions unless one was specified
@@ -117,8 +135,11 @@ sub smart_search {
}
- if ( $search =~ /@/ ) { #invoicing email address
+ if ( $search =~ /@/ ) { #email address
+
+ # invoicing email address
push @cust_main,
+ grep $agentnums_href->{$_->agentnum}, #agent virt
map $_->cust_main,
qsearch( {
'table' => 'cust_main_invoice',
@@ -126,6 +147,17 @@ sub smart_search {
}
);
+ # contact email address
+ push @cust_main,
+ grep $agentnums_href->{$_->agentnum}, #agent virt
+ grep $_, #skip contacts that don't have cust_main records
+ map $_->contact->cust_main,
+ qsearch( {
+ 'table' => 'contact_email',
+ 'hashref' => { 'emailaddress' => $search },
+ }
+ );
+
# custnum search (also try agent_custid), with some tweaking options if your
# legacy cust "numbers" have letters
} elsif ( $search =~ /^\s*(\d+)\s*$/
@@ -159,7 +191,7 @@ sub smart_search {
# for all agents this user can see, if any of them have custnum prefixes
# that match the search string, include customers that match the rest
# of the custnum and belong to that agent
- foreach my $agentnum ( $FS::CurrentUser::CurrentUser->agentnums ) {
+ foreach my $agentnum ( keys %$agentnums_href ) {
my $p = $conf->config('cust_main-custnum-display_prefix', $agentnum);
next if !$p;
if ( $p eq substr($num, 0, length($p)) ) {
@@ -216,10 +248,12 @@ sub smart_search {
$agentnums_sql,
),
} ),
+
#contacts?
+ # probably not necessary for the "something a browser remembered" case
} elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { # value search
- # try (ship_){last,company}
+ # try {first,last,company}
my $value = lc($1);
@@ -256,12 +290,25 @@ sub smart_search {
my $sql = scalar(keys %options) ? ' AND ' : ' WHERE ';
$sql .= "( LOWER(cust_main.last) = $q_last AND LOWER(cust_main.first) = $q_first )";
+ #cust_main
push @cust_main, qsearch( {
'table' => 'cust_main',
'hashref' => \%options,
'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization
} );
- #contacts?
+
+ #contacts
+ push @cust_main,
+ grep $agentnums_href->{$_->agentnum}, #agent virt
+ grep $_, #skip contacts that don't have cust_main records
+ map $_->cust_main,
+ qsearch( {
+ 'table' => 'contact',
+ 'hashref' => { 'first' => $first,
+ 'last' => $last,
+ },
+ }
+ );
# or it just be something that was typed in... (try that in a sec)
@@ -271,18 +318,28 @@ sub smart_search {
#exact
my $sql = scalar(keys %options) ? ' AND ' : ' WHERE ';
- $sql .= " ( LOWER(last) = $q_value
- OR LOWER(company) = $q_value
+ $sql .= " ( LOWER(cust_main.first) = $q_value
+ OR LOWER(cust_main.last) = $q_value
+ OR LOWER(cust_main.company) = $q_value
";
- #yes, it's a kludge
- $sql .= " OR EXISTS(
- SELECT 1 FROM cust_location
- WHERE LOWER(cust_location.address1) = $q_value
- AND cust_location.custnum = cust_main.custnum
- )
- "
+
+ #address1 (yes, it's a kludge)
+ $sql .= " OR EXISTS (
+ SELECT 1 FROM cust_location
+ WHERE LOWER(cust_location.address1) = $q_value
+ AND cust_location.custnum = cust_main.custnum
+ )"
if $conf->exists('address1-search');
- $sql .= " )";
+
+ #contacts (look, another kludge)
+ $sql .= " OR EXISTS ( SELECT 1 FROM contact
+ WHERE ( LOWER(contact.first) = $q_value
+ OR LOWER(contact.last) = $q_value
+ )
+ AND contact.custnum IS NOT NULL
+ AND contact.custnum = cust_main.custnum
+ )
+ ) ";
push @cust_main, qsearch( {
'table' => 'cust_main',
@@ -304,7 +361,6 @@ sub smart_search {
);
if ( $first && $last ) {
- #contacts? ship_first/ship_last are gone
push @hashrefs,
{ 'first' => { op=>'ILIKE', value=>"%$first%" },
@@ -315,6 +371,7 @@ sub smart_search {
} else {
push @hashrefs,
+ { 'first' => { op=>'ILIKE', value=>"%$value%" }, },
{ 'last' => { op=>'ILIKE', value=>"%$value%" }, },
;
}
@@ -334,14 +391,35 @@ sub smart_search {
if ( $conf->exists('address1-search') ) {
push @cust_main, qsearch( {
- 'table' => 'cust_main',
- 'addl_from' => 'JOIN cust_location USING (custnum)',
- 'extra_sql' => 'WHERE cust_location.address1 ILIKE '.
- dbh->quote("%$value%"),
+ table => 'cust_main',
+ addl_from => 'JOIN cust_location USING (custnum)',
+ extra_sql => 'WHERE '.
+ ' cust_location.address1 ILIKE '.dbh->quote("%$value%").
+ " AND $agentnums_sql", #agent virtualizaiton
} );
}
+ #contact substring
+
+ shift @hashrefs; #no company column in contact table
+
+ foreach my $hashref ( @hashrefs ) {
+
+ push @cust_main,
+ grep $agentnums_href->{$_->agentnum}, #agent virt
+ grep $_, #skip contacts that don't have cust_main records
+ map $_->cust_main,
+ qsearch({
+ 'table' => 'contact',
+ 'hashref' => { %$hashref,
+ #%options,
+ },
+ #'extra_sql' => " AND $agentnums_sql", #agent virt
+ });
+
+ }
+
#fuzzy
my %fuzopts = (
'hashref' => \%options,
@@ -355,15 +433,30 @@ sub smart_search {
'first' => $first }, #
%fuzopts
);
+ push @cust_main, FS::cust_main::Search->fuzzy_search(
+ { 'contact.last' => $last, #fuzzy hashref
+ 'contact.first' => $first }, #
+ %fuzopts
+ );
+ }
+ foreach my $field ( 'first', 'last', 'company' ) {
+ push @cust_main, FS::cust_main::Search->fuzzy_search(
+ { $field => $value },
+ %fuzopts
+ );
}
- foreach my $field ( 'last', 'company' ) {
- push @cust_main,
- FS::cust_main::Search->fuzzy_search( { $field => $value }, %fuzopts );
+ foreach my $field ( 'first', 'last' ) {
+ push @cust_main, FS::cust_main::Search->fuzzy_search(
+ { "contact.$field" => $value },
+ %fuzopts
+ );
}
if ( $conf->exists('address1-search') ) {
push @cust_main,
FS::cust_main::Search->fuzzy_search(
- { 'cust_location.address1' => $value }, %fuzopts );
+ { 'cust_location.address1' => $value },
+ %fuzopts
+ );
}
}
@@ -668,22 +761,6 @@ sub search {
unless $params->{'cancelled_pkgs'};
##
- # parse without census tract checkbox
- ##
-
- push @where, "(ship_location.censustract = '' or ship_location.censustract is null)"
- if $params->{'no_censustract'};
-
- ##
- # parse with hardcoded tax location checkbox
- ##
-
- my $tax_prefix = FS::Conf->new->exists('tax-ship_location') ? 'ship_'
- : 'bill_';
- push @where, "${tax_prefix}location.geocode is not null"
- if $params->{'with_geocode'};
-
- ##
# "with email address(es)" checkbox
##
@@ -950,19 +1027,6 @@ sub search {
}
- if ( $params->{'with_geocode'} ) {
-
- unshift @extra_headers, 'Tax location override', 'Calculated tax location';
- unshift @extra_fields, sub { my $c = shift; $c->get('geocode'); },
- sub { my $c = shift;
- $c->set('geocode', '');
- $c->geocode('cch'); #XXX only cch right now
- };
- push @select, 'geocode';
- push @select, 'zip' unless grep { $_ eq 'zip' } @select;
- push @select, 'ship_zip' unless grep { $_ eq 'ship_zip' } @select;
- }
-
my $select = join(', ', @select);
my $sql_query = {
@@ -976,7 +1040,7 @@ sub search {
'extra_headers' => \@extra_headers,
'extra_fields' => \@extra_fields,
};
- warn Data::Dumper::Dumper($sql_query);
+ #warn Data::Dumper::Dumper($sql_query);
$sql_query;
}
@@ -1033,8 +1097,10 @@ sub fuzzy_search {
$extra_sql .= "$field $in_matches";
my $addl_from = $fuzopts{addl_from};
- if ( $field =~ /^cust_location/ ) {
+ if ( $field =~ /^cust_location\./ ) {
$addl_from .= ' JOIN cust_location USING (custnum)';
+ } elsif ( $field =~ /^contact\./ ) {
+ $addl_from .= ' JOIN contact USING (custnum)';
}
push @cust_main, qsearch({
@@ -1064,7 +1130,14 @@ sub fuzzy_search {
sub check_and_rebuild_fuzzyfiles {
my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- rebuild_fuzzyfiles() if grep { ! -e "$dir/cust_main.$_" } @fuzzyfields;
+ rebuild_fuzzyfiles()
+ if grep { ! -e "$dir/$_" }
+ map {
+ my ($field, $table) = reverse split('\.', $_);
+ $table ||= 'cust_main';
+ "$table.$field"
+ }
+ @fuzzyfields;
}
=item rebuild_fuzzyfiles
@@ -1117,34 +1190,47 @@ sub append_fuzzyfiles {
check_and_rebuild_fuzzyfiles();
- use Fcntl qw(:flock);
+ #foreach my $fuzzy (@fuzzyfields) {
+ foreach my $fuzzy ( 'cust_main.first', 'cust_main.last', 'cust_main.company',
+ 'cust_location.address1',
+ ) {
- my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+ append_fuzzyfiles_fuzzyfield($fuzzy, shift);
- foreach my $fuzzy (@fuzzyfields) {
+ }
- my ($field, $table) = reverse split('\.', $fuzzy);
- $table ||= 'cust_main';
+ 1;
+}
- my $value = shift;
+=item append_fuzzyfiles_fuzzyfield COLUMN VALUE
- if ( $value ) {
+=item append_fuzzyfiles_fuzzyfield TABLE.COLUMN VALUE
- open(CACHE, '>>:encoding(UTF-8)', "$dir/$table.$field" )
- or die "can't open $dir/$table.$field: $!";
- flock(CACHE,LOCK_EX)
- or die "can't lock $dir/$table.$field: $!";
+=cut
- print CACHE "$value\n";
+use Fcntl qw(:flock);
+sub append_fuzzyfiles_fuzzyfield {
+ my( $fuzzyfield, $value ) = @_;
- flock(CACHE,LOCK_UN)
- or die "can't unlock $dir/$table.$field: $!";
- close CACHE;
- }
+ my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
- }
- 1;
+ my ($field, $table) = reverse split('\.', $fuzzyfield);
+ $table ||= 'cust_main';
+
+ return unless length($value);
+
+ open(CACHE, '>>:encoding(UTF-8)', "$dir/$table.$field" )
+ or die "can't open $dir/$table.$field: $!";
+ flock(CACHE,LOCK_EX)
+ or die "can't lock $dir/$table.$field: $!";
+
+ print CACHE "$value\n";
+
+ flock(CACHE,LOCK_UN)
+ or die "can't unlock $dir/$table.$field: $!";
+ close CACHE;
+
}
=item all_X
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index eb6a714fe..5abdbe2ee 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -50,6 +50,8 @@ use FS::Conf;
our ($disable_agentcheck, $DEBUG, $me, $import) = (0, 0, '[FS::cust_pkg]', 0);
+our $upgrade = 0; #go away after setup+start dates cleaned up for old customers
+
sub _cache {
my $self = shift;
my ( $hashref, $cache ) = @_;
@@ -655,7 +657,7 @@ sub check {
return $error if $error;
return "A package with both start date (future start) and setup date (already started) will never bill"
- if $self->start_date && $self->setup;
+ if $self->start_date && $self->setup && ! $upgrade;
return "A future unsuspend date can only be set for a package with a suspend date"
if $self->resume and !$self->susp and !$self->adjourn;
@@ -4282,6 +4284,32 @@ boolean; if true, returns only packages with more than 0 FCC phone lines.
Limit to packages with a service location in the specified state and country.
For FCC 477 reporting, mostly.
+=item location_cust
+
+Limit to packages whose service locations are the same as the customer's
+default service location.
+
+=item location_nocust
+
+Limit to packages whose service locations are not the customer's default
+service location.
+
+=item location_census
+
+Limit to packages whose service locations have census tracts.
+
+=item location_nocensus
+
+Limit to packages whose service locations do not have a census tract.
+
+=item location_geocode
+
+Limit to packages whose locations have geocodes.
+
+=item location_geocode
+
+Limit to packages whose locations do not have geocodes.
+
=back
=cut
@@ -4514,6 +4542,22 @@ sub search {
}
###
+ # location_* flags
+ ###
+ if ( $params->{location_cust} xor $params->{location_nocust} ) {
+ my $op = $params->{location_cust} ? '=' : '!=';
+ push @where, "cust_location.locationnum $op cust_main.ship_locationnum";
+ }
+ if ( $params->{location_census} xor $params->{location_nocensus} ) {
+ my $op = $params->{location_census} ? "IS NOT NULL" : "IS NULL";
+ push @where, "cust_location.censustract $op";
+ }
+ if ( $params->{location_geocode} xor $params->{location_nogeocode} ) {
+ my $op = $params->{location_geocode} ? "IS NOT NULL" : "IS NULL";
+ push @where, "cust_location.geocode $op";
+ }
+
+ ###
# parse part_pkg
###
diff --git a/FS/FS/discount.pm b/FS/FS/discount.pm
index f6f994599..e66d78ced 100644
--- a/FS/FS/discount.pm
+++ b/FS/FS/discount.pm
@@ -1,8 +1,9 @@
package FS::discount;
+use base qw( FS::Record );
use strict;
-use base qw( FS::Record );
use FS::Record qw( qsearch qsearchs );
+use FS::discount_class;
=head1 NAME
@@ -130,6 +131,7 @@ sub check {
my $error =
$self->ut_numbern('discountnum')
+ || $self->ut_foreign_keyn('classnum', 'discount_class', 'classnum')
|| $self->ut_textn('name')
|| $self->ut_money('amount')
|| $self->ut_float('percent') #actually decimal, but this will do
@@ -140,16 +142,19 @@ sub check {
;
return $error if $error;
- #discourage non-integer months for package discounts
- if ($self->discountnum) {
- my $sql =
- "SELECT count(*) FROM part_pkg_discount WHERE part_pkg_discount.discountnum = ".
- $self->discountnum;
-
- my $count = $self->scalar_sql($sql);
- return "months must be integers greater than 1"
- if ( $count && ($self->ut_number('months') || $self->months < 2) );
- }
+#causes "months must be integers greater than 1" errors when you go back and
+# try to edit an existing discount (because the months format as NN.000)
+#not worth whatever reason it came in with "prepayment discounts rt#5318" for
+# #discourage non-integer months for package discounts
+# if ($self->discountnum) {
+# my $sql =
+# "SELECT count(*) FROM part_pkg_discount WHERE part_pkg_discount.discountnum = ".
+# $self->discountnum;
+#
+# my $count = $self->scalar_sql($sql);
+# return "months must be integers greater than 1"
+# if ( $count && ($self->ut_number('months') || $self->months < 2) );
+# }
$self->SUPER::check;
}
@@ -195,6 +200,18 @@ sub description {
$desc;
}
+sub classname {
+ my $self = shift;
+ my $discount_class = $self->discount_class;
+ $discount_class ? $discount_class->classname : '(none)';
+}
+
+sub discount_class {
+ my $self = shift;
+ qsearchs('discount_class', { 'classnum' => $self->classnum });
+}
+
+
=back
=head1 BUGS
diff --git a/FS/FS/discount_class.pm b/FS/FS/discount_class.pm
new file mode 100644
index 000000000..f5b8769f3
--- /dev/null
+++ b/FS/FS/discount_class.pm
@@ -0,0 +1,109 @@
+package FS::discount_class;
+use base qw( FS::class_Common );
+
+use strict;
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::discount_class - Object methods for discount_class records
+
+=head1 SYNOPSIS
+
+ use FS::discount_class;
+
+ $record = new FS::discount_class \%hash;
+ $record = new FS::discount_class { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::discount_class object represents a discount class. FS::discount_class
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item classnum
+
+primary key
+
+=item classname
+
+classname
+
+=item disabled
+
+disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new discount class. To add the discount class to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'discount_class'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid discount class. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('classnum')
+ || $self->ut_text('classname')
+ || $self->ut_enum('disabled', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::discount>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/part_pkg/prorate_calendar.pm b/FS/FS/part_pkg/prorate_calendar.pm
new file mode 100644
index 000000000..83a80f5d0
--- /dev/null
+++ b/FS/FS/part_pkg/prorate_calendar.pm
@@ -0,0 +1,223 @@
+package FS::part_pkg::prorate_calendar;
+
+use strict;
+use vars qw(@ISA %info);
+use DateTime;
+use Tie::IxHash;
+use base 'FS::part_pkg::flat';
+
+# weird stuff in here
+
+%info = (
+ 'name' => 'Prorate to specific calendar day(s), then flat-rate',
+ 'shortname' => 'Prorate (calendar cycle)',
+ 'inherit_fields' => [ 'flat', 'usage_Mixin', 'global_Mixin' ],
+ 'fields' => {
+ 'recur_temporality' => {'disabled' => 1},
+ 'sync_bill_date' => {'disabled' => 1},# god help us all
+
+ 'cutoff_day' => { 'name' => 'Billing day (1 - end of cycle)',
+ 'default' => 1,
+ },
+
+ # add_full_period is not allowed
+
+ # prorate_round_day is always on
+ 'prorate_round_day' => { 'disabled' => 1 },
+
+ 'prorate_defer_bill'=> {
+ 'name' => 'Defer the first bill until the billing day',
+ 'type' => 'checkbox',
+ },
+ 'prorate_verbose' => {
+ 'name' => 'Show prorate details on the invoice',
+ 'type' => 'checkbox',
+ },
+ },
+ 'fieldorder' => [ 'cutoff_day', 'prorate_defer_bill', 'prorate_round_day', 'prorate_verbose' ],
+ 'freq' => 'm',
+ 'weight' => 20,
+);
+
+my %freq_max_days = ( # the length of the shortest period of each cycle type
+ '1' => 28,
+ '2' => 59, # Jan - Feb
+ '3' => 90, # Jan - Mar
+ '4' => 120, # Jan - Apr
+ '6' => 181, # Jan - Jun
+ '12' => 365,
+);
+
+my %freq_cutoff_days = (
+ '1' => [ 31, 28, 31, 30, 31, 30,
+ 31, 31, 30, 31, 30, 31 ],
+ '2' => [ 59, 61, 61, 62, 61, 61 ],
+ '3' => [ 90, 91, 92, 92 ],
+ '4' => [ 120, 123, 122 ],
+ '6' => [ 181, 184 ],
+ '12' => [ 365 ],
+);
+
+sub check {
+ # yes, this package plan is such a special snowflake it needs its own
+ # check method.
+ my $self = shift;
+
+ if ( !exists($freq_max_days{$self->freq}) ) {
+ return 'Prorate (calendar cycle) billing interval must be an integer factor of one year';
+ }
+ $self->SUPER::check;
+}
+
+sub cutoff_day {
+ my( $self, $cust_pkg ) = @_;
+ my @periods = @{ $freq_cutoff_days{$self->freq} };
+ my @cutoffs = ($self->option('cutoff_day') || 1); # Jan 1 = 1
+ pop @periods; # we don't care about the last one
+ foreach (@periods) {
+ push @cutoffs, $cutoffs[-1] + $_;
+ }
+ @cutoffs;
+}
+
+sub calc_prorate {
+ # it's not the same algorithm
+ my ($self, $cust_pkg, $sdate, $details, $param, @cutoff_days) = @_;
+ die "no cutoff_day" unless @cutoff_days;
+ die "prepaid terms not supported with calendar prorate packages"
+ if $param->{freq_override}; # XXX if we ever use this again
+
+ #XXX should we still be doing this with multi-currency support?
+ my $money_char = FS::Conf->new->config('money_char') || '$';
+
+ my $charge = $self->base_recur($cust_pkg, $sdate) || 0;
+ my $now = DateTime->from_epoch(epoch => $$sdate, time_zone => 'local');
+
+ my $add_period = 0;
+ # if this is the first bill but the bill date has been set
+ # (by prorate_defer_bill), calculate from the setup date,
+ # append the setup fee to @$details, and make sure to bill for
+ # a full period after the bill date.
+
+ if ( $self->option('prorate_defer_bill', 1)
+ and !$cust_pkg->getfield('last_bill')
+ and $cust_pkg->setup )
+ {
+ $param->{'setup_fee'} = $self->calc_setup($cust_pkg, $$sdate, $details);
+ $now = DateTime->from_epoch(epoch => $cust_pkg->setup, time_zone => 'local');
+ $add_period = 1;
+ }
+
+ # DON'T sync to the existing billing day; cutoff days work differently here.
+
+ $now->truncate(to => 'day');
+ my ($end, $start) = $self->calendar_endpoints($now, @cutoff_days);
+
+ #warn "[prorate_calendar] now = ".$now->ymd.", start = ".$start->ymd.", end = ".$end->ymd."\n";
+
+ my $periods = $end->delta_days($now)->delta_days /
+ $end->delta_days($start)->delta_days;
+ if ( $periods < 1 and $add_period ) {
+ $periods++; # charge for the extra time
+ $start->add(months => $self->freq); # and push the next bill date forward
+ }
+ if ( $self->option('prorate_verbose',1) and $periods > 0 ) {
+ if ( $periods < 1 ) {
+ push @$details,
+ 'Prorated (' . $now->strftime('%b %d') .
+ ' - ' . $end->strftime('%b %d') . '): ' . $money_char .
+ sprintf('%.2f', $charge * $periods + 0.00000001);
+ } elsif ( $periods > 1 ) {
+ push @$details,
+ 'Prorated (' . $now->strftime('%b %d') .
+ ' - ' . $end->strftime('%b %d') . '): ' . $money_char .
+ sprintf('%.2f', $charge * ($periods - 1) + 0.00000001),
+
+ 'First full period: ' . $money_char . sprintf('%.2f', $charge);
+ } # else exactly one period
+ }
+
+ $$sdate = $start->epoch;
+ return sprintf('%.2f', $charge * $periods + 0.00000001);
+}
+
+sub prorate_setup {
+ my $self = shift;
+ my ($cust_pkg, $sdate) = @_;
+ my @cutoff_days = $self->cutoff_day;
+ if ( ! $cust_pkg->bill
+ and $self->option('prorate_defer_bill')
+ and @cutoff_days )
+ {
+ my $now = DateTime->from_epoch(epoch => $sdate, time_zone => 'local');
+ $now->truncate(to => 'day');
+ my ($end, $start) = $self->calendar_endpoints($now, @cutoff_days);
+ if ( $now->compare($start) == 0 ) {
+ $cust_pkg->setup($start->epoch);
+ $cust_pkg->bill($start->epoch);
+ } else {
+ $cust_pkg->bill($end->epoch);
+ }
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+=item calendar_endpoints NOW CUTOFF_DAYS
+
+Given a current date (DateTime object) and a list of cutoff day-of-year
+numbers, finds the next upcoming cutoff day (in either the current or the
+upcoming year) and the cutoff day before that, and returns them both.
+
+=cut
+
+sub calendar_endpoints {
+ my $self = shift;
+ my $now = shift;
+ my @cutoff_day = sort {$a <=> $b} @_;
+
+ my $year = $now->year;
+ my $day = $now->day_of_year;
+ # Feb 29 = 60
+ # For cutoff day purposes, it's the same day as Feb 28
+ $day-- if $now->is_leap_year and $day >= 60;
+
+ # select the first cutoff day that's after the current day
+ my $i = 0;
+ while ( $cutoff_day[$i] and $cutoff_day[$i] <= $day ) {
+ $i++;
+ }
+ # $cutoff_day[$i] is now later in the calendar than today
+ # or today is between the last cutoff day and the end of the year
+
+ my ($start, $end);
+ if ( $i == 0 ) {
+ # then today is on or before the first cutoff day
+ $start = DateTime->from_day_of_year(year => $year - 1,
+ day_of_year => $cutoff_day[-1],
+ time_zone => 'local');
+ $end = DateTime->from_day_of_year(year => $year,
+ day_of_year => $cutoff_day[0],
+ time_zone => 'local');
+ } elsif ( $i > 0 and $i < scalar(@cutoff_day) ) {
+ # today is between two cutoff days
+ $start = DateTime->from_day_of_year(year => $year,
+ day_of_year => $cutoff_day[$i - 1],
+ time_zone => 'local');
+ $end = DateTime->from_day_of_year(year => $year,
+ day_of_year => $cutoff_day[$i],
+ time_zone => 'local');
+ } else {
+ # today is after the last cutoff day
+ $start = DateTime->from_day_of_year(year => $year,
+ day_of_year => $cutoff_day[-1],
+ time_zone => 'local');
+ $end = DateTime->from_day_of_year(year => $year + 1,
+ day_of_year => $cutoff_day[0],
+ time_zone => 'local');
+ }
+ return ($end, $start);
+}
+
+1;
diff --git a/FS/FS/payinfo_Mixin.pm b/FS/FS/payinfo_Mixin.pm
index ef260cf2c..6d58a3d67 100644
--- a/FS/FS/payinfo_Mixin.pm
+++ b/FS/FS/payinfo_Mixin.pm
@@ -226,7 +226,7 @@ sub payinfo_check {
}
-=item payby_payinfo_pretty
+=item payby_payinfo_pretty [ LOCALE ]
Returns payment method and information (suitably masked, if applicable) as
a human-readable string, such as:
@@ -241,22 +241,25 @@ or
sub payby_payinfo_pretty {
my $self = shift;
+ my $locale = shift;
+ my $lh = FS::L10N->get_handle($locale);
if ( $self->payby eq 'CARD' ) {
- 'Card #'. $self->paymask;
+ $lh->maketext('Card #') . $self->paymask;
} elsif ( $self->payby eq 'CHEK' ) {
- 'E-check acct#'. $self->payinfo;
+ $lh->maketext('E-check acct#') . $self->payinfo;
} elsif ( $self->payby eq 'BILL' ) {
- 'Check #'. $self->payinfo;
+ $lh->maketext('Check #') . $self->payinfo;
} elsif ( $self->payby eq 'PREP' ) {
- 'Prepaid card #'. $self->payinfo;
+ $lh->maketext('Prepaid card #') . $self->payinfo;
} elsif ( $self->payby eq 'CASH' ) {
- 'Cash '. $self->payinfo;
+ $lh->maketext('Cash') . ' ' . $self->payinfo;
} elsif ( $self->payby eq 'WEST' ) {
- 'Western Union'; #. $self->payinfo;
+ # does Western Union localize their name?
+ $lh->maketext('Western Union');
} elsif ( $self->payby eq 'MCRD' ) {
- 'Manual credit card'; #. $self->payinfo;
+ $lh->maketext('Manual credit card');
} elsif ( $self->payby eq 'PPAL' ) {
- 'PayPal transaction#' . $self->order_number;
+ $lh->maketext('PayPal transaction#') . $self->order_number;
} else {
$self->payby. ' '. $self->payinfo;
}
diff --git a/FS/FS/sales.pm b/FS/FS/sales.pm
index 82c875a36..bdeaf1b68 100644
--- a/FS/FS/sales.pm
+++ b/FS/FS/sales.pm
@@ -131,35 +131,78 @@ sub sales_cust_main {
qsearchs( 'cust_main', { 'custnum' => $self->sales_custnum } );
}
-sub cust_bill_pkg {
+=item cust_bill_pkg START END OPTIONS
+
+Returns the package line items (see L<FS::cust_bill_pkg>) for which this
+sales person could receive commission.
+
+START and END are an optional date range to limit the results.
+
+OPTIONS may contain:
+- I<cust_main_sales>: if this is a true value, sales of packages that have no
+package sales person will be included if this is their customer sales person.
+- I<classnum>: limit to this package classnum.
+- I<paid>: limit to sales that have no unpaid balance.
+
+=cut
+
+sub cust_bill_pkg_search {
my( $self, $sdate, $edate, %search ) = @_;
my $cmp_salesnum = delete $search{'cust_main_sales'}
? ' COALESCE( cust_pkg.salesnum, cust_main.salesnum )'
: ' cust_pkg.salesnum ';
+ my $salesnum = $self->salesnum;
+ die "bad salesnum" unless $salesnum =~ /^(\d+)$/;
+ my @where = ( "$cmp_salesnum = $salesnum",
+ "sales_pkg_class.salesnum = $salesnum"
+ );
+ push @where, "cust_bill._date >= $sdate" if $sdate;
+ push @where, "cust_bill._date < $edate" if $edate;
+
my $classnum_sql = '';
if ( exists( $search{'classnum'} ) ) {
- my $classnum = $search{'classnum'};
- $classnum_sql = " AND part_pkg.classnum ". ( $classnum ? " = $classnum "
- : ' IS NULL ' );
+ my $classnum = $search{'classnum'} || '';
+ die "bad classnum" unless $classnum =~ /^(\d*)$/;
+
+ push @where,
+ "part_pkg.classnum ". ( $classnum ? " = $classnum " : ' IS NULL ' );
}
- qsearch({ 'table' => 'cust_bill_pkg',
- 'select' => 'cust_bill_pkg.*',
- 'addl_from' => ' LEFT JOIN cust_bill USING ( invnum ) '.
- ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
- ' LEFT JOIN part_pkg USING ( pkgpart ) '.
- ' LEFT JOIN cust_main ON ( cust_pkg.custnum = cust_main.custnum )',
- 'extra_sql' => ( keys %{ $search{'hashref'} }
- ? ' AND ' : 'WHERE '
- ).
- " cust_bill._date >= $sdate ".
- " AND cust_bill._date < $edate ".
- " AND $cmp_salesnum = ". $self->salesnum.
- $classnum_sql,
- #%search,
- });
+ # sales_pkg_class number-of-months limit, grr
+ # (we should be able to just check for the cust_event record from the
+ # commission credit, but the report is supposed to act as a check on that)
+ #
+ # Pg-specific, of course
+ my $setup_date = 'TO_TIMESTAMP( cust_pkg.setup )';
+ my $interval = "(sales_pkg_class.commission_duration || ' months')::interval";
+ my $charge_date = 'TO_TIMESTAMP( cust_bill._date )';
+ push @where, "CASE WHEN sales_pkg_class.commission_duration IS NOT NULL ".
+ "THEN $charge_date < $setup_date + $interval ".
+ "ELSE TRUE END";
+
+ if ( $search{'paid'} ) {
+ push @where, FS::cust_bill_pkg->owed_sql . ' <= 0.005';
+ }
+
+ my $extra_sql = "WHERE ".join(' AND ', map {"( $_ )"} @where);
+
+ { 'table' => 'cust_bill_pkg',
+ 'select' => 'cust_bill_pkg.*',
+ 'addl_from' => ' LEFT JOIN cust_bill USING ( invnum ) '.
+ ' LEFT JOIN cust_pkg USING ( pkgnum ) '.
+ ' LEFT JOIN part_pkg USING ( pkgpart ) '.
+ ' LEFT JOIN cust_main ON ( cust_pkg.custnum = cust_main.custnum )'.
+ ' JOIN sales_pkg_class ON ( '.
+ ' COALESCE( sales_pkg_class.classnum, 0) = COALESCE( part_pkg.classnum, 0) )',
+ 'extra_sql' => $extra_sql,
+ };
+}
+
+sub cust_bill_pkg {
+ my $self = shift;
+ qsearch( $self->cust_bill_pkg_search(@_) )
}
sub cust_credit {
diff --git a/FS/FS/tower.pm b/FS/FS/tower.pm
index 052836e27..5497c7217 100644
--- a/FS/FS/tower.pm
+++ b/FS/FS/tower.pm
@@ -90,6 +90,13 @@ sub check {
$self->ut_numbern('towernum')
|| $self->ut_text('towername')
|| $self->ut_enum('disabled', [ '', 'Y' ])
+ || $self->ut_coordn('latitude')
+ || $self->ut_coordn('longitude')
+ || $self->ut_enum('coord_auto', [ '', 'Y' ])
+ || $self->ut_floatn('altitude')
+ || $self->ut_floatn('height')
+ || $self->ut_floatn('veg_height')
+ || $self->ut_alphan('color')
;
return $error if $error;
diff --git a/FS/FS/tower_sector.pm b/FS/FS/tower_sector.pm
index 36051901a..70642fb18 100644
--- a/FS/FS/tower_sector.pm
+++ b/FS/FS/tower_sector.pm
@@ -108,6 +108,11 @@ sub check {
|| $self->ut_number('towernum', 'tower', 'towernum')
|| $self->ut_text('sectorname')
|| $self->ut_textn('ip_addr')
+ || $self->ut_floatn('height')
+ || $self->ut_numbern('freq_mhz')
+ || $self->ut_numbern('direction')
+ || $self->ut_numbern('width')
+ || $self->ut_floatn('range')
;
return $error if $error;
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 7a460dac3..e63583118 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -1,6 +1,5 @@
Changes
MANIFEST
-MANIFEST.SKIP
Makefile.PL
bin/freeside-addoutsource
bin/freeside-addoutsourceuser
@@ -728,3 +727,5 @@ FS/cable_provider.pm
t/cable_provider.t
FS/cust_credit_void.pm
t/cust_credit_void.t
+FS/discount_class.pm
+t/discount_class.t
diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade
index 06ec96205..7cacf105c 100755
--- a/FS/bin/freeside-upgrade
+++ b/FS/bin/freeside-upgrade
@@ -1,8 +1,8 @@
#!/usr/bin/perl -w
use strict;
-use vars qw($opt_d $opt_s $opt_q $opt_v $opt_r);
-use vars qw($DEBUG $DRY_RUN);
+use vars qw( $opt_d $opt_s $opt_q $opt_v $opt_r $opt_c );
+use vars qw( $DEBUG $DRY_RUN );
use Getopt::Std;
use DBIx::DBSchema 0.31; #0.39
use FS::UID qw(adminsuidsetup checkeuid datasrc driver_name);
@@ -17,7 +17,7 @@ my $start = time;
die "Not running uid freeside!" unless checkeuid();
-getopts("dqrs");
+getopts("dqrcs");
$DEBUG = !$opt_q;
#$DEBUG = $opt_v;
@@ -154,6 +154,18 @@ unless ( driver_name =~ /^mysql/i ) {
@statements;
}
+if ( $opt_c ) {
+
+ @statements =
+ grep { $_ !~ /^ *ALTER +TABLE +(h_)?cdr /i }
+ @statements;
+
+ @statements =
+ grep { $_ !~ /^ *CREATE +INDEX +(h_)?cdr\d+ /i }
+ @statements;
+
+}
+
if ( $DRY_RUN ) {
print
join(";\n", @statements ). ";\n";
@@ -312,7 +324,7 @@ freeside-upgrade - Upgrades database schema for new freeside verisons.
=head1 SYNOPSIS
- freeside-upgrade [ -d ] [ -r ] [ -s ] [ -q | -v ]
+ freeside-upgrade [ -d ] [ -r ] [ -c ] [ -s ] [ -q | -v ]
=head1 DESCRIPTION
@@ -337,6 +349,8 @@ Also performs other upgrade functions:
[ -r ]: Skip sqlradius updates. Useful for occassions where the sqlradius
databases may be inaccessible.
+ [ -c ]: Skip cdr and h_cdr updates.
+
[ -v ]: Run verbosely, sending debugging information to STDERR. This is the
current default.
diff --git a/FS/t/discount_class.t b/FS/t/discount_class.t
new file mode 100644
index 000000000..1ccf92e21
--- /dev/null
+++ b/FS/t/discount_class.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::discount_class;
+$loaded=1;
+print "ok 1\n";
diff --git a/eg/table_template.pm b/eg/table_template.pm
index 9c71b3adc..0a6f85156 100644
--- a/eg/table_template.pm
+++ b/eg/table_template.pm
@@ -108,7 +108,7 @@ The author forgot to customize this manpage.
=head1 SEE ALSO
-L<FS::Record>, schema.html from the base documentation.
+L<FS::Record>
=cut
diff --git a/httemplate/browse/discount.html b/httemplate/browse/discount.html
index f26c161ca..d3cf873d0 100644
--- a/httemplate/browse/discount.html
+++ b/httemplate/browse/discount.html
@@ -8,8 +8,9 @@
'count_query' => 'SELECT COUNT(*) FROM discount',
'disableable' => 1,
'disabled_statuspos' => 1,
- 'header' => [ 'Name', 'Discount', ],
+ 'header' => [ 'Name', 'Class', 'Discount', ],
'fields' => [ 'name',
+ 'classname',
'description',
],
'links' => [ $link,
diff --git a/httemplate/browse/discount_class.html b/httemplate/browse/discount_class.html
new file mode 100644
index 000000000..7f09102ec
--- /dev/null
+++ b/httemplate/browse/discount_class.html
@@ -0,0 +1,34 @@
+<% include( 'elements/browse.html',
+ 'title' => 'Discount classes',
+ 'html_init' => $html_init,
+ 'name' => 'discount classes',
+ 'disableable' => 1,
+ 'disabled_statuspos' => 1,
+ 'query' => { 'table' => 'discount_class',
+ 'hashref' => {},
+ 'order_by' => 'ORDER BY classnum',
+ },
+ 'count_query' => $count_query,
+ 'header' => $header,
+ 'fields' => $fields,
+ 'links' => $links,
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $html_init =
+ 'Discount classes define reporing classifications for discounts.<BR><BR>'.
+ qq!<A HREF="${p}edit/discount_class.html"><I>Add a discount class</I></A><BR><BR>!;
+
+my $count_query = 'SELECT COUNT(*) FROM discount_class';
+
+my $link = [ $p.'edit/discount_class.html?', 'classnum' ];
+
+my $header = [ '#', 'Class' ];
+my $fields = [ 'classnum', 'classname' ];
+my $links = [ $link, $link ];
+
+</%init>
diff --git a/httemplate/browse/tower.html b/httemplate/browse/tower.html
index 7f096a748..e2f9fd0bd 100644
--- a/httemplate/browse/tower.html
+++ b/httemplate/browse/tower.html
@@ -8,12 +8,13 @@
'count_query' => 'SELECT COUNT(*) FROM tower',
'disableable' => 1,
'disabled_statuspos' => 1,
- 'header' => [ 'Name', 'Sectors', 'Coordinates'],
+ 'header' => [ 'Name', 'Location', 'Sectors', ],
'fields' => [ $tower_sub,
- $sector_sub,
$coord_sub,
+ $sector_sub,
],
'links' => [ ],
+ 'cell_style' => [ $tagdesc_style ],
)
%>
<%init>
@@ -21,6 +22,9 @@
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+#false laziness w/ browse/part_tag.html
+my $tagdesc_style = sub { 'background-color:#'.shift->color };
+
my $num_svc_links = sub {
my ($query_string, $sectors) = @_;
return if !$sectors;
@@ -57,7 +61,12 @@ my $coord_sub = sub {
[
[
- { 'data' => "Latitude: " . $tower->latitude . "<br>Longitude: " . $tower->longitude, },
+ { 'data' => "Latitude: ". $tower->latitude.
+ "<br>Longitude: ". $tower->longitude.
+ "<br>Altitude: ". $tower->altitude.
+ "<br>Height: ". $tower->height.
+ "<br>Veg. height: ". $tower->veg_height,
+ },
{ 'data' => $coords, 'link' => "Coordinates", },
],
]
@@ -70,7 +79,7 @@ my $tower_sub = sub {
);
[ #rows
[
- { 'data' => $tower->towername, },
+ { 'data' => $tower->towername. '&nbsp;', },
{ 'data' => ' (edit) ', size => '-1',
'link' => $p.'edit/tower.html?' . $tower->towernum },
],
@@ -89,7 +98,7 @@ my $sector_sub = sub {
my $sectornum = $sector->sectornum;
[
{
- 'data' => $sector->sectorname,
+ 'data' => $sector->sectorname. '&nbsp;',
'link' => ( $sector->ip_addr ? 'http://'. $sector->ip_addr : '' ),
},
diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html
index b087943c2..2d39f969c 100644
--- a/httemplate/edit/access_user.html
+++ b/httemplate/edit/access_user.html
@@ -7,17 +7,19 @@
'last',
'first',
{ field=>'user_custnum', type=>'search-cust_main', },
+ { field=>'report_salesnum', type=>'select-sales', empty_label=>'all', },
{ field=>'disabled', type=>'checkbox', value=>'Y' },
],
'labels' => {
- 'usernum' => 'User number',
- 'username' => 'Username',
- '_password' => 'Password',
- '_password2' => 'Re-enter Password',
- 'last' => 'Last name',
- 'first' => 'First name',
- 'user_custnum' => 'Customer (optional)',
- 'disabled' => 'Disable employee',
+ 'usernum' => 'User number',
+ 'username' => 'Username',
+ '_password' => 'Password',
+ '_password2' => 'Re-enter Password',
+ 'last' => 'Last name',
+ 'first' => 'First name',
+ 'user_custnum' => 'Customer (optional)',
+ 'report_salesnum' => 'Limit commission report to sales person',
+ 'disabled' => 'Disable employee',
},
'edit_callback' => \&edit_callback,
'field_callback'=> \&field_callback,
@@ -68,8 +70,8 @@ my $check_user_custnum_search = <<END;
END
sub edit_callback {
- my ($c, $o, $f, $opt) = @_;
- $o->set('_password', '');
+ my ($cgi, $access_user, $fields_listref, $opt_hashref) = @_;
+ $access_user->_password('');
}
sub field_callback {
diff --git a/httemplate/edit/discount.html b/httemplate/edit/discount.html
index 9bcd1e724..c2853bd47 100644
--- a/httemplate/edit/discount.html
+++ b/httemplate/edit/discount.html
@@ -3,6 +3,7 @@
'table' => 'discount',
'fields' => [
'name',
+ { field => 'classnum', type => 'select-discount_class' },
{ field => 'disabled', type => 'checkbox', value=>'Y', },
# a weird kind of false laziness
# w/elements/tr-select-discount.html
@@ -27,6 +28,7 @@
'labels' => {
'discountnum' => 'Discount #',
'name' => 'Name&nbsp;',
+ 'classnum' => 'Class',
'disabled' => 'Disabled&nbsp;',
'_type' => 'Type&nbsp;',
'amount' => 'Amount&nbsp;',
diff --git a/httemplate/edit/discount_class.html b/httemplate/edit/discount_class.html
new file mode 100644
index 000000000..2bf27d996
--- /dev/null
+++ b/httemplate/edit/discount_class.html
@@ -0,0 +1,10 @@
+<% include( 'elements/class_Common.html',
+ 'name_singular' => 'Discount class',
+ 'table' => 'discount_class',
+ 'nocat' => 1,
+ 'addl_labels' => { 'classnum' => 'Class',
+ 'classname' => 'Class',
+ 'disabled' => 'Disable',
+ },
+ )
+%>
diff --git a/httemplate/edit/elements/class_Common.html b/httemplate/edit/elements/class_Common.html
index 0a0916ebc..723227f23 100644
--- a/httemplate/edit/elements/class_Common.html
+++ b/httemplate/edit/elements/class_Common.html
@@ -30,7 +30,13 @@ unless ( $opt{'nocat'} ) {
my $fields = [ 'classname',
(scalar(@category)
- ? { field=>'categorynum', type=>'select-table', 'empty_label'=>'(none)', 'table'=>$category_table, 'name_col'=>'categoryname' }
+ ? { field => 'categorynum',
+ type => 'select-table',
+ table => $category_table,
+ hashref => { 'disabled' => '' },
+ name_col => 'categoryname',
+ empty_label => '(none)',
+ }
: { field=>'categorynum', type=>'hidden' }
),
{ field=>'disabled', type=>'checkbox', value=>'Y', },
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
index 321c68545..413150858 100644
--- a/httemplate/edit/elements/svc_Common.html
+++ b/httemplate/edit/elements/svc_Common.html
@@ -101,14 +101,17 @@
my $columndef = $part_svc->part_svc_column($f->{'field'});
my $flag = $columndef->columnflag;
- if ( $flag eq 'F' ) {
+
+ if ( $flag eq 'F' ) { #fixed
$f->{'type'} = length($columndef->columnvalue)
? 'fixed'
: 'hidden';
$f->{'value'} = $columndef->columnvalue;
- } elsif ( $flag eq 'A' ) {
+
+ } elsif ( $flag eq 'A' ) { #auto assign from inventory
$f->{'type'} = 'hidden';
- } elsif ( $flag eq 'M' ) {
+
+ } elsif ( $flag eq 'M' ) { #manually assign from inventory
$f->{'type'} = 'select-inventory_item';
$f->{'empty_label'} = 'Select inventory item';
$f->{'extra_sql'} = 'WHERE ( svcnum IS NULL ' .
@@ -116,11 +119,18 @@
')';
$f->{'classnum'} = $columndef->columnvalue;
$f->{'disable_empty'} = $object->svcnum ? 1 : 0;
- } elsif ( $flag eq 'H' ) {
+
+ } elsif ( $flag eq 'H' ) { #hardware
$f->{'type'} = 'select-hardware_type';
$f->{'hashref'} = {
'classnum'=>$columndef->columnvalue
};
+
+ } elsif ( $flag eq 'S' ) { #selectable choice
+ $f->{type} = 'select';
+ $f->{options} = [ split( /\s*,\s*/,
+ $columndef->columnvalue)
+ ];
}
if ( $f->{'type'} eq 'select-svc_pbx'
diff --git a/httemplate/edit/process/discount_class.html b/httemplate/edit/process/discount_class.html
new file mode 100644
index 000000000..e7249464b
--- /dev/null
+++ b/httemplate/edit/process/discount_class.html
@@ -0,0 +1,11 @@
+<% include( 'elements/process.html',
+ 'table' => 'discount_class',
+ 'viewall_dir' => 'browse',
+ )
+%>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/tower.html b/httemplate/edit/process/tower.html
index 7353784e5..bbfc1a670 100644
--- a/httemplate/edit/process/tower.html
+++ b/httemplate/edit/process/tower.html
@@ -2,6 +2,6 @@
table => 'tower',
viewall_dir => 'browse',
process_o2m => { 'table' => 'tower_sector',
- 'fields' => [qw( sectorname ip_addr )],
+ 'fields' => [qw( sectorname ip_addr height freq_mhz direction width range )],
},
&>
diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html
index 673a2713a..00c9adde1 100644
--- a/httemplate/edit/tower.html
+++ b/httemplate/edit/tower.html
@@ -4,24 +4,32 @@
viewall_dir => 'browse',
fields => [ 'towername',
{ field=>'disabled', type=>'checkbox', value=>'Y', },
- { field => 'default_ip_addr',
+ { field=>'color', type=>'pickcolor' },
+ { field => 'default_ip_addr',
curr_value_callback => $default_ip_addr_callback },
+ 'latitude',
+ 'longitude',
+ 'altitude',
+ 'height',
+ 'veg_height',
{ field => 'sectornum',
type => 'tower_sector',
o2m_table => 'tower_sector',
m2_label => 'Sector',
m2_error_callback => $m2_error_callback,
},
- 'latitude',
- 'longitude',
],
- labels => { 'towernum' => 'Tower',
- 'towername' => 'Name',
- 'sectornum' => 'Sector',
- 'disabled' => 'Disabled',
+ labels => { 'towernum' => 'Tower',
+ 'towername' => 'Name',
+ 'sectornum' => 'Sector',
+ 'disabled' => 'Disabled',
'default_ip_addr' => 'Tower IP address',
- 'latitude' => 'Latitude',
- 'longitude' => 'Longitude',
+ 'latitude' => 'Latitude',
+ 'longitude' => 'Longitude',
+ 'altitude' => 'Altitude',
+ 'height' => 'Height',
+ 'veg_height' => 'Vegetation height',
+ 'color' => 'Color',
},
&>
<%init>
@@ -29,7 +37,7 @@
my $m2_error_callback = sub { # reconstruct the list
my ($cgi, $object) = @_;
- my @fields = qw(sectorname ip_addr);
+ my @fields = qw( sectorname ip_addr height freq_mhz direction width range );
map {
my $k = $_;
new FS::tower_sector {
diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
index 3d5177612..8abce058e 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -31,7 +31,7 @@
% 'phonetypenum' => $1,
% });
% if ( $contact_phone ) {
-% $value = $contact_phone->phonenum;
+% $value = $contact_phone->phonenum_pretty;
% $value .= 'x'.$contact_phone->extension
% if $contact_phone->extension;
% $value = '+'. $contact_phone->countrycode. " $value"
diff --git a/httemplate/elements/input-text.html b/httemplate/elements/input-text.html
index 827941501..18272ebe2 100644
--- a/httemplate/elements/input-text.html
+++ b/httemplate/elements/input-text.html
@@ -5,6 +5,7 @@
<% $size %>
<% $maxlength %>
<% $style %>
+ <% $opt{autocomplete} ? 'autocomplete="off"' : '' %>
<% $opt{disabled} %>
<% $onchange %>
><% $opt{'postfix'} %>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 8cb967518..b8575238d 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -564,6 +564,7 @@ if ( $curuser->access_right('Configuration') ) {
#eo package grouping sub-menu
$config_pkg{'Discounts'} = [ $fsurl.'browse/discount.html', '' ];
+ $config_pkg{'Discount classes'} = [ $fsurl.'browse/discount_class.html', '' ];
$config_pkg{'Cancel/Suspend Reasons'} = [ \%config_pkg_reason, '' ];
}
diff --git a/httemplate/elements/select-discount_class.html b/httemplate/elements/select-discount_class.html
new file mode 100644
index 000000000..41a27c53a
--- /dev/null
+++ b/httemplate/elements/select-discount_class.html
@@ -0,0 +1,18 @@
+<% include( '/elements/select-table.html',
+ 'table' => 'discount_class',
+ 'name_col' => 'classname',
+ 'value' => $classnum,
+ 'empty_label' => '(none)',
+ 'hashref' => { 'disabled' => '' },
+ %opt,
+ )
+%>
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'records'} = delete $opt{'discount_class'}
+ if $opt{'discount_class'};
+
+</%init>
diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js
index ad774d897..debd9e7c4 100644
--- a/httemplate/elements/standardize_locations.js
+++ b/httemplate/elements/standardize_locations.js
@@ -202,23 +202,32 @@ function post_standardization() {
var cf = document.<% $formname %>;
- if ( new String(cf.elements['<% $taxpre %>zip'].value).length < 10 )
+ var prefix = '<% $taxpre %>';
+ // fix edge case with cust_main
+ if ( cf.elements['same']
+ && cf.elements['same'].checked
+ && prefix == 'ship_' ) {
+
+ prefix = 'bill_';
+ }
+
+ if ( new String(cf.elements[prefix + 'zip'].value).length < 10 )
{
- var country_el = cf.elements['<% $taxpre %>country'];
+ var country_el = cf.elements[prefix + 'country'];
var country = country_el.options[ country_el.selectedIndex ].value;
- var geocode = cf.elements['<% $taxpre %>geocode'].value;
+ var geocode = cf.elements[prefix + 'geocode'].value;
if ( country == 'CA' || country == 'US' ) {
- var state_el = cf.elements['<% $taxpre %>state'];
+ var state_el = cf.elements[prefix + 'state'];
var state = state_el.options[ state_el.selectedIndex ].value;
var url = "<% $p %>/misc/choose_tax_location.html" +
"?data_vendor=cch-zip" +
- ";city=" + cf.elements['<% $taxpre %>city'].value +
+ ";city=" + cf.elements[prefix + 'city'].value +
";state=" + state +
- ";zip=" + cf.elements['<% $taxpre %>zip'].value +
+ ";zip=" + cf.elements[prefix + 'zip'].value +
";country=" + country +
";geocode=" + geocode +
";formname=" + '<% $formname %>' +
@@ -229,14 +238,14 @@ function post_standardization() {
} else {
- cf.elements['<% $taxpre %>geocode'].value = 'DEFAULT';
+ cf.elements[prefix + 'geocode'].value = 'DEFAULT';
<% $post_geocode %>;
}
} else {
- cf.elements['<% $taxpre %>geocode'].value = '';
+ cf.elements[prefix + 'geocode'].value = '';
<% $post_geocode %>;
}
@@ -255,13 +264,19 @@ function update_geocode() {
set_geocode = function (what) {
var cf = document.<% $formname %>;
+ var prefix = '<% $taxpre %>';
+ if ( cf.elements['same']
+ && cf.elements['same'].checked
+ && prefix == 'ship_' ) {
+ prefix = 'bill_';
+ }
//alert(what.options[what.selectedIndex].value);
var argsHash = eval('(' + what.options[what.selectedIndex].value + ')');
- cf.elements['<% $taxpre %>city'].value = argsHash['city'];
- setselect(cf.elements['<% $taxpre %>state'], argsHash['state']);
- cf.elements['<% $taxpre %>zip'].value = argsHash['zip'];
- cf.elements['<% $taxpre %>geocode'].value = argsHash['geocode'];
+ cf.elements[prefix + 'city'].value = argsHash['city'];
+ setselect(cf.elements[prefix + 'state'], argsHash['state']);
+ cf.elements[prefix + 'zip'].value = argsHash['zip'];
+ cf.elements[prefix + 'geocode'].value = argsHash['geocode'];
<% $post_geocode %>;
}
diff --git a/httemplate/elements/tower_sector.html b/httemplate/elements/tower_sector.html
index a8bbbc5d1..406895ed9 100644
--- a/httemplate/elements/tower_sector.html
+++ b/httemplate/elements/tower_sector.html
@@ -53,6 +53,11 @@ my %size = ( 'title' => 12 );
tie my %label, 'Tie::IxHash',
'sectorname' => 'Name',
'ip_addr' => 'IP Address',
+ 'height' => 'Height',
+ 'freq_mhz' => 'Freq. (MHz)',
+ 'direction' => 'Direction', # or a button to set these to 0 for omni
+ 'width' => 'Width', #
+ 'range' => 'Range',
;
my @fields = keys %label;
diff --git a/httemplate/elements/tr-input-beginning_ending.html b/httemplate/elements/tr-input-beginning_ending.html
index ffc903875..be6e03c6f 100644
--- a/httemplate/elements/tr-input-beginning_ending.html
+++ b/httemplate/elements/tr-input-beginning_ending.html
@@ -7,7 +7,7 @@
<TR>
<TD ALIGN="right">From date: </TD>
- <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>beginning" ID="<% $opt{prefix} %>beginning_text" VALUE="<% $from %>" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>beginning_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>beginning_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD>
+ <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>beginning" ID="<% $opt{prefix} %>beginning_text" VALUE="<% $from ? time2str($date_format, $from) : '' %>" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>beginning_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>beginning_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD>
<SCRIPT TYPE="text/javascript">
Calendar.setup({
inputField: "<% $opt{prefix} %>beginning_text",
@@ -26,7 +26,7 @@
% }
<TD ALIGN="right">To date: </TD>
- <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>ending" ID="<% $opt{prefix} %>ending_text" VALUE="<% $to %>" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>ending_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>ending_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD>
+ <TD><INPUT TYPE="text" NAME="<% $opt{prefix} %>ending" ID="<% $opt{prefix} %>ending_text" VALUE="<% $to ? time2str($date_format, $to) : '' %>" SIZE=<%$size%> MAXLENGTH=<%$maxlength%>> <IMG SRC="<%$fsurl%>images/calendar.png" ID="<% $opt{prefix} %>ending_button" STYLE="cursor: pointer" TITLE="Select date"><IMG SRC="<%$fsurl%>images/calendar-disabled.png" ID="<% $opt{prefix} %>ending_disabled" STYLE="display:none"><BR><i>m/d/y<% $time_hint %></i></TD>
<SCRIPT TYPE="text/javascript">
Calendar.setup({
inputField: "<% $opt{prefix} %>ending_text",
diff --git a/httemplate/elements/tr-input-mask.html b/httemplate/elements/tr-input-mask.html
index 33725b9a5..922f22f4b 100644
--- a/httemplate/elements/tr-input-mask.html
+++ b/httemplate/elements/tr-input-mask.html
@@ -5,6 +5,7 @@
% }
<& /elements/tr-input-text.html, id => $id, @_ &>
<script type="text/javascript">
+<&| /elements/onload.js &>
MaskedInput({
elm: document.getElementById('<%$id%>'),
format: '<% $opt{format} %>',
@@ -12,7 +13,41 @@ MaskedInput({
<% $opt{typeon} ? "typeon: '$opt{typeon}'," : '' %>
});
document.getElementById('<%$id%>').value = <% $value |js_string %>;
+% if ( $clipboard_hack ) {
+var t = document.getElementById('<% $id %>');
+var container = document.getElementById('<%$id%>_clipboard');
+var KeyHandlerDown = t.onkeydown
+t.onkeydown = function(e) {
+ // intercept ctrl-c and ctrl-x
+ // and cmd-c and cmd-x on mac
+ // when text is selected
+ if ( ( e.ctrlKey || e.metaKey ) ) {
+ // do the dance
+ var separators = /[\\/:-]/g;
+ var s = t.value.substr(t.selectionStart, t.selectionEnd);
+ if ( s ) {
+ container.value = s.replace(separators, '');
+ container.previous = t;
+ container.focus();
+ container.select();
+ return true;
+ }
+ }
+ return KeyHandlerDown.call(t, e);
+};
+container.onkeyup = function(e) {
+ if ( container.previous ) {
+ setTimeout(function() {
+ container.previous.value = container.value;
+ container.previous.focus();
+ }, 10);
+ }
+ return true;
+}
+% } # clipboard hack
+</&>
</script>
+<textarea id="<%$id%>_clipboard" style="opacity:0"></textarea>
<%shared>
my $init = 0;
</%shared>
@@ -21,6 +56,8 @@ my %opt = @_;
# must have a DOM id
my $id = $opt{id} || sprintf('input%04d',int(rand(10000)));
my $value = length($opt{curr_value}) ? $opt{curr_value} : $opt{value} || '';
+
+my $clipboard_hack = $FS::CurrentUser::CurrentUser->option('enable_mask_clipboard_hack');
</%init>
<%doc>
Set up a text input field with input masking.
diff --git a/httemplate/elements/tr-password.html b/httemplate/elements/tr-password.html
index d19c4ed0e..8ad303c67 100644
--- a/httemplate/elements/tr-password.html
+++ b/httemplate/elements/tr-password.html
@@ -1,3 +1,4 @@
<& tr-input-text.html, @_,
- 'type' => 'password',
+ 'type' => 'password',
+ 'autocomplete' => 1,
&>
diff --git a/httemplate/elements/tr-select-discount_class.html b/httemplate/elements/tr-select-discount_class.html
new file mode 100644
index 000000000..5489fe6e8
--- /dev/null
+++ b/httemplate/elements/tr-select-discount_class.html
@@ -0,0 +1,27 @@
+% if ( scalar(@{ $opt{'discount_class'} }) == 0 ) {
+
+ <INPUT TYPE="hidden" NAME="<% $opt{'element_name'} || $opt{'field'} || 'classnum' %>" VALUE="">
+
+% } else {
+
+ <TR>
+ <TD ALIGN="right"><% $opt{'label'} || 'Discount class' %></TD>
+ <TD>
+ <% include( '/elements/select-discount_class.html',
+ 'curr_value' => $classnum,
+ %opt
+ )
+ %>
+ </TD>
+ </TR>
+
+% }
+
+<%init>
+
+my %opt = @_;
+my $classnum = $opt{'curr_value'} || $opt{'value'};
+
+$opt{'discount_class'} ||= [ qsearch( 'discount_class', { disabled=>'' } ) ];
+
+</%init>
diff --git a/httemplate/graph/report_cust_bill_pkg_discount.html b/httemplate/graph/report_cust_bill_pkg_discount.html
index c599e71f1..6de84f80b 100644
--- a/httemplate/graph/report_cust_bill_pkg_discount.html
+++ b/httemplate/graph/report_cust_bill_pkg_discount.html
@@ -1,28 +1,35 @@
-<% include('/elements/header.html', 'Discount Report' ) %>
+<& /elements/header.html', 'Discount Report' &>
<FORM ACTION="cust_bill_pkg_discount.html" METHOD="GET">
<TABLE>
-<% include('/elements/tr-select-from_to.html' ) %>
+<!--
+ <& /elements/tr-select-discount_class.html,
+ 'field' => 'discount_classnum',
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(none)'
+ &>
+-->
-<% include('/elements/tr-select-agent.html',
- 'label' => 'For agent: ',
- 'disable_empty' => 0,
- )
-%>
+ <& /elements/tr-select-from_to.html &>
-%# anything about line items, discounts or packages really
-%# otaker?
-%# package class?
-%# discount picker? (discount classes and categories? eek!)
+ <& /elements/tr-select-agent.html,
+ 'label' => 'For agent: ',
+ 'disable_empty' => 0,
+ &>
+
+% # anything about line items, discounts or packages really
+% # otaker?
+% # package class?
+% # discount picker? (discount classes and categories? haha yup!)
</TABLE>
<BR><INPUT TYPE="submit" VALUE="Display">
</FORM>
-<% include('/elements/footer.html') %>
+<& /elements/footer.html &>
<%init>
die "access denied"
diff --git a/httemplate/graph/report_money_time_daily.html b/httemplate/graph/report_money_time_daily.html
index a436d0879..e80f5862c 100644
--- a/httemplate/graph/report_money_time_daily.html
+++ b/httemplate/graph/report_money_time_daily.html
@@ -4,12 +4,11 @@
<TABLE>
-<% include( '/elements/tr-input-beginning_ending.html',
+<& /elements/tr-input-beginning_ending.html,
'datesrequired' => 1,
- 'from' => time2str('%m/%d/%Y',$from),
- 'to' => time2str('%m/%d/%Y',time),
- )
-%>
+ 'from' => $from,
+ 'to' => time,
+&>
<% include('/elements/tr-select-agent.html',
'label' => 'For agent: ',
diff --git a/httemplate/loginout/login.html b/httemplate/loginout/login.html
index d06d0a8fc..3c6e2ae9f 100644
--- a/httemplate/loginout/login.html
+++ b/httemplate/loginout/login.html
@@ -14,7 +14,7 @@
%# <FORM METHOD="POST" ACTION="<%$url_string%>loginout/login">
<FORM METHOD="POST" ACTION="/login">
- <INPUT TYPE="hidden" NAME="destination" VALUE="<% $r->prev->uri %>">
+ <INPUT TYPE="hidden" NAME="destination" VALUE="<% $r->prev->unparsed_uri %>">
<TABLE CELLSPACING=0 CELLPADDING=4 BGCOLOR="#cccccc">
<TR>
diff --git a/httemplate/misc/confirm-address_standardize.html b/httemplate/misc/confirm-address_standardize.html
index 33d22195b..2eae011c4 100644
--- a/httemplate/misc/confirm-address_standardize.html
+++ b/httemplate/misc/confirm-address_standardize.html
@@ -19,52 +19,57 @@ Confirm address standardization
% }
% for my $pre (@prefixes) {
% my $name = $pre eq 'bill_' ? 'billing' : 'service';
-% if ( $new{$pre.'addr_clean'} ) {
+% if ( $new{$pre.'error'} ) {
<TR>
<TH>Entered <%$name%> address</TH>
- <TH>Standardized <%$name%> address</TH>
</TR>
- <TR>
% if ( $old{$pre.'company'} ) {
<TR>
<TD><% $old{$pre.'company'} %></TD>
- <TD><% $new{$pre.'company'} %></TD>
</TR>
% }
<TR>
<TD><% $old{$pre.'address1'} %></TD>
- <TD><% $new{$pre.'address1'} %></TD>
+ <TD ROWSPAN=3><FONT COLOR="#ff0000"><B><% $new{$pre.'error'} %></B></FONT></TD>
</TR>
<TR>
<TD><% $old{$pre.'address2'} %></TD>
- <TD><% $new{$pre.'address2'} %></TD>
</TR>
<TR>
<TD><% $old{$pre.'city'} %>, <% $old{$pre.'state'} %> <% $old{$pre.'zip'} %></TD>
- <TD><% $new{$pre.'city'} %>, <% $new{$pre.'state'} %> <% $new{$pre.'zip'} %></TD>
</TR>
-
-% } # if addr_clean
-% elsif ( $new{$pre.'error'} ) {
+% } else { # not an error
<TR>
<TH>Entered <%$name%> address</TH>
+ <TH>Standardized <%$name%> address</TH>
+ </TR>
+% if ( !$new{$pre.'addr_clean'} ) {
+ <TR>
+ <TD></TD>
+ <TH STYLE="font-size:smaller;color:#ff0000">(unverified)</TH>
</TR>
+% }
+ <TR>
% if ( $old{$pre.'company'} ) {
<TR>
<TD><% $old{$pre.'company'} %></TD>
+ <TD><% $new{$pre.'company'} %></TD>
</TR>
% }
<TR>
<TD><% $old{$pre.'address1'} %></TD>
- <TD ROWSPAN=3><FONT COLOR="#ff0000"><B><% $new{$pre.'error'} %></B></FONT></TD>
+ <TD><% $new{$pre.'address1'} %></TD>
</TR>
<TR>
<TD><% $old{$pre.'address2'} %></TD>
+ <TD><% $new{$pre.'address2'} %></TD>
</TR>
<TR>
<TD><% $old{$pre.'city'} %>, <% $old{$pre.'state'} %> <% $old{$pre.'zip'} %></TD>
+ <TD><% $new{$pre.'city'} %>, <% $new{$pre.'state'} %> <% $new{$pre.'zip'} %></TD>
</TR>
-% } #if error
+
+% } # if error
% } # for $pre
%# only do this part if address standardization provided a censustract
@@ -88,7 +93,7 @@ Confirm address standardization
</TR>
% } #if censustract
-% if ( $new{bill_error} or $new{ship_error} ) {
+% if ( grep {$new{$_.'error'}} @prefixes ) {
<TR>
<TD ALIGN="center">
<BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address();">
@@ -99,8 +104,7 @@ Confirm address standardization
<IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
</BUTTON></TD>
</TR>
-% }
-% else {
+% } else {
<TR>
<TD ALIGN="center">
<BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address();">
diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html
index 962ee51b6..7edf8921c 100644
--- a/httemplate/pref/pref-process.html
+++ b/httemplate/pref/pref-process.html
@@ -54,6 +54,7 @@ unless ( $error ) { # if ($access_user) {
spreadsheet_format mobile_menu
enable_fuzzy_on_exact
disable_html_editor disable_enter_submit_onetimecharge
+ enable_mask_clipboard_hack
email_address
snom-ip snom-username snom-password
vonage-fromnumber vonage-username vonage-password
diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html
index d2b8835e7..ccfeecd77 100644
--- a/httemplate/pref/pref.html
+++ b/httemplate/pref/pref.html
@@ -137,6 +137,13 @@ Interface
</TD>
</TR>
+ <TR>
+ <TH ALIGN="right">Don't copy MAC address delimiters to clipboard</TH>
+ <TD ALIGN="left" COLSPAN=2>
+ <INPUT TYPE="checkbox" NAME="enable_mask_clipboard_hack" VALUE="1" <% $curuser->option('enable_mask_clipboard_hack') ? 'CHECKED' : '' %>>
+ </TD>
+ </TR>
+
</TABLE>
<BR>
diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi
index 4c5e90f6a..d86641de3 100644
--- a/httemplate/search/cust_bill_pkg.cgi
+++ b/httemplate/search/cust_bill_pkg.cgi
@@ -144,8 +144,8 @@ Filtering parameters:
- taxnum: Limit to items whose tax definition matches this taxnum.
With "nottax" that means items that are subject to that tax;
- with "istax" it's the tax charges themselves. Can be specified
- more than once to include multiple taxes.
+ with "istax" it's the tax charges themselves. Can be a comma-separated
+ list to include multiple taxes.
- country, state, county, city: Limit to items whose tax location
matches these fields. If "nottax" it's the tax location of the package;
@@ -283,24 +283,7 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.agentnum = $1";
}
-# salesnum
-if ( $cgi->param('salesnum') =~ /^(\d+)$/ ) {
-
- my $salesnum = $1;
-
- my $cmp_salesnum = $cgi->param('cust_main_sales')
- ? ' COALESCE( cust_pkg.salesnum, cust_main.salesnum )'
- : ' cust_pkg.salesnum ';
-
- push @where, "$cmp_salesnum = $salesnum";
-
- #because currently we're called from sales_pkg_class.html for a specific
- # class (or empty class) but not for all classes
- #will have to do something to distinguish if someone wants the sales report
- # (report_cust_bill_pkg.html) to have a sales person dropdown
- $cgi->param('classnum', 0) unless $cgi->param('classnum');
-}
-
+# salesnum--see below
# refnum
if ( $cgi->param('refnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.refnum = $1";
@@ -350,7 +333,7 @@ if ( $cgi->param('nottax') ) {
# 0: empty class
# N: classnum
if ( grep { $_ eq 'classnum' } $cgi->param ) {
- my @classnums = grep /^\d+$/, $cgi->param('classnum');
+ my @classnums = grep /^\d*$/, $cgi->param('classnum');
push @where, "COALESCE($part_pkg.classnum, 0) IN ( ".
join(',', @classnums ).
' )'
@@ -390,11 +373,8 @@ if ( $cgi->param('nottax') ) {
# we don't handle exempt_monthly here
if ( $cgi->param('taxname') ) { # specific taxname
- push @tax_where, 'cust_main_county.taxname = '.
+ push @tax_where, "COALESCE(cust_main_county.taxname, 'Tax') = ".
dbh->quote($cgi->param('taxname'));
- } elsif ( $cgi->param('taxnameNULL') ) {
- push @tax_where, 'cust_main_county.taxname IS NULL OR '.
- 'cust_main_county.taxname = \'Tax\'';
}
# country:state:county:city:district (may be repeated)
@@ -422,12 +402,8 @@ if ( $cgi->param('nottax') ) {
}
# specific taxnums
- if ( $cgi->param('taxnum') ) {
- my $taxnum_in = join(',',
- grep /^\d+$/, $cgi->param('taxnum')
- );
- push @tax_where, "cust_main_county.taxnum IN ($taxnum_in)"
- if $taxnum_in;
+ if ( $cgi->param('taxnum') =~ /^([\d,]+)$/) {
+ push @tax_where, "cust_main_county.taxnum IN ($1)";
}
# If we're showing exempt items, we need to find those with
@@ -457,22 +433,16 @@ if ( $cgi->param('nottax') ) {
USING (billpkgnum)";
}
- if ( @tax_where or $cgi->param('taxable') or $cgi->param('out') ) {
- # process tax restrictions
- unshift @tax_where,
- 'cust_main_county.tax > 0';
+ # process tax restrictions
+ unshift @tax_where,
+ 'cust_bill_pkg_tax_location.taxable_billpkgnum = cust_bill_pkg.billpkgnum',
+ 'cust_main_county.tax > 0';
- my $tax_sub = "SELECT invnum, cust_bill_pkg_tax_location.pkgnum
+ my $tax_sub = "SELECT 1
FROM cust_bill_pkg_tax_location
JOIN cust_bill_pkg AS tax_item USING (billpkgnum)
JOIN cust_main_county USING (taxnum)
- WHERE ". join(' AND ', @tax_where).
- " GROUP BY invnum, cust_bill_pkg_tax_location.pkgnum";
-
- $join_pkg .= " LEFT JOIN ($tax_sub) AS item_tax
- ON (item_tax.invnum = cust_bill_pkg.invnum AND
- item_tax.pkgnum = cust_bill_pkg.pkgnum)";
- }
+ WHERE ". join(' AND ', @tax_where);
# now do something with that
if ( @exempt_where ) {
@@ -489,23 +459,17 @@ if ( $cgi->param('nottax') ) {
my $taxable = 'cust_bill_pkg.setup + cust_bill_pkg.recur '.
'- COALESCE(item_exempt.exempt_amount, 0)';
- push @where, 'item_tax.invnum IS NOT NULL';
push @select, "($taxable) AS taxable_amount";
+ push @where, "EXISTS($tax_sub)";
push @peritem, 'taxable_amount';
push @peritem_desc, 'Taxable';
push @total, "SUM($taxable)";
push @total_desc, "$money_char%.2f taxable";
- } elsif ( $cgi->param('out') ) {
-
- push @where, 'item_tax.invnum IS NULL',
- 'item_exempt.billpkgnum IS NULL';
-
} elsif ( @tax_where ) {
# union of taxable + all exempt_ cases
- push @where,
- '(item_tax.invnum IS NOT NULL OR item_exempt.billpkgnum IS NOT NULL)';
+ push @where, "(EXISTS($tax_sub) OR item_exempt.billpkgnum IS NOT NULL)";
}
@@ -572,6 +536,21 @@ if ( $cgi->param('nottax') ) {
# don't double-count the components of consolidated taxes
$total[0] = 'COUNT(DISTINCT cust_bill_pkg.billpkgnum)';
$total[1] = 'SUM(cust_bill_pkg_tax_location.amount)';
+
+ # package classnum
+ if ( grep { $_ eq 'classnum' } $cgi->param ) {
+ my @classnums = grep /^\d*$/, $cgi->param('classnum');
+ $join_pkg .= '
+ JOIN cust_pkg AS taxed_pkg
+ ON (cust_bill_pkg_tax_location.pkgnum = taxed_pkg.pkgnum)
+ JOIN part_pkg AS taxed_part_pkg
+ ON (taxed_pkg.pkgpart = taxed_part_pkg.pkgpart)
+ ';
+ push @where, "COALESCE(taxed_part_pkg.classnum, 0) IN ( ".
+ join(',', @classnums ).
+ ' )'
+ if @classnums;
+ }
}
# taxclass
@@ -589,25 +568,8 @@ if ( $cgi->param('nottax') ) {
}
# specific taxnums
- if ( $cgi->param('taxnum') ) {
- my $taxnum_in = join(',',
- grep /^\d+$/, $cgi->param('taxnum')
- );
- push @where, "cust_main_county.taxnum IN ($taxnum_in)"
- if $taxnum_in;
- }
-
- # report group (itemdesc)
- if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) {
- my ( $group_op, $group_value ) = ( $1, $2 );
- if ( $group_op eq '=' ) {
- #push @where, 'itemdesc LIKE '. dbh->quote($group_value.'%');
- push @where, 'itemdesc = '. dbh->quote($group_value);
- } elsif ( $group_op eq '!=' ) {
- push @where, '( itemdesc != '. dbh->quote($group_value) .' OR itemdesc IS NULL )';
- } else {
- die "guru meditation #00de: group_op $group_op\n";
- }
+ if ( $cgi->param('taxnum') =~ /^([\d,]+)$/) {
+ push @where, "cust_main_county.taxnum IN ($1)";
}
# itemdesc, for some reason
@@ -704,6 +666,28 @@ if ( $cgi->param('credit') ) {
push @select, 'cust_main.custnum', FS::UI::Web::cust_sql_fields();
+#salesnum
+if ( $cgi->param('salesnum') =~ /^(\d+)$/ ) {
+
+ my $salesnum = $1;
+ my $sales = FS::sales->by_key($salesnum)
+ or die "salesnum $salesnum not found";
+
+ my $subsearch = $sales->cust_bill_pkg_search('', '',
+ 'cust_main_sales' => ($cgi->param('cust_main_sales') ? 1 : 0),
+ 'paid' => ($cgi->param('paid') ? 1 : 0),
+ 'classnum' => scalar($cgi->param('classnum'))
+ );
+ $join_pkg .= " JOIN sales_pkg_class ON ( COALESCE(sales_pkg_class.classnum, 0) = COALESCE( part_pkg.classnum, 0) )";
+
+ my $extra_sql = $subsearch->{extra_sql};
+ $extra_sql =~ s/^WHERE//;
+ push @where, $extra_sql;
+
+ $cgi->param('classnum', 0) unless $cgi->param('classnum');
+}
+
+
my $where = join(' AND ', @where);
$where &&= "WHERE $where";
diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html
index f598341a0..6da5787a0 100644
--- a/httemplate/search/cust_bill_pkg_discount.html
+++ b/httemplate/search/cust_bill_pkg_discount.html
@@ -7,6 +7,7 @@
'header' => [
#'#',
'Discount',
+ 'Class',
'Amount',
'Months',
'Package',
@@ -17,6 +18,7 @@
'fields' => [
#'billpkgdiscountnum',
sub { $_[0]->cust_pkg_discount->discount->description },
+ sub { $_[0]->cust_pkg_discount->discount->classname },
sub { sprintf($money_char.'%.2f', shift->amount ) },
sub { my $m = shift->months;
$m =~ /\./ ? sprintf('%.2f', $m) : $m;
@@ -28,6 +30,7 @@
],
'sort_fields' => [
'',
+ '',
'amount',
'months',
'pkg',
@@ -40,6 +43,7 @@
'',
'',
'',
+ '',
$ilink,
$ilink,
( map { $_ ne 'Cust. Status' ? $clink : '' }
@@ -47,7 +51,7 @@
),
],
#'align' => 'rlrrrc'.FS::UI::Web::cust_aligns(),
- 'align' => 'lrrlrr'.FS::UI::Web::cust_aligns(),
+ 'align' => 'lcrrlrr'.FS::UI::Web::cust_aligns(),
'color' => [
#'',
'',
@@ -56,6 +60,7 @@
'',
'',
'',
+ '',
FS::UI::Web::cust_colors(),
],
'style' => [
@@ -66,6 +71,7 @@
'',
'',
'',
+ '',
FS::UI::Web::cust_styles(),
],
@@ -98,7 +104,51 @@ if ( $cgi->param('usernum') =~ /^(\d+)$/ ) {
push @where, "cust_pkg_discount.usernum = $1";
}
-# #classnum
+# (discount) classnum
+my $join_discount = '';
+#false laziness w/cust_pkg_discount.html and cust_pkg.pm::search
+if ( grep { $_ eq 'discount_classnum' } $cgi->param ) {
+
+# my @classnum = ();
+# if ( ref($params->{'discount_classnum'}) ) {
+#
+# if ( ref($params->{'discount_classnum'}) eq 'HASH' ) {
+# @classnum = grep $params->{'discount_classnum'}{$_}, keys %{ $params->{'discount_classnum'} };
+# } elsif ( ref($params->{'discount_classnum'}) eq 'ARRAY' ) {
+# @classnum = @{ $params->{'discount_classnum'} };
+# } else {
+# die 'unhandled discount_classnum ref '. $params->{'discount_classnum'};
+# }
+#
+#
+# } elsif ( $params->{'discount_classnum'} =~ /^(\d*)$/ && $1 ne '0' ) {
+# @classnum = ( $1 );
+# }
+#
+# if ( @classnum ) {
+
+ if ( $cgi->param('discount_classnum') =~ /^(\d*)$/ && $1 ne '0' ) {
+ my @classnum = ( $1 );
+
+ $join_discount = 'LEFT JOIN discount USING (discountnum)';
+
+ my @c_where = ();
+ my @nums = grep $_, @classnum;
+ push @c_where, 'discount.classnum IN ('. join(',',@nums). ')' if @nums;
+ my $null = scalar( grep { $_ eq '' } @classnum );
+ push @c_where, 'discount.classnum IS NULL' if $null;
+
+ if ( scalar(@c_where) == 1 ) {
+ push @where, @c_where;
+ } elsif ( @c_where ) {
+ push @where, ' ( '. join(' OR ', @c_where). ' ) ';
+ }
+
+ }
+
+}
+
+# #(package) classnum
# # not specified: all classes
# # 0: empty class
# # N: classnum
@@ -121,7 +171,7 @@ if ( $cgi->param('usernum') =~ /^(\d+)$/ ) {
# }
# }
-my $count_query = "SELECT COUNT(*), SUM(amount)";
+my $count_query = "SELECT COUNT(*), SUM(cust_bill_pkg_discount.amount)";
my $join_cust_pkg_discount =
'LEFT JOIN cust_pkg_discount USING (pkgdiscountnum)';
@@ -137,11 +187,11 @@ my $join_pkg =
#LEFT JOIN part_pkg AS override
# ON pkgpart_override = override.pkgpart ';
+my $join = "$join_cust_pkg_discount $join_discount $join_pkg $join_cust";
+
my $where = ' WHERE '. join(' AND ', @where);
-$count_query .=
- " FROM cust_bill_pkg_discount $join_cust_pkg_discount $join_pkg $join_cust ".
- $where;
+$count_query .= " FROM cust_bill_pkg_discount $join $where";
my @select = (
'cust_bill_pkg_discount.*',
@@ -155,7 +205,7 @@ push @select, 'cust_main.custnum',
my $query = {
'table' => 'cust_bill_pkg_discount',
- 'addl_from' => "$join_cust_pkg_discount $join_pkg $join_cust",
+ 'addl_from' => $join,
'hashref' => {},
'select' => join(', ', @select ),
'extra_sql' => $where,
diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi
index 995779a46..54bfa00bf 100755
--- a/httemplate/search/cust_pkg.cgi
+++ b/httemplate/search/cust_pkg.cgi
@@ -175,6 +175,10 @@ for my $param (qw( censustract censustract2 )) {
if grep { $_ eq $param } $cgi->param;
}
+#location flags (checkboxes)
+my @loc = grep /^\w+$/, $cgi->param('loc');
+$search_hash{"location_$_"} = 1 foreach @loc;
+
my $report_option = $cgi->param('report_option');
$search_hash{report_option} = $report_option if $report_option;
diff --git a/httemplate/search/cust_pkg_discount.html b/httemplate/search/cust_pkg_discount.html
index 23af1802e..f0c7447a5 100644
--- a/httemplate/search/cust_pkg_discount.html
+++ b/httemplate/search/cust_pkg_discount.html
@@ -6,6 +6,7 @@
#'redirect' => $link,
'header' => [ 'Status',
'Discount',
+ 'Class',
'Months used',
'Employee',
'Package',
@@ -16,6 +17,7 @@
'fields' => [
sub { ucfirst( shift->status ) },
sub { shift->discount->description },
+ sub { shift->discount->classname },
sub { my $m = shift->months_used;
$m =~ /\./ ? sprintf('%.2f',$m) : $m;
},
@@ -29,17 +31,19 @@
'',
'',
'',
+ '',
( map { $_ ne 'Cust. Status' ? $clink : ''}
FS::UI::Web::cust_header()
),
],
- 'align' => 'clrll'. FS::UI::Web::cust_aligns(),
+ 'align' => 'clcrll'. FS::UI::Web::cust_aligns(),
'color' => [
'',
'',
'',
'',
'',
+ '',
FS::UI::Web::cust_colors(),
],
'style' => [
@@ -48,6 +52,7 @@
'',
'',
'',
+ '',
FS::UI::Web::cust_styles(),
],
@@ -78,6 +83,47 @@ if ( $cgi->param('status') eq 'active' ) {
"; #XXX also end date
}
+#classnum
+#false laziness w/cust_pkg.pm::search
+if ( grep { $_ eq 'classnum' } $cgi->param ) {
+
+# my @classnum = ();
+# if ( ref($params->{'classnum'}) ) {
+#
+# if ( ref($params->{'classnum'}) eq 'HASH' ) {
+# @classnum = grep $params->{'classnum'}{$_}, keys %{ $params->{'classnum'} };
+# } elsif ( ref($params->{'classnum'}) eq 'ARRAY' ) {
+# @classnum = @{ $params->{'classnum'} };
+# } else {
+# die 'unhandled classnum ref '. $params->{'classnum'};
+# }
+#
+#
+# } elsif ( $params->{'classnum'} =~ /^(\d*)$/ && $1 ne '0' ) {
+# @classnum = ( $1 );
+# }
+#
+# if ( @classnum ) {
+
+ if ( $cgi->param('classnum') =~ /^(\d*)$/ && $1 ne '0' ) {
+ my @classnum = ( $1 );
+
+ my @c_where = ();
+ my @nums = grep $_, @classnum;
+ push @c_where, 'discount.classnum IN ('. join(',',@nums). ')' if @nums;
+ my $null = scalar( grep { $_ eq '' } @classnum );
+ push @c_where, 'discount.classnum IS NULL' if $null;
+
+ if ( scalar(@c_where) == 1 ) {
+ push @where, @c_where;
+ } elsif ( @c_where ) {
+ push @where, ' ( '. join(' OR ', @c_where). ' ) ';
+ }
+
+ }
+
+}
+
#usernum
if ( $cgi->param('usernum') =~ /^(\d+)$/ ) {
push @where, "cust_pkg_discount.usernum = $1";
diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi
index 40b9ed78f..382dbc476 100644
--- a/httemplate/search/cust_tax_exempt_pkg.cgi
+++ b/httemplate/search/cust_tax_exempt_pkg.cgi
@@ -101,7 +101,7 @@ my $join = "
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions');
-my @where = ("exempt_monthly = 'Y'");
+my @where = ( "exempt_monthly = 'Y'" );
my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
if ( $beginning || $ending ) {
@@ -118,28 +118,7 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
push @where, "cust_main.custnum = $1";
}
-if ( $cgi->param('out') ) {
- # wtf? how would you ever get exemptions on a non-taxable package location?
-
- push @where, "
- 0 = (
- SELECT COUNT(*) FROM cust_main_county AS county_out
- WHERE ( county_out.county = cust_main.county
- OR ( county_out.county IS NULL AND cust_main.county = '' )
- OR ( county_out.county = '' AND cust_main.county IS NULL)
- OR ( county_out.county IS NULL AND cust_main.county IS NULL)
- )
- AND ( county_out.state = cust_main.state
- OR ( county_out.state IS NULL AND cust_main.state = '' )
- OR ( county_out.state = '' AND cust_main.state IS NULL )
- OR ( county_out.state IS NULL AND cust_main.state IS NULL )
- )
- AND county_out.country = cust_main.country
- AND county_out.tax > 0
- )
- ";
-
-} elsif ( $cgi->param('country' ) ) {
+if ( $cgi->param('country' ) ) {
my $county = dbh->quote( $cgi->param('county') );
my $state = dbh->quote( $cgi->param('state') );
@@ -150,11 +129,19 @@ if ( $cgi->param('out') ) {
push @where, 'taxclass = '. dbh->quote( $cgi->param('taxclass') )
if $cgi->param('taxclass');
-} elsif ( $cgi->param('taxnum') ) {
+}
+
+if ( $cgi->param('taxnum') ) {
- my $taxnum_in = join(',', grep /^\d+$/, $cgi->param('taxnum') );
- push @where, "taxnum IN ($taxnum_in)" if $taxnum_in;
+ my @taxnums = grep /^\d+$/, map { split(',', $_) } $cgi->param('taxnum');
+ if ( $cgi->param('taxnum') =~ /^([\d,]+)$/) {
+ push @where, "cust_tax_exempt_pkg.taxnum IN ($1)";
+ }
+
+}
+if ( $cgi->param('classnum') =~ /^(\d+)$/ ) {
+ push @where, "COALESCE(part_pkg.classnum,0) = $1";
}
my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : '';
diff --git a/httemplate/search/report_cust_bill_pkg_discount.html b/httemplate/search/report_cust_bill_pkg_discount.html
index f9ab901b5..77affd19d 100644
--- a/httemplate/search/report_cust_bill_pkg_discount.html
+++ b/httemplate/search/report_cust_bill_pkg_discount.html
@@ -1,30 +1,35 @@
-<% include('/elements/header.html', 'Discount report' ) %>
+<& /elements/header.html, 'Discount report' &>
<FORM ACTION="cust_bill_pkg_discount.html" METHOD="GET">
<TABLE>
- <% include( '/elements/tr-select-user.html',
- 'label' => 'Discounts by employee: ',
- 'access_user' => \%access_user,
- )
- %>
+ <& /elements/tr-select-discount_class.html,
+ 'field' => 'discount_classnum',
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(none)'
+ &>
- <% include( '/elements/tr-select-agent.html',
- 'curr_value' => scalar( $cgi->param('agentnum') ),
- 'label' => 'for agent: ',
- 'disable_empty' => 0,
- )
- %>
+ <& /elements/tr-select-user.html,
+ 'label' => 'Discounts by employee: ',
+ 'access_user' => \%access_user,
+ &>
- <% include( '/elements/tr-input-beginning_ending.html' ) %>
+ <& /elements/tr-select-agent.html,
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ &>
- <% include( '/elements/tr-input-lessthan_greaterthan.html',
- 'label' => 'Amount',
- 'field' => 'amount',
- )
- %>
+ <& /elements/tr-input-beginning_ending.html &>
+
+<!-- doesn't actually work yet, needs support in cust_bill_pkg_discount.html
+ <& /elements/tr-input-lessthan_greaterthan.html,
+ 'label' => 'Amount',
+ 'field' => 'amount',
+ &>
+-->
</TABLE>
@@ -33,7 +38,7 @@
</FORM>
-<% include('/elements/footer.html') %>
+<& /elements/footer.html &>
<%init>
die "access denied"
diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html
index ebff7fadb..1ceb48e1e 100755
--- a/httemplate/search/report_cust_main.html
+++ b/httemplate/search/report_cust_main.html
@@ -172,20 +172,6 @@
&>
<TR>
- <TD ALIGN="right" VALIGN="center"><% mt('Without census tract') |h %></TD>
- <TD><INPUT TYPE="checkbox" NAME="no_censustract"></TD>
- </TR>
-
-% if ( $conf->exists('enable_taxproducts') ) {
-
- <TR>
- <TD ALIGN="right" VALIGN="center"><% mt('With hardcoded tax location') |h %></TD>
- <TD><INPUT TYPE="checkbox" NAME="with_geocode"></TD>
- </TR>
-
-% }
-
- <TR>
<TD ALIGN="right" VALIGN="center"><% mt('With email address(es)') |h %></TD>
<TD><INPUT TYPE="checkbox" NAME="with_email"></TD>
</TR>
diff --git a/httemplate/search/report_cust_pkg.html b/httemplate/search/report_cust_pkg.html
index f9aabfc7a..e75a0985b 100755
--- a/httemplate/search/report_cust_pkg.html
+++ b/httemplate/search/report_cust_pkg.html
@@ -8,11 +8,7 @@
<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
- <TR>
- <TH CLASS="background" COLSPAN=2 ALIGN="left">
- <FONT SIZE="+1">Customer search options</FONT>
- </TH>
- </TR>
+ <& /elements/tr-title.html, value => mt('Customer search options') &>
<& /elements/tr-select-agent.html,
'curr_value' => scalar( $cgi->param('agentnum') ),
@@ -56,11 +52,7 @@
<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
- <TR>
- <TH CLASS="background" COLSPAN=2 ALIGN="left">
- <FONT SIZE="+1">Package search options</FONT>
- </TH>
- </TR>
+ <& /elements/tr-title.html, value => mt('Package search options') &>
<& /elements/tr-select-sales.html,
'label' => 'Package sales person',
@@ -70,11 +62,10 @@
'disable_empty' => 1,
&>
- <% include( '/elements/tr-select-cust_pkg-status.html',
+ <& /elements/tr-select-cust_pkg-status.html,
'label' => 'Package status',
'onchange' => 'status_changed(this);',
- )
- %>
+ &>
<SCRIPT TYPE="text/javascript">
@@ -120,23 +111,21 @@
</SCRIPT>
- <% include( '/elements/tr-select-pkg_class.html',
+ <& /elements/tr-select-pkg_class.html,
'pre_options' => [ '0' => 'all' ],
'empty_label' => '(empty class)',
- )
- %>
+ &>
% if ( scalar( qsearch( 'part_pkg_report_option', { 'disabled' => '' } ) ) ) {
- <% include( '/elements/tr-select-table.html',
+ <& /elements/tr-select-table.html,
'label' => 'Report classes',
'table' => 'part_pkg_report_option',
'name_col' => 'name',
'hashref' => { 'disabled' => '' },
'element_name' => 'report_option',
'multiple' => 'multiple',
- )
- %>
+ &>
% }
<TR>
@@ -189,24 +178,38 @@
</SCRIPT>
- <% include( '/elements/tr-checkbox.html',
+ <& /elements/tr-checkbox.html,
'label' => 'Custom packages',
'field' => 'custom',
'value' => 1,
'onchange' => 'custom_changed(this);',
- )
- %>
+ &>
- <% include( '/elements/tr-selectmultiple-part_pkg.html' ) %>
+ <& /elements/tr-selectmultiple-part_pkg.html &>
- <TR>
- <TH CLASS="background" COLSPAN=2>&nbsp;</TH>
- </TR>
+ <& /elements/tr-title.html, value => mt('Location search options') &>
- <TR>
- <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Display options</FONT></TH>
- </TR>
- <% include( '/elements/tr-select-cust-fields.html' ) %>
+% my @location_options = qw(cust nocust census nocensus);
+% if ( $conf->exists('enable_taxproducts') ) {
+% push @location_options, 'geocode', 'nogeocode';
+% }
+ <& /elements/tr-checkbox-multiple.html,
+ 'label' => 'Where package location:',
+ 'field' => 'loc',
+ 'options' => \@location_options,
+ 'labels' => { 'cust' => "is the customer's default location",
+ 'nocust' => "is not the customer's default location",
+ 'census' => "has a census tract",
+ 'nocensus' => "does not have a census tract",
+ 'nogeocode'=> 'has an implicit tax location',
+ 'geocode' => 'has a hardcoded tax location',
+ },
+ 'value' => { map { $_ => 1 } @location_options },
+ &>
+
+ <& /elements/tr-title.html, value => mt('Display options') &>
+
+ <& /elements/tr-select-cust-fields.html &>
</TABLE>
@@ -276,4 +279,5 @@ my %checkbox = (
'cancel' => 1,
);
+my $conf = FS::Conf->new;
</%once>
diff --git a/httemplate/search/report_cust_pkg_discount.html b/httemplate/search/report_cust_pkg_discount.html
index 31774c384..7f0e55fed 100644
--- a/httemplate/search/report_cust_pkg_discount.html
+++ b/httemplate/search/report_cust_pkg_discount.html
@@ -1,4 +1,4 @@
-<% include('/elements/header.html', 'Package discount report' ) %>
+<& /elements/header.html, 'Package discount report' &>
<FORM ACTION="cust_pkg_discount.html" METHOD="GET">
@@ -6,7 +6,7 @@
<TABLE>
<TR>
- <TD>Discount status</TD>
+ <TD ALIGN="right">Discount status</TD>
<TD>
<SELECT NAME="status">
<OPTION VALUE="active">Active
@@ -16,18 +16,21 @@
</TD>
</TR>
- <% include( '/elements/tr-select-user.html',
- 'label' => 'Discounts by employee: ',
- 'access_user' => \%access_user,
- )
- %>
+ <& /elements/tr-select-discount_class.html,
+ 'pre_options' => [ '0' => 'all' ],
+ 'empty_label' => '(none)'
+ &>
- <% include( '/elements/tr-select-agent.html',
- 'curr_value' => scalar( $cgi->param('agentnum') ),
- 'label' => 'for agent: ',
- 'disable_empty' => 0,
- )
- %>
+ <& /elements/tr-select-user.html,
+ 'label' => 'Discounts by employee: ',
+ 'access_user' => \%access_user,
+ &>
+
+ <& /elements/tr-select-agent.html,
+ 'curr_value' => scalar( $cgi->param('agentnum') ),
+ 'label' => 'for agent: ',
+ 'disable_empty' => 0,
+ &>
</TABLE>
@@ -36,7 +39,7 @@
</FORM>
-<% include('/elements/footer.html') %>
+<& /elements/footer.html &>
<%init>
die "access denied"
diff --git a/httemplate/search/report_sales_commission.html b/httemplate/search/report_sales_commission.html
index cc17e6bed..19af428d8 100644
--- a/httemplate/search/report_sales_commission.html
+++ b/httemplate/search/report_sales_commission.html
@@ -4,30 +4,47 @@
<TABLE BGCOLOR="#cccccc" CELLSPACING=0>
-<& /elements/tr-select-agent.html,
- 'onchange' => 'agent_changed(this)',
-&>
+% if ( $curuser->report_salesnum ) {
+
+ <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $curuser->report_sales->agentnum %>">
+ <INPUT TYPE="hidden" NAME="salesnum" VALUE="<% $curuser->report_salesnum %>">
+
+% } else {
+
+ <& /elements/tr-select-agent.html,
+ 'onchange' => 'agent_changed(this)',
+ &>
-<SCRIPT TYPE="text/javascript">
+ <SCRIPT TYPE="text/javascript">
- function agent_changed(what) {
- salesnum_agentnum_changed(what);
- }
+ function agent_changed(what) {
+ salesnum_agentnum_changed(what);
+ }
- <&| /elements/onload.js &>
- agent_changed(document.getElementById('agentnum'))
- </&>
+ <&| /elements/onload.js &>
+ agent_changed(document.getElementById('agentnum'))
+ </&>
-</SCRIPT>
+ </SCRIPT>
-<& /elements/tr-select-sales.html &>
+ <& /elements/tr-select-sales.html,
+ 'empty_label' => 'all',
+ &>
+
+% }
<& /elements/tr-checkbox.html,
- 'label' => 'Customer sales person if there is no package sales person',
- 'field' => 'cust_main_sales',
- 'value' => 'Y',
+ 'label' => 'Customer sales person if there is no package sales person',
+ 'field' => 'cust_main_sales',
+ 'value' => 'Y',
&>
+<& /elements/tr-checkbox.html,
+ 'label' => 'Show paid sales only',
+ 'field' => 'paid',
+ 'value' => 'Y',
+&>
+
<& /elements/tr-input-beginning_ending.html &>
</TABLE>
@@ -38,7 +55,8 @@
<% include('/elements/footer.html') %>
<%init>
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Financial reports');
+my $curuser = $FS::CurrentUser::CurrentUser;
+
+die "access denied" unless $curuser->access_right('Financial reports');
</%init>
diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi
index d71fcf90b..111f22d3d 100755
--- a/httemplate/search/report_tax.cgi
+++ b/httemplate/search/report_tax.cgi
@@ -1,283 +1,184 @@
-<% include("/elements/header.html", "$agentname Tax Report - ".
- ( $beginning
- ? time2str('%h %o %Y ', $beginning )
- : ''
- ).
- 'through '.
- ( $ending == 4294967295
- ? 'now'
- : time2str('%h %o %Y', $ending )
- )
- )
-%>
+<& /elements/header.html, "$agentname Tax Report: ".
+ ( $beginning
+ ? time2str('%h %o %Y ', $beginning )
+ : ''
+ ).
+ 'through '.
+ ( $ending == 4294967295
+ ? 'now'
+ : time2str('%h %o %Y', $ending )
+ ). ' - ' . $taxname
+&>
<TD ALIGN="right">
Download full results<BR>
as <A HREF="<% $p.'search/report_tax-xls.cgi?'.$cgi->query_string%>">Excel spreadsheet</A>
</TD>
<STYLE type="text/css">
-td.sectionhead {
+TD.sectionhead {
background-color: #777777;
color: #ffffff;
font-weight: bold;
text-align: left;
}
+.grid TH { background-color: #cccccc; padding: 0px 3px 2px }
+.row0 TD { background-color: #eeeeee; padding: 0px 3px 2px; text-align: right}
+.row1 TD { background-color: #ffffff; padding: 0px 3px 2px; text-align: right}
+TD.rowhead { font-weight: bold; text-align: left }
+.bigmath { font-size: large; font-weight: bold; font: sans-serif; text-align: center }
</STYLE>
-<% include('/elements/table-grid.html') %>
+<& /elements/table-grid.html &>
<TR>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=9>Sales</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3>Rate</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3>Tax owed</TH>
-% unless ( $cgi->param('show_taxclasses') ) {
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3>Tax invoiced</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3>Tax credited</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=3>Tax collected</TH>
-% }
+ <TH ROWSPAN=3></TH>
+ <TH COLSPAN=5>Sales</TH>
+ <TH ROWSPAN=3></TH>
+ <TH ROWSPAN=3>Rate</TH>
+ <TH ROWSPAN=3></TH>
+ <TH ROWSPAN=3>Estimated tax</TH>
+ <TH ROWSPAN=3>Tax invoiced</TH>
+ <TH ROWSPAN=3></TH>
+ <TH ROWSPAN=3>Tax credited</TH>
+ <TH ROWSPAN=3></TH>
+ <TH ROWSPAN=3>Net tax due</TH>
</TR>
<TR>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Total</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=1>Non-taxable</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=1>Non-taxable</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=1>Non-taxable</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc" ROWSPAN=2>Taxable</TH>
+ <TH ROWSPAN=2>Total</TH>
+ <TH ROWSPAN=1>Non-taxable</TH>
+ <TH ROWSPAN=1>Non-taxable</TH>
+ <TH ROWSPAN=1>Non-taxable</TH>
+ <TH ROWSPAN=2>Taxable</TH>
</TR>
- <TR>
- <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>(tax-exempt customer)</FONT></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>(tax-exempt package)</FONT></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"><FONT SIZE=-1>(monthly exemption)</FONT></TH>
+ <TR STYLE="font-size:small">
+ <TH>(tax-exempt customer)</TH>
+ <TH>(tax-exempt package)</TH>
+ <TH>(monthly exemption)</TH>
</TR>
-% foreach my $class (@pkgclasses ) {
-% next if @{ $class->{regions} } == 0;
-% if ( $class->{classname} ) {
- <TR>
- <TD COLSPAN=19 CLASS="sectionhead"><% $class->{classname} %></TD>
+% my $row = 0;
+% my $classlink = '';
+% my $descend;
+% $descend = sub {
+% my ($data, $label) = @_;
+% if ( ref $data eq 'ARRAY' ) {
+% # then we've reached the bottom
+% my (%taxnums, %values);
+% foreach (@$data) {
+% $taxnums{ $_->[0] } = $_->[1];
+% $values{ $_->[0] } = $_->[2];
+% }
+% # finally, output
+ <TR CLASS="row<% $row %>">
+% # Row label
+ <TD CLASS="rowhead"><% $label |h %></TD>
+% # Total Sales
+% my $sales = $money_sprintf->(
+% $values{taxable} +
+% $values{exempt_cust} +
+% $values{exempt_pkg} +
+% $values{exempt_monthly}
+% );
+% my %sales_taxnums;
+% foreach my $x (qw(taxable exempt_cust exempt_pkg exempt_monthly)) {
+% foreach (split(',', $taxnums{$x})) {
+% $sales_taxnums{$_} = 1;
+% }
+% }
+% my $sales_taxnums = join(',', keys %sales_taxnums);
+ <TD>
+ <A HREF="<% "$saleslink;$classlink;taxnum=$sales_taxnums" %>">
+ <% $sales %>
+ </A>
+ </TD>
+% # exemptions
+% foreach(qw(cust pkg)) {
+ <TD>
+ <A HREF="<% "$saleslink;$classlink;exempt_$_=Y;taxnum=".$taxnums{"exempt_$_"} %>">
+ <% $money_sprintf->($values{"exempt_$_"}) %>
+ </A>
+ </TD>
+% }
+ <TD>
+ <A HREF="<% "$exemptlink;$classlink;taxnum=".$taxnums{"exempt_monthly"} %>">
+ <% $money_sprintf->($values{"exempt_monthly"}) %>
+ </A>
+ </TD>
+% # taxable
+ <TD>
+ <A HREF="<% "$saleslink;$classlink;taxable=1;taxnum=$taxnums{taxable}" %>">
+ <% $money_sprintf->($values{taxable}) %>
+ </A>
+ </TD>
+% # tax rate
+% my $rate;
+% foreach(split(',', $taxnums{tax})) {
+% $rate ||= $taxrates{$_};
+% if ($rate != $taxrates{$_}) {
+% $rate = 'variable';
+% last;
+% }
+% }
+% $rate = sprintf('%.2f', $rate) . '%' if ($rate and $rate ne 'variable');
+ <TD CLASS="bigmath"> &times; </TD>
+ <TD><% $rate %></TD>
+% # estimated tax
+ <TD CLASS="bigmath"> = </TD>
+ <TD><% $rate eq 'variable'
+ ? ''
+ : $money_sprintf->( $values{taxable} * $rate / 100 ) %>
+ </TD>
+% # invoiced tax
+ <TD>
+ <A HREF="<% "$taxlink;$classlink;taxnum=$taxnums{taxable}" %>">
+ <% $money_sprintf->( $values{tax} ) %>
+ </A>
+ </TD>
+% # credited tax
+ <TD CLASS="bigmath"> &minus; </TD>
+ <TD>
+ <A HREF="<% "$creditlink;$classlink;taxnum=$taxnums{credited}" %>">
+ <% $money_sprintf->( $values{credited} ) %>
+ </A>
+ </TD>
+% # net tax due
+ <TD CLASS="bigmath"> = </TD>
+ <TD><% $money_sprintf->( $values{tax} - $values{credited} ) %></TD>
</TR>
-% }
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor;
-
-% my @regions = @{ $class->{regions} };
-% foreach my $region ( @regions ) {
-%
-% my $link = '';
-% if ( $with_pkgclass and length($class->{classnum}) ) {
-% $link = ';classnum='.$class->{classnum};
-% } # else we're not breaking down pkg class, or this is the grand total
-%
-% if ( $region->{'label'} eq $out ) {
-% $link .= ';out=1';
-% } elsif ( $region->{'taxnums'} ) {
-% # might be nicer to specify this as country:state:city
-% $link .= ';'.join(';', map { "taxnum=$_" } @{ $region->{'taxnums'} });
-% }
-%
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
+% $row = $row ? 0 : 1;
%
-% my $hicolor = $bgcolor;
-% unless ( $cgi->param('show_taxclasses') ) {
-% my $diff = abs( sprintf( '%.2f', $region->{'owed'} )
-% - sprintf( '%.2f', $region->{'tax'} )
-% );
-% if ( $diff > 0.02 ) {
-% $hicolor = $hicolor eq '#eeeeee' ? '#eeee99' : '#ffffcc';
+% } else { # we're not at the lowest classification
+% my @keys = sort { $a <=> $b or $a cmp $b } keys(%$data);
+% foreach my $key (@keys) {
+% my $sublabel = join(', ', grep $_, $label, $key);
+% &{ $descend }($data->{$key}, $sublabel);
% }
% }
-%
-%
-% my $td = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
-% my $tdh = qq(TD CLASS="grid" BGCOLOR="$hicolor");
-% my $bigmath = '<FONT FACE="sans-serif" SIZE="+1"><B>';
-% my $bme = '</B></FONT>';
-
-% if ( $region->{'is_total'} ) {
- <TR STYLE="font-style: italic">
- <TD STYLE="text-align: right; padding-right: 1ex; background-color:<%$bgcolor%>">Total</TD>
-% } else {
- <TR>
- <<%$td%>><% $region->{'label'} %></TD>
+% };
+
+% my @pkgclasses = sort { $a <=> $b } keys %data;
+% foreach my $pkgclass (@pkgclasses) {
+% my $class = FS::pkg_class->by_key($pkgclass) ||
+% FS::pkg_class->new({ classname => 'Unclassified' });
+ <TBODY>
+% if ( $breakdown{pkgclass} ) {
+ <TR>
+ <TD COLSPAN=19 CLASS="sectionhead"><% $class->classname %></TD>
+ </TR>
% }
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;nottax=1"
- ><% &$money_sprintf( $region->{'sales'} ) %></A>
- </TD>
-% if ( $region->{'label'} eq $out ) {
- <<%$td%> COLSPAN=12></TD>
-% } else { #not $out
- <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;nottax=1;exempt_cust=Y"
- ><% &$money_sprintf( $region->{'exempt_cust'} ) %></A>
- </TD>
- <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;nottax=1;exempt_pkg=Y"
- ><% &$money_sprintf( $region->{'exempt_pkg'} ) %></A>
- </TD>
- <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $exemptlink. $link %>"
- ><% &$money_sprintf( $region->{'exempt_monthly'} ) %></A>
- </TD>
- <<%$td%>><FONT SIZE="+1"><B> = </B></FONT></TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;nottax=1;taxable=1"
- ><% &$money_sprintf( $region->{'taxable'} ) %></A>
- </TD>
- <<%$td%>><% $region->{'label'} eq 'Total' ? '' : "$bigmath X $bme" %></TD>
- <<%$td%> ALIGN="right"><% $region->{'rate'} %></TD>
- <<%$td%>><% $region->{'label'} eq 'Total' ? '' : "$bigmath = $bme" %></TD>
- <<%$tdh%> ALIGN="right">
- <% &$money_sprintf( $region->{'owed'} ) %>
- </TD>
-% } # if !$out
-% unless ( $cgi->param('show_taxclasses') ) {
-% my $invlink = $region->{'url_param_inv'}
-% ? ';'. $region->{'url_param_inv'}
-% : $link;
-
-% if ( $region->{'label'} eq $out ) {
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $invlink %>;istax=1"
- ><% &$money_sprintf_nonzero( $region->{'tax'} ) %></A>
- </TD>
- <<%$td%>></TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $creditlink. $invlink %>;istax=1"
- ><% &$money_sprintf_nonzero( $region->{'credit'} ) %></A>
- </TD>
- <<%$td%> COLSPAN=2></TD>
-% } else { #not $out
- <<%$tdh%> ALIGN="right">
- <A HREF="<% $baselink. $invlink %>;istax=1"
- ><% &$money_sprintf( $region->{'tax'} ) %></A>
- </TD>
- <<%$tdh%>><FONT SIZE="+1"><B> - </B></FONT></TD>
- <<%$tdh%> ALIGN="right">
- <A HREF="<% $creditlink. $invlink %>;istax=1"
- ><% &$money_sprintf( $region->{'credit'} ) %></A>
- </TD>
- <<%$tdh%>><FONT SIZE="+1"><B> = </B></FONT></TD>
- <<%$tdh%> ALIGN="right">
- <% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %>
- </TD>
-% }
-% } # show_taxclasses
-
- </TR>
-% } # foreach $region
-
-%} # foreach $class
-
+% $row = 0;
+% $classlink = "classnum=".($pkgclass || 0) if $breakdown{pkgclass};
+% &{ $descend }( $data{$pkgclass}, '' );
+% # and now totals
+ </TBODY>
+ <TBODY CLASS="total">
+% &{ $descend }( $total{$pkgclass}, 'Total' );
+ </TBODY>
+% } # foreach $pkgclass
</TABLE>
-% if ( $cgi->param('show_taxclasses') ) {
-
- <BR>
- <% include('/elements/table-grid.html') %>
- <TR>
- <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc">Tax invoiced</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc">Tax credited</TH>
- <TH CLASS="grid" BGCOLOR="#cccccc"></TH>
- <TH CLASS="grid" BGCOLOR="#cccccc">Tax collected</TH>
- </TR>
-
-% #some false laziness w/above
-% foreach my $class (@pkgclasses) {
-% if ( $class->{classname} ) {
- <TR>
- <TD COLSPAN=6 CLASS="sectionhead"><% $class->{classname} %></TD>
- </TR>
-% }
-
-% my $bgcolor1 = '#eeeeee';
-% my $bgcolor2 = '#ffffff';
-% my $bgcolor;
-%
-% foreach my $region ( @{ $class->{base_regions} } ) {
-%
-% my $link = '';
-% if ( $with_pkgclass and length($class->{classnum}) ) {
-% $link = ';classnum='.$class->{classnum};
-% }
-%
-% if ( $region->{'label'} eq $out ) {
-% $link .= ';out=1';
-% } else {
-% $link .= ';'. $region->{'url_param'}
-% if $region->{'url_param'};
-% }
-%
-% if ( $bgcolor eq $bgcolor1 ) {
-% $bgcolor = $bgcolor2;
-% } else {
-% $bgcolor = $bgcolor1;
-% }
-% my $td = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
-% my $tdh = qq(TD CLASS="grid" BGCOLOR="$bgcolor");
-%
-% #?
-% my $invlink = $region->{'url_param_inv'}
-% ? ';'. $region->{'url_param_inv'}
-% : $link;
-
- <TR>
- <<%$td%>><% $region->{'label'} %></TD>
-% if ( $region->{'label'} eq $out ) {
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $invlink %>;istax=1"
- ><% &$money_sprintf_nonzero( $region->{'tax'} ) %></A>
- </TD>
- <<%$td%>></TD>
- <<%$td%> ALIGN="right">
- <A HREF="<% $creditlink. $invlink %>;istax=1"
- ><% &$money_sprintf_nonzero( $region->{'credit'} ) %></A>
- </TD>
- <<%$td%> COLSPAN=2></TD>
-% } else { #not $out
- <<%$td%> ALIGN="right">
- <A HREF="<% $baselink. $link %>;istax=1"
- ><% &$money_sprintf( $region->{'tax'} ) %></A>
- </TD>
- <<%$td%>><FONT SIZE="+1"><B> - </B></FONT></TD>
- <<%$tdh%> ALIGN="right">
- <A HREF="<% $creditlink. $invlink %>;istax=1"
- ><% &$money_sprintf( $region->{'credit'} ) %></A>
- </TD>
- <<%$td%>><FONT SIZE="+1"><B> = </B></FONT></TD>
- <<%$tdh%> ALIGN="right">
- <% &$money_sprintf( $region->{'tax'} - $region->{'credit'} ) %>
- </TD>
- </TR>
-% } # if $out
-% } #foreach $region
-% } #foreach $class
-
- </TABLE>
-
-% } # if show_taxclasses
-
-<% include('/elements/footer.html') %>
-
+<& /elements/footer.html &>
<%init>
die "access denied"
@@ -287,17 +188,38 @@ my $DEBUG = $cgi->param('debug') || 0;
my $conf = new FS::Conf;
-my $out = 'Out of taxable region(s)';
+my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
-my %label_opt = ( out => 1 ); #enable 'Out of Taxable Region' label
-$label_opt{with_city} = 1 if $cgi->param('show_cities');
-$label_opt{with_district} = 1 if $cgi->param('show_districts');
+my ($taxname, $country, %breakdown);
-$label_opt{with_taxclass} = 1 if $cgi->param('show_taxclasses');
+if ( $cgi->param('taxname') =~ /^([\w\s]+)$/ ) {
+ $taxname = $1;
+} else {
+ die "taxname required"; # UI prevents this
+}
-my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+if ( $cgi->param('country') =~ /^(\w\w)$/ ) {
+ $country = $1;
+} else {
+ die "country required";
+}
-my $join_cust = ' JOIN cust_bill USING ( invnum )
+# %breakdown: short name => field identifier
+foreach ($cgi->param('breakdown')) {
+ if ( $_ eq 'taxclass' ) {
+ $breakdown{'taxclass'} = 'part_pkg.taxclass';
+ } elsif ( $_ eq 'pkgclass' ) {
+ $breakdown{'pkgclass'} = 'part_pkg.classnum';
+ } elsif ( $_ eq 'city' ) {
+ $breakdown{'city'} = 'cust_main_county.city';
+ $breakdown{'district'} = 'cust_main_county.district';
+ }
+}
+# always break these down
+$breakdown{'state'} = 'cust_main_county.state';
+$breakdown{'county'} = 'cust_main_county.county';
+
+my $join_cust = ' JOIN cust_bill USING ( invnum )
LEFT JOIN cust_main USING ( custnum ) ';
my $join_cust_pkg = $join_cust.
@@ -306,7 +228,7 @@ my $join_cust_pkg = $join_cust.
my $from_join_cust_pkg = " FROM cust_bill_pkg $join_cust_pkg ";
-my $with_pkgclass = $cgi->param('show_pkgclasses');
+# all queries MUST be linked to both cust_bill and cust_main_county
# either or both of these can be used to link cust_bill_pkg to cust_main_county
my $pkg_tax = "SELECT SUM(amount) as tax_amount, invnum, taxnum, ".
@@ -317,21 +239,32 @@ my $pkg_tax = "SELECT SUM(amount) as tax_amount, invnum, taxnum, ".
my $pkg_tax_exempt = "SELECT SUM(amount) AS exempt_charged, billpkgnum, taxnum ".
"FROM cust_tax_exempt_pkg EXEMPT_WHERE GROUP BY billpkgnum, taxnum";
-my $where = "WHERE _date >= $beginning AND _date <= $ending ";
+my $where = "WHERE _date >= $beginning AND _date <= $ending ".
+ "AND COALESCE(cust_main_county.taxname,'Tax') = '$taxname' ".
+ "AND cust_main_county.country = '$country'";
# SELECT/GROUP clauses for first-level queries
-# classnum is a placeholder; they all go in one class in this case.
-my $select = "SELECT NULL AS classnum, cust_main_county.taxnum, ";
-my $group = "GROUP BY cust_main_county.taxnum";
+my $select = "SELECT ";
+my $group = "GROUP BY ";
+foreach (qw(pkgclass taxclass state county city district)) {
+ if ( $breakdown{$_} ) {
+ $select .= "$breakdown{$_} AS $_, ";
+ $group .= "$breakdown{$_}, ";
+ } else {
+ $select .= "NULL AS $_, ";
+ }
+}
+$select .= "array_to_string(array_agg(DISTINCT(cust_main_county.taxnum)), ',') AS taxnums, ";
+$group =~ s/, $//;
+
# SELECT/GROUP clauses for second-level (totals) queries
-my $select_all = "SELECT NULL AS classnum, ";
-my $group_all = "";
-
-if ( $with_pkgclass ) {
- $select = "SELECT COALESCE(part_pkg.classnum,0), cust_main_county.taxnum, ";
- $group = "GROUP BY part_pkg.classnum, cust_main_county.taxnum";
- $select_all = "SELECT COALESCE(part_pkg.classnum,0), ";
- $group_all = "GROUP BY COALESCE(part_pkg.classnum,0)";
+# breakdown by package class only, if anything
+my $select_all = "SELECT NULL AS pkgclass, ";
+my $group_all = "";
+if ( $breakdown{pkgclass} ) {
+ $select_all = "SELECT $breakdown{pkgclass} AS pkgclass, ";
+ $group_all = "GROUP BY $breakdown{pkgclass}";
}
+$select_all .= "array_to_string(array_agg(DISTINCT(cust_main_county.taxnum)), ',') AS taxnums, ";
my $agentname = '';
if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) {
@@ -356,7 +289,8 @@ my $exempt = "$select SUM(exempt_charged)
JOIN ($pkg_tax_exempt) AS pkg_tax_exempt
USING (taxnum)
JOIN cust_bill_pkg USING (billpkgnum)
- $join_cust_pkg $where AND $nottax $group";
+ $join_cust_pkg $where AND $nottax
+ $group";
my $all_exempt = "$select_all SUM(exempt_charged)
FROM cust_main_county
@@ -393,24 +327,19 @@ $sql{taxable} = "$select
LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt
ON (pkg_tax_exempt.billpkgnum = cust_bill_pkg.billpkgnum
AND pkg_tax_exempt.taxnum = cust_main_county.taxnum)
- $join_cust_pkg $where AND $nottax $group";
+ $join_cust_pkg $where AND $nottax
+ $group";
-# Here we're going to sum all line items that are taxable _at all_,
-# under any tax. exempt_charged is the sum of all exemptions for a
-# particular billpkgnum + taxnum; we take the taxnum that has the
-# smallest sum of exemptions and subtract that from the charged amount.
$all_sql{taxable} = "$select_all
- SUM(cust_bill_pkg.setup + cust_bill_pkg.recur - COALESCE(min_exempt, 0))
- FROM cust_bill_pkg
- JOIN (
- SELECT invnum, pkgnum, MIN(exempt_charged) AS min_exempt
- FROM ($pkg_tax) AS pkg_tax
- JOIN cust_bill_pkg USING (invnum, pkgnum)
- LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt USING (billpkgnum, taxnum)
- GROUP BY invnum, pkgnum
- ) AS pkg_is_taxable
- USING (invnum, pkgnum)
- $join_cust_pkg $where AND $nottax $group_all";
+ SUM(cust_bill_pkg.setup + cust_bill_pkg.recur - COALESCE(exempt_charged, 0))
+ FROM cust_main_county
+ JOIN ($pkg_tax) AS pkg_tax USING (taxnum)
+ JOIN cust_bill_pkg USING (invnum, pkgnum)
+ LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt
+ ON (pkg_tax_exempt.billpkgnum = cust_bill_pkg.billpkgnum
+ AND pkg_tax_exempt.taxnum = cust_main_county.taxnum)
+ $join_cust_pkg $where AND $nottax
+ $group_all";
$sql{taxable} =~ s/EXEMPT_WHERE//; # unrestricted
$all_sql{taxable} =~ s/EXEMPT_WHERE//;
@@ -428,7 +357,7 @@ my $taxfrom = " FROM cust_bill_pkg
LEFT JOIN cust_bill_pkg_tax_location USING ( billpkgnum )
LEFT JOIN cust_main_county USING ( taxnum )";
-if ( $with_pkgclass ) {
+if ( $breakdown{pkgclass} ) {
# If we're not grouping by package class, this is unnecessary, and
# probably really expensive.
$taxfrom .= "
@@ -439,17 +368,14 @@ if ( $with_pkgclass ) {
}
my $istax = "cust_bill_pkg.pkgnum = 0";
-my $named_tax =
- "COALESCE(taxname,'Tax') = COALESCE(cust_bill_pkg.itemdesc,'Tax')";
$sql{tax} = "$select SUM(cust_bill_pkg_tax_location.amount)
$taxfrom
- $where AND $istax AND $named_tax
+ $where AND $istax
$group";
-$all_sql{tax} = "$select_all SUM(cust_bill_pkg.setup)
- FROM cust_bill_pkg
- $join_cust
+$all_sql{tax} = "$select_all SUM(cust_bill_pkg_tax_location.amount)
+ $taxfrom
$where AND $istax
$group_all";
@@ -463,325 +389,86 @@ my $creditwhere = $where .
$sql{credit} = "$select SUM(cust_credit_bill_pkg.amount)
$creditfrom
- $creditwhere AND $istax AND $named_tax
+ $creditwhere AND $istax
$group";
$all_sql{credit} = "$select_all SUM(cust_credit_bill_pkg.amount)
- FROM cust_credit_bill_pkg
- JOIN cust_bill_pkg USING (billpkgnum)
- $join_cust
- $where AND $istax
+ $creditfrom
+ $creditwhere AND $istax
$group_all";
-if ( $with_pkgclass ) {
- # the slightly more complicated version, with lots of joins that are
- # unnecessary if you're not breaking down by package class
- $all_sql{tax} = "$select_all SUM(cust_bill_pkg_tax_location.amount)
- $taxfrom
- $where AND $istax
- $group_all";
-
- $all_sql{credit} = "$select_all SUM(cust_credit_bill_pkg.amount)
- $creditfrom
- $creditwhere AND $istax
- $group_all";
-}
-
-# "out of taxable region" sales
-$all_sql{out_sales} =
- "$select_all SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)
- FROM (cust_bill_pkg $join_cust_pkg)
- LEFT JOIN ($pkg_tax) AS pkg_tax USING (invnum, pkgnum)
- LEFT JOIN ($pkg_tax_exempt) AS pkg_tax_exempt USING (billpkgnum)
- $where AND $nottax
- AND pkg_tax.taxnum IS NULL AND pkg_tax_exempt.taxnum IS NULL
- $group_all"
-;
-
-$all_sql{out_sales} =~ s/EXEMPT_WHERE//;
-
my %data;
my %total;
+my %taxclass_name = { '' => '' };
+if ( $breakdown{taxclass} ) {
+ $taxclass_name{$_->taxclassnum} = $_->taxclass
+ foreach qsearch('tax_class');
+ $taxclass_name{''} = 'Unclassified';
+}
foreach my $k (keys(%sql)) {
my $stmt = $sql{$k};
warn "\n".uc($k).":\n".$stmt."\n" if $DEBUG;
my $sth = dbh->prepare($stmt);
- # three columns: classnum, taxnum, value
+ # eight columns: pkgclass, taxclass, state, county, city, district
+ # taxnums (comma separated), value
+ # *sigh*
$sth->execute
or die "failed to execute $k query: ".$sth->errstr;
while ( my $row = $sth->fetchrow_arrayref ) {
- $data{$k}{$row->[0]}{$row->[1]} = $row->[2];
+ my $bin = $data
+ {$row->[0]}
+ {$taxclass_name{$row->[1]}}
+ {$row->[2]}
+ {$row->[3] ? $row->[3] . ' County' : ''}
+ {$row->[4]}
+ {$row->[5]}
+ ||= [];
+ push @$bin, [ $k, $row->[6], $row->[7] ];
}
}
warn "DATA:\n".Dumper(\%data) if $DEBUG > 1;
foreach my $k (keys %all_sql) {
- warn "\n".$all_sql{$k}."\n" if $DEBUG;
+ warn "\nTOTAL ".uc($k).":\n".$all_sql{$k}."\n" if $DEBUG;
my $sth = dbh->prepare($all_sql{$k});
- # two columns: classnum, value
+ # three columns: pkgclass, taxnums (comma separated), value
$sth->execute
or die "failed to execute $k totals query: ".$sth->errstr;
while ( my $row = $sth->fetchrow_arrayref ) {
- $total{$k}{$row->[0]} = $row->[1];
- }
-}
-warn "TOTALS:\n".Dumper(\%total);# if $DEBUG > 1;
-# so $data{tax}, for example, is now a hash with one entry
-# for each classnum, containing a hash with one entry for each
-# taxnum, containing the tax billed on that taxnum.
-# if with_pkgclass is off, then the classnum is always null.
-
-# integrity checks
-# unlinked tax collected
-my $out_tax_sql =
- "SELECT SUM(cust_bill_pkg.setup)
- FROM (cust_bill_pkg $join_cust)
- LEFT JOIN cust_bill_pkg_tax_location USING (billpkgnum)
- $where AND $istax AND cust_bill_pkg_tax_location.billpkgnum IS NULL"
-;
-my $unlinked_tax = FS::Record->scalar_sql($out_tax_sql);
-# unlinked tax credited
-my $out_credit_sql =
- "SELECT SUM(cust_credit_bill_pkg.amount)
- FROM cust_credit_bill_pkg
- JOIN cust_bill_pkg USING (billpkgnum)
- $join_cust
- $where AND $istax AND cust_credit_bill_pkg.billpkgtaxlocationnum IS NULL"
-;
-my $unlinked_credit = FS::Record->scalar_sql($out_credit_sql);
-
-# all sales
-my $all_sales = FS::Record->scalar_sql(
- "SELECT SUM(cust_bill_pkg.setup + cust_bill_pkg.recur)
- FROM cust_bill_pkg $join_cust $where AND $nottax"
-);
-
-#tax-report_groups filtering
-my($group_op, $group_value) = ( '', '' );
-if ( $cgi->param('report_group') =~ /^(=|!=) (.*)$/ ) {
- ( $group_op, $group_value ) = ( $1, $2 );
-}
-my $group_test = sub { # to be applied to a tax label
- my $label = shift;
- return 1 unless $group_op; #in case we get called inadvertantly
- if ( $label eq $out ) { #don't display "out of taxable region" in this case
- 0;
- } elsif ( $group_op eq '=' ) {
- $label =~ /^$group_value/;
- } elsif ( $group_op eq '!=' ) {
- $label !~ /^$group_value/;
- } else {
- die "guru meditation #00de: group_op $group_op\n";
+ my $bin = $total{$row->[0]} ||= [];
+ push @$bin, [ $k, $row->[1], $row->[2] ];
}
-};
-
-my @pkgclasses;
-if ($with_pkgclass) {
- @pkgclasses = qsearch('pkg_class', {});
- push @pkgclasses, FS::pkg_class->new({
- classnum => '0',
- classname => 'Unclassified',
- });
-} else {
- @pkgclasses = ( FS::pkg_class->new({
- classnum => '',
- classname => '',
- }) );
}
-my %pkgclass_data;
-
-foreach my $class (@pkgclasses) {
- my $classnum = $class->classnum;
- my $classname = $class->classname;
-
- # if show_taxclasses is on, %base_regions will contain the same data
- # as %regions, but with taxclasses merged together (and ignoring report_group
- # filtering).
- my (%regions, %base_regions);
-
- my @loc_params = qw(country state county);
- push @loc_params, 'city' if $cgi->param('show_cities');
- push @loc_params, 'district' if $cgi->param('show_districts');
-
- foreach my $r ( qsearch({ 'table' => 'cust_main_county', })) {
- my $taxnum = $r->taxnum;
- # set up a %regions entry for this region's tax label
- my $label = $r->label(%label_opt);
- next if $label eq $out;
- $regions{$label} ||= { label => $label };
-
- $regions{$label}->{$_} = $r->get($_) foreach @loc_params;
- $regions{$label}->{taxnums} ||= [];
- push @{ $regions{$label}->{taxnums} }, $r->taxnum;
-
- my %x; # keys are data items (like 'tax', 'exempt_cust', etc.)
- foreach my $k (keys %data) {
- next unless exists($data{$k}{$classnum}{$taxnum});
- $x{$k} = $data{$k}{$classnum}{$taxnum};
- $regions{$label}{$k} += $x{$k};
- if ( $k eq 'taxable' or $k =~ /^exempt/ ) {
- $regions{$label}->{'sales'} += $x{$k};
- }
- }
-
- my $owed = $data{'taxable'}{$classnum}{$taxnum} * ($r->tax/100);
- $regions{$label}->{'owed'} += $owed;
- $total{'owed'}{$classnum} += $owed;
-
- if ( defined($regions{$label}->{'rate'})
- && $regions{$label}->{'rate'} != $r->tax.'%' ) {
- $regions{$label}->{'rate'} = 'variable';
- } else {
- $regions{$label}->{'rate'} = $r->tax.'%';
- }
-
- if ( $cgi->param('show_taxclasses') ) {
- my $base_label = $r->label(%label_opt, 'with_taxclass' => 0);
- $base_regions{$base_label} ||=
- {
- label => $base_label,
- tax => 0,
- credit => 0,
- };
- $base_regions{$base_label}->{tax} += $x{tax};
- $base_regions{$base_label}->{credit} += $x{credit};
- }
+warn "TOTALS:\n".Dumper(\%total) if $DEBUG > 1;
- }
-
- my @regions = map { $_->{label} }
- sort {
- ($b eq $out) <=> ($a eq $out)
- or $a->{country} cmp $b->{country}
- or $a->{state} cmp $b->{state}
- or $a->{county} cmp $b->{county}
- or $a->{city} cmp $b->{city}
- }
- grep { $_->{sales} > 0 or $_->{tax} > 0 or $_->{credit} > 0 }
- values %regions;
-
- #tax-report_groups filtering
- @regions = grep &{$group_test}($_), @regions
- if $group_op;
-
- #calculate totals
- my %taxclasses = ();
- my %county = ();
- my %state = ();
- my %country = ();
- foreach my $label (@regions) {
- $taxclasses{$regions{$_}->{'taxclass'}} = 1
- if $regions{$_}->{'taxclass'};
- $county{$regions{$_}->{'county'}} = 1;
- $state{$regions{$_}->{'state'}} = 1;
- $country{$regions{$_}->{'country'}} = 1;
- }
-
- my $total_url_param = '';
- my $total_url_param_invoiced = '';
- if ( $group_op ) {
-
- my @country = keys %country;
- warn "WARNING: multiple countries on this grouped report; total links broken"
- if scalar(@country) > 1;
- my $country = $country[0];
-
- my @state = keys %state;
- warn "WARNING: multiple countries on this grouped report; total links broken"
- if scalar(@state) > 1;
- my $state = $state[0];
-
- $total_url_param_invoiced =
- $total_url_param =
- 'report_group='.uri_escape("$group_op $group_value").';'.
- join(';', map 'taxclass='.uri_escape($_), keys %taxclasses );
- $total_url_param .= ';'.
- "country=$country;state=".uri_escape($state).';'.
- join(';', map 'county='.uri_escape($_), keys %county ) ;
-
- }
-
- #ordering
- @regions =
- map $regions{$_},
- sort { $a cmp $b }
- @regions;
-
- my @base_regions =
- map $base_regions{$_},
- sort { $a cmp $b }
- keys %base_regions;
-
- #add "Out of taxable" and total lines
- if ( $total{out_sales}{$classnum} ) {
- my %out = (
- 'sales' => $total{out_sales}{$classnum},
- 'label' => $out,
- 'rate' => ''
- );
- push @regions, \%out;
- push @base_regions, \%out;
- }
-
- if ( @regions ) {
- my %class_total = map { $_ => $total{$_}{$classnum} } keys(%total);
- $class_total{is_total} = 1;
- $class_total{sales} = sum(
- @class_total{ 'taxable',
- 'out_sales',
- grep(/^exempt/, keys %class_total) }
- );
-
- push @regions, \%class_total;
- push @base_regions, \%class_total;
- }
-
- $pkgclass_data{$classname} = {
- classnum => $classnum,
- classname => $classname,
- regions => \@regions,
- base_regions => \@base_regions,
- };
-}
-
-if ( $with_pkgclass ) {
- my $class_zero = delete( $pkgclass_data{'Unclassified'} );
- @pkgclasses = map { $pkgclass_data{$_} }
- sort { $a cmp $b }
- keys %pkgclass_data;
- push @pkgclasses, $class_zero;
-
- my %grand_total = map {
- $_ => sum( values(%{ $total{$_} }) )
- } keys(%total);
-
- $grand_total{sales} = $all_sales;
-
- push @pkgclasses, {
- classnum => '',
- classname => 'Total',
- regions => [ \%grand_total ],
- base_regions => [ \%grand_total ],
- }
-} else {
- @pkgclasses = $pkgclass_data{''};
-}
-
-#--
+# $data{$pkgclass}{$taxclass}{$state}{$county}{$city}{$district} = [
+# [ 'taxable', taxnums, amount ],
+# [ 'exempt_cust', taxnums, amount ],
+# ...
+# ]
+# non-requested grouping levels simply collapse into key = ''
my $money_char = $conf->config('money_char') || '$';
my $money_sprintf = sub {
$money_char. sprintf('%.2f', shift );
};
-my $money_sprintf_nonzero = sub {
- $_[0] == 0 ? '' : &$money_sprintf($_[0])
-};
my $dateagentlink = "begin=$beginning;end=$ending";
$dateagentlink .= ';agentnum='. $cgi->param('agentnum')
if length($agentname);
-my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink";
+my $saleslink = $p. "search/cust_bill_pkg.cgi?$dateagentlink;nottax=1";
+my $taxlink = $p. "search/cust_bill_pkg.cgi?$dateagentlink;istax=1";
my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink";
-my $creditlink = $p. "search/cust_bill_pkg.cgi?$dateagentlink;credit=1";
+my $creditlink = $p. "search/cust_bill_pkg.cgi?$dateagentlink;credit=1;istax=1";
+
+my %taxrates;
+foreach my $tax (
+ qsearch('cust_main_county', {
+ country => $country,
+ tax => { op => '>', value => 0 }
+ }) )
+ {
+ $taxrates{$tax->taxnum} = $tax->tax;
+}
</%init>
diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html
index 8a207aafb..20aa07fc8 100755
--- a/httemplate/search/report_tax.html
+++ b/httemplate/search/report_tax.html
@@ -4,68 +4,34 @@
<TABLE>
-% if ( $conf->config('tax-report_groups') ) {
-% my @lines = $conf->config('tax-report_groups');
-
- <TR>
- <TD ALIGN="right">Tax group</TD>
- <TD>
- <SELECT NAME="report_group">
-
- <OPTION VALUE="">all</OPTION>
-
-% foreach my $line ( @lines ) {
-% $line =~ /^\s*(.+)\s+(=|!=)\s+(.*)\s*$/ #or next;
-% or do { warn "bad report_group line: $line\n"; next; };
-% my($label, $op, $value) = ($1, $2, $3);
-
- <OPTION VALUE="<% "$op $value" %>"><% $label %></OPTION>
-% }
-
- </SELECT>
- </TD>
- </TR>
-
-% }
-
- <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %>
-
- <% include( '/elements/tr-input-beginning_ending.html' ) %>
-
-% if ( $city ) {
- <TR>
- <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_cities" VALUE="1" onclick="toggle_show_cities(this)"></TD>
- <TD>Show cities</TD>
- </TR>
- <TR>
- <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_districts" VALUE="1" DISABLED></TD>
- <TD>Show districts</TD>
- </TR>
- <SCRIPT TYPE="text/javascript">
- function toggle_show_cities() {
- what = document.getElementsByName('show_cities')[0];
- what.form.show_districts.disabled = !what.checked;
- what.form.show_districts.checked = what.checked;
- }
- toggle_show_cities();
- </SCRIPT>
-% }
-
-% if ( $conf->exists('enable_taxclasses') ) {
- <TR>
- <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_taxclasses" VALUE="1"></TD>
- <TD>Show tax classes</TD>
- </TR>
-% }
-
-% my @pkg_class = qsearch('pkg_class', {});
-% if ( @pkg_class ) {
- <TR>
- <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_pkgclasses" VALUE="1"></TD>
- <TD>Show package classes</TD>
- </TR>
-% }
-
+ <& /elements/tr-select-agent.html, 'disable_empty'=>0 &>
+
+ <& /elements/tr-input-beginning_ending.html &>
+
+ <& /elements/tr-select.html,
+ 'label' => 'Country',
+ 'field' => 'country',
+ 'options' => \@countries,
+ 'curr_value' => ($conf->config('countrydefault') || 'US'),
+ &>
+
+ <& /elements/tr-select.html,
+ 'label' => 'For tax named ',
+ 'field' => 'taxname',
+ 'options' => \@taxnames,
+ 'disable_empty' => 1,
+ &>
+
+ <& /elements/tr-checkbox-multiple.html,
+ 'label' => 'Break down by ',
+ 'field' => 'breakdown',
+ 'options' => \@breakdown,
+ 'option_labels' => {
+ taxclass => 'Tax class',
+ pkgclass => 'Package class',
+ city => 'City',
+ },
+ &>
</TABLE>
<BR><INPUT TYPE="submit" VALUE="Get Report">
@@ -80,12 +46,23 @@ die "access denied"
my $conf = new FS::Conf;
-my $city_sql = "SELECT COUNT(*) FROM cust_main_county
- WHERE city != '' AND city IS NOT NULL
- LIMIT 1";
-
-my $city_sth = dbh->prepare($city_sql) or die dbh->errstr;
-$city_sth->execute or die $city_sth->errstr;
-my $city = $city_sth->fetchrow_arrayref->[0];
+my $sth = dbh->prepare('SELECT DISTINCT(COALESCE(taxname, \'Tax\')) FROM cust_main_county');
+$sth->execute or die $sth->errstr;
+my @taxnames = map { $_->[0] } @{ $sth->fetchall_arrayref };
+
+$sth = dbh->prepare('SELECT DISTINCT(country) FROM cust_location');
+$sth->execute or die $sth->errstr;
+my @countries = map { $_->[0] } @{ $sth->fetchall_arrayref };
+
+my @breakdown;
+if ( $conf->exists('enable_taxclasses') ) {
+ push @breakdown, 'taxclass';
+}
+if ( FS::pkg_class->count() > 0 ) {
+ push @breakdown, 'pkgclass';
+}
+if ( FS::cust_main_county->count("city is not null and city != ''") > 0 ) {
+ push @breakdown, 'city';
+}
</%init>
diff --git a/httemplate/search/sales_commission.html b/httemplate/search/sales_commission.html
index b4d40aeb2..e74f3792e 100644
--- a/httemplate/search/sales_commission.html
+++ b/httemplate/search/sales_commission.html
@@ -36,6 +36,8 @@ my $count_query = "SELECT COUNT(*) FROM sales";
my $salesnum;
if ( $cgi->param('salesnum') =~ /^(\d+)$/ ) {
$salesnum = $1;
+} else {
+ $cgi->delete('salesnum');
}
my $title = 'Sales person commission';
@@ -43,13 +45,14 @@ $title .= ': '. time2str($date_format, $beginning). ' to '.
time2str($date_format, $ending)
if $beginning;
+my $paid = $cgi->param('paid') ? 1 : 0;
+$title .= ' - paid sales only' if $paid;
+
my $cust_main_sales = $cgi->param('cust_main_sales') eq 'Y' ? 'Y' : '';
my $sales_link = [ 'sales_pkg_class.html?'.
- "begin=$beginning;".
- "end=$ending;".
- "cust_main_sales=$cust_main_sales;".
- "salesnum=",
+ # pass all of our parameters along
+ $cgi->query_string. ';salesnum=',
'salesnum'
];
@@ -64,6 +67,7 @@ my $sales_sub_maker = sub {
$beginning,
$ending,
'cust_main_sales' => $cust_main_sales,
+ 'paid' => $paid,
);
$total += $_->get($field) foreach @cust_bill_pkg;
diff --git a/httemplate/search/sales_pkg_class.html b/httemplate/search/sales_pkg_class.html
index da5d512d5..8bb6bde4c 100644
--- a/httemplate/search/sales_pkg_class.html
+++ b/httemplate/search/sales_pkg_class.html
@@ -9,7 +9,7 @@
$sales_sub_maker->('setup'),
$sales_sub_maker->('recur'),
$commission_sub, ],
- 'links' => [ '', '', '', $commission_link ],
+ 'links' => [ '', $sales_link, $sales_link, $commission_link ],
'align' => 'lrrr',
'query' => { 'table' => 'sales_pkg_class',
'hashref' => { 'salesnum' => $salesnum },
@@ -40,6 +40,9 @@ $title .= ': '. time2str($date_format, $beginning). ' to '.
if $beginning;
my $cust_main_sales = $cgi->param('cust_main_sales') eq 'Y' ? 'Y' : '';
+my $paid = $cgi->param('paid') ? 1 : 0;
+
+$title .= " - paid sales only" if $paid;
my $sales_link = [ 'cust_bill_pkg.cgi?'.
"begin=$beginning;".
@@ -55,18 +58,17 @@ my $sales_sub_maker = sub {
my $field = shift;
sub {
my $sales_pkg_class = shift;
-
- #efficiency improvement: ask the db for a sum instead of all the records
- my $total = 0;
- my @cust_bill_pkg = $sales->cust_bill_pkg(
+ # could be even more efficient but this is pretty good
+ my $search = $sales->cust_bill_pkg_search(
$beginning,
$ending,
'cust_main_sales' => $cust_main_sales,
'classnum' => $sales_pkg_class->classnum,
+ 'paid' => $paid,
);
- $total += $_->get($field) foreach @cust_bill_pkg;
-
- $money_char. sprintf('%.2f', $total);
+ $search->{'select'} = "SUM(cust_bill_pkg.$field) AS total";
+ my $result = qsearchs($search);
+ $money_char. sprintf('%.2f', $result ? $result->get('total') : 0);
};
};
@@ -85,6 +87,15 @@ my $commission_sub = sub {
$money_char. sprintf('%.2f', $total_credit);
};
+my $sales_link = [ 'cust_bill_pkg.cgi?'.
+ "begin=$beginning;".
+ "end=$ending;".
+ "cust_main_sales=$cust_main_sales;".
+ "salesnum=$salesnum;".
+ "classnum=",
+ 'classnum'
+ ];
+
my $commission_link = [ 'cust_credit.html?'.
"begin=$beginning;".
"end=$ending;".
diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html
index e286305f4..e0dd7b904 100644
--- a/httemplate/view/cust_main/billing.html
+++ b/httemplate/view/cust_main/billing.html
@@ -221,7 +221,10 @@
% if ( $conf->exists('enable_taxproducts') ) {
<TR>
<TD ALIGN="right"><% mt('Tax location') |h %></TD>
- <TD BGCOLOR="#ffffff"><% $cust_main->geocode('cch') %></TD>
+% my $tax_location = $conf->exists('tax-ship_address')
+% ? $cust_main->ship_location
+% : $cust_main->bill_location;
+ <TD BGCOLOR="#ffffff"><% $tax_location->geocode('cch') %></TD>
</TR>
% }
<TR>
diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html
index 63a050c04..a851d99ed 100644
--- a/httemplate/view/cust_main/contacts_new.html
+++ b/httemplate/view/cust_main/contacts_new.html
@@ -22,7 +22,7 @@
% })
% or next;
<TD ALIGN="right">&nbsp;&nbsp;&nbsp;<% $phone_type->typename %> phone</TD>
- <TD BGCOLOR="#FFFFFF"><% $contact_phone->phonenum |h %></TD>
+ <TD BGCOLOR="#FFFFFF"><% $contact_phone->phonenum_pretty |h %></TD>
% }
</TR>
diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi
index 077dc77b7..833e86247 100644
--- a/httemplate/view/svc_external.cgi
+++ b/httemplate/view/svc_external.cgi
@@ -23,6 +23,7 @@
</TABLE></TD></TR></TABLE>
+<BR>
<& /elements/table-tickets.html, object => $cust_svc &>