summaryrefslogtreecommitdiff
path: root/FS/FS/part_event/Condition.pm
diff options
context:
space:
mode:
Diffstat (limited to 'FS/FS/part_event/Condition.pm')
-rw-r--r--FS/FS/part_event/Condition.pm448
1 files changed, 448 insertions, 0 deletions
diff --git a/FS/FS/part_event/Condition.pm b/FS/FS/part_event/Condition.pm
new file mode 100644
index 0000000..ddd8a61
--- /dev/null
+++ b/FS/FS/part_event/Condition.pm
@@ -0,0 +1,448 @@
+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
+
+FS::part_event::Condition - Base class for event conditions
+
+=head1 SYNOPSIS
+
+package FS::part_event::Condition::mycondition;
+
+use base FS::part_event::Condition;
+
+=head1 DESCRIPTION
+
+FS::part_event::Condition is a base class for event conditions classes.
+
+=head1 METHODS
+
+These methods are implemented in each condition class.
+
+=over 4
+
+=item description
+
+Condition classes must define a description method. This method should return
+a scalar description of the condition.
+
+=item eventtable_hashref
+
+Condition classes must define an eventtable_hashref method if they can only be
+tested against some kinds of tables. This method should return a hash reference
+of eventtables (values set true indicate the condition can be tested):
+
+ sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 0,
+ 'cust_pay_batch' => 0,
+ 'cust_statement' => 0,
+ };
+ }
+
+=cut
+
+#fallback
+sub eventtable_hashref {
+ { 'cust_main' => 1,
+ 'cust_bill' => 1,
+ 'cust_pkg' => 1,
+ 'cust_pay_batch' => 1,
+ 'cust_statement' => 1,
+ };
+}
+
+=item option_fields
+
+Condition classes may define an option_fields method to indicate that they
+accept one or more options.
+
+This method should return a list of option names and option descriptions.
+Each option description can be a scalar description, for simple options, or a
+hashref with the following values:
+
+=over 4
+
+=item label - Description
+
+=item type - Currently text, money, checkbox, checkbox-multiple, select, select-agent, select-pkg_class, select-part_referral, select-table, fixed, hidden, (others can be implemented as httemplate/elements/tr-TYPE.html mason components). Defaults to text.
+
+=item options - For checkbox-multiple and select, a list reference of available option values.
+
+=item option_labels - For checkbox-multiple (and select?), a hash reference of availble option values and labels.
+
+=item value - for checkbox, fixed, hidden (also a default for text, money, more?)
+
+=item table - for select-table
+
+=item name_col - for select-table
+
+=item NOTE: See httemplate/elements/select-table.html for a full list of the optinal options for the select-table type
+
+=back
+
+NOTE: A database connection is B<not> yet available when this subroutine is
+executed.
+
+Example:
+
+ sub option_fields {
+ (
+ 'field' => 'description',
+
+ 'another_field' => { 'label'=>'Amount', 'type'=>'money', },
+
+ 'third_field' => { 'label' => 'Types',
+ 'type' => 'checkbox-multiple',
+ 'options' => [ 'h', 's' ],
+ 'option_labels' => { 'h' => 'Happy',
+ 's' => 'Sad',
+ },
+ );
+ }
+
+=cut
+
+#fallback
+sub option_fields {
+ ();
+}
+
+=item condition CUSTOMER_EVENT_OBJECT
+
+Condition classes must define a condition method. This method is evaluated
+to determine if the condition has been met. The object which triggered the
+event (an FS::cust_main, FS::cust_bill or FS::cust_pkg object) is passed as
+the first argument. Additional arguments are list of key-value pairs.
+
+To retreive option values, call the option method on the desired option, i.e.:
+
+ my( $self, $cust_object, %opts ) = @_;
+ $value_of_field = $self->option('field');
+
+Available additional arguments:
+
+ $time = $opt{'time'}; #use this instead of time or $^T
+
+ $cust_event = $opt{'cust_event'}; #to retreive the cust_event object being tested
+
+Return a true value if the condition has been met, and a false value if it has
+not.
+
+=item condition_sql EVENTTABLE
+
+Condition classes may optionally define a condition_sql method. This B<class>
+method should return an SQL fragment that tests for this condition. The
+fragment is evaluated and a true value of this expression indicates that the
+condition has been met. The event table (cust_main, cust_bill or cust_pkg) is
+passed as an argument.
+
+This method is used for optimizing event queries. You may want to add indices
+for any columns referenced. It is acceptable to return an SQL fragment which
+partially tests the condition; doing so will still reduce the number of
+records which much be returned and tested with the B<condition> method.
+
+=cut
+
+# fallback.
+sub condition_sql {
+ my( $class, $eventtable ) = @_;
+ #...
+ 'true';
+}
+
+=item disabled
+
+Condition classes may optionally define a disabled method. Returning a true
+value disbles the condition entirely.
+
+=cut
+
+sub disabled {
+ 0;
+}
+
+=item implicit_flag
+
+This is used internally by the I<once> and I<balance> conditions. You probably
+do B<not> want to define this method for new custom conditions, unless you're
+sure you want B<every> new action to start with your condition.
+
+Condition classes may define an implicit_flag method that returns true to
+indicate that all new events should start with this condition. (Currently,
+condition classes which do so should be applicable to all kinds of
+I<eventtable>s.) The numeric value of the flag also defines the ordering of
+implicit conditions.
+
+=cut
+
+#fallback
+sub implicit_flag { 0; }
+
+=item remove_warning
+
+Again, used internally by the I<once> and I<balance> conditions; probably not
+a good idea for new custom conditions.
+
+Condition classes may define a remove_warning method containing a string
+warning message to enable a confirmation dialog triggered when the condition
+is removed from an event.
+
+=cut
+
+#fallback
+sub remove_warning { ''; }
+
+=item order_sql
+
+This is used internally by the I<balance_age> and I<cust_bill_age> conditions
+to declare ordering; probably not of general use for new custom conditions.
+
+=item order_sql_weight
+
+In conjunction with order_sql, this defines which order the ordering fragments
+supplied by different B<order_sql> should be used.
+
+=cut
+
+sub order_sql_weight { ''; }
+
+=back
+
+=head1 BASE METHODS
+
+These methods are defined in the base class for use in condition classes.
+
+=over 4
+
+=item cust_main CUST_OBJECT
+
+Return the customer object (see L<FS::cust_main>) associated with the provided
+object (the object itself if it is already a customer object).
+
+=cut
+
+sub cust_main {
+ my( $self, $cust_object ) = @_;
+
+ $cust_object->isa('FS::cust_main') ? $cust_object : $cust_object->cust_main;
+
+}
+
+=item option_label OPTIONNAME
+
+Returns the label for the specified option name.
+
+=cut
+
+sub option_label {
+ my( $self, $optionname ) = @_;
+
+ my %option_fields = $self->option_fields;
+
+ ref( $option_fields{$optionname} )
+ ? $option_fields{$optionname}->{'label'}
+ : $option_fields{$optionname}
+ or $optionname;
+}
+
+=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<condition>.
+
+=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
+option. It is primarily intended for use in B<condition_sql>.
+
+=cut
+
+sub condition_sql_option {
+ my( $class, $option ) = @_;
+
+ ( my $condname = $class ) =~ s/^.*:://;
+
+ "( SELECT optionvalue FROM part_event_condition_option
+ WHERE part_event_condition_option.eventconditionnum =
+ cond_$condname.eventconditionnum
+ AND part_event_condition_option.optionname = '$option'
+ )";
+}
+
+=item condition_sql_option_age_from OPTION FROM_TIMESTAMP
+
+This is a class method that returns an SQL fragment that will retreive a
+condition option, parse it from a frequency (such as "1d", "1w" or "12m"),
+and subtract that interval from the supplied timestamp. It is primarily
+intended for use in B<condition_sql>.
+
+=cut
+
+sub condition_sql_option_age_from {
+ my( $class, $option, $from ) = @_;
+
+ my $value = $class->condition_sql_option($option);
+
+# my $str2time = str2time_sql;
+
+ if ( driver_name =~ /^Pg/i ) {
+
+ #can we do better with Pg now that we have $from? yes we can, bob
+ "( $from - EXTRACT( EPOCH FROM REPLACE( $value, 'm', 'mon')::interval ) )";
+
+ } elsif ( driver_name =~ /^mysql/i ) {
+
+ #hmm... is there a way we can save $value? we're just an expression, hmm
+ #we might be able to do something like "AS ${option}_value" except we get
+ #used in more complicated expressions and we need some sort of unique
+ #identifer passed down too... yow
+
+ "CASE WHEN $value IS NULL OR $value = ''
+ THEN $from
+ WHEN $value LIKE '%m'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'm', '' ) MONTH
+ )
+ WHEN $value LIKE '%y'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'y', '' ) YEAR
+ )
+ WHEN $value LIKE '%w'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'w', '' ) WEEK
+ )
+ WHEN $value LIKE '%d'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'd', '' ) DAY
+ )
+ WHEN $value LIKE '%h'
+ THEN UNIX_TIMESTAMP(
+ FROM_UNIXTIME($from) - INTERVAL REPLACE( $value, 'h', '' ) HOUR
+ )
+ END
+ "
+ } else {
+
+ die "FATAL: don't know how to subtract frequencies from dates for ".
+ driver_name. " databases";
+
+ }
+
+}
+
+=item condition_sql_option_age OPTION
+
+This is a class method that returns an SQL fragment for retreiving a condition
+option, and additionaly parsing it from a frequency (such as "1d", "1w" or
+"12m") into an approximate number of seconds.
+
+Note that since months vary in length, the results of this method should B<not>
+be used in computations (use condition_sql_option_age_from for that). They are
+useful for for ordering and comparison to other ages.
+
+This method is primarily intended for use in B<order_sql>.
+
+=cut
+
+sub condition_sql_option_age {
+ my( $class, $option ) = @_;
+ $class->age2seconds_sql( $class->condition_sql_option($option) );
+}
+
+=item age2seconds_sql
+
+Class method returns an SQL fragment for parsing an arbitrary frequeny (such
+as "1d", "1w", "12m", "2y" or "12h") into an approximate number of seconds.
+
+Approximate meaning: months are considered to be 30 days, years to be
+365.25 days. Otherwise the numbers of seconds returned is exact.
+
+=cut
+
+sub age2seconds_sql {
+ my( $class, $value ) = @_;
+
+ if ( driver_name =~ /^Pg/i ) {
+
+ "EXTRACT( EPOCH FROM REPLACE( $value, 'm', 'mon')::interval )";
+
+ } elsif ( driver_name =~ /^mysql/i ) {
+
+ #hmm... is there a way we can save $value? we're just an expression, hmm
+ #we might be able to do something like "AS ${option}_age" except we get
+ #used in more complicated expressions and we need some sort of unique
+ #identifer passed down too... yow
+ # 2592000 = 30d "1 month"
+ # 31557600 = 365.25d "1 year"
+
+ "CASE WHEN $value IS NULL OR $value = ''
+ THEN 0
+ WHEN $value LIKE '%m'
+ THEN REPLACE( $value, 'm', '' ) * 2592000
+ WHEN $value LIKE '%y'
+ THEN REPLACE( $value, 'y', '' ) * 31557600
+ WHEN $value LIKE '%w'
+ THEN REPLACE( $value, 'w', '' ) * 604800
+ WHEN $value LIKE '%d'
+ THEN REPLACE( $value, 'd', '' ) * 86400
+ WHEN $value LIKE '%h'
+ THEN REPLACE( $value, 'h', '' ) * 3600
+ END
+ "
+ } else {
+
+ die "FATAL: don't know how to approximate frequencies for ". driver_name.
+ " databases";
+
+ }
+
+}
+
+=head1 NEW CONDITION CLASSES
+
+A module should be added in FS/FS/part_event/Condition/ which implements the
+methods desribed above in L</METHODS>. An example may be found in the
+eg/part_event-Condition-template.pm file.
+
+=cut
+
+1;
+
+