diff options
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/Conf.pm | 18 | ||||
-rw-r--r-- | FS/FS/Report/Table.pm | 21 | ||||
-rw-r--r-- | FS/FS/Schema.pm | 9 | ||||
-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.pm | 135 |
8 files changed, 282 insertions, 13 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 2bc1f1960..1b01aa64a 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -694,6 +694,13 @@ sub reason_type_options { }, { + 'key' => 'part_pkg-lineage', + 'section' => '', + 'description' => 'When editing a package definition, if setup or recur fees are changed, create a new package rather than changing the existing package.', + 'type' => 'checkbox', + }, + + { 'key' => 'apacheip', #not actually deprecated yet #'section' => 'deprecated', @@ -2284,6 +2291,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.', @@ -2926,7 +2940,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', ], }, { @@ -2962,7 +2976,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 ab853e6ce..5147432a1 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1343,12 +1343,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' ] ], @@ -1690,6 +1695,8 @@ sub tables_hashref { 'no_auto', 'char', 'NULL', 1, '', '', 'recur_show_zero', 'char', 'NULL', 1, '', '', 'setup_show_zero', 'char', 'NULL', 1, '', '', + 'successor', 'int', 'NULL', '', '', '', + 'family_pkgpart','int', 'NULL', '', '', '', ], 'primary_key' => 'pkgpart', 'unique' => [], 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 32751974c..b3948153e 100644 --- a/FS/FS/part_event/Condition.pm +++ b/FS/FS/part_event/Condition.pm @@ -360,7 +360,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.pm b/FS/FS/part_pkg.pm index 373982b84..061001bdc 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -103,6 +103,13 @@ inherits from FS::Record. The following fields are currently supported: =item fcc_ds0s - Optional DS0 equivalency number for FCC form 477 +=item successor - Foreign key for the part_pkg that replaced this record. +If this record is not obsolete, will be null. + +=item family_pkgpart - Foreign key for the part_pkg that was the earliest +ancestor of this record. If this record is not a successor to another +part_pkg, will be equal to pkgpart. + =back =head1 METHODS @@ -192,6 +199,16 @@ sub insert { return $error; } + # set family_pkgpart + if ( $self->get('family_pkgpart') eq '' ) { + $self->set('family_pkgpart' => $self->pkgpart); + $error = $self->SUPER::replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + my $conf = new FS::Conf; if ( $conf->exists('agent_defaultpkg') ) { warn " agent_defaultpkg set; allowing all agents to purchase package" @@ -294,7 +311,7 @@ sub insert { } } - warn " commiting transaction" if $DEBUG; + warn " committing transaction" if $DEBUG and $oldAutoCommit; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -360,6 +377,28 @@ sub replace { my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; + + my $conf = new FS::Conf; + if ( $conf->exists('part_pkg-lineage') ) { + if ( grep { $options->{options}->{$_} ne $old->option($_, 1) } + qw(setup_fee recur_fee) #others? config? + ) { + + warn " superseding package" if $DEBUG; + + my $error = $new->supersede($old, %$options); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + else { + warn " committing transaction" if $DEBUG and $oldAutoCommit; + $dbh->commit if $oldAutoCommit; + return $error; + } + } + #else nothing + } #plandata shit stays in replace for upgrades until after 2.0 (or edit #_upgrade_data) @@ -501,8 +540,18 @@ sub replace { } } } + + # propagate changes to certain core fields + if ( $conf->exists('part_pkg-lineage') ) { + warn " propagating changes to family" if $DEBUG; + my $error = $new->propagate($old); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } - warn " commiting transaction" if $DEBUG; + warn " committing transaction" if $DEBUG and $oldAutoCommit; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; } @@ -573,6 +622,8 @@ sub check { : $self->ut_agentnum_acl('agentnum', \@null_agentnum_right) ) || $self->ut_numbern('fcc_ds0s') + || $self->ut_foreign_keyn('successor', 'part_pkg', 'pkgpart') + || $self->ut_foreign_keyn('family_pkgpart', 'part_pkg', 'pkgpart') || $self->SUPER::check ; return $error if $error; @@ -587,6 +638,76 @@ sub check { ''; } +=item supersede OLD [, OPTION => VALUE ... ] + +Inserts this package as a successor to the package OLD. All options are as +for C<insert>. After inserting, disables OLD and sets the new package as its +successor. + +=cut + +sub supersede { + my ($new, $old, %options) = @_; + my $error; + + $new->set('pkgpart' => ''); + $new->set('family_pkgpart' => $old->family_pkgpart); + warn " inserting successor package\n" if $DEBUG; + $error = $new->insert(%options); + return $error if $error; + + warn " disabling superseded package\n" if $DEBUG; + $old->set('successor' => $new->pkgpart); + $old->set('disabled' => 'Y'); + $error = $old->SUPER::replace; # don't change its options/pkg_svc records + return $error if $error; + + warn " propagating changes to family" if $DEBUG; + $new->propagate($old); +} + +=item propagate OLD + +If any of certain fields have changed from OLD to this package, then, +for all packages in the same lineage as this one, sets those fields +to their values in this package. + +=cut + +my @propagate_fields = ( + qw( pkg classnum setup_cost recur_cost taxclass + setuptax recurtax pay_weight credit_weight + ) +); + +sub propagate { + my $new = shift; + my $old = shift; + my %fields = ( + map { $_ => $new->get($_) } + grep { $new->get($_) ne $old->get($_) } + @propagate_fields + ); + + my @part_pkg = qsearch('part_pkg', { + 'family_pkgpart' => $new->family_pkgpart + }); + my @error; + foreach my $part_pkg ( @part_pkg ) { + my $pkgpart = $part_pkg->pkgpart; + next if $pkgpart == $new->pkgpart; # don't modify $new + warn " propagating to pkgpart $pkgpart\n" if $DEBUG; + foreach ( keys %fields ) { + $part_pkg->set($_, $fields{$_}); + } + # SUPER::replace to avoid changing non-core fields + my $error = $part_pkg->SUPER::replace; + push @error, "pkgpart $pkgpart: $error" + if $error; + } + join("\n", @error); +} + =item pkg_comment [ OPTION => VALUE... ] Returns an (internal) string representing this package. Currently, @@ -1277,7 +1398,7 @@ sub _rebless { } return $self if ref($self) =~ /::$plan$/; #already blessed into plan subclass my $class = ref($self). "::$plan"; - warn "reblessing $self into $class" if $DEBUG; + warn "reblessing $self into $class" if $DEBUG > 1; eval "use $class;"; die $@ if $@; bless($self, $class) unless $@; @@ -1410,6 +1531,14 @@ sub _upgrade_data { # class method die $error if $error; } + # set family_pkgpart on any packages that don't have it + @part_pkg = qsearch('part_pkg', { 'family_pkgpart' => '' }); + foreach my $part_pkg (@part_pkg) { + $part_pkg->set('family_pkgpart' => $part_pkg->pkgpart); + my $error = $part_pkg->SUPER::replace; + die $error if $error; + } + my @part_pkg_option = qsearch('part_pkg_option', { 'optionname' => 'unused_credit', 'optionvalue' => 1, |