diff options
Diffstat (limited to 'FS')
| -rw-r--r-- | FS/FS/AccessRight.pm | 3 | ||||
| -rw-r--r-- | FS/FS/Conf.pm | 18 | ||||
| -rw-r--r-- | FS/FS/Report/Table.pm | 21 | ||||
| -rw-r--r-- | FS/FS/Schema.pm | 20 | ||||
| -rw-r--r-- | FS/FS/Upgrade.pm | 1 | ||||
| -rw-r--r-- | FS/FS/access_right.pm | 38 | ||||
| -rw-r--r-- | FS/FS/cust_bill.pm | 34 | ||||
| -rw-r--r-- | FS/FS/cust_pay.pm | 27 | ||||
| -rw-r--r-- | FS/FS/part_event/Condition.pm | 2 | ||||
| -rw-r--r-- | FS/FS/part_event/Condition/has_cust_tag.pm | 49 | ||||
| -rw-r--r-- | FS/FS/part_pkg/discount_Mixin.pm | 4 | ||||
| -rwxr-xr-x | FS/FS/svc_broadband.pm | 48 | ||||
| -rw-r--r-- | FS/FS/upgrade_journal.pm | 151 | ||||
| -rw-r--r-- | FS/MANIFEST | 2 | ||||
| -rw-r--r-- | FS/t/upgrade_journal.t | 5 |
15 files changed, 403 insertions, 20 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 06263c27b..d2417f069 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -253,6 +253,7 @@ tie my %rights, 'Tie::IxHash', ### 'Reporting/listing rights' => [ 'List customers', + 'List all customers', 'List zip codes', #NEW 'List invoices', 'List packages', @@ -267,6 +268,8 @@ tie my %rights, 'Tie::IxHash', { rightname=> 'List inventory', global=>1 }, { rightname=>'View email logs', global=>1 }, + 'Download report data', + #{ rightname => 'List customers of all agents', global=>1 }, ], diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 895346928..4a4f92c8a 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1572,6 +1572,13 @@ and customer address. Include units.', }, { + 'key' => 'disable_maxselect', + 'section' => 'UI', + 'description' => 'Prevent changing the number of records per page.', + 'type' => 'checkbox', + }, + + { 'key' => 'session-start', 'section' => 'session', 'description' => 'If defined, the command which is executed on the Freeside machine when a session begins. The contents of the file are treated as a double-quoted perl string, with the following variables available: <code>$ip</code>, <code>$nasip</code> and <code>$nasfqdn</code>, which are the IP address of the starting session, and the IP address and fully-qualified domain name of the NAS this session is on.', @@ -2269,6 +2276,13 @@ and customer address. Include units.', }, { + 'key' => 'require_cash_deposit_info', + 'section' => 'billing', + 'description' => 'When recording cash payments, display bank deposit information fields.', + 'type' => 'checkbox', + }, + + { 'key' => 'paymentforcedtobatch', 'section' => 'deprecated', 'description' => 'See batch-enable_payby and realtime-disable_payby. Used to (for CHEK): Cause per customer payment entry to be forced to a batch processor rather than performed realtime.', @@ -2911,7 +2925,7 @@ and customer address. Include units.', 'section' => 'invoicing', 'description' => 'Enable FTP of raw invoice data - format.', 'type' => 'select', - 'select_enum' => [ '', 'default', 'billco', ], + 'select_enum' => [ '', 'default', 'oneline', 'billco', ], }, { @@ -2947,7 +2961,7 @@ and customer address. Include units.', 'section' => 'invoicing', 'description' => 'Enable spooling of raw invoice data - format.', 'type' => 'select', - 'select_enum' => [ '', 'default', 'billco', ], + 'select_enum' => [ '', 'default', 'oneline', 'billco', ], }, { diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm index 3942543b5..b0e911f84 100644 --- a/FS/FS/Report/Table.pm +++ b/FS/FS/Report/Table.pm @@ -32,21 +32,32 @@ options in %opt. =over 4 -=item signups: The number of customers signed up. +=item signups: The number of customers signed up. Options are "refnum" +(limit by advertising source) and "indirect" (boolean, tells us to limit +to customers that have a referral_custnum that matches the advertising source). =cut sub signups { my( $self, $speriod, $eperiod, $agentnum, %opt ) = @_; - my @where = ( - $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'signupdate') + my @where = ( $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, + 'cust_main.signupdate') ); - if ( $opt{'refnum'} ) { + my $join = ''; + if ( $opt{'indirect'} ) { + $join = " JOIN cust_main AS referring_cust_main". + " ON (cust_main.referral_custnum = referring_cust_main.custnum)"; + + if ( $opt{'refnum'} ) { + push @where, "referring_cust_main.refnum = ".$opt{'refnum'}; + } + } + elsif ( $opt{'refnum'} ) { push @where, "refnum = ".$opt{'refnum'}; } $self->scalar_sql( - "SELECT COUNT(*) FROM cust_main WHERE ".join(' AND ', @where) + "SELECT COUNT(*) FROM cust_main $join WHERE ".join(' AND ', @where) ); } diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 961862da0..fdf29e0a0 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1332,12 +1332,17 @@ sub tables_hashref { # index into payby table # eventually 'payinfo', 'varchar', 'NULL', 512, '', '', #see cust_main above - 'paymask', 'varchar', 'NULL', $char_d, '', '', + 'paymask', 'varchar', 'NULL', $char_d, '', '', 'paydate', 'varchar', 'NULL', 10, '', '', 'paybatch', 'varchar', 'NULL', $char_d, '', '', #for auditing purposes. 'payunique', 'varchar', 'NULL', $char_d, '', '', #separate paybatch "unique" functions from current usage 'closed', 'char', 'NULL', 1, '', '', 'pkgnum', 'int', 'NULL', '', '', '', #desired pkgnum for pkg-balances + # cash/check deposit info fields + 'bank', 'varchar', 'NULL', $char_d, '', '', + 'depositor', 'varchar', 'NULL', $char_d, '', '', + 'account', 'varchar', 'NULL', 20, '', '', + 'teller', 'varchar', 'NULL', 20, '', '', ], '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' ] ], @@ -3599,6 +3604,19 @@ sub tables_hashref { 'index' => [], }, + 'upgrade_journal' => { + 'columns' => [ + 'upgradenum', 'serial', '', '', '', '', + '_date', 'int', '', '', '', '', + 'upgrade', 'varchar', '', $char_d, '', '', + 'status', 'varchar', '', $char_d, '', '', + 'statustext', 'varchar', 'NULL', $char_d, '', '', + ], + 'primary_key' => 'upgradenum', + 'unique' => [ [ 'upgradenum' ] ], + 'index' => [ [ 'upgrade' ] ], + }, + %{ tables_hashref_torrus() }, # tables of ours for doing torrus virtual port combining diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm index 8f66c66b5..aabc4e72f 100644 --- a/FS/FS/Upgrade.pm +++ b/FS/FS/Upgrade.pm @@ -7,6 +7,7 @@ use Tie::IxHash; use FS::UID qw( dbh driver_name ); use FS::Conf; use FS::Record qw(qsearchs qsearch str2time_sql); +use FS::upgrade_journal; use FS::svc_domain; $FS::svc_domain::whois_hack = 1; diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm index ef8cc6cd8..d2a39aac8 100644 --- a/FS/FS/access_right.pm +++ b/FS/FS/access_right.pm @@ -180,6 +180,44 @@ sub _upgrade_data { # class method } + my @all_groups = qsearch('access_group', {}); + + ### ACL_list_all_customers + if ( !FS::upgrade_journal->is_done('ACL_list_all_customers') ) { + + # grant "List all customers" to all users who have "List customers" + for my $group (@all_groups) { + if ( $group->access_right('List customers') ) { + my $access_right = FS::access_right->new( { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $group->groupnum, + 'rightname' => 'List all customers', + } ); + my $error = $access_right->insert; + die $error if $error; + } + } + + FS::upgrade_journal->set_done('ACL_list_all_customers'); + } + + ### ACL_download_report_data + if ( !FS::upgrade_journal->is_done('ACL_download_report_data') ) { + + # grant to everyone + for my $group (@all_groups) { + my $access_right = FS::access_right->new( { + 'righttype' => 'FS::access_group', + 'rightobjnum' => $group->groupnum, + 'rightname' => 'Download report data', + } ); + my $error = $access_right->insert; + die $error if $error; + } + + FS::upgrade_journal->set_done('ACL_download_report_data'); + } + ''; } diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 3aa75eca5..945771e0d 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2005,6 +2005,36 @@ sub print_csv { '0', # 29 | Other Taxes & Fees*** NUM* 9 ); + } elsif ( lc($opt{'format'}) eq 'oneline' ) { #name? + + my ($previous_balance) = $self->previous; + my $totaldue = sprintf('%.2f', $self->owed + $previous_balance); + my @items = map { + ($_->{pkgnum} || ''), + $_->{description}, + $_->{amount} + } $self->_items_pkg; + + $csv->combine( + $cust_main->agentnum, + $self->custnum, + $cust_main->first, + $cust_main->last, + $cust_main->address1, + $cust_main->address2, + $cust_main->city, + $cust_main->state, + $cust_main->zip, + + # invoice fields + time2str("%x", $self->_date), + $self->invnum, + $self->charged, + $totaldue, + + @items, + ); + } else { $csv->combine( @@ -2044,6 +2074,10 @@ sub print_csv { } + } elsif ( lc($opt{'format'}) eq 'oneline' ) { + + #do nothing + } else { foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index d98d11ecb..ef30809b0 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -113,6 +113,22 @@ books closed flag, empty or `Y' Desired pkgnum when using experimental package balances. +=item bank + +The bank where the payment was deposited. + +=item depositor + +The name of the depositor. + +=item account + +The deposit account number. + +=item teller + +The teller number. + =back =head1 METHODS @@ -493,8 +509,11 @@ sub check { || $self->ut_textn('payunique') || $self->ut_enum('closed', [ '', 'Y' ]) || $self->ut_foreign_keyn('pkgnum', 'cust_pkg', 'pkgnum') + || $self->ut_textn('bank') + || $self->ut_alphan('depositor') + || $self->ut_numbern('account') + || $self->ut_numbern('teller') || $self->payinfo_check() - || $self->ut_numbern('discount_term') ; return $error if $error; @@ -509,6 +528,12 @@ sub check { return "invalid discount_term" if ($self->discount_term && $self->discount_term < 2); + if ( $self->payby eq 'CASH' and $conf->exists('require_cash_deposit_info') ) { + foreach (qw(bank depositor account teller)) { + return "$_ required" if $self->get($_) eq ''; + } + } + #i guess not now, with cust_pay_pending, if we actually make it here, we _do_ want to record it # # UNIQUE index should catch this too, without race conditions, but this # # should give a better error message the other 99.9% of the time... diff --git a/FS/FS/part_event/Condition.pm b/FS/FS/part_event/Condition.pm index ef3d0b561..c75990893 100644 --- a/FS/FS/part_event/Condition.pm +++ b/FS/FS/part_event/Condition.pm @@ -344,7 +344,7 @@ sub condition_sql_option_option { } -#used for part_event/Condition/cust_bill_has_service.pm +#used for part_event/Condition/cust_bill_has_service.pm and has_cust_tag.pm #a little false laziness w/above and condition_sql_option_integer sub condition_sql_option_option_integer { my( $class, $option, $driver_name ) = @_; diff --git a/FS/FS/part_event/Condition/has_cust_tag.pm b/FS/FS/part_event/Condition/has_cust_tag.pm new file mode 100644 index 000000000..cde933881 --- /dev/null +++ b/FS/FS/part_event/Condition/has_cust_tag.pm @@ -0,0 +1,49 @@ +package FS::part_event::Condition::has_cust_tag; + +use strict; + +use base qw( FS::part_event::Condition ); +use FS::Record qw( qsearch ); + +sub description { + 'Customer has tag', +} + +sub eventtable_hashref { + { 'cust_main' => 1, + 'cust_bill' => 1, + 'cust_pkg' => 1, + }; +} + +#something like this +sub option_fields { + ( + 'tagnum' => { 'label' => 'Customer tag', + 'type' => 'select-cust_tag', + 'multiple' => 1, + }, + ); +} + +sub condition { + my( $self, $object ) = @_; + + my $cust_main = $self->cust_main($object); + + my $hashref = $self->option('tagnum') || {}; + grep $hashref->{ $_->tagnum }, $cust_main->cust_tag; +} + +sub condition_sql { + my( $self, $table ) = @_; + + my $matching_tags = + "SELECT tagnum FROM cust_tag WHERE cust_tag.custnum = $table.custnum". + " AND cust_tag.tagnum IN ". + $self->condition_sql_option_option_integer('tagnum'); + + "EXISTS($matching_tags)"; +} + +1; diff --git a/FS/FS/part_pkg/discount_Mixin.pm b/FS/FS/part_pkg/discount_Mixin.pm index 7de3bfe42..7c53a8c4c 100644 --- a/FS/FS/part_pkg/discount_Mixin.pm +++ b/FS/FS/part_pkg/discount_Mixin.pm @@ -84,7 +84,9 @@ sub calc_discount { $amount += $discount->amount if $cust_pkg->pkgpart == $param->{'real_pkgpart'}; $amount += sprintf('%.2f', $discount->percent * $br / 100 ); - my $chg_months = $param->{'months'} || $cust_pkg->part_pkg->freq; + my $chg_months = defined($param->{'months'}) ? + $param->{'months'} : + $cust_pkg->part_pkg->freq; my $months = $discount->months ? min( $chg_months, diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 3a936a972..67b1ffd2b 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -463,7 +463,7 @@ sub assign_ip_addr { my @blocks; my $ip_addr; - if ( $self->blocknum and $self->addr_block->routernum == $self->routernum ) { + if ( $self->addr_block and $self->addr_block->routernum == $self->routernum ) { # simple case: user chose a block, find an address in that block # (this overrides an existing IP address if it's not in the block) @blocks = ($self->addr_block); @@ -482,15 +482,13 @@ sub assign_ip_addr { return ''; } $ip_addr = $block->next_free_addr; - last if $ip_addr; - } - if ( $ip_addr ) { - $self->set(ip_addr => $ip_addr->addr); - return ''; - } - else { - return 'No IP address available on this router'; + if ( $ip_addr ) { + $self->set(ip_addr => $ip_addr->addr); + $self->set(blocknum => $block->blocknum); + return ''; + } } + return 'No IP address available on this router'; } =item assign_router @@ -667,6 +665,38 @@ sub _upgrade_data { ": no routernum in address block ".$addr_block->cidr.", skipped\n"; } } + + # assign blocknums to services that should have them + my @all_blocks = qsearch('addr_block', { }); + SVC: foreach my $self ( + qsearch({ + 'select' => 'svc_broadband.*', + 'table' => 'svc_broadband', + 'addl_from' => 'JOIN router USING (routernum)', + 'hashref' => {}, + 'extra_sql' => 'WHERE svc_broadband.blocknum IS NULL '. + 'AND router.manual_addr IS NULL', + }) + ) { + + next SVC if $self->ip_addr eq ''; + my $NetAddr = $self->NetAddr; + # inefficient, but should only need to run once + foreach my $block (@all_blocks) { + if ($block->NetAddr->contains($NetAddr)) { + $self->set(blocknum => $block->blocknum); + my $error = $self->replace; + warn "WARNING: error assigning blocknum ".$block->blocknum. + " to service ".$self->svcnum."\n$error; skipped\n" + if $error; + next SVC; + } + } + warn "WARNING: no block found containing ".$NetAddr->addr." for service ". + $self->svcnum; + #next SVC; + } + ''; } diff --git a/FS/FS/upgrade_journal.pm b/FS/FS/upgrade_journal.pm new file mode 100644 index 000000000..8f6d121a3 --- /dev/null +++ b/FS/FS/upgrade_journal.pm @@ -0,0 +1,151 @@ +package FS::upgrade_journal; + +use strict; +use base qw( FS::Record ); +use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::upgrade_journal - Object methods for upgrade_journal records + +=head1 SYNOPSIS + + use FS::upgrade_journal; + + $record = new FS::upgrade_journal \%hash; + $record = new FS::upgrade_journal { 'column' => 'value' }; + + $error = $record->insert; + + # Typical use case + my $upgrade = 'rename_all_customers_to_Bob'; + if ( ! FS::upgrade_journal->is_done($upgrade) ) { + ... # do the upgrade, then, if it succeeds + FS::upgrade_journal->set_done($upgrade); + } + +=head1 DESCRIPTION + +An FS::upgrade_journal object records an upgrade procedure that was run +on the database. FS::upgrade_journal inherits from FS::Record. The +following fields are currently supported: + +=over 4 + +=item upgradenum - primary key + +=item _date - unix timestamp when the upgrade was run + +=item upgrade - string identifier for the upgrade procedure; must match /^\w+$/ + +=item status - either 'done' or 'failed' + +=item statustext - any other message that needs to be recorded + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new upgrade record. To add it 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 { 'upgrade_journal'; } + +=item insert + +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 + +sub delete { die "upgrade_journal records can't be deleted" } +sub replace { die "upgrade_journal records can't be modified" } + +=item check + +Checks all fields to make sure this is a valid example. 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; + + if ( !$self->_date ) { + $self->_date(time); + } + + my $error = + $self->ut_numbern('upgradenum') + || $self->ut_number('_date') + || $self->ut_alpha('upgrade') + || $self->ut_text('status') + || $self->ut_textn('statustext') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item is_done UPGRADE + +Returns the upgrade entry with identifier UPGRADE and status 'done', if +there is one. This is an easy way to check whether an upgrade has been done. + +=cut + +sub is_done { + my ($class, $upgrade) = @_; + qsearch('upgrade_journal', { 'status' => 'done', 'upgrade' => $upgrade }) +} + +=item set_done UPGRADE + +Creates and inserts an upgrade entry with the current time, status 'done', +and identifier UPGRADE. Dies on error. + +=cut + +sub set_done { + my ($class, $upgrade) = @_; + my $new = $class->new({ 'status' => 'done', 'upgrade' => $upgrade }); + my $error = $new->insert; + die $error if $error; + $new; +} + + +=head1 BUGS + +Despite how it looks, this is not currently suitable for use as a mutex. + +=head1 SEE ALSO + +L<FS::Record>, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index 49d27b437..55c45b936 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -630,3 +630,5 @@ FS/tower_sector.pm t/tower_sector.t FS/h_svc_cert.pm t/h_svc_cert.t +FS/upgrade_journal.pm +t/upgrade_journal.t diff --git a/FS/t/upgrade_journal.t b/FS/t/upgrade_journal.t new file mode 100644 index 000000000..0822effc5 --- /dev/null +++ b/FS/t/upgrade_journal.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::upgrade_journal; +$loaded=1; +print "ok 1\n"; |
