From 58f99accce35aa76abe9ff852f6c6ee84e8ce712 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 22 Nov 2008 22:17:28 +0000 Subject: [PATCH] referral credits overhaul, use billing events, agents can self-configure, limit to once-per-customer, depend on any time from referred package, referred customer payment, specific packages, partial staged credits, RT#3983 --- FS/FS/Conf.pm | 8 +- FS/FS/Schema.pm | 1 + FS/FS/cust_credit.pm | 8 +- FS/FS/cust_main.pm | 166 +++++++++++++-------- FS/FS/cust_pkg.pm | 108 +++++++------- FS/FS/part_event.pm | 28 +++- FS/FS/part_event/Action/addpost.pm | 8 +- FS/FS/part_event/Action/apply.pm | 8 +- FS/FS/part_event/Action/bill.pm | 8 +- FS/FS/part_event/Action/cancel.pm | 11 +- FS/FS/part_event/Action/collect.pm | 8 +- FS/FS/part_event/Action/cust_bill_batch.pm | 12 +- FS/FS/part_event/Action/cust_bill_comp.pm | 12 +- FS/FS/part_event/Action/cust_bill_fee_percent.pm | 8 +- FS/FS/part_event/Action/cust_bill_realtime_card.pm | 8 +- .../part_event/Action/cust_bill_realtime_check.pm | 8 +- FS/FS/part_event/Action/cust_bill_realtime_lec.pm | 8 +- FS/FS/part_event/Action/cust_bill_send.pm | 8 +- FS/FS/part_event/Action/cust_bill_send_agent.pm | 4 +- .../part_event/Action/cust_bill_send_alternate.pm | 8 +- FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm | 12 +- .../part_event/Action/cust_bill_send_if_newest.pm | 4 +- FS/FS/part_event/Action/cust_bill_spool_csv.pm | 12 +- .../Action/cust_bill_suspend_if_balance.pm | 14 +- FS/FS/part_event/Action/fee.pm | 10 +- FS/FS/part_event/Action/pkg_referral_credit.pm | 60 ++++++++ FS/FS/part_event/Action/pkg_referral_credit_pkg.pm | 57 +++++++ FS/FS/part_event/Action/suspend.pm | 10 +- FS/FS/part_event/Action/suspend_if_pkgpart.pm | 12 +- FS/FS/part_event/Action/suspend_unless_pkgpart.pm | 12 +- FS/FS/part_event/Condition.pm | 36 ++++- FS/FS/part_event/Condition/balance.pm | 2 +- FS/FS/part_event/Condition/balance_age.pm | 31 +--- FS/FS/part_event/Condition/balance_under.pm | 2 +- FS/FS/part_event/Condition/cust_bill_age.pm | 35 +---- .../part_event/Condition/cust_bill_has_service.pm | 2 +- FS/FS/part_event/Condition/cust_bill_owed.pm | 2 +- FS/FS/part_event/Condition/cust_bill_owed_under.pm | 2 +- FS/FS/part_event/Condition/cust_payments.pm | 43 ++++++ FS/FS/part_event/Condition/has_referral_custnum.pm | 24 +++ FS/FS/part_event/Condition/once_percust.pm | 67 +++++++++ FS/FS/part_event/Condition/pkg_age.pm | 58 +++++++ FS/FS/part_event/Condition/pkg_notchange.pm | 31 ++++ FS/FS/part_event/Condition/pkg_pkgpart.pm | 39 +++++ FS/FS/part_event/Condition/pkg_recurring.pm | 31 ++++ FS/FS/part_event/Condition/pkg_unless_pkgpart.pm | 39 +++++ FS/FS/part_pkg/flat.pm | 8 + 47 files changed, 752 insertions(+), 341 deletions(-) create mode 100644 FS/FS/part_event/Action/pkg_referral_credit.pm create mode 100644 FS/FS/part_event/Action/pkg_referral_credit_pkg.pm create mode 100644 FS/FS/part_event/Condition/cust_payments.pm create mode 100644 FS/FS/part_event/Condition/has_referral_custnum.pm create mode 100644 FS/FS/part_event/Condition/once_percust.pm create mode 100644 FS/FS/part_event/Condition/pkg_age.pm create mode 100644 FS/FS/part_event/Condition/pkg_notchange.pm create mode 100644 FS/FS/part_event/Condition/pkg_pkgpart.pm create mode 100644 FS/FS/part_event/Condition/pkg_recurring.pm create mode 100644 FS/FS/part_event/Condition/pkg_unless_pkgpart.pm diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index b86bfa8e6..b65b71943 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1823,8 +1823,8 @@ worry that config_items is freeside-specific and icky. }, { 'key' => 'referral_credit', - 'section' => 'billing', - 'description' => "Enables one-time referral credits in the amount of one month referred customer's recurring fee (irregardless of frequency).", + 'section' => 'deprecated', + 'description' => "Used to enable one-time referral credits in the amount of one month referred customer's recurring fee (irregardless of frequency). Replace with a billing event on appropriate packages.", 'type' => 'checkbox', }, @@ -2340,8 +2340,8 @@ worry that config_items is freeside-specific and icky. { 'key' => 'referral_credit_type', - 'section' => 'billing', - 'description' => 'The group to use for new, automatically generated credit reasons resulting from referrals.', + 'section' => 'deprecated', + 'description' => 'Used to be the group to use for new, automatically generated credit reasons resulting from referrals. Now set in a package billing event for the referral.', 'type' => 'select-sub', 'options_sub' => sub { require FS::Record; require FS::reason_type; diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 26900b085..73f4f264f 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -558,6 +558,7 @@ sub tables_hashref { 'otaker', 'varchar', '', 32, '', '', 'reason', 'text', 'NULL', '', '', '', 'reasonnum', 'int', 'NULL', '', '', '', + 'addlinfo', 'text', 'NULL', '', '', '', 'closed', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'crednum', diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm index d5b6ff465..99c63cb7f 100644 --- a/FS/FS/cust_credit.pm +++ b/FS/FS/cust_credit.pm @@ -87,6 +87,10 @@ Text ( deprecated ) Reason (see L) +=item addlinfo + +Text + =item closed Books closed flag, empty or `Y' @@ -288,6 +292,7 @@ sub check { || $self->ut_alpha('otaker') || $self->ut_textn('reason') || $self->ut_foreign_key('reasonnum', 'reason', 'reasonnum') + || $self->ut_textn('addlinfo') || $self->ut_enum('closed', [ '', 'Y' ]) ; return $error if $error; @@ -412,7 +417,8 @@ sub reason { $dbh->commit or die $dbh->errstr if $oldAutoCommit; - $reason ? $reason->reason : ''; + ( $reason ? $reason->reason : '' ). + ( $self->addlinfo ? ' '.$self->addlinfo : '' ); } # _upgrade_data diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 5554f9f48..8b57b934c 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -4246,7 +4246,9 @@ sub batch_card { die $error; } - my $unapplied = $self->total_credited + $self->total_unapplied_payments + $self->in_transit_payments; + my $unapplied = $self->total_unapplied_credits + + $self->total_unapplied_payments + + $self->in_transit_payments; foreach my $cust_bill ($self->open_cust_bill) { #$dbh->commit or die $dbh->errstr if $oldAutoCommit; my $cust_bill_pay_batch = new FS::cust_bill_pay_batch { @@ -4273,39 +4275,6 @@ sub batch_card { ''; } -=item total_owed - -Returns the total owed for this customer on all invoices -(see L). - -=cut - -sub total_owed { - my $self = shift; - $self->total_owed_date(2145859200); #12/31/2037 -} - -=item total_owed_date TIME - -Returns the total owed for this customer on all invoices with date earlier than -TIME. TIME is specified as a UNIX timestamp; see L). Also -see L and L for conversion functions. - -=cut - -sub total_owed_date { - my $self = shift; - my $time = shift; - my $total_bill = 0; - foreach my $cust_bill ( - grep { $_->_date <= $time } - qsearch('cust_bill', { 'custnum' => $self->custnum, } ) - ) { - $total_bill += $cust_bill->owed; - } - sprintf( "%.2f", $total_bill ); -} - =item apply_payments_and_credits Applies unapplied payments and credits. @@ -4375,7 +4344,7 @@ sub apply_credits { $self->select_for_update; #mutex - unless ( $self->total_credited ) { + unless ( $self->total_unapplied_credits ) { $dbh->commit or die $dbh->errstr if $oldAutoCommit; return 0; } @@ -4416,11 +4385,11 @@ sub apply_credits { } - my $total_credited = $self->total_credited; + my $total_unapplied_credits = $self->total_unapplied_credits; $dbh->commit or die $dbh->errstr if $oldAutoCommit; - return $total_credited; + return $total_unapplied_credits; } =item apply_payments @@ -4452,11 +4421,13 @@ sub apply_payments { #return 0 unless - my @payments = sort { $b->_date <=> $a->_date } ( grep { $_->unapplied > 0 } - qsearch('cust_pay', { 'custnum' => $self->custnum } ) ); + my @payments = sort { $b->_date <=> $a->_date } + grep { $_->unapplied > 0 } + $self->cust_pay; - my @invoices = sort { $a->_date <=> $b->_date} (grep { $_->owed > 0 } - qsearch('cust_bill', { 'custnum' => $self->custnum } ) ); + my @invoices = sort { $a->_date <=> $b->_date} + grep { $_->owed > 0 } + $self->cust_bill; my $payment; @@ -4495,21 +4466,72 @@ sub apply_payments { return $total_unapplied_payments; } -=item total_credited +=item total_owed + +Returns the total owed for this customer on all invoices +(see L). + +=cut + +sub total_owed { + my $self = shift; + $self->total_owed_date(2145859200); #12/31/2037 +} + +=item total_owed_date TIME + +Returns the total owed for this customer on all invoices with date earlier than +TIME. TIME is specified as a UNIX timestamp; see L). Also +see L and L for conversion functions. + +=cut + +sub total_owed_date { + my $self = shift; + my $time = shift; + my $total_bill = 0; + foreach my $cust_bill ( + grep { $_->_date <= $time } + qsearch('cust_bill', { 'custnum' => $self->custnum, } ) + ) { + $total_bill += $cust_bill->owed; + } + sprintf( "%.2f", $total_bill ); +} + +=item total_paid + +Returns the total amount of all payments. + +=cut + +sub total_paid { + my $self = shift; + my $total = 0; + $total += $_->paid foreach $self->cust_pay; + sprintf( "%.2f", $total ); +} + +=item total_unapplied_credits Returns the total outstanding credit (see L) for this customer. See L. +=item total_credited + +Old name for total_unapplied_credits. Don't use. + =cut sub total_credited { + #carp "total_credited deprecated, use total_unapplied_credits"; + shift->total_unapplied_credits(@_); +} + +sub total_unapplied_credits { my $self = shift; my $total_credit = 0; - foreach my $cust_credit ( qsearch('cust_credit', { - 'custnum' => $self->custnum, - } ) ) { - $total_credit += $cust_credit->credited; - } + $total_credit += $_->credited foreach $self->cust_credit; sprintf( "%.2f", $total_credit ); } @@ -4523,11 +4545,7 @@ See L. sub total_unapplied_payments { my $self = shift; my $total_unapplied = 0; - foreach my $cust_pay ( qsearch('cust_pay', { - 'custnum' => $self->custnum, - } ) ) { - $total_unapplied += $cust_pay->unapplied; - } + $total_unapplied += $_->unapplied foreach $self->cust_pay; sprintf( "%.2f", $total_unapplied ); } @@ -4541,18 +4559,14 @@ customer. See L. sub total_unapplied_refunds { my $self = shift; my $total_unapplied = 0; - foreach my $cust_refund ( qsearch('cust_refund', { - 'custnum' => $self->custnum, - } ) ) { - $total_unapplied += $cust_refund->unapplied; - } + $total_unapplied += $_->unapplied foreach $self->cust_refund; sprintf( "%.2f", $total_unapplied ); } =item balance Returns the balance for this customer (total_owed plus total_unrefunded, minus -total_credited minus total_unapplied_payments). +total_unapplied_credits minus total_unapplied_payments). =cut @@ -4561,7 +4575,7 @@ sub balance { sprintf( "%.2f", $self->total_owed + $self->total_unapplied_refunds - - $self->total_credited + - $self->total_unapplied_credits - $self->total_unapplied_payments ); } @@ -4582,7 +4596,7 @@ sub balance_date { sprintf( "%.2f", $self->total_owed_date($time) + $self->total_unapplied_refunds - - $self->total_credited + - $self->total_unapplied_credits - $self->total_unapplied_payments ); } @@ -4870,21 +4884,47 @@ sub referring_cust_main { qsearchs('cust_main', { 'custnum' => $self->referral_custnum } ); } -=item credit AMOUNT, REASON +=item credit AMOUNT, REASON [ , OPTION => VALUE ... ] Applies a credit to this customer. If there is an error, returns the error, otherwise returns false. +REASON can be a text string, an FS::reason object, or a scalar reference to +a reasonnum. If a text string, it will be automatically inserted as a new +reason, and a 'reason_type' option must be passed to indicate the +FS::reason_type for the new reason. + +An I option may be passed to set the credit's I field. + +Any other options are passed to FS::cust_credit::insert. + =cut sub credit { my( $self, $amount, $reason, %options ) = @_; + my $cust_credit = new FS::cust_credit { 'custnum' => $self->custnum, 'amount' => $amount, - 'reason' => $reason, }; + + if ( ref($reason) ) { + + if ( ref($reason) eq 'SCALAR' ) { + $cust_credit->reasonnum( $$reason ); + } else { + $cust_credit->reasonnum( $reason->reasonnum ); + } + + } else { + $cust_credit->set('reason', $reason) + } + + $cust_credit->addlinfo( delete $options{'addlinfo'} ) + if exists($options{'addlinfo'}); + $cust_credit->insert(%options); + } =item charge AMOUNT [ PKG [ COMMENT [ TAXCLASS ] ] ] @@ -5474,7 +5514,7 @@ sub balance_sql { " Returns an SQL fragment to retreive the balance for this customer, only considering invoices with date earlier than START_TIME, and optionally not -later than END_TIME (total_owed_date minus total_credited minus +later than END_TIME (total_owed_date minus total_unapplied_credits minus total_unapplied_payments). Times are specified as SQL fragments or numeric diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 6d2c6018b..e359fc9fc 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -104,38 +104,76 @@ inherits from FS::Record. The following fields are currently supported: =over 4 -=item pkgnum - primary key (assigned automatically for new billing items) +=item pkgnum -=item custnum - Customer (see L) +primary key (assigned automatically for new billing items) -=item pkgpart - Billing item definition (see L) +=item custnum -=item setup - date +Customer (see L) -=item bill - date (next bill date) +=item pkgpart + +Billing item definition (see L) -=item last_bill - last bill date +=item setup -=item adjourn - date +date -=item susp - date +=item bill -=item expire - date +date (next bill date) -=item cancel - date +=item last_bill -=item otaker - order taker (assigned automatically if null, see L) +last bill date -=item manual_flag - If this field is set to 1, disables the automatic -unsuspension of this package when using the B config file. +=item adjourn -=item quantity - If not set, defaults to 1 +date + +=item susp + +date + +=item expire + +date + +=item cancel + +date + +=item otaker + +order taker (assigned automatically if null, see L) + +=item manual_flag + +If this field is set to 1, disables the automatic +unsuspension of this package when using the B config option. + +=item quantity + +If not set, defaults to 1 + +=item change_date + +Date of change from previous package + +=item change_pkgnum + +Previous pkgnum + +=item change_pkgpart + +Previous pkgpart =back -Note: setup, bill, adjourn, susp, expire and cancel are specified as UNIX timestamps; -see L. Also see L and L for -conversion functions. +Note: setup, last_bill, bill, adjourn, susp, expire, cancel and change_date +are specified as UNIX timestamps; see L. Also see +L and L for conversion functions. =head1 METHODS @@ -223,42 +261,6 @@ sub insert { #} my $conf = new FS::Conf; - my $cust_main = $self->cust_main; - my $part_pkg = $self->part_pkg; - if ( $conf->exists('referral_credit') - && $cust_main->referral_custnum - && ! $options{'change'} - && $part_pkg->freq !~ /^0\D?$/ - ) - { - my $referring_cust_main = $cust_main->referring_cust_main; - if ( $referring_cust_main->status ne 'cancelled' ) { - my $error; - if ( $part_pkg->freq !~ /^\d+$/ ) { - warn 'WARNING: Not crediting customer '. $cust_main->referral_custnum. - ' for package '. $self->pkgnum. - ' ( customer '. $self->custnum. ')'. - ' - One-time referral credits not (yet) available for '. - ' packages with '. $part_pkg->freq_pretty. ' frequency'; - } else { - - my $amount = sprintf( "%.2f", $part_pkg->base_recur / $part_pkg->freq ); - my $error = - $referring_cust_main-> - credit( $amount, - 'Referral credit for '.$cust_main->name, - 'reason_type' => $conf->config('referral_credit_type') - ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "Error crediting customer ". $cust_main->referral_custnum. - " for referral: $error"; - } - - } - - } - } if ($conf->config('welcome_letter') && $self->cust_main->num_pkgs == 1) { my $queue = new FS::queue { diff --git a/FS/FS/part_event.pm b/FS/FS/part_event.pm index d0ab65e3f..6f2c5366d 100644 --- a/FS/FS/part_event.pm +++ b/FS/FS/part_event.pm @@ -286,18 +286,32 @@ i.e. 'cust_main'=>'cust_main.custnum' =cut sub eventtable_pkey_sql { - #my $class = shift; + my $class = shift; - my %hash = ( - 'cust_main' => 'cust_main.custnum', - 'cust_bill' => 'cust_bill.invnum', - 'cust_pkg' => 'cust_pkg.pkgnum', - 'cust_pay_batch' => 'cust_pay_batch.paybatchnum', - ); + my $hashref = $class->eventtable_pkey; + + my %hash = map { $_ => "$_.". $hashref->{$_} } keys %$hashref; \%hash; } +=item eventtable_pkey + +Returns a hash reference of full SQL primary key names for eventtable values, +i.e. 'cust_main'=>'custnum' + +=cut + +sub eventtable_pkey { + #my $class = shift; + + { + 'cust_main' => 'custnum', + 'cust_bill' => 'invnum', + 'cust_pkg' => 'pkgnum', + 'cust_pay_batch' => 'paybatchnum', + }; +} =item eventtables diff --git a/FS/FS/part_event/Action/addpost.pm b/FS/FS/part_event/Action/addpost.pm index e0e3fa878..f92e72ea0 100644 --- a/FS/FS/part_event/Action/addpost.pm +++ b/FS/FS/part_event/Action/addpost.pm @@ -3,13 +3,9 @@ package FS::part_event::Action::addpost; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Add postal invoicing'; -} +sub description { 'Add postal invoicing'; } -sub default_weight { - 20; -} +sub default_weight { 20; } sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/apply.pm b/FS/FS/part_event/Action/apply.pm index f91c6047e..823d1e0d3 100644 --- a/FS/FS/part_event/Action/apply.pm +++ b/FS/FS/part_event/Action/apply.pm @@ -7,13 +7,9 @@ sub description { 'Apply unapplied payments and credits'; } -sub deprecated { - 1; -} +sub deprecated { 1; } -sub default_weight { - 70; -} +sub default_weight { 70; } sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/bill.pm b/FS/FS/part_event/Action/bill.pm index fec025f62..b96614d21 100644 --- a/FS/FS/part_event/Action/bill.pm +++ b/FS/FS/part_event/Action/bill.pm @@ -8,13 +8,9 @@ sub description { 'Generate invoices (normally only used with a Late Fee event)'; } -sub deprecated { - 1; -} +sub deprecated { 1; } -sub default_weight { - 60; -} +sub default_weight { 60; } sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/cancel.pm b/FS/FS/part_event/Action/cancel.pm index 94f314602..b9d6d2996 100644 --- a/FS/FS/part_event/Action/cancel.pm +++ b/FS/FS/part_event/Action/cancel.pm @@ -3,9 +3,7 @@ package FS::part_event::Action::cancel; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Cancel'; -} +sub description { 'Cancel'; } sub option_fields { ( @@ -14,13 +12,10 @@ sub option_fields { 'reason_class' => 'C', }, ); - -}; - -sub default_weight { - 20; } +sub default_weight { 20; } + sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/collect.pm b/FS/FS/part_event/Action/collect.pm index fa94b7def..98814404e 100644 --- a/FS/FS/part_event/Action/collect.pm +++ b/FS/FS/part_event/Action/collect.pm @@ -8,13 +8,9 @@ sub description { 'Collect on invoices (normally only used with a Late Fee and Generate Invoice events)'; } -sub deprecated { - 1; -} +sub deprecated { 1; } -sub default_weight { - 80; -} +sub default_weight { 80; } sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_batch.pm b/FS/FS/part_event/Action/cust_bill_batch.pm index aec09250b..50c306a45 100644 --- a/FS/FS/part_event/Action/cust_bill_batch.pm +++ b/FS/FS/part_event/Action/cust_bill_batch.pm @@ -3,21 +3,15 @@ package FS::part_event::Action::cust_bill_batch; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Add card or check to a pending batch'; -} +sub description { 'Add card or check to a pending batch'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; } -sub default_weight { - 40; -} +sub default_weight { 40; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_comp.pm b/FS/FS/part_event/Action/cust_bill_comp.pm index 636a66df5..76fd27414 100644 --- a/FS/FS/part_event/Action/cust_bill_comp.pm +++ b/FS/FS/part_event/Action/cust_bill_comp.pm @@ -3,21 +3,15 @@ package FS::part_event::Action::cust_bill_comp; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Pay invoice with a complimentary "payment"'; -} +sub description { 'Pay invoice with a complimentary "payment"'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; } -sub default_weight { - 30; -} +sub default_weight { 30; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_fee_percent.pm b/FS/FS/part_event/Action/cust_bill_fee_percent.pm index 100fc8bc3..570fd6316 100644 --- a/FS/FS/part_event/Action/cust_bill_fee_percent.pm +++ b/FS/FS/part_event/Action/cust_bill_fee_percent.pm @@ -3,9 +3,7 @@ package FS::part_event::Action::cust_bill_fee_percent; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Late fee (percentage of invoice)'; -} +sub description { 'Late fee (percentage of invoice)'; } sub eventtable_hashref { { 'cust_bill' => 1 }; @@ -18,9 +16,7 @@ sub option_fields { ); } -sub default_weight { - 10; -} +sub default_weight { 10; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_realtime_card.pm b/FS/FS/part_event/Action/cust_bill_realtime_card.pm index 471c946dc..c1fdba96a 100644 --- a/FS/FS/part_event/Action/cust_bill_realtime_card.pm +++ b/FS/FS/part_event/Action/cust_bill_realtime_card.pm @@ -8,17 +8,13 @@ sub description { 'Run card with a Business::OnlinePayment realtime gateway'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; } -sub default_weight { - 30; -} +sub default_weight { 30; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_realtime_check.pm b/FS/FS/part_event/Action/cust_bill_realtime_check.pm index 9a52830ae..11b13a970 100644 --- a/FS/FS/part_event/Action/cust_bill_realtime_check.pm +++ b/FS/FS/part_event/Action/cust_bill_realtime_check.pm @@ -8,17 +8,13 @@ sub description { 'Run check with a Business::OnlinePayment realtime gateway'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; } -sub default_weight { - 30; -} +sub default_weight { 30; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm b/FS/FS/part_event/Action/cust_bill_realtime_lec.pm index db091dadb..cd03ddc3b 100644 --- a/FS/FS/part_event/Action/cust_bill_realtime_lec.pm +++ b/FS/FS/part_event/Action/cust_bill_realtime_lec.pm @@ -8,17 +8,13 @@ sub description { 'Run phone bill ("LEC") billing with a Business::OnlinePayment realtime gateway'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; } -sub default_weight { - 30; -} +sub default_weight { 30; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_send.pm b/FS/FS/part_event/Action/cust_bill_send.pm index 9330c6113..663caf174 100644 --- a/FS/FS/part_event/Action/cust_bill_send.pm +++ b/FS/FS/part_event/Action/cust_bill_send.pm @@ -3,17 +3,13 @@ package FS::part_event::Action::cust_bill_send; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Send invoice (email/print/fax)'; -} +sub description { 'Send invoice (email/print/fax)'; } sub eventtable_hashref { { 'cust_bill' => 1 }; } -sub default_weight { - 50; -} +sub default_weight { 50; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_send_agent.pm b/FS/FS/part_event/Action/cust_bill_send_agent.pm index fcf000736..670a32c5b 100644 --- a/FS/FS/part_event/Action/cust_bill_send_agent.pm +++ b/FS/FS/part_event/Action/cust_bill_send_agent.pm @@ -24,9 +24,7 @@ sub option_fields { ); } -sub default_weight { - 50; -} +sub default_weight { 50; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_send_alternate.pm b/FS/FS/part_event/Action/cust_bill_send_alternate.pm index 6afb89a99..cfd9264d8 100644 --- a/FS/FS/part_event/Action/cust_bill_send_alternate.pm +++ b/FS/FS/part_event/Action/cust_bill_send_alternate.pm @@ -3,9 +3,7 @@ package FS::part_event::Action::cust_bill_send_alternate; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Send invoice (email/print/fax) with alternate template'; -} +sub description { 'Send invoice (email/print/fax) with alternate template'; } sub eventtable_hashref { { 'cust_bill' => 1 }; @@ -19,9 +17,7 @@ sub option_fields { ); } -sub default_weight { - 50; -} +sub default_weight { 50; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm index db3554e01..bf472683f 100644 --- a/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm +++ b/FS/FS/part_event/Action/cust_bill_send_csv_ftp.pm @@ -3,13 +3,9 @@ package FS::part_event::Action::cust_bill_send_csv_ftp; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Upload CSV invoice data to an FTP server'; -} +sub description { 'Upload CSV invoice data to an FTP server'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; @@ -31,9 +27,7 @@ sub option_fields { ); } -sub default_weight { - 50; -} +sub default_weight { 50; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_send_if_newest.pm b/FS/FS/part_event/Action/cust_bill_send_if_newest.pm index 916983ebe..083da8b08 100644 --- a/FS/FS/part_event/Action/cust_bill_send_if_newest.pm +++ b/FS/FS/part_event/Action/cust_bill_send_if_newest.pm @@ -24,9 +24,7 @@ sub option_fields { ); } -sub default_weight { - 50; -} +sub default_weight { 50; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_spool_csv.pm b/FS/FS/part_event/Action/cust_bill_spool_csv.pm index 4300b6120..f20ee46c9 100644 --- a/FS/FS/part_event/Action/cust_bill_spool_csv.pm +++ b/FS/FS/part_event/Action/cust_bill_spool_csv.pm @@ -3,13 +3,9 @@ package FS::part_event::Action::cust_bill_spool_csv; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Spool CSV invoice data'; -} +sub description { 'Spool CSV invoice data'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; @@ -43,9 +39,7 @@ sub option_fields { ); } -sub default_weight { - 50; -} +sub default_weight { 50; } sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm b/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm index 655994963..13188ab7c 100644 --- a/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm +++ b/FS/FS/part_event/Action/cust_bill_suspend_if_balance.pm @@ -3,13 +3,9 @@ package FS::part_event::Action::cust_bill_suspend_if_balance; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Suspend if balance (this invoice and previous) over'; -} +sub description { 'Suspend if balance (this invoice and previous) over'; } -sub deprecated { - 1; -} +sub deprecated { 1; } sub eventtable_hashref { { 'cust_bill' => 1 }; @@ -23,12 +19,10 @@ sub option_fields { 'reason_class' => 'S', }, ); -}; - -sub default_weight { - 10; } +sub default_weight { 10; } + sub do_action { my( $self, $cust_bill ) = @_; diff --git a/FS/FS/part_event/Action/fee.pm b/FS/FS/part_event/Action/fee.pm index 81a84498a..3cf50fbca 100644 --- a/FS/FS/part_event/Action/fee.pm +++ b/FS/FS/part_event/Action/fee.pm @@ -3,21 +3,17 @@ package FS::part_event::Action::fee; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Late fee (flat)'; -} +sub description { 'Late fee (flat)'; } sub option_fields { ( 'charge' => { label=>'Amount', type=>'money', }, # size=>7, }, 'reason' => 'Reason', ); -}; - -sub default_weight { - 10; } +sub default_weight { 10; } + sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/pkg_referral_credit.pm b/FS/FS/part_event/Action/pkg_referral_credit.pm new file mode 100644 index 000000000..98d982066 --- /dev/null +++ b/FS/FS/part_event/Action/pkg_referral_credit.pm @@ -0,0 +1,60 @@ +package FS::part_event::Action::pkg_referral_credit; + +use strict; +use base qw( FS::part_event::Action ); + +sub description { 'Credit the referring customer a specific amount'; } + +sub eventtable_hashref { + { 'cust_pkg' => 1 }; +} + +sub option_fields { + ( + 'reasonnum' => { 'label' => 'Credit reason', + 'type' => 'select-reason', + 'reason_class' => 'R', + }, + 'amount' => { 'label' => 'Credit amount', + 'type' => 'money', + }, + ); + +} + +#a little false laziness w/pkg_referral_credit_pkg +sub do_action { + my( $self, $cust_pkg ) = @_; + + my $cust_main = $self->cust_main($cust_pkg); + +# my $part_pkg = $cust_pkg->part_pkg; + + return 'No referring customer' unless $cust_main->referral_custnum; + + my $referring_cust_main = $cust_main->referring_cust_main; + return 'Referring customer is cancelled' + if $referring_cust_main->status eq 'cancelled'; + + my $amount = $self->_calc_referral_credit($cust_pkg); + my $reasonnum = $self->option('reasonnum'); + + my $error = $referring_cust_main->credit( + $amount, + \$reasonnum, + 'addlinfo' => + 'for customer #'. $cust_main->display_custnum. ': '.$cust_main->name, + ); + die "Error crediting customer ". $cust_main->referral_custnum. + " for referral: $error" + if $error; + +} + +sub _calc_referral_credit { + my( $self, $cust_pkg ) = @_; + + $self->option('amount'); +} + +1; diff --git a/FS/FS/part_event/Action/pkg_referral_credit_pkg.pm b/FS/FS/part_event/Action/pkg_referral_credit_pkg.pm new file mode 100644 index 000000000..08cf9a8fd --- /dev/null +++ b/FS/FS/part_event/Action/pkg_referral_credit_pkg.pm @@ -0,0 +1,57 @@ +package FS::part_event::Action::pkg_referral_credit_pkg; + +use strict; +use base qw( FS::part_event::Action::pkg_referral_credit ); + +sub description { 'Credit the referring customer an amount based on the referred package'; } + +#sub eventtable_hashref { +# { 'cust_pkg' => 1 }; +#} + +sub option_fields { + ( + 'reasonnum' => { 'label' => 'Credit reason', + 'type' => 'select-reason', + 'reason_class' => 'R', + }, + 'percent' => { 'label' => 'Percent', + 'type' => 'input-percentage', + 'default' => '100', + }, + 'what' => { 'label' => 'Of', + 'type' => 'select', + #also add some way to specify in the package def, no? + 'options' => [ qw( base_recur_permonth ) ], + 'labels' => { 'base_recur_permonth' => 'Base monthly fee', }, + }, + ); + +} + +sub _calc_referral_credit { + my( $self, $cust_pkg ) = @_; + + my $cust_main = $self->cust_main($cust_pkg); + + my $part_pkg = $cust_pkg->part_pkg; + + my $what = $self->option('what'); + + if ( $what eq 'base_recur_permonth' ) { #huh. yuck. + if ( $part_pkg->freq !~ /^\d+$/ ) { + die 'WARNING: Not crediting customer '. $cust_main->referral_custnum. + ' for package '. $cust_pkg->pkgnum. + ' ( customer '. $cust_pkg->custnum. ')'. + ' - Referral credits not (yet) available for '. + ' packages with '. $part_pkg->freq_pretty. ' frequency'; + } + } + + my $percent = $self->option('percent'); + + sprintf('%.2f', $part_pkg->$what($cust_pkg) * $percent / 100 ); + +} + +1; diff --git a/FS/FS/part_event/Action/suspend.pm b/FS/FS/part_event/Action/suspend.pm index ec440ffd2..c77728e61 100644 --- a/FS/FS/part_event/Action/suspend.pm +++ b/FS/FS/part_event/Action/suspend.pm @@ -3,9 +3,7 @@ package FS::part_event::Action::suspend; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Suspend'; -} +sub description { 'Suspend'; } sub option_fields { ( @@ -14,12 +12,10 @@ sub option_fields { 'reason_class' => 'S', }, ); -}; - -sub default_weight { - 10; } +sub default_weight { 10; } + sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/suspend_if_pkgpart.pm b/FS/FS/part_event/Action/suspend_if_pkgpart.pm index 9bdc9be53..6f2007c93 100644 --- a/FS/FS/part_event/Action/suspend_if_pkgpart.pm +++ b/FS/FS/part_event/Action/suspend_if_pkgpart.pm @@ -3,9 +3,9 @@ package FS::part_event::Action::suspend_if_pkgpart; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Suspend packages'; -} +sub description { 'Suspend packages'; } + +#i should be deprecated in favor of using the if_pkgpart condition sub option_fields { ( @@ -18,12 +18,10 @@ sub option_fields { 'reason_class' => 'S', }, ); -}; - -sub default_weight { - 10; } +sub default_weight { 10; } + sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Action/suspend_unless_pkgpart.pm b/FS/FS/part_event/Action/suspend_unless_pkgpart.pm index f9bf1e860..efc7a2d00 100644 --- a/FS/FS/part_event/Action/suspend_unless_pkgpart.pm +++ b/FS/FS/part_event/Action/suspend_unless_pkgpart.pm @@ -3,9 +3,9 @@ package FS::part_event::Action::suspend_unless_pkgpart; use strict; use base qw( FS::part_event::Action ); -sub description { - 'Suspend packages except'; -} +sub description { 'Suspend packages except'; } + +#i should be deprecated in favor of using the unless_pkgpart condition sub option_fields { ( @@ -18,12 +18,10 @@ sub option_fields { 'reason_class' => 'S', }, ); -}; - -sub default_weight { - 10; } +sub default_weight { 10; } + sub do_action { my( $self, $cust_object ) = @_; diff --git a/FS/FS/part_event/Condition.pm b/FS/FS/part_event/Condition.pm index 2b71fbb77..544b560b6 100644 --- a/FS/FS/part_event/Condition.pm +++ b/FS/FS/part_event/Condition.pm @@ -2,7 +2,7 @@ package FS::part_event::Condition; use strict; use base qw( FS::part_event_condition ); - +use Time::Local qw(timelocal_nocheck); use FS::UID qw( driver_name ); =head1 NAME @@ -251,6 +251,40 @@ sub option_label { =back +=item option_age_from OPTION FROM_TIMESTAMP + +Retreives a condition option, parses it from a frequency (such as "1d", "1w" or +"12m"), and subtracts that interval from the supplied timestamp. It is +primarily intended for use in B. + +=cut + +sub option_age_from { + my( $self, $option, $time ) = @_; + my $age = $self->option($option); + $age = '0m' unless length($age); + + my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5]; + + if ( $age =~ /^(\d+)m$/i ) { + $mon -= $1; + until ( $mon >= 0 ) { $mon += 12; $year--; } + } elsif ( $age =~ /^(\d+)y$/i ) { + $year -= $1; + } elsif ( $age =~ /^(\d+)w$/i ) { + $mday -= $1 * 7; + } elsif ( $age =~ /^(\d+)d$/i ) { + $mday -= $1; + } elsif ( $age =~ /^(\d+)h$/i ) { + $hour -= $hour; + } else { + die "unparsable age: $age"; + } + + timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year); + +} + =item condition_sql_option OPTION This is a class method that returns an SQL fragment for retreiving a condition diff --git a/FS/FS/part_event/Condition/balance.pm b/FS/FS/part_event/Condition/balance.pm index 263941351..65670c030 100644 --- a/FS/FS/part_event/Condition/balance.pm +++ b/FS/FS/part_event/Condition/balance.pm @@ -40,7 +40,7 @@ sub condition_sql { my $balance_sql = FS::cust_main->balance_sql; - "$balance_sql > $over"; + "$balance_sql > CAST( $over AS numeric )"; } diff --git a/FS/FS/part_event/Condition/balance_age.pm b/FS/FS/part_event/Condition/balance_age.pm index ec3624a6d..f1a970796 100644 --- a/FS/FS/part_event/Condition/balance_age.pm +++ b/FS/FS/part_event/Condition/balance_age.pm @@ -1,9 +1,6 @@ package FS::part_event::Condition::balance_age; -require 5.006; use strict; -use Time::Local qw(timelocal_nocheck); - use base qw( FS::part_event::Condition ); sub description { 'Customer balance age'; } @@ -28,29 +25,9 @@ sub condition { my $over = $self->option('balance'); $over = 0 unless length($over); - #false laziness w/cust_bill_age - my $time = $opt{'time'}; - my $age = $self->option('age'); - $age = '0m' unless length($age); - - my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5]; - if ( $age =~ /^(\d+)m$/i ) { - $mon -= $1; - until ( $mon >= 0 ) { $mon += 12; $year--; } - } elsif ( $age =~ /^(\d+)y$/i ) { - $year -= $1; - } elsif ( $age =~ /^(\d+)w$/i ) { - $mday -= $1 * 7; - } elsif ( $age =~ /^(\d+)d$/i ) { - $mday -= $1; - } elsif ( $age =~ /^(\d+)h$/i ) { - $hour -= $hour; - } else { - die "unparsable age: $age"; - } - my $age_date = timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year); - - $cust_main->balance_date($age_date) > $over; + my $age = $self->option_age_from('age', $opt{'time'} ); + + $cust_main->balance_date($age) > $over; } sub condition_sql { @@ -61,7 +38,7 @@ sub condition_sql { my $balance_sql = FS::cust_main->balance_date_sql( $age ); - "$balance_sql > $over"; + "$balance_sql > CAST( $over AS numeric )"; } sub order_sql { diff --git a/FS/FS/part_event/Condition/balance_under.pm b/FS/FS/part_event/Condition/balance_under.pm index 5e1903468..9c7159011 100644 --- a/FS/FS/part_event/Condition/balance_under.pm +++ b/FS/FS/part_event/Condition/balance_under.pm @@ -34,7 +34,7 @@ sub condition_sql { my $balance_sql = FS::cust_main->balance_sql; - "$balance_sql <= $under"; + "$balance_sql <= CAST( $under AS numeric )"; } diff --git a/FS/FS/part_event/Condition/cust_bill_age.pm b/FS/FS/part_event/Condition/cust_bill_age.pm index 5c1e46869..f34367320 100644 --- a/FS/FS/part_event/Condition/cust_bill_age.pm +++ b/FS/FS/part_event/Condition/cust_bill_age.pm @@ -1,14 +1,9 @@ package FS::part_event::Condition::cust_bill_age; -require 5.006; use strict; -use Time::Local qw(timelocal_nocheck); - use base qw( FS::part_event::Condition ); -sub description { - 'Invoice age'; -} +sub description { 'Invoice age'; } sub eventtable_hashref { { 'cust_main' => 0, @@ -17,10 +12,8 @@ sub eventtable_hashref { }; } -#something like this sub option_fields { ( - #'days' => { label=>'Days', size=>3, }, 'age' => { label=>'Age', type=>'freq', }, ); } @@ -28,34 +21,12 @@ sub option_fields { sub condition { my( $self, $cust_bill, %opt ) = @_; - #false laziness w/balance_age - my $time = $opt{'time'}; - my $age = $self->option('age'); - $age = '0m' unless length($age); + my $age = $self->option_age_from('age', $opt{'time'} ); - my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5]; - if ( $age =~ /^(\d+)m$/i ) { - $mon -= $1; - until ( $mon >= 0 ) { $mon += 12; $year--; } - } elsif ( $age =~ /^(\d+)y$/i ) { - $year -= $1; - } elsif ( $age =~ /^(\d+)w$/i ) { - $mday -= $1 * 7; - } elsif ( $age =~ /^(\d+)d$/i ) { - $mday -= $1; - } elsif ( $age =~ /^(\d+)h$/i ) { - $hour -= $hour; - } else { - die "unparsable age: $age"; - } - my $age_date = timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year); - - $cust_bill->_date <= $age_date; + $cust_bill->_date <= $age; } -# and seconds <= $time - cust_bill._date - sub condition_sql { my( $class, $table, %opt ) = @_; diff --git a/FS/FS/part_event/Condition/cust_bill_has_service.pm b/FS/FS/part_event/Condition/cust_bill_has_service.pm index 7e63e0e6d..91d75ddac 100644 --- a/FS/FS/part_event/Condition/cust_bill_has_service.pm +++ b/FS/FS/part_event/Condition/cust_bill_has_service.pm @@ -45,7 +45,7 @@ sub condition_sql { FROM cust_bill_pkg cbp, cust_svc cs WHERE cbp.invnum = cust_bill.invnum AND cs.pkgnum = cbp.pkgnum - AND cs.svcpart = $servicenum + AND cs.svcpart = CAST( $servicenum AS integer ) ) |; return $sql; diff --git a/FS/FS/part_event/Condition/cust_bill_owed.pm b/FS/FS/part_event/Condition/cust_bill_owed.pm index 5e582ef69..0fd992282 100644 --- a/FS/FS/part_event/Condition/cust_bill_owed.pm +++ b/FS/FS/part_event/Condition/cust_bill_owed.pm @@ -48,7 +48,7 @@ sub condition_sql { my $owed_sql = FS::cust_bill->owed_sql; - "$owed_sql > $over"; + "$owed_sql > CAST( $over AS numeric )"; } 1; diff --git a/FS/FS/part_event/Condition/cust_bill_owed_under.pm b/FS/FS/part_event/Condition/cust_bill_owed_under.pm index 460e6a4be..a0bf92f27 100644 --- a/FS/FS/part_event/Condition/cust_bill_owed_under.pm +++ b/FS/FS/part_event/Condition/cust_bill_owed_under.pm @@ -43,7 +43,7 @@ sub condition_sql { my $owed_sql = FS::cust_bill->owed_sql; - "$owed_sql <= $under"; + "$owed_sql <= CAST( $under AS numeric )"; } 1; diff --git a/FS/FS/part_event/Condition/cust_payments.pm b/FS/FS/part_event/Condition/cust_payments.pm new file mode 100644 index 000000000..41ef6c74a --- /dev/null +++ b/FS/FS/part_event/Condition/cust_payments.pm @@ -0,0 +1,43 @@ +package FS::part_event::Condition::cust_payments; + +use strict; +use base qw( FS::part_event::Condition ); + +sub description { 'Customer total payments'; } + +sub option_fields { + ( + 'over' => { 'label' => 'Customer total payments at least', + 'type' => 'money', + 'value' => '0.00', #default + }, + ); +} + +sub condition { + my($self, $object) = @_; + + my $cust_main = $self->cust_main($object); + + my $over = $self->option('over'); + $over = 0 unless length($over); + + $cust_main->total_paid >= $over; + +} + +#XXX add for efficiency. could use cust_main::total_paid_sql +#use FS::cust_main; +#sub condition_sql { +# my( $class, $table ) = @_; +# +# my $over = $class->condition_sql_option('balance'); +# +# my $balance_sql = FS::cust_main->balance_sql; +# +# "$balance_sql > $over"; +# +#} + +1; + diff --git a/FS/FS/part_event/Condition/has_referral_custnum.pm b/FS/FS/part_event/Condition/has_referral_custnum.pm new file mode 100644 index 000000000..d43d6c0c7 --- /dev/null +++ b/FS/FS/part_event/Condition/has_referral_custnum.pm @@ -0,0 +1,24 @@ +package FS::part_event::Condition::has_referral_custnum; + +use strict; +use FS::cust_main; + +use base qw( FS::part_event::Condition ); + +sub description { 'Customer has a referring customer'; } + +sub condition { + my($self, $object) = @_; + + my $cust_main = $self->cust_main($object); + + $cust_main->referral_custnum; +} + +sub condition_sql { + #my( $class, $table ) = @_; + + "cust_main.referral_custnum IS NOT NULL"; +} + +1; diff --git a/FS/FS/part_event/Condition/once_percust.pm b/FS/FS/part_event/Condition/once_percust.pm new file mode 100644 index 000000000..b8a8fbfb6 --- /dev/null +++ b/FS/FS/part_event/Condition/once_percust.pm @@ -0,0 +1,67 @@ +package FS::part_event::Condition::once_percust; + +use strict; +use FS::Record qw( qsearch ); +use FS::part_event; +use FS::cust_event; + +use base qw( FS::part_event::Condition ); + +sub description { "Don't run more than once per customer"; } + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 1, + 'cust_pkg' => 1, + }; +} + +sub condition { + my($self, $object, %opt) = @_; + + my $obj_pkey = $object->primary_key; + my $obj_table = $object->table; + my $custnum = $object->custnum; + + my @where = ( + "tablenum IN ( SELECT $obj_pkey FROM $obj_table WHERE custnum = $custnum )" + ); + if ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/ ) { + push @where, " eventnum != $1 "; + } + my $extra_sql = ' AND '. join(' AND ', @where); + + my @existing = qsearch( { + 'table' => 'cust_event', + 'hashref' => { + 'eventpart' => $self->eventpart, + #'tablenum' => $tablenum, + 'status' => { op=>'!=', value=>'failed' }, + }, + 'extra_sql' => $extra_sql, + } ); + + ! scalar(@existing); + +} + +#XXX test? +sub condition_sql { + my( $self, $table ) = @_; + + my %pkey = %{ FS::part_event->eventtable_pkey }; + + my $pkey = $pkey{$table}; + + "0 = ( SELECT COUNT(*) FROM cust_event + WHERE cust_event.eventpart = part_event.eventpart + AND cust_event.tablenum IN ( + SELECT $pkey FROM $table AS once_percust + WHERE once_percust.custnum = cust_main.custnum ) + AND status != 'failed' + ) + "; + +} + +1; diff --git a/FS/FS/part_event/Condition/pkg_age.pm b/FS/FS/part_event/Condition/pkg_age.pm new file mode 100644 index 000000000..8b3b4c971 --- /dev/null +++ b/FS/FS/part_event/Condition/pkg_age.pm @@ -0,0 +1,58 @@ +package FS::part_event::Condition::pkg_age; + +use strict; +use base qw( FS::part_event::Condition ); +use FS::Record qw( qsearch ); + +sub description { + 'Package Age'; +} + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +#something like this +sub option_fields { + ( + 'age' => { 'label' => 'Package date age', + 'type' => 'freq', + }, + 'field' => { 'label' => 'Compare date', + 'type' => 'select', + 'options' => + [qw( setup last_bill bill adjourn susp expire cancel )], + 'labels' => { + 'setup' => 'Setup date', + 'last_bill' => 'Last bill date', + 'bill' => 'Next bill date', + 'adjourn' => 'Adjournment date', + 'susp' => 'Suspension date', + 'expire' => 'Expiration date', + 'cancel' => 'Cancellation date', + }, + }, + ); +} + +sub condition { + my( $self, $cust_pkg, %opt ) = @_; + + my $age = $self->option_age_from('age', $opt{'time'} ); + + my $pkg_date = $cust_pkg->get( $self->option('field') ); + + $pkg_date && $pkg_date <= $age; + +} + +#XXX write me for efficiency +#sub condition_sql { +# +#} + +1; + diff --git a/FS/FS/part_event/Condition/pkg_notchange.pm b/FS/FS/part_event/Condition/pkg_notchange.pm new file mode 100644 index 000000000..4c103c22d --- /dev/null +++ b/FS/FS/part_event/Condition/pkg_notchange.pm @@ -0,0 +1,31 @@ +package FS::part_event::Condition::pkg_notchange; + +use strict; + +use base qw( FS::part_event::Condition ); +use FS::Record qw( qsearch ); + +sub description { + 'Package is a new order, not a change'; +} + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +sub condition { + my( $self, $cust_pkg ) = @_; + + ! $cust_pkg->change_date; + +} + +sub condition_sql { + '( cust_pkg.change_date IS NULL OR cust_pkg.change_date = 0 )'; +} + +1; + diff --git a/FS/FS/part_event/Condition/pkg_pkgpart.pm b/FS/FS/part_event/Condition/pkg_pkgpart.pm new file mode 100644 index 000000000..6adef8eb6 --- /dev/null +++ b/FS/FS/part_event/Condition/pkg_pkgpart.pm @@ -0,0 +1,39 @@ +package FS::part_event::Condition::pkg_pkgpart; + +use strict; + +use base qw( FS::part_event::Condition ); + +sub description { 'Package definitions'; } + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +sub option_fields { + ( + 'if_pkgpart' => { 'label' => 'Only packages: ', + 'type' => 'select-part_pkg', + 'multiple' => 1, + }, + ); +} + +sub condition { + my( $self, $cust_pkg) = @_; + + #XXX test + my $if_pkgpart = $self->option('if_pkgpart') || {}; + $if_pkgpart->{ $cust_pkg->pkgpart }; + +} + +#XXX +#sub condition_sql { +# +#} + +1; diff --git a/FS/FS/part_event/Condition/pkg_recurring.pm b/FS/FS/part_event/Condition/pkg_recurring.pm new file mode 100644 index 000000000..1b6682126 --- /dev/null +++ b/FS/FS/part_event/Condition/pkg_recurring.pm @@ -0,0 +1,31 @@ +package FS::part_event::Condition::pkg_recurring; + +use strict; + +use base qw( FS::part_event::Condition ); + +sub description { 'Package is recurring'; } + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +sub condition { + my( $self, $cust_pkg ) = @_; + + $cust_pkg->part_pkg->freq !~ /^0+\D?$/; #just in case, probably just != '0' + +} + + +#XXX join part_pkg USING (pkgpart) +# part_pkg.freq != '0' +#sub condition_sql { +# +#} + +1; + diff --git a/FS/FS/part_event/Condition/pkg_unless_pkgpart.pm b/FS/FS/part_event/Condition/pkg_unless_pkgpart.pm new file mode 100644 index 000000000..47fa8c321 --- /dev/null +++ b/FS/FS/part_event/Condition/pkg_unless_pkgpart.pm @@ -0,0 +1,39 @@ +package FS::part_event::Condition::pkg_unless_pkgpart; + +use strict; + +use base qw( FS::part_event::Condition ); + +sub description { 'Except package definitions'; } + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +sub option_fields { + ( + 'unless_pkgpart' => { 'label' => 'Except packages: ', + 'type' => 'select-part_pkg', + 'multiple' => 1, + }, + ); +} + +sub condition { + my( $self, $cust_pkg) = @_; + + #XXX test + my $unless_pkgpart = $self->option('unless_pkgpart') || {}; + ! $unless_pkgpart->{ $cust_pkg->pkgpart }; + +} + +#XXX +#sub condition_sql { +# +#} + +1; diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 21b3f1eab..7a78d0ba4 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -142,6 +142,14 @@ sub base_recur { $self->option('recur_fee', 1) || 0; } +sub base_recur_permonth { + my($self, $cust_pkg) = @_; #$cust_pkg? + + return 0 unless $self->freq =~ /^\d+$/ && $self->freq > 0; + + sprintf('%.2f', $self->base_recur / $self->freq ); +} + sub calc_remain { my ($self, $cust_pkg, %options) = @_; -- 2.11.0